Skip to content

Instantly share code, notes, and snippets.

@AndiSHFR
Created December 3, 2019 10:06
Show Gist options
  • Save AndiSHFR/499a03e4d7c53ab8c71647690be624a8 to your computer and use it in GitHub Desktop.
Save AndiSHFR/499a03e4d7c53ab8c71647690be624a8 to your computer and use it in GitHub Desktop.
IDEA: How to handle relative time ranges in query selection parameters
<!DOCTYPE html>
<!--
IDEA: How to handle relative time ranges in query selection parameters
-->
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<!-- The above 3 meta tags *must* come first in the head; any other head content must come *after* these tags -->
<title>Time Ranges w/ IS8601 Durations</title>
<link href="https://fonts.googleapis.com/css?family=Varela+Round" rel="stylesheet">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/css/bootstrap-datetimepicker-standalone.min.css" integrity="sha256-SMGbWcp5wJOVXYlZJyAXqoVWaE/vgFA5xfrH3i/jVw0=" crossorigin="anonymous" />
<!-- HTML5 shim and Respond.js for IE8 support of HTML5 elements and media queries -->
<!-- WARNING: Respond.js doesn't work if you view the page via file:// -->
<!--[if lt IE 9]>
<script src="https://oss.maxcdn.com/html5shiv/3.7.3/html5shiv.min.js"></script>
<script src="https://oss.maxcdn.com/respond/1.4.2/respond.min.js"></script>
<![endif]-->
<style>
/*! Minimal styling here */
body {
font-family: 'Varela Round', 'Segoe UI', 'Trebuchet MS', sans-serif;
}
footer { font-size: 80%; }
footer hr { margin-bottom: 5px; }
</style>
</head>
<body>
<div class="container">
<div class="row"><div class="alertContainer col-xs-12"></div></div>
<div class="page-header">Time Ranges w/ IS8601 Durations</div>
<div class="row">
<div class="col-xs-10 col-xs-offset-1">
<div class="form-horizontal">
<div class="form-group" style="margin-bottom: 50px">
<label for="timeRanges" class="col-xs-2 control-label">Range</label>
<div class="col-xs-10">
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-exclamation-sign" data-toggle="tooltip" data-placement="bottom" title="Enter the production order id you are looking for.<br/>You can use partial information.<br/>Example: 378365"></i></span>
<select id="timeRanges" class="form-control"></select>
</div>
</div>
</div>
<div class="form-group">
<label class="col-xs-2 control-label">Start: </label>
<div class="col-xs-4">
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-exclamation-sign" data-toggle="tooltip" data-placement="bottom" title="The format specifier describing the start of the range. See ISO8601 for more"></i></span>
<input id="formatFrom" type="text" class="form-control" value="">
</div>
</div>
<div class="col-xs-6">
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-exclamation-sign" data-toggle="tooltip" data-placement="bottom" title="The start date and time representing the choosen range."></i></span>
<input id="dateTimeFrom" type="text" class="form-control" value="">
</div>
</div>
</div>
<div class="form-group">
<label class="col-xs-2 control-label">End: </label>
<div class="col-xs-4">
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-exclamation-sign" data-toggle="tooltip" data-placement="bottom" title="The format specifier describing the end of the range."></i></span>
<input id="formatTo" type="text" class="form-control" value="">
</div>
</div>
<div class="col-xs-6">
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-exclamation-sign" data-toggle="tooltip" data-placement="bottom" title="The end date and time representing the choosen range."></i></span>
<input id="dateTimeTo" type="text" class="form-control" value="">
</div>
</div>
</div>
</div>
<!--
HERE WE WILL CREATE A MINIMAL UI TO SETUP RELATIVE TIME RANGES....
<div class="page-header">Roll you own...</div>
<div class="form-horizontal">
<div class="form-group" style="margin-bottom: 50px">
<label for="timeRanges" class="col-xs-2 control-label">Origin</label>
<div class="col-xs-10">
<div class="input-group">
<span class="input-group-addon"><i class="glyphicon glyphicon-exclamation-sign" data-toggle="tooltip" data-placement="bottom" title="Enter the production order id you are looking for.<br/>You can use partial information.<br/>Example: 378365"></i></span>
<input id="dateTmeOrigin" type="text" class="form-control" value="">
</div>
</div>
</div>
-->
<div>
<pre class="hidden">
Search preset
[add] [save] [del] [fav]
[......................]
</pre>
</div>
</div>
<footer>
<hr>
<p>Copyright &copy; 2019 Andreas Schaefer &lt;[email protected]&gt; | <a href="">Privacy Policy</a> | <a href="">Terms of Use</a></p>
</footer>
</div><!-- @end: .container -->
<!-- Script tags are placed at the end of the file to make html appearing faster -->
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js" crossorigin="anonymous"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js" integrity="sha256-4iQZ6BVL4qNKlQ27TExEhBN1HFPvAvAMbFavKKosSWQ=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/bootstrap-datetimepicker/4.17.47/js/bootstrap-datetimepicker.min.js" integrity="sha256-5YmaxAwMjIpMrVlK84Y/+NjCpKnFYa8bWWBbUHSBGfU=" crossorigin="anonymous"></script>
<script>
/*!
* Construct a dummy object for the console to make console usage more reliable.
* In IE the console object is undefined when the developer tools are not open.
* This leads to an exception when using console.log(...); w/o dev tools opened.
*/
console = (window.console = window.console || { log: function() {} });
</script>
<script>
/*!
* timespan.js - v1.0.0 - 2016-04-10
*/
/**
* Wrap the initialization into an anonymous self-invoking function
*/
;(function(document, window, undefined) {
/**
* Class like javascript object to handle a timespan.
*/
Timespan.prototype.initialize = function(sign, years, months, weeks, days, hours, minutes, seconds, milliseconds) {
this.error = false;
this.sign = sign;
this.years = parseInt(years);
this.months = parseInt(months);
this.weeks = parseInt(weeks);
this.days = parseInt(days);
this.hours = parseInt(hours);
this.minutes = parseInt(minutes);
this.seconds = parseInt(seconds);
this.milliseconds = parseInt(milliseconds);
}
Timespan.prototype._initWithTimespan = function(ts) {
this.initialize(
ts.sign,
ts.years,
ts.months,
ts.weeks,
ts.days,
ts.hours,
ts.minutes,
ts.seconds,
ts.milliseconds
);
}
Timespan.prototype._initWithDates = function(date1, date2) {
this.initialize(
'',
date2.getFullYear() - date1.getFullYear(),
date2.getMonth() - date1.getMonth(),
0,
date2.getDate() - date1.getDate(),
date2.getHours() - date1.getHours(),
date2.getMinutes() - date1.getMinutes(),
date2.getSeconds() - date1.getSeconds(),
date2.getMilliseconds() - date1.getMilliseconds()
);
}
Timespan.prototype._initWithIso8601Duration = function(iso8601Duration) {
// Doing this with a regex means we cannot detect errornous formats like "+PT1W" or "+P1YT3C"
var iso8601DurationRegex = /(-|\+)?P(?:([\.,\d]+)Y)?(?:([\.,\d]+)M)?(?:([\.,\d]+)W)?(?:([\.,\d]+)D)?T?(?:([\.,\d]+)H)?(?:([\.,\d]+)M)?(?:([\.,\d]+)S)?/;
var matches = iso8601Duration.match(iso8601DurationRegex);
if (!matches) {
this.error = 'Invalid ISO8601 format in "' + iso8601Duration + '".';
return;
// throw new Error('Invalid ISO8601 format in "' + iso8601Duration + '".');
}
this.initialize(
matches[1] === undefined ? '+' : matches[1], // sign
matches[2] === undefined ? 0 : matches[2], // years
matches[3] === undefined ? 0 : matches[3], // months
matches[4] === undefined ? 0 : matches[4], // weeks
matches[5] === undefined ? 0 : matches[5], // days
matches[6] === undefined ? 0 : matches[6], // hours
matches[7] === undefined ? 0 : matches[7], // minutes
matches[8] === undefined ? 0 : matches[8], // seconds
0
);
}
Timespan.prototype.getDescription = function() {
function createItem(value, singular, plural) {
return value + ' ' + (Math.abs(value) > 1 ? plural : singular );
}
var singulars = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second' ];
var plurals = ['years', 'months', 'weeks', 'days', 'hours', 'minutes', 'seconds' ];
var values = [this.years, this.months, this.weeks, this.days, this.hours, this.minutes, this.seconds];
var items = [];
for (var i=0; i<values.length; i++) {
if (values[i] > 0) {
items.push(createItem(values[i], singulars[i], plurals[i]));
}
}
return items.join(', ');
}
Timespan.prototype.getError = function() { return this.error; }
Timespan.prototype.getSign = function() { return this.sign; }
Timespan.prototype.getYears = function() { return this.years; }
Timespan.prototype.getMonths = function() { return this.months; }
Timespan.prototype.getWeeks = function() { return this.weeks; }
Timespan.prototype.getDays = function() { return this.days; }
Timespan.prototype.getHours = function() { return this.hours; }
Timespan.prototype.getMinutes = function() { return this.minutes; }
Timespan.prototype.getSeconds = function() { return this.seconds; }
Timespan.prototype.getMilliseconds = function() { return this.milliseconds; }
Timespan.prototype.totalMilliseconds = function() {
return (this.days * 86400000 )
+ (this.hours * 3600000 )
+ (this.minutes * 60000 )
+ (this.seconds * 1000 )
+ this.milliseconds;
}
Timespan.prototype.totalSeconds = function() {
return (this.totalMilliseconds() / 1000.0);
}
Timespan.prototype.totalMinutes = function() {
return (this.totalMilliseconds() / 60000.0);
}
Timespan.prototype.totalHours = function() {
return (this.totalMilliseconds() / 3600000.0);
}
Timespan.prototype.totalDays = function() {
return (this.totalMilliseconds() / 86400000.0);
}
Timespan.prototype.addTimestamp = function(ts) {
var t = new Timestamp(this);
return t;
}
Timespan.prototype.toIso8601 = function() {
return this.sign
+ 'P'
+ ( this.years ? this.years + 'Y' : '' )
+ ( this.months ? this.months + 'M' : '' )
+ ( this.weeks ? this.weeks + 'W' : '' )
+ ( this.days ? this.days + 'D' : '' )
+ ( this.hours || this.minutes || this.seconds ? 'T' : '' )
+ ( this.hours ? this.hours + 'H' : '' )
+ ( this.minutes ? this.minutes + 'M' : '' )
+ ( this.seconds ? this.seconds + 'S' : '' )
+ '';
}
// datepart: 'y', 'm', 'w', 'd', 'h', 'n', 's'
Timespan.dateDiff = function(datepart, fromdate, todate) {
datepart = datepart.toLowerCase();
var diff = todate - fromdate;
var divideBy = { w:604800000,
d:86400000,
h:3600000,
n:60000,
s:1000 };
return Math.floor( diff/divideBy[datepart]);
}
function Timespan(a, b) {
function typeOf(v) {
return Object.prototype.toString.call(v).replace(/^\[object (.+)\]$/,"$1").toLowerCase();
}
// Initialize the object for the first time
this.initialize('', 0, 0, 0, 0, 0, 0, 0, 0);
// no args -> construct empty/ zeroed timespan
// arg a suplied. -> Either new Timestamp(anotherTimestamp) or new Timestamp("P1Y2M3DT4H23M")
// arg a and b -> new Timespan(dateA, dateB)
if ( a===undefined && b===undefined ) return;
if (b===undefined) {
if ('object' == typeof(a)) {
this._initWithTimespan(a);
}else {
this._initWithIso8601Duration(a);
}
} else {
this._initWithDates(a, b);
}
}
window.Timespan = Timespan;
Date.prototype.addTimespan = function(ts) {
var t = new Date(this);
var sign = ( ts.getSign() == '-' ? -1 : 1 );
t.setYear( t.getFullYear() + sign * ts.getYears());
t.setMonth( t.getMonth() + sign * ts.getMonths());
t.setDate( t.getDate() + sign * (ts.getDays() + (7 * ts.getWeeks())) );
t.setHours( t.getHours() + sign * ts.getHours());
t.setMinutes( t.getMinutes() + sign * ts.getMinutes());
t.setSeconds( t.getSeconds() + sign * ts.getSeconds());
return t;
}
})(document, window);
$(function () {
var
// Helper to make console output more reliable. In IE a call to
// console.log() will fail when the developer tools are not active.
console = (window.console = window.console || {}),
DEBUG = true,
/**
* Output debug information to the developer console
*
* @param {object} args_
* @return
* @api private
*/
debug = function(args_) {
if(DEBUG && console) {
var args = [].slice.call(arguments);
args.unshift('*** DEBUG: ');
console.log.apply(null, args);
}
},
/**
* bsAlert()
* Show a bootstrap alert in the alert container
*
* @param {string} msg The message to be shown
* @param {string} style The style (danger|warning|success) to use
* @param {number} autoClose The number of seconds after the alert be be automaticalle closed. 0 == never
* @returns {undefined} The function has no return value
* @copyright by Andreas Schaefer (['asc'].join('.') + '@schaefer-it.net')
* @license This function is in the public domain. Do what you want with it, no strings attached.
*/
$alertContainer = $('.alertContainer'),
bsAlert = function (msg, style, autoClose) {
msg = msg || '';
style = style || 'danger';
autoClose = autoClose || 0;
if('' === msg) {
$alertContainer.empty();
return;
}
debug(msg);
var $alert = $([
'<div class="alert alert-', style, ' alert-dismissable">',
'<a href="#" class="close" data-dismiss="alert" aria-label="close">&times;</a>',
'<span></span>',
'</div>'
].join(''));
$alert.find('span').html(msg);
$alertContainer.prepend($alert);
if(autoClose > 0) {
setTimeout(function() {
$alert.fadeTo(2000, 500).slideUp(500, function() {
$alert.slideUp(500, function() {
$alert.alert('close');
});
});
},
1000 * autoClose
);
}
}
;
var
TimePeriodConverter = (function(){
var
_tc = {}
;
_tc.parse = function(v, d) {
var hasError = false;
var
date = d || new Date()
items = ( v || '' ) // If value is undefined use an empty string
.toUpperCase()
.replace(/\s/g,'') // Remove all spaces
.replace(/\+/g,'|+') // Replace + with |+
.replace(/-/g,'|-') // Replace - with |-
.split('|') // Split the whole string at the | character
;
for(var i=0; i<items.length; i++) {
switch(items[i]) {
case 'NOW': date = moment(date).toDate(); break;
case 'ZD': date = moment(date).startOf('day').toDate(); break;
case 'ZW': date = moment(date).startOf('isoWeek').toDate(); break;
case 'ZM': date = moment(date).startOf('month').toDate(); break;
case 'ZY': date = moment(date).startOf('year').toDate(); break;
default:
var ts = new Timespan(items[i]);
hasError = ts.getError();
if (!hasError) date = date.addTimespan(ts);
break;
}
}
return (hasError ? undefined : date);
}
return _tc;
}),
periods = [
{id: 'Today', from: 'ZD', to: 'P1D'},
{id: 'Today (Danone)', from: 'ZD + P6H', to: 'P1D'},
{id: 'Yesterday', from: 'ZD - P1D', to: 'P1D'},
{id: 'This week', from: 'ZW', to: 'P1W'},
{id: 'Last week', from: 'ZW - P1W', to: 'P1W'},
{id: 'Last week (Danone)', from: 'ZW - P1W + P6H', to: 'P1W'},
{id: 'This month', from: 'ZM', to: 'P1M'},
{id: 'Last month', from: 'ZM - P1M', to: 'P1M'},
{id: 'This year', from: 'ZY', to: 'P1Y'},
{id: 'Last year', from: 'ZY - P1Y', to: 'P1Y'},
{id: 'This century', from: 'ZC', to: 'P100Y'},
],
tp = new TimePeriodConverter(periods)
options = $.map(periods, function(p) { return '<option value="' + p.from + '|' + p.to + '">' + p.id +'</options>' })
updateUi = function(el) {
var
$this = $(el),
$option = $this.children('option:selected')
id = $option.html(),
val = $option.val(),
fromTo = val.split('|')
dtFrom = tp.parse(fromTo[0]),
dtTo = tp.parse(fromTo[1], dtFrom)
;
$('#formatFrom').val(fromTo[0]);
$('#formatTo').val(fromTo[1]);
$('#dateTimeFrom').val(moment(dtFrom).format('L LT'));
$('#dateTimeTo').val(moment(dtTo).format('L LT'));
}
;
$('#timeRanges')
.append(options.join(''))
.on('change', function(event) {
event.preventDefault();
updateUi(this);
return false;
})
;
$('#dateTImeOrigin').datetimepicker();
updateUi($('#timeRanges')[0]);
$('[data-toggle="tooltip"]').tooltip({ html: true });
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment