| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955 |
- /*
- * Utilities: A classic collection of JavaScript utilities
- * Copyright 2112 Matthew Eernisse ([email protected])
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- */
- var string = require('./string')
- , date
- , log = require('./log');
- /**
- @name date
- @namespace date
- */
- date = new (function () {
- var _this = this
- , _date = new Date();
- var _US_DATE_PAT = /^(\d{1,2})(?:\-|\/|\.)(\d{1,2})(?:\-|\/|\.)(\d{4})/;
- var _DATETIME_PAT = /^(\d{4})(?:\-|\/|\.)(\d{1,2})(?:\-|\/|\.)(\d{1,2})(?:T| )?(\d{2})?(?::)?(\d{2})?(?::)?(\d{2})?(?:\.)?(\d+)?(?: *)?(Z|[+-]\d{4}|[+-]\d{2}:\d{2}|[+-]\d{2})?/;
- // TODO Add am/pm parsing instead of dumb, 24-hour clock.
- var _TIME_PAT = /^(\d{1,2})?(?::)?(\d{2})?(?::)?(\d{2})?(?:\.)?(\d+)?$/;
- var _dateMethods = [
- 'FullYear'
- , 'Month'
- , 'Date'
- , 'Hours'
- , 'Minutes'
- , 'Seconds'
- , 'Milliseconds'
- ];
- var _isArray = function (obj) {
- return obj &&
- typeof obj === 'object' &&
- typeof obj.length === 'number' &&
- typeof obj.splice === 'function' &&
- !(obj.propertyIsEnumerable('length'));
- };
- this.weekdayLong = ['Sunday', 'Monday', 'Tuesday',
- 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
- this.weekdayShort = ['Sun', 'Mon', 'Tue', 'Wed',
- 'Thu', 'Fri', 'Sat'];
- this.monthLong = ['January', 'February', 'March',
- 'April', 'May', 'June', 'July', 'August', 'September',
- 'October', 'November', 'December'];
- this.monthShort = ['Jan', 'Feb', 'Mar', 'Apr',
- 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
- this.meridiem = {
- 'AM': 'AM',
- 'PM': 'PM'
- }
- // compat
- this.meridian = this.meridiem
- /**
- @name date#supportedFormats
- @public
- @object
- @description List of supported strftime formats
- */
- this.supportedFormats = {
- // abbreviated weekday name according to the current locale
- 'a': function (dt) { return _this.weekdayShort[dt.getDay()]; },
- // full weekday name according to the current locale
- 'A': function (dt) { return _this.weekdayLong[dt.getDay()]; },
- // abbreviated month name according to the current locale
- 'b': function (dt) { return _this.monthShort[dt.getMonth()]; },
- 'h': function (dt) { return _this.strftime(dt, '%b'); },
- // full month name according to the current locale
- 'B': function (dt) { return _this.monthLong[dt.getMonth()]; },
- // preferred date and time representation for the current locale
- 'c': function (dt) { return _this.strftime(dt, '%a %b %d %T %Y'); },
- // century number (the year divided by 100 and truncated
- // to an integer, range 00 to 99)
- 'C': function (dt) { return _this.calcCentury(dt.getFullYear());; },
- // day of the month as a decimal number (range 01 to 31)
- 'd': function (dt) { return string.lpad(dt.getDate(), '0', 2); },
- // same as %m/%d/%y
- 'D': function (dt) { return _this.strftime(dt, '%m/%d/%y') },
- // day of the month as a decimal number, a single digit is
- // preceded by a space (range ' 1' to '31')
- 'e': function (dt) { return string.lpad(dt.getDate(), ' ', 2); },
- // month as a decimal number, a single digit is
- // preceded by a space (range ' 1' to '12')
- 'f': function () { return _this.strftimeNotImplemented('f'); },
- // same as %Y-%m-%d
- 'F': function (dt) { return _this.strftime(dt, '%Y-%m-%d'); },
- // like %G, but without the century.
- 'g': function () { return _this.strftimeNotImplemented('g'); },
- // The 4-digit year corresponding to the ISO week number
- // (see %V). This has the same format and value as %Y,
- // except that if the ISO week number belongs to the
- // previous or next year, that year is used instead.
- 'G': function () { return _this.strftimeNotImplemented('G'); },
- // hour as a decimal number using a 24-hour clock (range
- // 00 to 23)
- 'H': function (dt) { return string.lpad(dt.getHours(), '0', 2); },
- // hour as a decimal number using a 12-hour clock (range
- // 01 to 12)
- 'I': function (dt) { return string.lpad(
- _this.hrMil2Std(dt.getHours()), '0', 2); },
- // day of the year as a decimal number (range 001 to 366)
- 'j': function (dt) { return string.lpad(
- _this.calcDays(dt), '0', 3); },
- // Hour as a decimal number using a 24-hour clock (range
- // 0 to 23 (space-padded))
- 'k': function (dt) { return string.lpad(dt.getHours(), ' ', 2); },
- // Hour as a decimal number using a 12-hour clock (range
- // 1 to 12 (space-padded))
- 'l': function (dt) { return string.lpad(
- _this.hrMil2Std(dt.getHours()), ' ', 2); },
- // month as a decimal number (range 01 to 12)
- 'm': function (dt) { return string.lpad((dt.getMonth()+1), '0', 2); },
- // minute as a decimal number
- 'M': function (dt) { return string.lpad(dt.getMinutes(), '0', 2); },
- // Linebreak
- 'n': function () { return '\n'; },
- // either `am' or `pm' according to the given time value,
- // or the corresponding strings for the current locale
- 'p': function (dt) { return _this.getMeridian(dt.getHours()); },
- // time in a.m. and p.m. notation
- 'r': function (dt) { return _this.strftime(dt, '%I:%M:%S %p'); },
- // time in 24 hour notation
- 'R': function (dt) { return _this.strftime(dt, '%H:%M'); },
- // second as a decimal number
- 'S': function (dt) { return string.lpad(dt.getSeconds(), '0', 2); },
- // Tab char
- 't': function () { return '\t'; },
- // current time, equal to %H:%M:%S
- 'T': function (dt) { return _this.strftime(dt, '%H:%M:%S'); },
- // weekday as a decimal number [1,7], with 1 representing
- // Monday
- 'u': function (dt) { return _this.convertOneBase(dt.getDay()); },
- // week number of the current year as a decimal number,
- // starting with the first Sunday as the first day of the
- // first week
- 'U': function () { return _this.strftimeNotImplemented('U'); },
- // week number of the year (Monday as the first day of the
- // week) as a decimal number [01,53]. If the week containing
- // 1 January has four or more days in the new year, then it
- // is considered week 1. Otherwise, it is the last week of
- // the previous year, and the next week is week 1.
- 'V': function () { return _this.strftimeNotImplemented('V'); },
- // week number of the current year as a decimal number,
- // starting with the first Monday as the first day of the
- // first week
- 'W': function () { return _this.strftimeNotImplemented('W'); },
- // day of the week as a decimal, Sunday being 0
- 'w': function (dt) { return dt.getDay(); },
- // preferred date representation for the current locale
- // without the time
- 'x': function (dt) { return _this.strftime(dt, '%D'); },
- // preferred time representation for the current locale
- // without the date
- 'X': function (dt) { return _this.strftime(dt, '%T'); },
- // year as a decimal number without a century (range 00 to
- // 99)
- 'y': function (dt) { return _this.getTwoDigitYear(dt.getFullYear()); },
- // year as a decimal number including the century
- 'Y': function (dt) { return string.lpad(dt.getFullYear(), '0', 4); },
- // time zone or name or abbreviation
- 'z': function () { return _this.strftimeNotImplemented('z'); },
- 'Z': function () { return _this.strftimeNotImplemented('Z'); },
- // Literal percent char
- '%': function (dt) { return '%'; }
- };
- /**
- @name date#getSupportedFormats
- @public
- @function
- @description return the list of formats in a string
- @return {String} The list of supported formats
- */
- this.getSupportedFormats = function () {
- var str = '';
- for (var i in this.supportedFormats) { str += i; }
- return str;
- }
- this.supportedFormatsPat = new RegExp('%[' +
- this.getSupportedFormats() + ']{1}', 'g');
- /**
- @name date#strftime
- @public
- @function
- @return {String} The `dt` formated with the given `format`
- @description Formats the given date with the strftime formated
- @param {Date} dt the date object to format
- @param {String} format the format to convert the date to
- */
- this.strftime = function (dt, format) {
- if (!dt) { return '' }
- var d = dt;
- var pats = [];
- var dts = [];
- var str = format;
- var key;
- // Allow either Date obj or UTC stamp
- d = typeof dt == 'number' ? new Date(dt) : dt;
- // Grab all instances of expected formats into array
- while (pats = this.supportedFormatsPat.exec(format)) {
- dts.push(pats[0]);
- }
- // Process any hits
- for (var i = 0; i < dts.length; i++) {
- key = dts[i].replace(/%/, '');
- str = str.replace('%' + key,
- this.supportedFormats[key](d));
- }
- return str;
- };
- this.strftimeNotImplemented = function (s) {
- throw('this.strftime format "' + s + '" not implemented.');
- };
- /**
- @name date#calcCentury
- @public
- @function
- @return {String} The century for the given date
- @description Find the century for the given `year`
- @param {Number} year The year to find the century for
- */
- this.calcCentury = function (year) {
- if(!year) {
- year = _date.getFullYear();
- }
- var ret = parseInt((year / 100) + 1);
- year = year.toString();
- // If year ends in 00 subtract one, because it's still the century before the one
- // it divides to
- if (year.substring(year.length - 2) === '00') {
- ret--;
- }
- return ret.toString();
- };
- /**
- @name date#calcDays
- @public
- @function
- @return {Number} The number of days so far for the given date
- @description Calculate the day number in the year a particular date is on
- @param {Date} dt The date to use
- */
- this.calcDays = function (dt) {
- var first = new Date(dt.getFullYear(), 0, 1);
- var diff = 0;
- var ret = 0;
- first = first.getTime();
- diff = (dt.getTime() - first);
- ret = parseInt(((((diff/1000)/60)/60)/24))+1;
- return ret;
- };
- /**
- * Adjust from 0-6 base week to 1-7 base week
- * @param d integer for day of week
- * @return Integer day number for 1-7 base week
- */
- this.convertOneBase = function (d) {
- return d == 0 ? 7 : d;
- };
- this.getTwoDigitYear = function (yr) {
- // Add a millenium to take care of years before the year 1000,
- // (e.g, the year 7) since we're only taking the last two digits
- // If we overshoot, it doesn't matter
- var millenYear = yr + 1000;
- var str = millenYear.toString();
- str = str.substr(2); // Get the last two digits
- return str
- };
- /**
- @name date#getMeridiem
- @public
- @function
- @return {String} Return 'AM' or 'PM' based on hour in 24-hour format
- @description Return 'AM' or 'PM' based on hour in 24-hour format
- @param {Number} h The hour to check
- */
- this.getMeridiem = function (h) {
- return h > 11 ? this.meridiem.PM :
- this.meridiem.AM;
- };
- // Compat
- this.getMeridian = this.getMeridiem;
- /**
- @name date#hrMil2Std
- @public
- @function
- @return {String} Return a 12 hour version of the given time
- @description Convert a 24-hour formatted hour to 12-hour format
- @param {String} hour The hour to convert
- */
- this.hrMil2Std = function (hour) {
- var h = typeof hour == 'number' ? hour : parseInt(hour);
- var str = h > 12 ? h - 12 : h;
- str = str == 0 ? 12 : str;
- return str;
- };
- /**
- @name date#hrStd2Mil
- @public
- @function
- @return {String} Return a 24 hour version of the given time
- @description Convert a 12-hour formatted hour with meridian flag to 24-hour format
- @param {String} hour The hour to convert
- @param {Boolean} pm hour is PM then this should be true
- */
- this.hrStd2Mil = function (hour, pm) {
- var h = typeof hour == 'number' ? hour : parseInt(hour);
- var str = '';
- // PM
- if (pm) {
- str = h < 12 ? (h+12) : h;
- }
- // AM
- else {
- str = h == 12 ? 0 : h;
- }
- return str;
- };
- // Constants for use in this.add
- var dateParts = {
- YEAR: 'year'
- , MONTH: 'month'
- , DAY: 'day'
- , HOUR: 'hour'
- , MINUTE: 'minute'
- , SECOND: 'second'
- , MILLISECOND: 'millisecond'
- , QUARTER: 'quarter'
- , WEEK: 'week'
- , WEEKDAY: 'weekday'
- };
- // Create a map for singular/plural lookup, e.g., day/days
- var datePartsMap = {};
- for (var p in dateParts) {
- datePartsMap[dateParts[p]] = dateParts[p];
- datePartsMap[dateParts[p] + 's'] = dateParts[p];
- }
- this.dateParts = dateParts;
- /**
- @name date#add
- @public
- @function
- @return {Date} Incremented date
- @description Add to a Date in intervals of different size, from
- milliseconds to years
- @param {Date} dt Date (or timestamp Number), date to increment
- @param {String} interv a constant representing the interval,
- e.g. YEAR, MONTH, DAY. See this.dateParts
- @param {Number} incr how much to add to the date
- */
- this.add = function (dt, interv, incr) {
- if (typeof dt == 'number') { dt = new Date(dt); }
- function fixOvershoot() {
- if (sum.getDate() < dt.getDate()) {
- sum.setDate(0);
- }
- }
- var key = datePartsMap[interv];
- var sum = new Date(dt);
- switch (key) {
- case dateParts.YEAR:
- sum.setFullYear(dt.getFullYear()+incr);
- // Keep increment/decrement from 2/29 out of March
- fixOvershoot();
- break;
- case dateParts.QUARTER:
- // Naive quarter is just three months
- incr*=3;
- // fallthrough...
- case dateParts.MONTH:
- sum.setMonth(dt.getMonth()+incr);
- // Reset to last day of month if you overshoot
- fixOvershoot();
- break;
- case dateParts.WEEK:
- incr*=7;
- // fallthrough...
- case dateParts.DAY:
- sum.setDate(dt.getDate() + incr);
- break;
- case dateParts.WEEKDAY:
- //FIXME: assumes Saturday/Sunday weekend, but even this is not fixed.
- // There are CLDR entries to localize this.
- var dat = dt.getDate();
- var weeks = 0;
- var days = 0;
- var strt = 0;
- var trgt = 0;
- var adj = 0;
- // Divide the increment time span into weekspans plus leftover days
- // e.g., 8 days is one 5-day weekspan / and two leftover days
- // Can't have zero leftover days, so numbers divisible by 5 get
- // a days value of 5, and the remaining days make up the number of weeks
- var mod = incr % 5;
- if (mod == 0) {
- days = (incr > 0) ? 5 : -5;
- weeks = (incr > 0) ? ((incr-5)/5) : ((incr+5)/5);
- }
- else {
- days = mod;
- weeks = parseInt(incr/5);
- }
- // Get weekday value for orig date param
- strt = dt.getDay();
- // Orig date is Sat / positive incrementer
- // Jump over Sun
- if (strt == 6 && incr > 0) {
- adj = 1;
- }
- // Orig date is Sun / negative incrementer
- // Jump back over Sat
- else if (strt == 0 && incr < 0) {
- adj = -1;
- }
- // Get weekday val for the new date
- trgt = strt + days;
- // New date is on Sat or Sun
- if (trgt == 0 || trgt == 6) {
- adj = (incr > 0) ? 2 : -2;
- }
- // Increment by number of weeks plus leftover days plus
- // weekend adjustments
- sum.setDate(dat + (7*weeks) + days + adj);
- break;
- case dateParts.HOUR:
- sum.setHours(sum.getHours()+incr);
- break;
- case dateParts.MINUTE:
- sum.setMinutes(sum.getMinutes()+incr);
- break;
- case dateParts.SECOND:
- sum.setSeconds(sum.getSeconds()+incr);
- break;
- case dateParts.MILLISECOND:
- sum.setMilliseconds(sum.getMilliseconds()+incr);
- break;
- default:
- // Do nothing
- break;
- }
- return sum; // Date
- };
- /**
- @name date#diff
- @public
- @function
- @return {Number} number of (interv) units apart that
- the two dates are
- @description Get the difference in a specific unit of time (e.g., number
- of months, weeks, days, etc.) between two dates.
- @param {Date} date1 First date to check
- @param {Date} date2 Date to compate `date1` with
- @param {String} interv a constant representing the interval,
- e.g. YEAR, MONTH, DAY. See this.dateParts
- */
- this.diff = function (date1, date2, interv) {
- // date1
- // Date object or Number equivalent
- //
- // date2
- // Date object or Number equivalent
- //
- // interval
- // A constant representing the interval, e.g. YEAR, MONTH, DAY. See this.dateParts.
- // Accept timestamp input
- if (typeof date1 == 'number') { date1 = new Date(date1); }
- if (typeof date2 == 'number') { date2 = new Date(date2); }
- var yeaDiff = date2.getFullYear() - date1.getFullYear();
- var monDiff = (date2.getMonth() - date1.getMonth()) + (yeaDiff * 12);
- var msDiff = date2.getTime() - date1.getTime(); // Millisecs
- var secDiff = msDiff/1000;
- var minDiff = secDiff/60;
- var houDiff = minDiff/60;
- var dayDiff = houDiff/24;
- var weeDiff = dayDiff/7;
- var delta = 0; // Integer return value
- var key = datePartsMap[interv];
- switch (key) {
- case dateParts.YEAR:
- delta = yeaDiff;
- break;
- case dateParts.QUARTER:
- var m1 = date1.getMonth();
- var m2 = date2.getMonth();
- // Figure out which quarter the months are in
- var q1 = Math.floor(m1/3) + 1;
- var q2 = Math.floor(m2/3) + 1;
- // Add quarters for any year difference between the dates
- q2 += (yeaDiff * 4);
- delta = q2 - q1;
- break;
- case dateParts.MONTH:
- delta = monDiff;
- break;
- case dateParts.WEEK:
- // Truncate instead of rounding
- // Don't use Math.floor -- value may be negative
- delta = parseInt(weeDiff);
- break;
- case dateParts.DAY:
- delta = dayDiff;
- break;
- case dateParts.WEEKDAY:
- var days = Math.round(dayDiff);
- var weeks = parseInt(days/7);
- var mod = days % 7;
- // Even number of weeks
- if (mod == 0) {
- days = weeks*5;
- }
- else {
- // Weeks plus spare change (< 7 days)
- var adj = 0;
- var aDay = date1.getDay();
- var bDay = date2.getDay();
- weeks = parseInt(days/7);
- mod = days % 7;
- // Mark the date advanced by the number of
- // round weeks (may be zero)
- var dtMark = new Date(date1);
- dtMark.setDate(dtMark.getDate()+(weeks*7));
- var dayMark = dtMark.getDay();
- // Spare change days -- 6 or less
- if (dayDiff > 0) {
- switch (true) {
- // Range starts on Sat
- case aDay == 6:
- adj = -1;
- break;
- // Range starts on Sun
- case aDay == 0:
- adj = 0;
- break;
- // Range ends on Sat
- case bDay == 6:
- adj = -1;
- break;
- // Range ends on Sun
- case bDay == 0:
- adj = -2;
- break;
- // Range contains weekend
- case (dayMark + mod) > 5:
- adj = -2;
- break;
- default:
- // Do nothing
- break;
- }
- }
- else if (dayDiff < 0) {
- switch (true) {
- // Range starts on Sat
- case aDay == 6:
- adj = 0;
- break;
- // Range starts on Sun
- case aDay == 0:
- adj = 1;
- break;
- // Range ends on Sat
- case bDay == 6:
- adj = 2;
- break;
- // Range ends on Sun
- case bDay == 0:
- adj = 1;
- break;
- // Range contains weekend
- case (dayMark + mod) < 0:
- adj = 2;
- break;
- default:
- // Do nothing
- break;
- }
- }
- days += adj;
- days -= (weeks*2);
- }
- delta = days;
- break;
- case dateParts.HOUR:
- delta = houDiff;
- break;
- case dateParts.MINUTE:
- delta = minDiff;
- break;
- case dateParts.SECOND:
- delta = secDiff;
- break;
- case dateParts.MILLISECOND:
- delta = msDiff;
- break;
- default:
- // Do nothing
- break;
- }
- // Round for fractional values and DST leaps
- return Math.round(delta); // Number (integer)
- };
- /**
- @name date#parse
- @public
- @function
- @return {Date} a JavaScript Date object
- @description Convert various sorts of strings to JavaScript
- Date objects
- @param {String} val The string to convert to a Date
- */
- this.parse = function (val, options) {
- var dt
- , opts = options || {}
- , matches
- , reordered
- , off
- , posOff
- , offHours
- , offMinutes
- , offSeconds
- , curr
- , stamp
- , utc;
- // Yay, we have a date, use it as-is
- if (val instanceof Date || typeof val.getFullYear == 'function') {
- dt = val;
- }
- // Timestamp?
- else if (typeof val == 'number') {
- dt = new Date(val);
- }
- // String or Array
- else {
- // Value preparsed, looks like [yyyy, mo, dd, hh, mi, ss, ms, (offset?)]
- if (_isArray(val)) {
- matches = val;
- matches.unshift(null);
- matches[8] = null;
- }
- // Oh, crap, it's a string -- parse this bitch
- else if (typeof val == 'string') {
- matches = val.match(_DATETIME_PAT);
- // Stupid US-only format?
- if (!matches) {
- matches = val.match(_US_DATE_PAT);
- if (matches) {
- reordered = [matches[0], matches[3], matches[1], matches[2]];
- // Pad the results to the same length as ISO8601
- reordered[8] = null;
- matches = reordered;
- }
- }
- // Time-stored-in-Date hack?
- if (!matches) {
- matches = val.match(_TIME_PAT);
- if (matches) {
- reordered = [matches[0], 0, 1, 0, matches[1],
- matches[2], matches[3], matches[4], null];
- matches = reordered;
- }
- }
- }
- // Sweet, the regex actually parsed it into something useful
- if (matches) {
- matches.shift(); // First match is entire match, DO NOT WANT
- off = matches.pop();
- // If there's an offset (or the 'Z' non-offset offset), use UTC
- // methods to set everything
- if (off) {
- if (off == 'Z') {
- utc = true;
- offSeconds = 0;
- }
- else {
- utc = false;
- // Convert from extended to basic if necessary
- off = off.replace(/:/g, '');
- // '+0000' will still be zero
- if (parseInt(off, 10) === 0) {
- utc = true;
- }
- else {
- posOff = off.indexOf('+') === 0;
- // Strip plus or minus
- off = off.substr(1);
- offHours = parseInt(off.substr(0, 2), 10);
- offMinutes = off.substr(2, 2);
- if (offMinutes) {
- offMinutes = parseInt(offMinutes, 10);
- }
- else {
- offMinutes = 0;
- }
- offSeconds = off.substr(4, 2);
- if (offSeconds) {
- offSeconds = parseInt(offSeconds, 10);
- }
- else {
- offSeconds = 0;
- }
- offSeconds += (offMinutes * 60)
- offSeconds += (offHours * 60 * 60);
- if (!posOff) {
- offSeconds = 0 - offSeconds;
- }
- }
- }
- }
- dt = new Date(0);
- // Stupid zero-based months
- matches[1] = parseInt(matches[1], 10) - 1;
- // Specific offset, iterate the array and set each date property
- // using UTC setters, then adjust time using offset
- if (off) {
- for (var i = matches.length - 1; i > -1; i--) {
- curr = parseInt(matches[i], 10) || 0;
- dt['setUTC' + _dateMethods[i]](curr);
- }
- // Add any offset
- dt.setSeconds(dt.getSeconds() - offSeconds);
- }
- // Otherwise we know nothing about the offset, just iterate the
- // array and set each date property using regular setters
- else {
- var lastValIndex;
- for (var i = matches.length - 1; i > -1; i--) {
- if (matches[i]) {
- curr = parseInt(matches[i], 10);
- if (typeof lastValIndex == 'undefined') {
- lastValIndex = i;
- }
- }
- else {
- curr = 0;
- }
- dt['set' + _dateMethods[i]](curr);
- }
- if (opts.setMax) {
- for (var i = lastValIndex + 1, ii = matches.length; i < ii; i++) {
- switch (i) {
- case 3:
- dt['set' + _dateMethods[i]](23);
- break;
- case 4:
- case 5:
- dt['set' + _dateMethods[i]](59);
- break;
- case 6:
- dt.setMilliseconds(999);
- break;
- }
- }
- }
- }
- }
- // Shit, last-ditch effort using Date.parse
- else {
- stamp = Date.parse(val);
- // Failures to parse yield NaN
- if (!isNaN(stamp)) {
- dt = new Date(stamp);
- }
- }
- }
- return dt || null;
- };
- /**
- @name date#relativeTime
- @public
- @function
- @return {String} A string describing the amount of time ago
- the passed-in Date is
- @description Convert a Date to an English sentence representing
- how long ago the Date was
- @param {Date} dt The Date to to convert to a relative time string
- @param {Object} [opts]
- @param {Boolean} [opts.abbreviated=false] Use short strings
- (e.g., '<1m') for the relative-time string
- */
- this.relativeTime = function (dt, options) {
- var opts = options || {}
- , now = opts.now || new Date()
- , abbr = opts.abbreviated || false
- , format = opts.format || '%F %T'
- // Diff in seconds
- , diff = (now.getTime() - dt.getTime()) / 1000
- , ret
- , num
- , hour = 60*60
- , day = 24*hour
- , week = 7*day
- , month = 30*day;
- switch (true) {
- case diff < 60:
- ret = abbr ? '<1m' : 'less than a minute ago';
- break;
- case diff < 120:
- ret = abbr ? '1m' : 'about a minute ago';
- break;
- case diff < (45*60):
- num = parseInt((diff / 60), 10);
- ret = abbr ? num + 'm' : num + ' minutes ago';
- break;
- case diff < (2*hour):
- ret = abbr ? '1h' : 'about an hour ago';
- break;
- case diff < (1*day):
- num = parseInt((diff / hour), 10);
- ret = abbr ? num + 'h' : 'about ' + num + ' hours ago';
- break;
- case diff < (2*day):
- ret = abbr ? '1d' : 'one day ago';
- break;
- case diff < (7*day):
- num = parseInt((diff / day), 10);
- ret = abbr ? num + 'd' : 'about ' + num + ' days ago';
- break;
- case diff < (11*day):
- ret = abbr ? '1w': 'one week ago';
- break;
- case diff < (1*month):
- num = Math.round(diff / week);
- ret = abbr ? num + 'w' : 'about ' + num + ' weeks ago';
- break;
- default:
- ret = date.strftime(dt, format);
- break;
- }
- return ret;
- };
- /**
- @name date#toISO8601
- @public
- @function
- @return {String} A string describing the amount of time ago
- @description Convert a Date to an ISO8601-formatted string
- @param {Date} dt The Date to to convert to an ISO8601 string
- */
- var _pad = function (n) {
- return n < 10 ? '0' + n : n;
- };
- this.toISO8601 = function (dt, options) {
- var opts = options || {}
- , off = dt.getTimezoneOffset()
- , offHours
- , offMinutes
- , str = this.strftime(dt, '%F') + 'T'
- + this.strftime(dt, '%T') + '.'
- + string.lpad(dt.getMilliseconds(), '0', 3);
- if (opts.tz) {
- // Pos and neg numbers are both truthy; only
- // zero is falsy
- if (off && !opts.utc) {
- str += off > 0 ? '-' : '+';
- offHours = parseInt(off / 60, 10);
- str += string.lpad(offHours, '0', 2);
- offMinutes = off % 60;
- if (offMinutes) {
- str += string.lpad(offMinutes, '0', 2);
- }
- }
- else {
- str += 'Z';
- }
- }
- return str;
- };
- // Alias
- this.toIso8601 = this.toISO8601;
- this.toUTC = function (dt) {
- return new Date(
- dt.getUTCFullYear()
- , dt.getUTCMonth()
- , dt.getUTCDate()
- , dt.getUTCHours()
- , dt.getUTCMinutes()
- , dt.getUTCSeconds()
- , dt.getUTCMilliseconds());
- };
- })();
- module.exports = date;
|