11
11
12
12
var d3 = require ( 'd3' ) ;
13
13
var isNumeric = require ( 'fast-isnumeric' ) ;
14
-
14
+ var BADNUM = require ( './constants' ) . BADNUM ;
15
15
16
16
/**
17
17
* dateTime2ms - turn a date object or string s of the form
18
18
* YYYY-mm-dd HH:MM:SS.sss into milliseconds (relative to 1970-01-01,
19
19
* per javascript standard)
20
20
* may truncate after any full field, and sss can be any length
21
21
* even >3 digits, though javascript dates truncate to milliseconds
22
- * returns false if it doesn't find a date
22
+ * returns BADNUM if it doesn't find a date
23
+ *
24
+ * Expanded to support negative years to -9999 but you must always
25
+ * give 4 digits, except for 2-digit positive years which we assume are
26
+ * near the present time.
27
+ * Note that we follow ISO 8601:2004: there *is* a year 0, which
28
+ * is 1BC/BCE, and -1===2BC etc.
23
29
*
24
30
* 2-digit to 4-digit year conversion, where to cut off?
25
31
* from http://support.microsoft.com/kb/244664:
26
32
* 1930-2029 (the most retro of all...)
27
33
* but in my mac chrome from eg. d=new Date(Date.parse('8/19/50')):
28
34
* 1950-2049
29
35
* by Java, from http://stackoverflow.com/questions/2024273/:
30
- * now-80 - now+20
36
+ * now-80 - now+19
31
37
* or FileMaker Pro, from
32
38
* http://www.filemaker.com/12help/html/add_view_data.4.21.html:
33
- * now-70 - now+30
39
+ * now-70 - now+29
34
40
* but python strptime etc, via
35
41
* http://docs.python.org/py3k/library/time.html:
36
42
* 1969-2068 (super forward-looking, but static, not sliding!)
@@ -39,8 +45,8 @@ var isNumeric = require('fast-isnumeric');
39
45
* they can learn the hard way not to use 2-digit years, as no choice we
40
46
* make now will cover all possibilities. mostly this will all be taken
41
47
* care of in initial parsing, should only be an issue for hand-entered data
42
- * currently (2012 ) this range is:
43
- * 1942-2041
48
+ * currently (2016 ) this range is:
49
+ * 1946-2045
44
50
*/
45
51
46
52
exports . dateTime2ms = function ( s ) {
@@ -49,63 +55,96 @@ exports.dateTime2ms = function(s) {
49
55
if ( s . getTime ) return + s ;
50
56
}
51
57
catch ( e ) {
52
- return false ;
58
+ return BADNUM ;
53
59
}
54
60
55
61
var y , m , d , h ;
56
62
// split date and time parts
57
63
var datetime = String ( s ) . split ( ' ' ) ;
58
- if ( datetime . length > 2 ) return false ;
64
+ if ( datetime . length > 2 ) return BADNUM ;
59
65
60
66
var p = datetime [ 0 ] . split ( '-' ) ; // date part
61
- if ( p . length > 3 || ( p . length !== 3 && datetime [ 1 ] ) ) return false ;
67
+
68
+ var CE = true ; // common era, ie positive year
69
+ if ( p [ 0 ] === '' ) {
70
+ // first part is blank: year starts with a minus sign
71
+ CE = false ;
72
+ p . splice ( 0 , 1 ) ;
73
+ }
74
+
75
+ if ( p . length > 3 || ( p . length !== 3 && datetime [ 1 ] ) ) return BADNUM ;
62
76
63
77
// year
64
78
if ( p [ 0 ] . length === 4 ) y = Number ( p [ 0 ] ) ;
65
79
else if ( p [ 0 ] . length === 2 ) {
80
+ if ( ! CE ) return BADNUM ;
66
81
var yNow = new Date ( ) . getFullYear ( ) ;
67
82
y = ( ( Number ( p [ 0 ] ) - yNow + 70 ) % 100 + 200 ) % 100 + yNow - 70 ;
68
83
}
69
- else return false ;
70
- if ( ! isNumeric ( y ) ) return false ;
71
- if ( p . length === 1 ) return new Date ( y , 0 , 1 ) . getTime ( ) ; // year only
72
-
73
- // month
74
- m = Number ( p [ 1 ] ) - 1 ; // new Date() uses zero-based months
75
- if ( p [ 1 ] . length > 2 || ! ( m >= 0 && m <= 11 ) ) return false ;
76
- if ( p . length === 2 ) return new Date ( y , m , 1 ) . getTime ( ) ; // year-month
77
-
78
- // day
79
- d = Number ( p [ 2 ] ) ;
80
- if ( p [ 2 ] . length > 2 || ! ( d >= 1 && d <= 31 ) ) return false ;
81
-
82
- // now save the date part
83
- d = new Date ( y , m , d ) . getTime ( ) ;
84
- if ( ! datetime [ 1 ] ) return d ; // year-month-day
85
- p = datetime [ 1 ] . split ( ':' ) ;
86
- if ( p . length > 3 ) return false ;
87
-
88
- // hour
89
- h = Number ( p [ 0 ] ) ;
90
- if ( p [ 0 ] . length > 2 || ! ( h >= 0 && h <= 23 ) ) return false ;
91
- d += 3600000 * h ;
92
- if ( p . length === 1 ) return d ;
93
-
94
- // minute
95
- m = Number ( p [ 1 ] ) ;
96
- if ( p [ 1 ] . length > 2 || ! ( m >= 0 && m <= 59 ) ) return false ;
97
- d += 60000 * m ;
98
- if ( p . length === 2 ) return d ;
99
-
100
- // second
101
- s = Number ( p [ 2 ] ) ;
102
- if ( ! ( s >= 0 && s < 60 ) ) return false ;
103
- return d + s * 1000 ;
84
+ else return BADNUM ;
85
+ if ( ! isNumeric ( y ) ) return BADNUM ;
86
+
87
+ // javascript takes new Date(0..99,m,d) to mean 1900-1999, so
88
+ // to support years 0-99 we need to use setFullYear explicitly
89
+ var baseDate = new Date ( 0 , 0 , 1 ) ;
90
+ baseDate . setFullYear ( CE ? y : - y ) ;
91
+ if ( p . length > 1 ) {
92
+
93
+ // month
94
+ m = Number ( p [ 1 ] ) - 1 ; // new Date() uses zero-based months
95
+ if ( p [ 1 ] . length > 2 || ! ( m >= 0 && m <= 11 ) ) return BADNUM ;
96
+ baseDate . setMonth ( m ) ;
97
+
98
+ if ( p . length > 2 ) {
99
+
100
+ // day
101
+ d = Number ( p [ 2 ] ) ;
102
+ if ( p [ 2 ] . length > 2 || ! ( d >= 1 && d <= 31 ) ) return BADNUM ;
103
+ baseDate . setDate ( d ) ;
104
+
105
+ // does that date exist in this month?
106
+ if ( baseDate . getDate ( ) !== d ) return BADNUM ;
107
+
108
+ if ( datetime [ 1 ] ) {
109
+
110
+ p = datetime [ 1 ] . split ( ':' ) ;
111
+ if ( p . length > 3 ) return BADNUM ;
112
+
113
+ // hour
114
+ h = Number ( p [ 0 ] ) ;
115
+ if ( p [ 0 ] . length > 2 || ! ( h >= 0 && h <= 23 ) ) return BADNUM ;
116
+ baseDate . setHours ( h ) ;
117
+
118
+ // does that hour exist in this day? (Daylight time!)
119
+ // (TODO: remove this check when we move to UTC)
120
+ if ( baseDate . getHours ( ) !== h ) return BADNUM ;
121
+
122
+ if ( p . length > 1 ) {
123
+ d = baseDate . getTime ( ) ;
124
+
125
+ // minute
126
+ m = Number ( p [ 1 ] ) ;
127
+ if ( p [ 1 ] . length > 2 || ! ( m >= 0 && m <= 59 ) ) return BADNUM ;
128
+ d += 60000 * m ;
129
+ if ( p . length === 2 ) return d ;
130
+
131
+ // second (and milliseconds)
132
+ s = Number ( p [ 2 ] ) ;
133
+ if ( ! ( s >= 0 && s < 60 ) ) return BADNUM ;
134
+ return d + s * 1000 ;
135
+ }
136
+ }
137
+ }
138
+ }
139
+ return baseDate . getTime ( ) ;
104
140
} ;
105
141
142
+ exports . MIN_MS = exports . dateTime2ms ( '-9999' ) ;
143
+ exports . MAX_MS = exports . dateTime2ms ( '9999' ) ;
144
+
106
145
// is string s a date? (see above)
107
146
exports . isDateTime = function ( s ) {
108
- return ( exports . dateTime2ms ( s ) !== false ) ;
147
+ return ( exports . dateTime2ms ( s ) !== BADNUM ) ;
109
148
} ;
110
149
111
150
// pad a number with zeroes, to given # of digits before the decimal point
@@ -123,6 +162,8 @@ function lpad(val, digits) {
123
162
exports . ms2DateTime = function ( ms , r ) {
124
163
if ( ! r ) r = 0 ;
125
164
165
+ if ( ms < exports . MIN_MS || ms > exports . MAX_MS ) return BADNUM ;
166
+
126
167
var d = new Date ( ms ) ,
127
168
s = d3 . time . format ( '%Y-%m-%d' ) ( d ) ;
128
169
0 commit comments