/*! Intimidatetime - v0.2.0 - 2014-07-08 * http://trentrichardson.com/examples/Intimidatetime * Copyright (c) 2014 Trent Richardson; Licensed MIT */ /*jslint white: true, undef: false, nomen: true */ (function($){ 'use strict'; /* Notes: - inst.settings.value is an array of js Date objects, we always act as if it is a range - http://about.me/jdunck click the Github link, modal pops up would be nice to have the title bar with Year dropdown, then list of months next row would be calendar and column 2 would be time - calendar look and feel http://www.andrewspixels.com/light-and-deep-ui-kit-freebie/ - use mode option to appy pre-defined modes (basically option overrides that can be defined at $.intimidatetime.modes = {} just like types */ //######################################################################## // create our base object //######################################################################## $.intimidatetime = function($this, options){ return this.constructor($this, options); }; //######################################################################## // extend our object instance properties and methods //######################################################################## $.extend($.intimidatetime.prototype, { /* * Create a new instance of our object * @param $this jQuery - the jquery/zepto object of the element we're attaching to * @param options object - the object option values * @return this */ constructor: function($this, options){ var k, s, d = new Date(); // apply default options //this.settings = $.extend(true, {}, $.intimidatetime.i18n[''], $.intimidatetime.defaults, options); this.settings = $.intimidatetime.extend({}, $.intimidatetime.i18n[''], $.intimidatetime.defaults, options); s = this.settings; // mode engage... if(s.mode !== null && $.intimidatetime.modes[s.mode] !== undefined){ $.intimidatetime.extend(s, $.intimidatetime.modes[s.mode]); } // picker format defaults to format for input field s.previewFormat = s.previewFormat || s.format; // turn on or off support depending on the format s.support = $.intimidatetime.detectSupport(s.format +' '+ s.previewFormat); for(k in s.units){ if(s.units[k].show === undefined){ s.units[k].show = s.support[k]; } } // find all our elements this.$el = $this; // the input or container if inline this.$p = null; // the picker object itself this.$w = $(window); // should I waist my energy explaining? this.$d = $(document); // should I waist my energy explaining? // compute the default value k = this.$el.val(); if(k !== undefined && k !== ''){ s.value = $.intimidatetime.dateRangeParse(k, s.format, s.rangeDelimiter, s); } if(s.value === null){ s.value = [new Date(d.getTime())]; } if(!$.isArray(s.value)){ s.value = [s.value]; } if(s.ranges > s.value.length-1){ for(k=0; k'); if(s.inline){ inst.$p.addClass(s.theme+'-inline'); inst.$el.append(inst.$p); } else{ inst.$p.css('display','none'); inst.$el.after(inst.$p); } } // create a set for each datetime in a range inst.$p.empty(); for(i=0; i<=s.ranges; i+=1){ $h = $('
'+ $.intimidatetime.dateFormat(s.value[i], s.previewFormat, s) +'
').appendTo(inst.$p); // build out the defined groups for(g=0,gl=s.groups.length; g').appendTo($h); for(j=0,jl=gv.units.length; j').appendTo($tmp1); $.intimidatetime.types[s.units[jv].type].create(inst, $tmp2, s.value[i]); } } // end units if(numUnits === 0){ $tmp1.remove(); } } // end groups } // end ranges // add any buttons: Done, Now, etc jl = s.buttons.length; if(jl > 0){ $tmp1 = $('
').appendTo(inst.$p); for(j=0; j'+ jv.text +'') //.on('click.intimidatetime', { instance: inst }, jv.action) // not with Zepto, lack of data parameter .on('click.intimidatetime', jv.action) .appendTo($tmp1); } } if(!s.inline){ inst._reposition(); } inst.$el.trigger('intimidatetime:refresh', [inst]); return inst.$el; }, /* * reposition the picker depending on the window/document/input * @return jQuery - the manager object */ _reposition: function(){ var inst = this, elpos = inst.$el.position(), outerSize = function($el){ var s = { w: $el.width(), h: $el.height() }, p = function(c){ var v = $el.css(c); return v ? v.replace('px','')*1 : 0; }; //if($el.css('box-sizing') !== 'border-box'){ s.w += p('padding-left') + p('padding-right') + p('border-left-width') + p('border-right-width'); s.h += p('padding-top') + p('padding-bottom') + p('border-top-width') + p('border-bottom-width'); //} return s; }, els = outerSize(inst.$el), ps = outerSize(inst.$p), docs = outerSize($(document.body)), top = elpos.top + els.h, left = elpos.left; // if this extends pass docheight show upward, // as long as its not out the top of document if((top + ps.h) > docs.h && (elpos.top-ps.h) > 0){ top = elpos.top - ps.h; } // if its wider than the document just align // with the side of document if((left + ps.w) > docs.w){ left = docs.w - ps.w; } inst.$p.css({ top: top, left: left }); return inst.$el; }, /* * the change event has occured on the input, update everything * @return jQuery - the input element */ _inputChange: function(e){ var inst = this, s = inst.settings, ranges = $.intimidatetime.dateRangeParse(inst.$el.val(), s.format, s.rangeDelimiter, s), eCustom, eResult; if(ranges && ranges.length === s.ranges+1){ eCustom = new $.Event('intimidatetime:change'); eResult = inst.$el.trigger(eCustom, [inst, ranges]); // do all user supplied event handlers accept the event? (Zepto may not implement this) if(eResult.isDefaultPrevented && eResult.isDefaultPrevented()){ e.preventDefault(); return false; } // update the picker with this new value s.value = ranges; inst.refresh(); } return this.$el; }, /* * the change event has occured in the picker, update everything * @return jQuery - the input element */ _change: function(e){ var inst = this, s = inst.settings, ranges = inst._collectPickerRanges(), eCustom = new $.Event('intimidatetime:change'), eResult = inst.$el.trigger(eCustom, [inst, ranges]); // do all user supplied event handlers accept the event? (Zepto may not implement this) if(eResult.isDefaultPrevented && eResult.isDefaultPrevented()){ e.preventDefault(); // the original html event return false; } // set the new value (validation happens in value()) inst.value(ranges); return inst.$el; }, /* * Validate the range as per settings, min, max, intervalMin, intervalMax * @return array - array of dates (date range) */ _validateRanges: function(){ var inst = this, s = inst.settings, l = $.isArray(s.value)? s.value.length : 0, mind, maxd,i,cd; if(l > 0){ mind = (typeof s.min === 'string')? $.intimidatetime.dateRelative(new Date(), s.min) : s.min; maxd = (typeof s.max === 'string')? $.intimidatetime.dateRelative(new Date(), s.max) : s.max; // restrict min/max for(i=0; i maxd){ // restrict max s.value[i] = maxd; } if(i > 0){ // restrict interval if(typeof s.rangeIntervalMin === 'string'){ cd = $.intimidatetime.dateRelative(s.value[i-1], s.rangeIntervalMin); if(s.value[i] < cd){ s.value[i] = cd; } } if(typeof s.rangeIntervalMax === 'string'){ cd = $.intimidatetime.dateRelative(s.value[i-1], s.rangeIntervalMax); if(s.value[i] > cd){ s.value[i] = cd; } } } }// for i->l }// l > 0 return inst; }, /* * Search the picker for all dates/ranges, return them * @return array - array of Dates (date range) */ _collectPickerRanges: function(){ var inst = this, s = inst.settings, range = [], defVals = {year:0, month:0, day:0, hour:0, minute:0, second:0, millisecond:0, microsecond:0}; // get each range inst.$p.children('.'+s.theme+'-range').each(function(i, el){ var $r = $(el), v = $.intimidatetime.extend({},defVals), d; // get each unit in the range $r.find('.'+s.theme+'-unit').each(function(j, uel){ var $u = $(uel), unit = $u.data('unit'); v[unit] = $.intimidatetime.types[s.units[unit].type].value(inst, $u); }); // create the new date d = new Date(v.year, v.month, v.day, v.hour, v.minute, v.second, v.millisecond); d.setMicroseconds(v.microsecond); if(v.timezone !== undefined){ d.setTimezone(v.timezone); } range[i] = d; }); return range; }, /* * update picker input values and previewFormat, called from this.value() * @return jQuery - the manager object */ _updatePickerRanges: function(){ var inst = this, s = inst.settings; // get each range inst.$p.children('.'+s.theme+'-range').each(function(i, el){ var $r = $(el), d = s.value[i]; // the date in the range // set each unit in the range $r.find('.'+s.theme+'-unit').each(function(j, uel){ var $u = $(uel), unit = $u.data('unit'); $.intimidatetime.types[s.units[unit].type].value(inst, $u, d['get'+s.units[unit].map]()); }); // update the preview in the picker $r.children('.'+s.theme+'-preview').text($.intimidatetime.dateFormat(d, s.previewFormat, s)); }); return inst.$el; } }); //######################################################################## // extend our object static properties and methods //######################################################################## $.extend($.intimidatetime, { i18n: { '': { // Default regional settings format: 'yyyy-MM-dd HH:mm', // default format 'yyyy-MM-dd hh:mm:ss:l tt z' units: { year: { format: 'yyyy', // how should hour be forrmated in drowdown label: 'Year' // year label }, month: { format: 'MMM', // how should hour be forrmated in drowdown label: 'Month', // month label names: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], namesAbbr: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] }, day: { format: 'd', // how should hour be forrmated in drowdown label: 'Day', // day label names: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], namesAbbr: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], namesHead: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"] }, hour: { format: 'HH', // how should hour be forrmated in drowdown label: 'Hour', // hour label am: ['AM', 'A'], // possible am names pm: ['PM', 'P'] // possible pm names }, minute: { format: 'mm', // how should minute be formatted in dropdown label: 'Minute' // minute label }, second: { format: 'ss', // how should second be formatted in dropdown label: 'Second' // second label }, millisecond: { format: 'l', // how should millisecond be formatted in dropdown label: 'Millisecond' // millisecond label }, microsecond: { format: 'c', // how should millisecond be formatted in dropdown label: 'Microsecond' // microsecond label }, timezone: { format: 'z', // how should timezone be formatted in dropdown label: 'Timezone' // minute label } }, rtl: false } }, defaults: { // Date object, array of Date objects, or strict formatted string to set as the value value: null, // picker time format previewFormat: null, // format shown in the alt field if used, defaults to format option altFormat: null, // jQuery element or selector for an alternate input alt: null, // min allowed date - date object or relative string '-1M' min: null, // max allowed date - date object or relative string '-1M' max: null, // number of ranges or pick list of dates, will generate a calendar side by side for each date ranges: 0, // string to delimit ranges. this string MUST NOT occur in the time format rangeDelimiter: ' - ', // relative date string for the minimum allowed difference between two dates rangeIntervalMin: '+0l', // relative date string for the maximum allowed difference between two dates rangeIntervalMax: null, // number of months to show at once months: 1, // 0-6, which day is the start of the week startOfWeek: 0, // whether the picker should be inline inline: false, // use theme to prefix all class names, change your css accordingly theme: 'intimidatetime', // how the picker is aligned: vertical, horizontal (maps to css styling, no logic). Or should this be "classes"? mode: 'basic', // define any and all units units: { year: { map: 'FullYear', type: 'select', range: 20, step: 1, format: 'yyyy', value: null}, month: { map: 'Month', type: 'select', min: 0, max: 11, step: 1, format: 'MMM', value: null }, day: { map: 'Date', type: 'select', min: 1, max: 31, step: 1, value: null }, hour: { map: 'Hours', type: 'select', min: 0, max: 23, step: 1, value: null }, minute: { map: 'Minutes', type: 'select', min: 0, max: 59, step: 1, value: null }, second: { map: 'Seconds', type: 'select', min: 0, max: 59, step: 1, value: null }, milli: { map: 'Milliseconds', type: 'select', min: 0, max: 999, step: 10, value: null }, micro: { map: 'Microseconds', type: 'select', min: 0, max: 999, step: 10, value: null }, timezone: { map: 'Timezone', type: 'select', value: null, options: [720,660,600,570,540,480,420,360,300,270,240,210,180,120,60, // just like Date.getTimezoneOffset() 0,-60,-120,-180,-210,-240,-270,-300,-330,-345,-360,-390,-420,-480,-525,-540,-570,-600,-630,-660,-690,-720,-765,-780,-840], names: {} // Key value pairs mapping timezone values to text values { "240": "EDT", "300": "CDT".. } } }, // the order which each module appears groups: [ { name: 'date', units: ['year', 'month', 'day'] }, { name: 'time', units: ['hour', 'minute', 'second', 'millisecond', 'microsecond', 'timezone'] } ], // buttons can be added at the bottom of the picker buttons: [ //{ text: 'Done', classes: '', tag: 'button', action: function(inst){} } ], events: { // event fired on value change request, use event.preventDefault() or return false to prevent value change change: function(e, date, inst){}, // event fired when the picker is (re)built refresh: function(e, inst){}, // event fired to validate a day, use event.preventDefault() or return false to disable the day enableDay: function(e, date, inst){}, // event fired when the picker is opened, use event.preventDefault() or return false to prevent opening open: function(e, inst){}, // event fired when the picker is closed, use event.preventDefault() or return false to prevent closing close: function(e, inst){} } }, /* * Override the default settings for all instances of the time picker. * @param settings object - the new settings to use as defaults (anonymous object) * @return jQuery - the manager object */ setDefaults: function(settings) { $.intimidatetime.extend($.intimidatetime.defaults, settings || {}); return this; }, /* * Create key pairs of options to set "modes", these may imply specific * options and styling, like using the calendar or dropdowns */ modes: { basic: {}, horizontal: {}, vertical: {} }, /* * object of available types to each unit. Must implement: * - create($.intimidatetime inst, string unit, function onChange) * generates the html and binds necessary change event functions * - option($.intimidatetime inst, string unit, string key, mixed val) * updates the html with the needed option * - value(Intimidateetime inst, string unit, val) * gets or sets the value of the specified unit */ types: { // static label label: { create: function(inst, $parent, date, onChange){ var s = inst.settings, unit = $parent.data('unit'), u = s.units[unit], h = ''; $parent.html(h); }, option: function(inst, $parent, key, val){ }, value: function(inst, $parent, val){ } }, // list of clickable links (or calendar for months) (uses hidden inputs) list: { create: function(inst, $parent, date, onChange){ date = (date === undefined || date.toString() === 'Invalid Date')? new Date() : date; var s = inst.settings, unit = $parent.data('unit'), u = s.units[unit], $label = $(''), $input = $(''), $list = $('
    '), val = date['get'+u.map](), tmpd = $.intimidatetime.dateClone(date), max = u.max, min = u.min, h = '', moMax, i, l; // because some months have diff number of days... if(unit === 'year' || unit === 'month'){ date.setDate(1); tmpd.setDate(1); val = date['get'+u.map](); } // min/max check if(unit === 'day'){ moMax = $.intimidatetime.daysInMonth(date.getMonth(), date.getFullYear()); if(moMax < max){ max = moMax; } } if(u.range !== undefined){ if(min === undefined){ min = date.getFullYear()-u.range; } if(max === undefined){ max = date.getFullYear()+u.range; } } // build the options if(u.options){ for(i=0,l=u.options.length; i'+ $.intimidatetime.dateFormat(tmpd, u.format, s) +''; } } else{ for(i=min; i<=max; i+=u.step){ tmpd['set'+u.map](i); h += '
  • '+ $.intimidatetime.dateFormat(tmpd, u.format, s) +'
  • '; } } // build the label if(u.label !== '' && u.label !== false){ $label.append(''+ u.label +''); } // append it all to the parent $input.val(val); $list.on('click.intimidatetime','a', function(e){ // select change event triggers the change e.preventDefault(); $list.find('a.selected').removeClass('selected'); $input.val($(this).addClass('selected').data('value')); inst._change.call(inst, e); }); $label.append($input).appendTo($parent); $list.html(h).appendTo($parent); }, option: function(inst, $parent, key, val){ }, value: function(inst, $parent, val){ var $sel = $parent.find('input'); //$parent.find('ul li a.selected').removeClass('selected'); //$parent.find('a[data-value='+val+']').addClass('selected'); if(val !== undefined){ $sel.val(val); return $parent; } return $sel.val(); } }, // dropdowns select: { create: function(inst, $parent, date, onChange){ date = (date === undefined || date.toString() === 'Invalid Date')? new Date() : date; var s = inst.settings, unit = $parent.data('unit'), u = s.units[unit], $label = $(''), $input = $(''), val = date['get'+u.map](), tmpd = $.intimidatetime.dateClone(date), max = u.max, min = u.min, h = '', moMax, i, l; // because some months have diff number of days... if(unit === 'year' || unit === 'month'){ date.setDate(1); tmpd.setDate(1); val = date['get'+u.map](); } // min/max check if(unit === 'day'){ moMax = $.intimidatetime.daysInMonth(date.getMonth(), date.getFullYear()); if(moMax < max){ max = moMax; } } if(u.range !== undefined){ if(min === undefined){ min = date.getFullYear()-u.range; } if(max === undefined){ max = date.getFullYear()+u.range; } } // build the options if(u.options){ for(i=0,l=u.options.length; i'+ $.intimidatetime.dateFormat(tmpd, u.format, s) +''; } } else{ for(i=min; i<=max; i+=u.step){ tmpd['set'+u.map](i); h += ''; } } // build the label if(u.label !== '' && u.label !== false){ $label.append(''+ u.label +''); } // append it all to the parent $input.html(h).val(val); $input.on('change.intimidatetime', function(e){ // select change event triggers the change inst._change.call(inst, e); }); $label.append($input).appendTo($parent); }, option: function(inst, $parent, key, val){ // just set the value in options and recreate it? // be beware in ranges setting the global options // this could step on other date settings toes... }, value: function(inst, $parent, val){ var $sel = $parent.find('select'); if(val !== undefined){ $sel.val(val); return $parent; } return $sel.val(); } } // select, label, calendar, ui slider }, /* * create a clone of the date with our custom extended properties * @param date string - base date to compute from * @return Date - a clone of the original date */ dateClone: function(d){ return (new Date(d.getTime())) .setMicroseconds(d.getMicroseconds()) .setTimezone(d.getTimezone()); }, /* * parse a date depending on the options * @param date string - date string to parse * @param format string - date string to parse by * @param options object - includes things like i18n * @return Date */ dateParse: function(date, format, options){ var o = $.intimidatetime.extend({},$.intimidatetime.i18n[''], $.intimidatetime.defaults, options || {}), getAmpmPattern = function(amNames, pmNames) { var markers = []; if (amNames) { $.merge(markers, amNames); } if (pmNames) { $.merge(markers, pmNames); } markers = $.map(markers, function(val) { return val.replace(/[.*+?|()\[\]{}\\]/g, '\\$&'); }); return '(' + markers.join('|') + ')?'; }, t1t2d = '(\\d{1,2})', // 0 - 99 t1t3d = '(\\d{1,3})', // 0 - 999 t2t4d = '(\\d{2,4})', // 0 - 9999 tword = '([0-9A-Za-z\\u00A0-\\u05FF\\u0700-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]+|[\\u0600-\\u06FF\\/]+)', // any word (or two) characters or numbers including two/three word month in arabic. //tword = '(\\w+)', ttz = '(Z|[-+]\\d\\d:?\\d\\d|\\S+)?', // +00:00 -00:00 +0000 -0000 or Z tstmp = '([\\+\\-]?\\d+(\\.\\d{1,3})?)', // 123456789 123456789.123 finds = format.match(/(u{1,2}|y{1,4}|M{1,4}|d{1,4}|h{1,2}|H{1,2}|m{1,2}|s{1,2}|l{1}|c{1}|t{1,2}|T{1,2}|z{1,3}|'.*?')/g), order = { u: -1, y: -1, M: -1, d: -1, h: -1, H: -1, m: -1, s: -1, l: -1, c: -1, t: -1, T: -1, z: -1 }, d = { u: 0, y: 0, M: 0, d: 0, h: 0, m: 0, s: 0, l: 0, c: 0, z: 0 }, char0, i, l, regstr, matches, newd; // figure out what order everything is in if (finds) { for (i=0, l=finds.length; i < l; i+=1) { char0 = finds[i].toString().charAt(0); // if d the days of week is irrelevent, we need the number day if (order[char0] === -1 && (char0 !== 'd' || !(/^d{3,4}$/).test(finds[i])) ) { order[char0] = i + 1; } } } // create a regex to parse the formatted time (considering that it could be words, numbers, etc) regstr = '^' + format.toString() .replace(/(u{1,2}|y{1,4}|M{1,4}|d{1,4}|h{1,2}|H{1,2}|m{1,2}|s{1,2}|l{1}|c{1}|t{1,2}|T{1,2}|z{1,3}|'.*?')/g, function (match) { var r; switch (match) { case 'u': r = tstmp; break; case 'yy': case 'yyyy': r = t2t4d; break; case 'M': case 'MM': r = t1t2d; break; case 'MMM': case 'MMMM': r = tword; break; case 'd': case 'dd': r = t1t2d; break; case 'ddd': case 'dddd': r = tword; break; case 'H': case 'HH': case 'h': case 'hh': case 'm': case 'mm': case 's': case 'ss': r = t1t2d; break; case 'l': case 'c': r = t1t3d; break; case 'z': case 'zz': r = ttz; break; case 'zzz': r = tword; break; case 't': r = getAmpmPattern(o.units.hour.am, o.units.hour.pm); break; default: // literal escaped in quotes return '(' + match.replace(/\'/g, "").replace(/(\.|\$|\^|\\|\/|\(|\)|\[|\]|\?|\+|\*)/g, function (m) { return "\\" + m; }) + ')?'; } return r; }) .replace(/\s/g, '\\s') + '$'; matches = date.match(new RegExp(regstr, 'i')); if(!matches){ $.intimidatetime.log('Unable to parse date '+ date +' with '+ regstr); } else{ // set the year if (order.y !== -1) { i = matches[order.y]; if(i < 100){ i = parseInt('20'+''+i, 10); } d.y=i; } // set the month if (order.M !== -1) { if(isNaN(matches[order.M])){ i = $.inArray(matches[order.M], o.units.month.namesAbbr); if(i === -1){ i = $.inArray(matches[order.M], o.units.month.names); } } else{ i = parseInt(matches[order.M],10)-1; } d.M = i; } // set the day if (order.d !== -1) { d.d = parseInt(matches[order.d],10); } // set the hours (12hr) if (order.h !== -1) { i = parseInt(matches[order.h],10); l = 'am'; if($.inArray(matches[order.t], o.units.hour.pm) || $.inArray(matches[order.T], o.units.hour.pm)){ l = 'pm'; } if(l === 'am' && i === 12){ i = 0; } else if(l === 'pm' && i !== 12){ i+=12; } d.h = i; } // set the hours if (order.H !== -1) { d.h = parseInt(matches[order.H],10); } // set the minutes if (order.m !== -1) { d.m = parseInt(matches[order.m],10); } // set the seconds if (order.s !== -1) { d.s = parseInt(matches[order.s],10); } // set the milliseconds if (order.l !== -1) { d.l = parseInt(matches[order.l],10); } // set the microseconds if (order.c !== -1) { d.c = parseInt(matches[order.c],10); } newd = new Date(d.y, d.M, d.d, d.h, d.m, d.s, d.l); newd.setMicroseconds(d.c); // set the timezone if (order.z !== -1) { newd.setTimezone($.intimidatetime.timezoneOffsetNumber(matches[order.z], o.units.timezone.names)); } // set the unix timestamp (seconds since Jan 01 1970), but js Date wants milliseconds if (order.u !== -1) { newd.setTime(matches[order.u] * 1000); } } return newd; }, /* * parse a date range depending on the options * @param dates string - date range as a string * @param format string - date string to parse by * @param delimiter string - string that delimits dates * @param options object - includes things like i18n * @return Date */ dateRangeParse: function(dates, format, delimiter, options){ var i, l, r=[]; dates = dates.split(delimiter); for(i=0,l=dates.length; i 11)? o.units.hour.pm[0] : o.units.hour.am[0], pad = function(str, num){// we never need over 2 :) return ('000'+str).slice(num*-1); }, hr24to12 = function(h){ return ((h > 12)? (h-12) : (h === 0)? 12 : h)+''; }, tzStr = function(tzMinutes, iso8601) { var o; if(isNaN(tzMinutes) || tzMinutes > 840 || tzMinutes < -720){ o = tzMinutes; } else{ var off = tzMinutes * -1, minutes = off % 60, hours = (off - minutes) / 60, iso = iso8601? ':':''; o = (off >= 0 ? '+' : '-') + ('0' + (hours * 101).toString()).slice(-2) + iso + ('0' + (minutes * 101).toString()).slice(-2); if(o === '+00:00'){ o = 'Z'; } } return o; }, tzLabel = function(tz){ return o.units.timezone.names[tz] || tzStr(tz,false); }; tmpdate = tmpdate.replace(/(?:u{1,3}|yyyy|yy|M{1,4}|d{1,4}|HH?|hh?|mm?|ss?|[tT]{1,2}|z{1,3}|[lc]|('.*?'|".*?"))/g, function(match) { var r=''; switch (match) { case 'u': r = parseInt(date.getTime()/1000,10); break; case 'uu': r = date.getTime()/1000; break; case 'uuu': r = date.getTime(); break; // date case 'yy': r = date.getYear(); break; case 'yyyy': r = date.getFullYear(); break; case 'M': r = date.getMonth()+1; break; case 'MM': r = pad(date.getMonth()+1, 2); break; case 'MMM': r = o.units.month.namesAbbr[date.getMonth()]; break; case 'MMMM': r = o.units.month.names[date.getMonth()]; break; case 'd': r = date.getDate(); break; case 'dd': r = pad(date.getDate(), 2); break; case 'ddd': r = o.units.day.namesAbbr[date.getDay()]; break; case 'dddd': r = o.units.day.names[date.getDay()]; break; // time case 'HH': r = pad(hour, 2); break; case 'H': r = hour; break; case 'hh': r = pad(hr24to12(hour), 2); break; case 'h': r = hr24to12(hour); break; case 'mm': r = pad(date.getMinutes(), 2); break; case 'm': r = date.getMinutes(); break; case 'ss': r = pad(date.getSeconds(), 2); break; case 's': r = date.getSeconds(); break; case 'l': r = pad(date.getMilliseconds(), 3); break; case 'c': r = pad(date.getMicroseconds(), 3); break; case 'T': r = ampm.charAt(0).toUpperCase(); break; case 'TT': r = ampm.toUpperCase(); break; case 't': r = ampm.charAt(0).toLowerCase(); break; case 'tt': r = ampm.toLowerCase(); break; // timezone case 'z': r = tzStr(date.getTimezone(), false); break; case 'zz': r = tzStr(date.getTimezone(), true); break; // iso8601 case 'zzz': r = tzLabel(date.getTimezone()); break; // uses timezone label if available // literal string default: r = match.replace(/\'/g, "") || "'"; } return r; }); tmpdate = $.trim(tmpdate); return tmpdate; }, /* * parse a date range depending on the options * @param dates array - array of date objects * @param format string - date string to parse by * @param delimiter string - string that delimits dates * @param options object - includes things like i18n * @return Date */ dateRangeFormat: function(dates, format, delimiter, options){ var i, l, r=[]; for(i=0,l=dates.length; i 0 && y % 100) || (y % 400) > 0 ? 29 : 28 : 31; }, /* * Determine by the time format which units are supported * @param string - timeFormat to search for tokens * @return object - booleans for each unit */ detectSupport: function(timeFormat){ var tf = timeFormat.replace(/\'.*?\'/g,''), // removes literals isIn = function(f, t){ // does the format contain the token? return f.indexOf(t) !== -1? true:false; }, u = isIn('U'), s = { year: u || isIn(tf,'y'), month: u || isIn(tf,'M'), day: u || isIn(tf,'d'), hour: u || isIn(tf,'h') || isIn(tf,'H'), minute: u || isIn(tf,'m'), second: u || isIn(tf,'s'), milli: isIn(tf,'l'), micro: isIn(tf,'c'), timezone: isIn(tf,'z') || isIn(tf,'Z'), ampm: isIn(tf,'t') && isIn(tf,'h'), iso8601: isIn(tf, 'Z') }; s.date = s.year || s.month || s.day; s.time = s.hour || s.minute || s.second || s.milli || s.micro || s.timezone; s.datetime = s.date && s.time; return s; }, /* * create a new object that extend each passed argument object recursively. Arrays do not merge. * @param object - base object to be extended and returned * @param object - object to extend over base object (one or more) * @return object - all argument objects merged */ extend: function(){ var o = arguments[0], i = 1, l = arguments.length, mrec = function(o1, o2){ var p, t, v; for (p in o2) { if(o2.hasOwnProperty(p)){ v = o2[p]; t = Object.prototype.toString.call(v); if(t === '[object Date]'){ // Date, make a clone o1[p] = $.intimidatetime.dateClone(v); } else if(t === '[object Array]'){ // Array, make a copy, and completely replace previous o1[p] = mrec([], v); } else if(t !== '[object Object]' || v === null){ // simple value, completely replace o1[p] = v; } else if(o1[p] !== undefined){ // must be an obj, prev value exists so merge o1[p] = mrec(o1[p], v); } else{ // no previous value so make a copy o1[p] = mrec({}, v); // make a copy } } } return o1; }; for(; i