Created
December 3, 2019 10:06
-
-
Save AndiSHFR/499a03e4d7c53ab8c71647690be624a8 to your computer and use it in GitHub Desktop.
IDEA: How to handle relative time ranges in query selection parameters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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 © 2019 Andreas Schaefer <[email protected]> | <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">×</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