vendor/assets/javascripts/kalendae.js in kalendae_assets-0.1.1 vs vendor/assets/javascripts/kalendae.js in kalendae_assets-0.2

- old
+ new

@@ -1,10 +1,10 @@ /******************************************************************** * Kalendae, a framework agnostic javascript date picker * * Copyright(c) 2012 Jarvis Badgley (chipersoft@gmail.com) * * http://github.com/ChiperSoft/Kalendae * - * Version 0.1 * + * Version 0.2 * ********************************************************************/ (function (undefined) { var today; @@ -28,10 +28,12 @@ $days, dayNodes = [], $span, i = 0, j = opts.months; + if (util.isIE8()) util.addClassName($container, 'ie8'); + //generate the column headers (Su, Mo, Tu, etc) i = 7; while (i--) { columnHeaders.push( startDay.format('ddd').substr(0,opts.columnHeaderLength) ); startDay.add('days',1); @@ -57,20 +59,33 @@ } else { vsd = moment(); } self.viewStartDate = vsd.date(1); + var viewDelta = ({ + 'past' : opts.months-1, + 'today-past' : opts.months-1, + 'any' : opts.months>2?Math.floor(opts.months/2):0, + 'today-future' : 0, + 'future' : 0 + })[this.settings.direction]; + + if (viewDelta && moment().month()==moment(self.viewStartDate).month()){ + self.viewStartDate = moment(self.viewStartDate).subtract({M:viewDelta}).date(1); + } + + if (typeof opts.blackout === 'function') { self.blackout = opts.blackout; } else if (!!opts.blackout) { var bdates = parseDates(opts.blackout, opts.parseSplitDelimiter); self.blackout = function (input) { - input = moment(input).hours(0).minutes(0).seconds(0).milliseconds(0).valueOf(); + input = moment(input).yearDay(); if (input < 1 || !self._sel || self._sel.length < 1) return false; var i = bdates.length; - while (i--) if (bdates[i].valueOf() === input) return true; + while (i--) if (bdates[i].yearDay() === input) return true; return false; } } else { self.blackout = function () {return false;} } @@ -91,12 +106,14 @@ else util.addClassName($cal, classes.monthMiddle); } //title bar $title = util.make('div', {'class':classes.title}, $cal); - util.make('a', {'class':classes.previous}, $title); //previous button - util.make('a', {'class':classes.next}, $title); //next button + util.make('a', {'class':classes.previousYear}, $title); //previous button + util.make('a', {'class':classes.previousMonth}, $title); //previous button + util.make('a', {'class':classes.nextYear}, $title); //next button + util.make('a', {'class':classes.nextMonth}, $title); //next button $caption = util.make('span', {'class':classes.caption}, $title); //title caption //column headers $header = util.make('div', {'class':classes.header}, $cal); i = 0; @@ -124,30 +141,47 @@ self.draw(); util.addEvent($container, 'mousedown', function (event, target) { var clickedDate; - if (util.hasClassName(target, classes.next)) { + if (util.hasClassName(target, classes.nextMonth)) { //NEXT MONTH BUTTON - if (self.publish('view-changed', self, ['next']) !== false) { + if (!self.disableNext && self.publish('view-changed', self, ['next-month']) !== false) { self.viewStartDate.add('months',1); self.draw(); } return false; - } else if (util.hasClassName(target, classes.previous)) { + } else if (util.hasClassName(target, classes.previousMonth)) { //PREVIOUS MONTH BUTTON - if (self.publish('view-changed', self, ['previous']) !== false) { + if (!self.disablePreviousMonth && self.publish('view-changed', self, ['previous-month']) !== false) { self.viewStartDate.subtract('months',1); self.draw(); } return false; + } else if (util.hasClassName(target, classes.nextYear)) { + //NEXT MONTH BUTTON + if (!self.disableNext && self.publish('view-changed', self, ['next-year']) !== false) { + self.viewStartDate.add('years',1); + self.draw(); + } + return false; + } else if (util.hasClassName(target, classes.previousYear)) { + //PREVIOUS MONTH BUTTON + if (!self.disablePreviousMonth && self.publish('view-changed', self, ['previous-year']) !== false) { + self.viewStartDate.subtract('years',1); + self.draw(); + } + return false; + + + } else if (util.hasClassName(target.parentNode, classes.days) && util.hasClassName(target, classes.dayActive) && (clickedDate = target.getAttribute('data-date'))) { //DAY CLICK - clickedDate = moment(clickedDate, opts.dayAttributeFormat); + clickedDate = moment(clickedDate, opts.dayAttributeFormat).hours(12); if (self.publish('date-clicked', self, [clickedDate]) !== false) { switch (opts.mode) { case 'multiple': if (!self.addSelected(clickedDate)) self.removeSelected(clickedDate); @@ -180,10 +214,11 @@ defaults : { attachTo: null, /* the element to attach the root container to. can be string or DOMElement */ months: 1, /* total number of months to display side by side */ weekStart: 0, /* day to use for the start of the week. 0 is Sunday */ direction: 'any', /* past, today-past, any, today-future, future */ + directionScrolling: true, /* if a direction other than any is defined, prevent scrolling out of range */ viewStartDate: null, /* date in the month to display. When multiple months, this is the left most */ blackout: null, /* array of dates, or function to be passed a date */ selected: null, /* dates already selected. can be string, date, or array of strings or dates. */ mode: 'single', /* single, multiple, range */ format: null, /* string used for parsing dates. */ @@ -204,29 +239,40 @@ calendar :'k-calendar', monthFirst :'k-first-month', monthMiddle :'k-middle-month', monthLast :'k-last-month', title :'k-title', - previous :'k-previous', - next :'k-next', + previousMonth :'k-btn-previous-month', + nextMonth :'k-btn-next-month', + previousYear :'k-btn-previous-year', + nextYear :'k-btn-next-year', caption :'k-caption', header :'k-header', days :'k-days', dayOutOfMonth :'k-out-of-month', dayActive :'k-active', daySelected :'k-selected', dayInRange :'k-range', dayToday :'k-today', - monthSeparator :'k-separator' + monthSeparator :'k-separator', + disablePreviousMonth :'k-disable-previous-month-btn', + disableNextMonth :'k-disable-next-month-btn', + disablePreviousYear :'k-disable-previous-year-btn', + disableNextYear :'k-disable-next-year-btn' }, + disablePreviousMonth: false, + disableNextMonth: false, + disablePreviousYear: false, + disableNextYear: false, + directions: { - 'past' :function (date) {return moment(date).valueOf() >= today.valueOf();}, - 'today-past' :function (date) {return moment(date).valueOf() > today.valueOf();}, + 'past' :function (date) {return moment(date).yearDay() >= today.yearDay();}, + 'today-past' :function (date) {return moment(date).yearDay() > today.yearDay();}, 'any' :function (date) {return false;}, - 'today-future' :function (date) {return moment(date).valueOf() < today.valueOf();}, - 'future' :function (date) {return moment(date).valueOf() <= today.valueOf();} + 'today-future' :function (date) {return moment(date).yearDay() < today.yearDay();}, + 'future' :function (date) {return moment(date).yearDay() <= today.yearDay();} }, getSelectedAsDates : function () { var out = []; var i=0, c = this._sel.length; @@ -270,82 +316,82 @@ return sel[0]; } }, isSelected : function (input) { - input = moment(input).hours(0).minutes(0).seconds(0).milliseconds(0).valueOf(); + input = moment(input).yearDay(); if (input < 1 || !this._sel || this._sel.length < 1) return false; switch (this.settings.mode) { case 'range': - var a = this._sel[0] ? this._sel[0].valueOf() : 0, - b = this._sel[1] ? this._sel[1].valueOf() : 0; + var a = this._sel[0] ? this._sel[0].yearDay() : 0, + b = this._sel[1] ? this._sel[1].yearDay() : 0; if (a === input || b === input) return 1; if (!a || !b) return 0; if ((input > a && input < b) || (a<b && input < a && input > b)) return -1; return false; case 'multiple': var i = this._sel.length; while (i--) { - if (this._sel[i].valueOf() === input) { + if (this._sel[i].yearDay() === input) { return true; } } return false; case 'single': /* falls through */ default: - return (this._sel[0] && (this._sel[0].valueOf() === input)); + return (this._sel[0] && (this._sel[0].yearDay() === input)); } return false; }, setSelected : function (input, draw) { this._sel = parseDates(input, this.settings.parseSplitDelimiter, this.settings.format); - this._sel.sort(function (a,b) {return a.valueOf() - b.valueOf();}); + this._sel.sort(function (a,b) {return a.yearDay() - b.yearDay();}); if (draw !== false) this.draw(); }, addSelected : function (date, draw) { - date = moment(date).hours(0).minutes(0).seconds(0).milliseconds(0); + date = moment(date).hours(12); switch (this.settings.mode) { case 'multiple': if (!this.isSelected(date)) this._sel.push(date); else return false; break; case 'range': if (this._sel.length !== 1) this._sel = [date]; else { - if (date.valueOf() > this._sel[0].valueOf()) this._sel[1] = date; + if (date.yearDay() > this._sel[0].yearDay()) this._sel[1] = date; else this._sel = [date, this._sel[0]]; } break; case 'single': /* falls through */ default: this._sel = [date]; break; } - this._sel.sort(function (a,b) {return a.valueOf() - b.valueOf();}); + this._sel.sort(function (a,b) {return a.yearDay() - b.yearDay();}); this.publish('change', this); if (draw !== false) this.draw(); return true; }, removeSelected : function (date, draw) { - date = moment(date).hours(0).minutes(0).seconds(0).milliseconds(0).valueOf(); + date = moment(date).yearDay(); var i = this._sel.length; while (i--) { - if (this._sel[i].valueOf() === date) { + if (this._sel[i].yearDay() === date) { this._sel.splice(i,1); this.publish('change', this); if (draw !== false) this.draw(); return true; } @@ -353,11 +399,11 @@ return false; }, draw : function draw() { // return; - var month = moment(this.viewStartDate), + var month = moment(this.viewStartDate).hours(12), //force middle of the day to avoid any weird date shifts day, classes = this.classes, cal, $span, klass, @@ -367,20 +413,10 @@ dateString, opts = this.settings; c = this.calendars.length; - var viewDelta = ({ - 'past' : c-1, - 'today-past' : c-1, - 'any' : c>2?Math.floor(c/2):0, - 'today-future' : 0, - 'future' : 0 - })[this.settings.direction]; - - if (viewDelta) month = month.subtract({M:viewDelta}); - do { day = moment(month).date(1); day.day( day.day() < this.settings.weekStart ? this.settings.weekStart-7 : this.settings.weekStart); //if the first day of the month is less than our week start, back up a week @@ -397,11 +433,11 @@ if (s) klass.push(({'-1':classes.dayInRange,'1':classes.daySelected, 'true':classes.daySelected})[s]); if (day.month() != month.month()) klass.push(classes.dayOutOfMonth); else if (!(this.blackout(day) || this.direction(day)) || s>0) klass.push(classes.dayActive); - if (Math.floor(today.diff(day, 'days', true)) === 0) klass.push(classes.dayToday); + if (day.yearDay() === today.yearDay()) klass.push(classes.dayToday); dateString = day.format(this.settings.dayAttributeFormat); if (opts.dateClassMap[dateString]) klass.push(opts.dateClassMap[dateString]); $span.innerHTML = day.format(opts.dayNumberFormat); @@ -412,10 +448,56 @@ day.add('days',1); } while (++j < 42); month.add('months',1); } while (++i < c); + if (opts.directionScrolling) { + var diff = -(moment().diff(month, 'months')); + if (opts.direction==='today-past' || opts.direction==='past') { + + if (diff <= 0) { + this.disableNextMonth = false; + util.removeClassName(this.container, classes.disableNextMonth); + } else { + this.disableNextMonth = true; + util.addClassName(this.container, classes.disableNextMonth); + } + + } else if (opts.direction==='today-future' || opts.direction==='future') { + + if (diff > opts.months) { + this.disablePreviousMonth = false; + util.removeClassName(this.container, classes.disablePreviousMonth); + } else { + this.disablePreviousMonth = true; + util.addClassName(this.container, classes.disablePreviousMonth); + } + + } + + + if (opts.direction==='today-past' || opts.direction==='past') { + if (month.add({Y:1}).diff(moment(), 'years') < 0) { + this.disableNextYear = false; + util.removeClassName(this.container, classes.disableNextYear); + } else { + this.disableNextYear = true; + util.addClassName(this.container, classes.disableNextYear); + } + + } else if (opts.direction==='today-future' || opts.direction==='future') { + if (month.subtract({Y:1}).diff(moment(), 'years') > 0) { + this.disablePreviousYear = false; + util.removeClassName(this.container, classes.disablePreviousYear); + } else { + this.disablePreviousYear = true; + util.addClassName(this.container, classes.disablePreviousYear); + } + + } + + } } } var parseDates = function (input, delimiter, format) { var output = []; @@ -424,24 +506,29 @@ input = input.split(delimiter); } else if (!util.isArray(input)) { input = [input]; } - c = input.length; + var c = input.length; i = 0; do { - if (input[i]) output.push( moment(input[i], format).hours(0).minutes(0).seconds(0).milliseconds(0) ); + if (input[i]) output.push( moment(input[i], format).hours(12) ); } while (++i < c); return output; } window.Kalendae = Kalendae; var util = Kalendae.util = { + + isIE8: function() { + return !!( (/msie 8./i).test(navigator.appVersion) && !(/opera/i).test(navigator.userAgent) && window.ActiveXObject && XDomainRequest && !window.msPerformance ); + }, + // ELEMENT FUNCTIONS $: function (elem) { return (typeof elem == 'string') ? document.getElementById(elem) : elem; }, @@ -462,10 +549,20 @@ isVisible: function (elem) { // shamelessly copied from jQuery return elem.offsetWidth > 0 || elem.offsetHeight > 0; }, + getStyle: function (elem, styleProp) { + var y; + if (elem.currentStyle) { + y = elem.currentStyle[styleProp]; + } else if (window.getComputedStyle) { + y = window.getComputedStyle(elem, null)[styleProp]; + } + return y; + }, + domReady:function (f){/in/.test(document.readyState) ? setTimeout(function() {util.domReady(f);},9) : f()}, // Adds a listener callback to a DOM element which is fired on a specified // event. Callback is sent the event object and the element that triggered the event addEvent: function (elem, eventName, callback) { @@ -514,10 +611,17 @@ removeClassName: function(elem, className) { //copied and modified from Prototype.js if (!(elem = util.$(elem))) return; elem.className = util.trimString(elem.className.replace(new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ')); }, + isFixed: function (elem) { + do { + if (util.getStyle(elem, 'position') === 'fixed') return true; + } while ((elem = elem.offsetParent)); + return false; + }, + getPosition: function (elem, isInner) { var x = elem.offsetLeft, y = elem.offsetTop, r = {}; @@ -627,10 +731,18 @@ else overwriteInput = true; //call our parent constructor Kalendae.call(self, opts); + //create the close button + if (opts.closeButton) { + var $closeButton = util.make('a', {'class':classes.closeButton}, self.container) + util.addEvent($closeButton, 'click', function () { + $input.blur(); + }); + } + if (overwriteInput) $input.value = self.getSelected(); var $container = self.container, noclose = false; @@ -668,16 +780,17 @@ Kalendae.Input.prototype = util.merge(Kalendae.prototype, { defaults : util.merge(Kalendae.prototype.defaults, { format: 'MM/DD/YYYY', side: 'bottom', + closeButton: true, offsetLeft: 0, offsetTop: 0 }), classes : util.merge(Kalendae.prototype.classes, { - positioned : 'k-floating' - + positioned : 'k-floating', + closeButton: 'k-btn-close' }), show : function () { var $container = this.container, style = $container.style, @@ -704,10 +817,12 @@ style.left = (pos.left + this.settings.offsetLeft) + 'px'; style.top = (pos.top + util.getHeight($input) + this.settings.offsetTop) + 'px'; break; } + style.position = util.isFixed($input) ? 'fixed' : 'absolute'; + }, hide : function () { this.container.style.display = 'none'; } @@ -799,38 +914,46 @@ subs.splice(len, 1); } } }; -};// Moment.js +};// moment.js // Altered slightly for use in Kalendae.js -// -// (c) 2011 Tim Wood -// Moment.js is freely distributable under the terms of the MIT license. -// -// Version 1.3.0 +// version : 1.5.0 +// author : Tim Wood +// license : MIT +// momentjs.com var moment = Kalendae.moment = (function (Date, undefined) { var moment, round = Math.round, languages = {}, hasModule = (typeof module !== 'undefined'), paramsToParse = 'months|monthsShort|monthsParse|weekdays|weekdaysShort|longDateFormat|calendar|relativeTime|ordinal|meridiem'.split('|'), i, + jsonRegex = /^\/?Date\((\-?\d+)/i, charactersToReplace = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|dddd?|do?|w[o|w]?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|zz?|ZZ?|LT|LL?L?L?)/g, nonuppercaseLetters = /[^A-Z]/g, timezoneRegex = /\([A-Za-z ]+\)|:[0-9]{2} [A-Z]{3} /g, tokenCharacters = /(\\)?(MM?M?M?|dd?d?d|DD?D?D?|YYYY|YY|a|A|hh?|HH?|mm?|ss?|ZZ?|T)/g, inputCharacters = /(\\)?([0-9]+|([a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+|([\+\-]\d\d:?\d\d))/gi, + isoRegex = /\d{4}.\d\d.\d\d(T(\d\d(.\d\d(.\d\d)?)?)?([\+\-]\d\d:?\d\d)?)?/, + isoFormat = 'YYYY-MM-DDTHH:mm:ssZ', + isoTimes = [ + ['HH:mm:ss', /T\d\d:\d\d:\d\d/], + ['HH:mm', /T\d\d:\d\d/], + ['HH', /T\d\d/] + ], timezoneParseRegex = /([\+\-]|\d\d)/gi, - VERSION = "1.3.0", + VERSION = "1.5.0", shortcuts = 'Month|Date|Hours|Minutes|Seconds|Milliseconds'.split('|'); // Moment prototype object - function Moment(date) { + function Moment(date, isUTC) { this._d = date; + this._isUTC = !!isUTC; } // left zero fill a number // see http://jsperf.com/left-zero-filling for performance comparison function leftZeroFill(number, targetLength) { @@ -845,11 +968,11 @@ function dateAddRemove(date, _input, adding, val) { var isString = (typeof _input === 'string'), input = isString ? {} : _input, ms, d, M, currentDate; if (isString && val) { - input[_input] = val; + input[_input] = +val; } ms = (input.ms || input.milliseconds || 0) + (input.s || input.seconds || 0) * 1e3 + // 1000 (input.m || input.minutes || 0) * 6e4 + // 1000 * 60 (input.h || input.hours || 0) * 36e5; // 1000 * 60 * 60 @@ -884,20 +1007,19 @@ function dateFromArray(input) { return new Date(input[0], input[1] || 0, input[2] || 1, input[3] || 0, input[4] || 0, input[5] || 0, input[6] || 0); } // format date using native date object - function formatDate(date, inputString) { - var m = new Moment(date), - currentMonth = m.month(), + function formatMoment(m, inputString) { + var currentMonth = m.month(), currentDate = m.date(), currentYear = m.year(), currentDay = m.day(), currentHours = m.hours(), currentMinutes = m.minutes(), currentSeconds = m.seconds(), - currentZone = m.zone(), + currentZone = -m.zone(), ordinal = moment.ordinal, meridiem = moment.meridiem; // check if the character is a format // return formatted string or non string. // @@ -987,22 +1109,22 @@ return leftZeroFill(currentSeconds, 2); // TIMEZONE case 'zz' : // depreciating 'zz' fall through to 'z' case 'z' : - return (date.toString().match(timezoneRegex) || [''])[0].replace(nonuppercaseLetters, ''); + return (m._d.toString().match(timezoneRegex) || [''])[0].replace(nonuppercaseLetters, ''); case 'Z' : - return (currentZone > 0 ? '+' : '-') + leftZeroFill(~~(Math.abs(currentZone) / 60), 2) + ':' + leftZeroFill(~~(Math.abs(currentZone) % 60), 2); + return (currentZone < 0 ? '-' : '+') + leftZeroFill(~~(Math.abs(currentZone) / 60), 2) + ':' + leftZeroFill(~~(Math.abs(currentZone) % 60), 2); case 'ZZ' : - return (currentZone > 0 ? '+' : '-') + leftZeroFill(~~(10 * Math.abs(currentZone) / 6), 4); + return (currentZone < 0 ? '-' : '+') + leftZeroFill(~~(10 * Math.abs(currentZone) / 6), 4); // LONG DATES case 'L' : case 'LL' : case 'LLL' : case 'LLLL' : case 'LT' : - return formatDate(date, moment.longDateFormat[input]); + return formatMoment(m, moment.longDateFormat[input]); // DEFAULT default : return input.replace(/(^\[)|(\\)|\]$/g, ""); } } @@ -1015,10 +1137,11 @@ timezoneHours = 0, timezoneMinutes = 0, isUsingUTC = false, inputParts = string.match(inputCharacters), formatParts = format.match(tokenCharacters), + len = Math.min(inputParts.length, formatParts.length), i, isPm; // function to convert string input to date function addTime(format, input) { @@ -1089,26 +1212,26 @@ // TIMEZONE case 'Z' : // fall through to ZZ case 'ZZ' : isUsingUTC = true; - a = input.match(timezoneParseRegex); - if (a[1]) { + a = (input || '').match(timezoneParseRegex); + if (a && a[1]) { timezoneHours = ~~a[1]; } - if (a[2]) { + if (a && a[2]) { timezoneMinutes = ~~a[2]; } // reverse offsets - if (a[0] === '-') { + if (a && a[0] === '+') { timezoneHours = -timezoneHours; timezoneMinutes = -timezoneMinutes; } break; } } - for (i = 0; i < formatParts.length; i++) { + for (i = 0; i < len; i++) { addTime(formatParts[i], inputParts[i]); } // handle am pm if (isPm && inArray[3] < 12) { inArray[3] += 12; @@ -1147,47 +1270,141 @@ i, curDate, curScore; for (i = 0; i < formats.length; i++) { curDate = makeDateFromStringAndFormat(string, formats[i]); - curScore = compareArrays(inputParts, formatDate(curDate, formats[i]).match(inputCharacters)); + curScore = compareArrays(inputParts, formatMoment(new Moment(curDate), formats[i]).match(inputCharacters)); if (curScore < scoreToBeat) { scoreToBeat = curScore; output = curDate; } } return output; } + // date from iso format + function makeDateFromString(string) { + var format = 'YYYY-MM-DDT', + i; + if (isoRegex.exec(string)) { + for (i = 0; i < 3; i++) { + if (isoTimes[i][1].exec(string)) { + format += isoTimes[i][0]; + break; + } + } + return makeDateFromStringAndFormat(string, format + 'Z'); + } + return new Date(string); + } + + // helper function for _date.from() and _date.fromNow() + function substituteTimeAgo(string, number, withoutSuffix) { + var rt = moment.relativeTime[string]; + return (typeof rt === 'function') ? + rt(number || 1, !!withoutSuffix, string) : + rt.replace(/%d/i, number || 1); + } + + function relativeTime(milliseconds, withoutSuffix) { + var seconds = round(Math.abs(milliseconds) / 1000), + minutes = round(seconds / 60), + hours = round(minutes / 60), + days = round(hours / 24), + years = round(days / 365), + args = seconds < 45 && ['s', seconds] || + minutes === 1 && ['m'] || + minutes < 45 && ['mm', minutes] || + hours === 1 && ['h'] || + hours < 22 && ['hh', hours] || + days === 1 && ['d'] || + days <= 25 && ['dd', days] || + days <= 45 && ['M'] || + days < 345 && ['MM', round(days / 30)] || + years === 1 && ['y'] || ['yy', years]; + args[2] = withoutSuffix; + return substituteTimeAgo.apply({}, args); + } + moment = function (input, format) { - if (input === null) { + if (input === null || input === '') { return null; } - var date; - // parse UnderscoreDate object + var date, + matched; + // parse Moment object if (input && input._d instanceof Date) { date = new Date(+input._d); // parse string and format } else if (format) { if (isArray(format)) { date = makeDateFromStringAndArray(input, format); } else { date = makeDateFromStringAndFormat(input, format); } - // parse everything else + // evaluate it as a JSON-encoded date } else { + matched = jsonRegex.exec(input); date = input === undefined ? new Date() : + matched ? new Date(+matched[1]) : input instanceof Date ? input : isArray(input) ? dateFromArray(input) : + typeof input === 'string' ? makeDateFromString(input) : new Date(input); } return new Moment(date); }; + // creating with utc + moment.utc = function (input, format) { + if (isArray(input)) { + return new Moment(new Date(Date.UTC.apply({}, input)), true); + } + return (format && input) ? moment(input + ' 0', format + ' Z').utc() : moment(input).utc(); + }; + + // humanizeDuration + moment.humanizeDuration = function (num, type, withSuffix) { + var difference = +num, + rel = moment.relativeTime, + output; + switch (type) { + case "seconds" : + difference *= 1000; // 1000 + break; + case "minutes" : + difference *= 60000; // 60 * 1000 + break; + case "hours" : + difference *= 3600000; // 60 * 60 * 1000 + break; + case "days" : + difference *= 86400000; // 24 * 60 * 60 * 1000 + break; + case "weeks" : + difference *= 604800000; // 7 * 24 * 60 * 60 * 1000 + break; + case "months" : + difference *= 2592000000; // 30 * 24 * 60 * 60 * 1000 + break; + case "years" : + difference *= 31536000000; // 365 * 24 * 60 * 60 * 1000 + break; + default : + withSuffix = !!type; + break; + } + output = relativeTime(difference, !withSuffix); + return withSuffix ? (difference <= 0 ? rel.past : rel.future).replace(/%s/i, output) : output; + }; + // version number moment.version = VERSION; + // default format + moment.defaultFormat = isoFormat; + // language switching and caching moment.lang = function (key, values) { var i, param, req, @@ -1261,38 +1478,15 @@ (b === 2) ? 'nd' : (b === 3) ? 'rd' : 'th'; } }); - // helper function for _date.from() and _date.fromNow() - function substituteTimeAgo(string, number, withoutSuffix) { - var rt = moment.relativeTime[string]; - return (typeof rt === 'function') ? - rt(number || 1, !!withoutSuffix, string) : - rt.replace(/%d/i, number || 1); - } + // compare moment object + moment.isMoment = function (obj) { + return obj instanceof Moment; + }; - function relativeTime(milliseconds, withoutSuffix) { - var seconds = round(Math.abs(milliseconds) / 1000), - minutes = round(seconds / 60), - hours = round(minutes / 60), - days = round(hours / 24), - years = round(days / 365), - args = seconds < 45 && ['s', seconds] || - minutes === 1 && ['m'] || - minutes < 45 && ['mm', minutes] || - hours === 1 && ['h'] || - hours < 22 && ['hh', hours] || - days === 1 && ['d'] || - days <= 25 && ['dd', days] || - days <= 45 && ['M'] || - days < 345 && ['MM', round(days / 30)] || - years === 1 && ['y'] || ['yy', years]; - args[2] = withoutSuffix; - return substituteTimeAgo.apply({}, args); - } - // shortcut for prototype moment.fn = Moment.prototype = { clone : function () { return moment(this); @@ -1300,11 +1494,11 @@ valueOf : function () { return +this._d; }, - nativeDate : function () { + 'native' : function () { return this._d; }, toString : function () { return this._d.toString(); @@ -1312,12 +1506,22 @@ toDate : function () { return this._d; }, + utc : function () { + this._isUTC = true; + return this; + }, + + local : function () { + this._isUTC = false; + return this; + }, + format : function (inputString) { - return formatDate(this._d, inputString); + return formatMoment(this, inputString ? inputString : moment.defaultFormat); }, add : function (input, val) { this._d = dateAddRemove(this._d, input, 1, val); return this; @@ -1328,45 +1532,41 @@ return this; }, diff : function (input, val, asFloat) { var inputMoment = moment(input), - diff = this._d - inputMoment._d, + zoneDiff = (this.zone() - inputMoment.zone()) * 6e4, + diff = this._d - inputMoment._d - zoneDiff, year = this.year() - inputMoment.year(), month = this.month() - inputMoment.month(), - day = this.day() - inputMoment.day(), + date = this.date() - inputMoment.date(), output; if (val === 'months') { - output = year * 12 + month + day / 30; + output = year * 12 + month + date / 30; } else if (val === 'years') { output = year + month / 12; } else { output = val === 'seconds' ? diff / 1e3 : // 1000 val === 'minutes' ? diff / 6e4 : // 1000 * 60 val === 'hours' ? diff / 36e5 : // 1000 * 60 * 60 val === 'days' ? diff / 864e5 : // 1000 * 60 * 60 * 24 val === 'weeks' ? diff / 6048e5 : // 1000 * 60 * 60 * 24 * 7 - val === 'days' ? diff / 3600 : diff; + diff; } return asFloat ? output : round(output); }, from : function (time, withoutSuffix) { - var difference = this.diff(time), - rel = moment.relativeTime, - output = relativeTime(difference, withoutSuffix); - return withoutSuffix ? output : (difference <= 0 ? rel.past : rel.future).replace(/%s/i, output); + return moment.humanizeDuration(this.diff(time), !withoutSuffix); }, fromNow : function (withoutSuffix) { return this.from(moment(), withoutSuffix); }, calendar : function () { - var today = moment(), - todayAtZeroHour = moment([today.year(), today.month(), today.date()]), - diff = this.diff(todayAtZeroHour, 'days', true), + var diff = this.diff(moment().sod(), 'days', true), calendar = moment.calendar, allElse = calendar.sameElse, format = diff < -6 ? allElse : diff < -1 ? calendar.lastWeek : diff < 0 ? calendar.lastDay : @@ -1380,28 +1580,54 @@ var year = this.year(); return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; }, isDST : function () { - return this.zone() !== moment([this.year()]).zone(); + return (this.zone() < moment([this.year()]).zone() || + this.zone() < moment([this.year(), 5]).zone()); }, day : function (input) { var day = this._d.getDay(); - return input == null ? day : + return (typeof input === 'undefined') ? day : this.add({ d : input - day }); + }, + + sod: function () { + return this.clone() + .hours(0) + .minutes(0) + .seconds(0) + .milliseconds(0); + }, + + eod: function () { + // end of day = start of day plus 1 day, minus 1 millisecond + return this.sod().add({ + d : 1, + ms : -1 + }); + }, + + zone : function () { + return this._isUTC ? 0 : this._d.getTimezoneOffset(); + }, + + daysInMonth : function () { + return this.clone().month(this.month() + 1).date(0).date(); } }; // helper for adding shortcuts function makeShortcut(name, key) { moment.fn[name] = function (input) { - if (input != null) { - this._d['set' + key](input); + var utc = this._isUTC ? 'UTC' : ''; + if (typeof input !== 'undefined') { + this._d['set' + utc + key](input); return this; } else { - return this._d['get' + key](); + return this._d['get' + utc + key](); } }; } // loop through and add shortcuts (Month, Date, Hours, Minutes, Seconds, Milliseconds) @@ -1410,18 +1636,27 @@ } // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear') makeShortcut('year', 'FullYear'); - // add shortcut for timezone offset (no setter) - moment.fn.zone = function () { - return this._d.getTimezoneOffset(); - }; - return moment; })(Date); -today = moment().hours(0).minutes(0).seconds(0).milliseconds(0); +//function to reset the date object to 00:00 GMT +moment.fn.stripTime = function () { + this._d = new Date(Math.floor(this._d.valueOf() / 86400000) * 86400000); + return this; +} + + +//function to get the total number of days since the epoch. +moment.fn.yearDay = function (input) { + var yearday = Math.floor(this._d / 86400000); + return (typeof input === 'undefined') ? yearday : + this.add({ d : input - yearday }); +} + +today = moment().stripTime(); if (typeof jQuery !== 'undefined') { jQuery.fn.kalendae = function (options) { this.each(function (i, e) { if (e.tagName === 'INPUT') {