1010'use strict' ;
1111
1212var d3 = require ( 'd3' ) ;
13- var isNumeric = require ( 'fast-isnumeric' ) ;
1413
1514var logError = require ( './loggers' ) . error ;
1615
@@ -21,6 +20,11 @@ var ONEHOUR = constants.ONEHOUR;
2120var ONEMIN = constants . ONEMIN ;
2221var ONESEC = constants . ONESEC ;
2322
23+ var DATETIME_REGEXP = / ^ \s * ( - ? \d \d \d \d | \d \d ) ( - ( 0 ? [ 1 - 9 ] | 1 [ 0 1 2 ] ) ( - ( [ 0 - 3 ] ? \d ) ( [ T t ] ( [ 0 1 ] ? \d | 2 [ 0 - 3 ] ) ( : ( [ 0 - 5 ] \d ) ( : ( [ 0 - 5 ] \d ( \. \d + ) ? ) ) ? ( Z | z | [ + \- ] \d \d : ? \d \d ) ? ) ? ) ? ) ? ) ? \s * $ / m;
24+
25+ // for 2-digit years, the first year we map them onto
26+ var YFIRST = new Date ( ) . getFullYear ( ) - 70 ;
27+
2428// is an object a javascript date?
2529exports . isJSDate = function ( v ) {
2630 return typeof v === 'object' && v !== null && typeof v . getTime === 'function' ;
@@ -32,20 +36,33 @@ exports.isJSDate = function(v) {
3236var MIN_MS , MAX_MS ;
3337
3438/**
35- * dateTime2ms - turn a date object or string s of the form
36- * YYYY-mm-dd HH:MM:SS.sss into milliseconds (relative to 1970-01-01,
37- * per javascript standard)
38- * may truncate after any full field, and sss can be any length
39- * even >3 digits, though javascript dates truncate to milliseconds
40- * returns BADNUM if it doesn't find a date
39+ * dateTime2ms - turn a date object or string s into milliseconds
40+ * (relative to 1970-01-01, per javascript standard)
41+ *
42+ * Returns BADNUM if it doesn't find a date
43+ *
44+ * strings should have the form:
45+ *
46+ * -?YYYY-mm-dd<sep>HH:MM:SS.sss<tzInfo>?
47+ *
48+ * <sep>: space (our normal standard) or T or t (ISO-8601)
49+ * <tzInfo>: Z, z, or [+\-]HH:?MM and we THROW IT AWAY
50+ * this format comes from https://tools.ietf.org/html/rfc3339#section-5.6
51+ * but we allow it even with a space as the separator
52+ *
53+ * May truncate after any full field, and sss can be any length
54+ * even >3 digits, though javascript dates truncate to milliseconds,
55+ * we keep as much as javascript numeric precision can hold, but we only
56+ * report back up to 100 microsecond precision, because most dates support
57+ * this precision (close to 1970 support more, very far away support less)
4158 *
4259 * Expanded to support negative years to -9999 but you must always
4360 * give 4 digits, except for 2-digit positive years which we assume are
4461 * near the present time.
4562 * Note that we follow ISO 8601:2004: there *is* a year 0, which
4663 * is 1BC/BCE, and -1===2BC etc.
4764 *
48- * 2-digit to 4 -digit year conversion, where to cut off ?
65+ * Where to cut off 2 -digit years between 1900s and 2000s ?
4966 * from http://support.microsoft.com/kb/244664:
5067 * 1930-2029 (the most retro of all...)
5168 * but in my mac chrome from eg. d=new Date(Date.parse('8/19/50')):
@@ -77,89 +94,31 @@ exports.dateTime2ms = function(s) {
7794 // otherwise only accept strings and numbers
7895 if ( typeof s !== 'string' && typeof s !== 'number' ) return BADNUM ;
7996
80- var y , m , d , h ;
81- // split date and time parts
82- // TODO: we strip leading/trailing whitespace but not other
83- // characters like we do for numbers - do we want to?
84- var datetime = String ( s ) . trim ( ) . split ( ' ' ) ;
85- if ( datetime . length > 2 ) return BADNUM ;
86-
87- var p = datetime [ 0 ] . split ( '-' ) ; // date part
88-
89- var CE = true ; // common era, ie positive year
90- if ( p [ 0 ] === '' ) {
91- // first part is blank: year starts with a minus sign
92- CE = false ;
93- p . splice ( 0 , 1 ) ;
94- }
95-
96- var plen = p . length ;
97- if ( plen > 3 || ( plen !== 3 && datetime [ 1 ] ) || ! plen ) return BADNUM ;
98-
99- // year
100- if ( p [ 0 ] . length === 4 ) y = Number ( p [ 0 ] ) ;
101- else if ( p [ 0 ] . length === 2 ) {
102- if ( ! CE ) return BADNUM ;
103- var yNow = new Date ( ) . getFullYear ( ) ;
104- y = ( ( Number ( p [ 0 ] ) - yNow + 70 ) % 100 + 200 ) % 100 + yNow - 70 ;
97+ var match = String ( s ) . match ( DATETIME_REGEXP ) ;
98+ if ( ! match ) return BADNUM ;
99+ var y = match [ 1 ] ,
100+ m = Number ( match [ 3 ] || 1 ) ,
101+ d = Number ( match [ 5 ] || 1 ) ,
102+ H = Number ( match [ 7 ] || 0 ) ,
103+ M = Number ( match [ 9 ] || 0 ) ,
104+ S = Number ( match [ 11 ] || 0 ) ;
105+ if ( y . length === 2 ) {
106+ y = ( Number ( y ) + 2000 - YFIRST ) % 100 + YFIRST ;
105107 }
106- else return BADNUM ;
107- if ( ! isNumeric ( y ) ) return BADNUM ;
108+ else y = Number ( y ) ;
108109
109110 // javascript takes new Date(0..99,m,d) to mean 1900-1999, so
110111 // to support years 0-99 we need to use setFullYear explicitly
111- var baseDate = new Date ( 0 , 0 , 1 ) ;
112- baseDate . setFullYear ( CE ? y : - y ) ;
113- if ( p . length > 1 ) {
112+ var date = new Date ( 2000 , m - 1 , d , H , M ) ;
113+ date . setFullYear ( y ) ;
114114
115- // month - may be 1 or 2 digits
116- m = Number ( p [ 1 ] ) - 1 ; // new Date() uses zero-based months
117- if ( p [ 1 ] . length > 2 || ! ( m >= 0 && m <= 11 ) ) return BADNUM ;
118- baseDate . setMonth ( m ) ;
115+ if ( date . getDate ( ) !== d ) return BADNUM ;
119116
120- if ( p . length > 2 ) {
117+ // does that hour exist in this day? (Daylight time!)
118+ // (TODO: remove this check when we move to UTC)
119+ if ( date . getHours ( ) !== H ) return BADNUM ;
121120
122- // day - may be 1 or 2 digits
123- d = Number ( p [ 2 ] ) ;
124- if ( p [ 2 ] . length > 2 || ! ( d >= 1 && d <= 31 ) ) return BADNUM ;
125- baseDate . setDate ( d ) ;
126-
127- // does that date exist in this month?
128- if ( baseDate . getDate ( ) !== d ) return BADNUM ;
129-
130- if ( datetime [ 1 ] ) {
131-
132- p = datetime [ 1 ] . split ( ':' ) ;
133- if ( p . length > 3 ) return BADNUM ;
134-
135- // hour - may be 1 or 2 digits
136- h = Number ( p [ 0 ] ) ;
137- if ( p [ 0 ] . length > 2 || ! p [ 0 ] . length || ! ( h >= 0 && h <= 23 ) ) return BADNUM ;
138- baseDate . setHours ( h ) ;
139-
140- // does that hour exist in this day? (Daylight time!)
141- // (TODO: remove this check when we move to UTC)
142- if ( baseDate . getHours ( ) !== h ) return BADNUM ;
143-
144- if ( p . length > 1 ) {
145- d = baseDate . getTime ( ) ;
146-
147- // minute - must be 2 digits
148- m = Number ( p [ 1 ] ) ;
149- if ( p [ 1 ] . length !== 2 || ! ( m >= 0 && m <= 59 ) ) return BADNUM ;
150- d += ONEMIN * m ;
151- if ( p . length === 2 ) return d ;
152-
153- // second (and milliseconds) - must have 2-digit seconds
154- if ( p [ 2 ] . split ( '.' ) [ 0 ] . length !== 2 ) return BADNUM ;
155- s = Number ( p [ 2 ] ) ;
156- if ( ! ( s >= 0 && s < 60 ) ) return BADNUM ;
157- return d + s * ONESEC ;
158- }
159- }
160- }
161- }
162- return baseDate . getTime ( ) ;
121+ return date . getTime ( ) + S * ONESEC ;
163122} ;
164123
165124MIN_MS = exports . MIN_MS = exports . dateTime2ms ( '-9999' ) ;
0 commit comments