398 lines
14 KiB
JavaScript
398 lines
14 KiB
JavaScript
/*
|
|
* Dateutil
|
|
* - provides formatting, parsing and other utility functions for dates.
|
|
*
|
|
* Copyright (c) 2009 Borgar Þorsteinsson
|
|
* Licensed under the terms of the MIT (LICENSE.txt) software license.
|
|
*
|
|
*/
|
|
/*jslint laxbreak: true, undef: true *//*global module */
|
|
(function(__global__){
|
|
|
|
var SECOND_SIZE = 1000;
|
|
var MINUTE_SIZE = SECOND_SIZE * 60;
|
|
var HOUR_SIZE = MINUTE_SIZE * 60;
|
|
var DAY_SIZE = HOUR_SIZE * 24;
|
|
var WEEK_SIZE = DAY_SIZE * 7;
|
|
var MONTH_SIZE = DAY_SIZE * 30.436875; // average month size
|
|
var YEAR_SIZE = DAY_SIZE * 365.2425; // average year size
|
|
|
|
var _gl = __global__.lang = { 'en': {} };
|
|
var _m = 'January February March April May June July August September October November December'.split(' ');
|
|
var _d = 'Sunday Monday Tuesday Wednesday Thursday Friday Saturday'.split(' ');
|
|
|
|
var method_size = {
|
|
'FullYear': 6, 'Month': 5, 'Date': 4, 'Hours': 3,
|
|
'Minutes': 2, 'Seconds': 1, 'Milliseconds': 0
|
|
};
|
|
var method_map = {
|
|
'yr': 'FullYear',
|
|
'year': 'FullYear',
|
|
'years': 'FullYear',
|
|
'mn': 'Month',
|
|
'month': 'Month',
|
|
'months': 'Month',
|
|
'day': 'Date',
|
|
'days': 'Date',
|
|
'date': 'Date',
|
|
'hr': 'Hours',
|
|
'hour': 'Hours',
|
|
'hours': 'Hours',
|
|
'min': 'Minutes',
|
|
'minute': 'Minutes',
|
|
'minutes': 'Minutes',
|
|
'sec': 'Seconds',
|
|
'second': 'Seconds',
|
|
'seconds': 'Seconds',
|
|
'ms': 'Milliseconds',
|
|
'millisecond': 'Milliseconds',
|
|
'milliseconds': 'Milliseconds'
|
|
};
|
|
|
|
// *****************************************
|
|
// *** *** *** formats & parsers *** *** ***
|
|
// *****************************************
|
|
|
|
var date_parsers = __global__._parsers = {
|
|
|
|
// year + month + day + time
|
|
// -- currently doesn't really support fractions on anything other than seconds >> FIXME
|
|
// -- does not support timezones other than Zulu
|
|
date_and_time: {
|
|
test: /^[+\-]?\d{4,6}(?:(?:\-\d\d){1,2}|\d{4})[T ](?:\d\d)(?::?\d\d){0,2}(?:[\.,]\d+)?(?:Z|[+\-]\d\d(:?\d\d)?)?$/,
|
|
size: 1,
|
|
parse: function ( str ) {
|
|
var b = str.split( /[T ]/ );
|
|
var date = date_parsers.date.parse( b[0] );
|
|
var m = b[1].replace( /:/g, '' )
|
|
.match( /^(\d\d)(\d\d)?(\d\d)?(?:[.,](\d+))?([+\-](?:\d\d){1,2})?/ );
|
|
// TODO: timezone (I have no need for this feature yet)
|
|
// if ( m[5] ) { var zone = m[5] || '0000'; }
|
|
var fs = 0, t = date.getTime() +
|
|
parseInt( m[1], 10 ) * HOUR_SIZE +
|
|
parseInt( m[2] || '0', 10 ) * MINUTE_SIZE +
|
|
parseInt( m[3] || '0', 10 ) * SECOND_SIZE;
|
|
if ( m[3] ) { fs = SECOND_SIZE; }
|
|
else if ( m[2] ) { fs = MINUTE_SIZE; }
|
|
else if ( m[1] ) { fs = HOUR_SIZE; }
|
|
t += parseFloat( '0.' + ( m[4] || '0' ) ) * fs;
|
|
date.setTime( t );
|
|
date.size = 0;
|
|
return date;
|
|
}
|
|
},
|
|
|
|
// year + month + day
|
|
date: {
|
|
test: /^[+\-]?\d{4,6}(?:\-\d\d\-\d\d|-?\d\d\d\d)$/,
|
|
size: DAY_SIZE,
|
|
parse: function ( str ) {
|
|
var s = str.replace( /\D/g, '' ),
|
|
d = __global__.date(
|
|
s.substring( 0, s.length -4 ),
|
|
+s.substr( s.length -4, 2 ) - 1,
|
|
s.substr( s.length -2 )
|
|
);
|
|
d.size = DAY_SIZE;
|
|
return d;
|
|
}
|
|
},
|
|
|
|
// year + month
|
|
year_and_month: {
|
|
test: /^[+\-]?\d{4,6}[\/\-](?:0[1-9]|1[012])$/,
|
|
size: MONTH_SIZE,
|
|
parse: function ( str ) {
|
|
var b = str.split( /[\/\-]/ );
|
|
var d = __global__.date( b[0], +b[1] - 1, 1 );
|
|
d.size = __global__.daysInMonth( d ) * DAY_SIZE;
|
|
return d;
|
|
}
|
|
},
|
|
|
|
// year
|
|
year: {
|
|
test: /^[+\-]?\d{4,6}$/,
|
|
size: YEAR_SIZE,
|
|
parse: function ( str ) {
|
|
var d = __global__.date( str, 0, 1 );
|
|
d.size = DAY_SIZE * ( __global__.isLeapYear( d ) ? 366 : 365 );
|
|
return d;
|
|
}
|
|
},
|
|
|
|
// year + iso week + [day]
|
|
year_and_week: {
|
|
test: /^[+\-]?\d{4,6}\-?[Ww]\d\d(?:\-?\d)?$/,
|
|
size: WEEK_SIZE,
|
|
parse: function ( str ) {
|
|
var s = str.toLowerCase().replace( /[^w\d]/g, '' ).split('w');
|
|
var d = __global__.date( s[0], 0, 3 ); // Jan 3
|
|
d.setUTCDate( 3 - d.getUTCDay() +
|
|
( parseInt( s[1].substr( 0, 2 ), 10 ) - 1 ) * 7 +
|
|
parseInt( s[1].substr( 2, 1 ) || '1', 10 ) );
|
|
d.size = WEEK_SIZE;
|
|
return d;
|
|
}
|
|
},
|
|
|
|
// year + day-of-year
|
|
// -- we don't allow the short form yyyyddd because of ambiguity with yyyymmdd
|
|
// -- 5 letter years would clash with cal-dates: yyyyyddd ~ yyyymmdd
|
|
year_and_ordinal: {
|
|
test: /^[+\-]?\d{4,6}\-[0-3]\d\d$/,
|
|
size: DAY_SIZE,
|
|
parse: function ( str ) {
|
|
var d = new Date(0);
|
|
d.setUTCFullYear( parseInt( str.substr(0,str.length-4), 10 ) );
|
|
d.setDate( parseInt( str.substr(str.length-3), 10 ) );
|
|
d.size = DAY_SIZE;
|
|
return d;
|
|
}
|
|
},
|
|
|
|
// year + quarter
|
|
year_and_quarter: {
|
|
test: /^[+\-]?\d{4,6}\-?[Qq][1-4]$/,
|
|
size: YEAR_SIZE / 4,
|
|
parse: function ( str ) {
|
|
var b = str.split(/\-?[Qq]/),
|
|
d = __global__.date( b[0], ( parseInt( b[1], 10 ) - 1 ) * 3 );
|
|
d.size = DAY_SIZE;
|
|
return d;
|
|
}
|
|
}
|
|
|
|
};
|
|
|
|
var date_formatters = __global__._formats = {
|
|
// Lowercase Ante meridiem and Post meridiem
|
|
a: function (d) { return d.getUTCHours() >= 12 ? 'pm' : 'am'; },
|
|
// Uppercase Ante meridiem and Post meridiem
|
|
A: function (d) { return d.getUTCHours() >= 12 ? 'PM' : 'AM'; },
|
|
// ISO 8601 date
|
|
c: function (d,l) { return __global__.format( d, 'Y-m-d\\TH:i:s.', l ) + __global__.pad( d.getUTCMilliseconds(), 3 ) + 'Z'; },
|
|
// Day of the month, 2 digits with leading zeros
|
|
d: function (d) { return __global__.pad( d.getUTCDate() ); },
|
|
// A textual representation of a day, three letters
|
|
D: function (d,l) { return __global__._( _d[ d.getUTCDay() ].substr( 0, 3 ), l ); },
|
|
// Time zone identifier
|
|
e: function (d) { return 'UTC'; },
|
|
// A full textual representation of a month
|
|
F: function (d,l) { return __global__._( _m[ d.getUTCMonth() ], l ); },
|
|
// 12-hour format of an hour without leading zeros
|
|
g: function (d) { return d.getUTCHours() % 12 || 12; },
|
|
// 24-hour format of an hour without leading zeros
|
|
G: function (d) { return d.getUTCHours(); },
|
|
// 12-hour format of an hour with leading zeros
|
|
h: function (d) { return __global__.pad( d.getUTCHours() % 12 || 12 ); },
|
|
// 24-hour format of an hour with leading zeros
|
|
H: function (d) { return __global__.pad( d.getUTCHours() ); },
|
|
// Minutes with leading zeros
|
|
i: function (d) { return __global__.pad( d.getUTCMinutes() ); },
|
|
// Day of the month without leading zeros
|
|
j: function (d) { return d.getUTCDate(); },
|
|
// A full textual representation of the day of the week
|
|
l: function (d,l) { return __global__._( _d[ d.getUTCDay() ], l ); },
|
|
// Whether it's a leap year (0 = yes, 1 = no)
|
|
L: function (d) { return __global__.isLeapYear( d ) * 1; },
|
|
// Numeric representation of a month, with leading zeros
|
|
m: function (d) { return __global__.pad( d.getUTCMonth() + 1 ); },
|
|
// A short textual representation of a month, three letters
|
|
M: function (d,l) { return __global__._( _m[ d.getUTCMonth() ].substr( 0, 3 ), l ); },
|
|
// Numeric representation of a month, without leading zeros
|
|
n: function (d) { return d.getUTCMonth() + 1; },
|
|
// ISO-8601 numeric representation of the day of the week
|
|
N: function (d) { return d.getUTCDay() || 7; },
|
|
// ISO-8601 year number
|
|
o: function (d) { return __global__.pad( __global__.isocalendar(d)[0], 4 ); },
|
|
// Time zone designator
|
|
O: function (d) { return '+0000'; },
|
|
// Time zone difference
|
|
P: function (d) { return '+00:00'; },
|
|
// Quarter of the year
|
|
q: function (d) { return ~~( d.getUTCMonth() / 3 ) + 1; },
|
|
// RFC 2822 formatted date
|
|
r: function (d,l) { return __global__.format( d, 'D, d M Y H:i:s O', l ); },
|
|
// Seconds, with leading zeros
|
|
s: function (d) { return __global__.pad( d.getUTCSeconds() ); },
|
|
// English ordinal suffix for the day of the month, 2 characters
|
|
S: function (d) {
|
|
var a = d.getUTCDate() % 10, b = d.getUTCDate() % 100;
|
|
return (a === 1) && (b !== 11) && 'st' ||
|
|
(a === 2) && (b !== 12) && 'nd' ||
|
|
(a === 3) && (b !== 13) && 'rd' || 'th';
|
|
},
|
|
// Number of days in the given month
|
|
t: function (d) { return __global__.daysInMonth( d ); },
|
|
// Time zone abbreviation
|
|
T: function (d) { return 'UTC'; },
|
|
// Microseconds
|
|
u: function (d) { return __global__.pad( d.getUTCMilliseconds(), 6 ); },
|
|
// Seconds since the Unix Epoch
|
|
U: function (d) { return ~~( d / 1000 ); },
|
|
// Numeric representation of the day of the week
|
|
w: function (d) { return d.getUTCDay(); },
|
|
// ISO-8601 week number of year, weeks starting on Monday
|
|
W: function (d) { return __global__.pad( __global__.isocalendar(d)[1] ); },
|
|
// A short numeric representation of a year, 2 digits
|
|
y: function (d) { return (d.getUTCFullYear() + '').substr(2); },
|
|
// A full numeric representation of a year, 4 digits
|
|
Y: function (d) { return d.getUTCFullYear(); },
|
|
// The day of the year (starting from 0)
|
|
z: function (d) { return Math.floor( ( d - (new Date(Date.UTC(d.getUTCFullYear(), 0, 1))) ) / DAY_SIZE ); }
|
|
};
|
|
|
|
// **************************************
|
|
// *** *** *** module methods *** *** ***
|
|
// **************************************
|
|
|
|
__global__.date = function ( y, m, d, h, n, s, ms ) {
|
|
var ts = ( !arguments.length ) ? __global__.now()
|
|
: Date.UTC( parseInt( y||0, 10 ), parseInt( m||0, 10 ), parseInt( d||1, 10 ),
|
|
parseInt( h||0, 10 ), parseInt( n||0, 10 ), parseInt( s||0, 10 ),
|
|
parseInt( ms||0, 10 ) );
|
|
return new Date( ts );
|
|
};
|
|
|
|
|
|
// zero pad a string n to l places
|
|
__global__.pad = function ( n, l ) {
|
|
var s = __global__.pad.z;
|
|
if ( !s ) { // This mess is here because JSlint breaks on new Array(999)
|
|
var a = []; a[999] = '';
|
|
s = __global__.pad.z = a.join('0');
|
|
}
|
|
s += n;
|
|
return s.substring( s.length -( l || 2 ) );
|
|
};
|
|
|
|
// is a given year a leap year
|
|
__global__.isLeapYear = function ( y ) {
|
|
if ( y instanceof Date ) { y = y.getUTCFullYear(); }
|
|
return (( y % 4 === 0 ) && ( y % 100 !== 0 )) || ( y % 400 === 0 );
|
|
};
|
|
|
|
|
|
// return the number of days in a date's month
|
|
__global__.daysInMonth = function ( dt ) {
|
|
var m = dt.getUTCMonth();
|
|
if ( m === 1 ) {
|
|
return __global__.isLeapYear( dt ) ? 29 : 28;
|
|
}
|
|
return [ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ][ m ];
|
|
};
|
|
|
|
|
|
// return a 3-tuple, (ISO year, ISO week number, ISO weekday).
|
|
__global__.isocalendar = function ( dt ) {
|
|
var d = dt.getUTCDay();
|
|
var t = new Date( dt.valueOf() );
|
|
t.setDate( t.getDate() - ((d + 6) % 7) + 3 );
|
|
var iso_year = t.getUTCFullYear();
|
|
var w = Math.floor( (t.getTime() - __global__.date(iso_year, 0, 1, -6)) / 86400000 );
|
|
return [ iso_year, 1+Math.floor(w/7), d||7 ];
|
|
};
|
|
|
|
|
|
// Allow setting multiple properties at once using object notation:
|
|
// `mydate.set({ hour: 8, minute: 12, second: 0 });`
|
|
__global__.set = function ( dt, values ) {
|
|
if ( typeof values === 'object' ) {
|
|
var s = [], n, i;
|
|
// step 1: collect a list of values to modify
|
|
for ( var key in values ) {
|
|
if ( key in method_map ) {
|
|
n = method_map[ key ];
|
|
s.push( [ values[ key ], n, method_size[ n ] ] );
|
|
}
|
|
}
|
|
// step 2: order values by size
|
|
s = s.sort(function ( a, b ) { return a[2] - b[2]; });
|
|
// step 3: little endian value zeroing
|
|
for (i=0; i<s.length; i++) {
|
|
dt[ 'setUTC' + s[i][1] ]( s[i][1] === 'Date' ? 1 : 0 );
|
|
}
|
|
// step 4: big endian value setting
|
|
for (i=s.length; i--; ) {
|
|
dt[ 'setUTC' + s[i][1] ]( s[i][0] );
|
|
}
|
|
}
|
|
return dt;
|
|
};
|
|
|
|
|
|
// parse a date
|
|
__global__.parse = function ( str ) {
|
|
var d;
|
|
if ( typeof str !== 'string' ) {
|
|
throw new Error( "dateutil parser can't parse non-strings." );
|
|
}
|
|
for ( var dtype in date_parsers ) {
|
|
if ( date_parsers[dtype].test.test( str ) ) {
|
|
d = date_parsers[ dtype ].parse( str );
|
|
d.type = dtype;
|
|
d.size = d.size || 0;
|
|
break;
|
|
}
|
|
}
|
|
// default parser supports RFC and a few more, or returns an "invalid date"
|
|
if ( !d ) {
|
|
d = new Date( str );
|
|
d.size = 0;
|
|
d.type = 'unknown_date';
|
|
}
|
|
return d;
|
|
};
|
|
|
|
|
|
// format a date to string
|
|
__global__.format = function ( d, fmt, lang ) {
|
|
if ( arguments.length === 1 && this instanceof Date ) {
|
|
fmt = d;
|
|
d = this;
|
|
}
|
|
else if ( !(d instanceof Date) ) {
|
|
throw new Error('No date passed to format.');
|
|
}
|
|
for ( var r=[], c, l=fmt.length, i=0; i<l; i++ ) {
|
|
c = fmt.charAt( i );
|
|
// format characters
|
|
if ( c !== '\\' ) {
|
|
r.push( (c in date_formatters) ? date_formatters[ c ]( d, lang ) : c );
|
|
}
|
|
// escaped characters & unreconized characters
|
|
else {
|
|
c = i < fmt.length ? fmt.charAt( ++i ) : c;
|
|
r.push( c );
|
|
}
|
|
}
|
|
return r.join( '' );
|
|
};
|
|
|
|
|
|
// return a Date object for the current date (0 time)
|
|
__global__.today = function () {
|
|
return __global__.set( __global__.date(), {
|
|
hour: 0, minute: 0, second: 0, millisecond: 0
|
|
});
|
|
};
|
|
|
|
|
|
// return timestamp for the moment
|
|
__global__.now = ( typeof Date.now === 'function' )
|
|
? Date.now
|
|
: function () { return +new Date(); };
|
|
|
|
|
|
// translation hook
|
|
__global__._ = function ( s, lang ) {
|
|
var l = lang && __global__.lang[ lang ];
|
|
return ( l && s in l ) ? l[s] : s;
|
|
};
|
|
|
|
}(
|
|
(typeof module !== 'undefined' && module.exports)
|
|
? module.exports
|
|
: (this.dateutil = {})
|
|
)); |