-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
Copy pathlib_date_test.js
471 lines (426 loc) · 20.7 KB
/
lib_date_test.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
var isNumeric = require('fast-isnumeric');
var Lib = require('@src/lib');
var calComponent = require('@src/components/calendars');
// use only the parts of world-calendars that we've imported for our tests
var calendars = require('@src/components/calendars/calendars');
describe('dates', function() {
'use strict';
var d1c = new Date(2000, 0, 1, 1, 0, 0, 600);
// first-century years must be set separately as Date constructor maps 2-digit years
// to near the present, but we accept them as part of 4-digit years
d1c.setFullYear(13);
var thisYear = new Date().getFullYear(),
thisYear_2 = thisYear % 100,
nowMinus70 = thisYear - 70,
nowMinus70_2 = nowMinus70 % 100,
nowPlus29 = thisYear + 29,
nowPlus29_2 = nowPlus29 % 100;
describe('dateTime2ms', function() {
it('should accept valid date strings', function() {
var tzOffset;
[
['2016', new Date(2016, 0, 1)],
['2016-05', new Date(2016, 4, 1)],
// leap year, and whitespace
['\r\n\t 2016-02-29\r\n\t ', new Date(2016, 1, 29)],
['9814-08-23', new Date(9814, 7, 23)],
['1564-03-14 12', new Date(1564, 2, 14, 12)],
['0122-04-08 08:22', new Date(122, 3, 8, 8, 22)],
['-0098-11-19 23:59:59', new Date(-98, 10, 19, 23, 59, 59)],
['-9730-12-01 12:34:56.789', new Date(-9730, 11, 1, 12, 34, 56, 789)],
// random whitespace before and after gets stripped
['\r\n\t -9730-12-01 12:34:56.789\r\n\t ', new Date(-9730, 11, 1, 12, 34, 56, 789)],
// first century, also allow month, day, and hour to be 1-digit, and not all
// three digits of milliseconds
['0013-1-1 1:00:00.6', d1c],
// we support tenths of msec too, though Date objects don't. Smaller than that
// and we hit the precision limit of js numbers unless we're close to the epoch.
// It won't break though.
['0013-1-1 1:00:00.6001', +d1c + 0.1],
['0013-1-1 1:00:00.60011111111', +d1c + 0.11111111],
// 2-digit years get mapped to now-70 -> now+29
[thisYear_2 + '-05', new Date(thisYear, 4, 1)],
[nowMinus70_2 + '-10-18', new Date(nowMinus70, 9, 18)],
[nowPlus29_2 + '-02-12 14:29:32', new Date(nowPlus29, 1, 12, 14, 29, 32)],
// including timezone info (that we discard)
['2014-03-04 08:15Z', new Date(2014, 2, 4, 8, 15)],
['2014-03-04 08:15:00.00z', new Date(2014, 2, 4, 8, 15)],
['2014-03-04 08:15:34+1200', new Date(2014, 2, 4, 8, 15, 34)],
['2014-03-04 08:15:34.567-05:45', new Date(2014, 2, 4, 8, 15, 34, 567)],
].forEach(function(v) {
// just for sub-millisecond precision tests, use timezoneoffset
// from the previous date object
if(v[1].getTimezoneOffset) tzOffset = v[1].getTimezoneOffset();
var expected = +v[1] - (tzOffset * 60000);
expect(Lib.dateTime2ms(v[0])).toBe(expected, v[0]);
// ISO-8601: all the same stuff with t or T as the separator
expect(Lib.dateTime2ms(v[0].trim().replace(' ', 't'))).toBe(expected, v[0].trim().replace(' ', 't'));
expect(Lib.dateTime2ms('\r\n\t ' + v[0].trim().replace(' ', 'T') + '\r\n\t ')).toBe(expected, v[0].trim().replace(' ', 'T'));
});
});
it('should accept 4-digit and 2-digit numbers', function() {
// not sure if we really *want* this behavior, but it's what we have.
// especially since the number 0 is *not* allowed it seems pretty unlikely
// to cause problems for people using milliseconds as dates, since the only
// values to get mistaken are between 1 and 10 seconds before and after
// the epoch, and between 10 and 99 milliseconds after the epoch
// (note that millisecond numbers are not handled by dateTime2ms directly,
// but in ax.d2c if dateTime2ms fails.)
[
1000, 9999, -1000, -9999
].forEach(function(v) {
expect(Lib.dateTime2ms(v)).toBe(Date.UTC(v, 0, 1), v);
});
[
[10, 2010],
[nowPlus29_2, nowPlus29],
[nowMinus70_2, nowMinus70],
[99, 1999]
].forEach(function(v) {
expect(Lib.dateTime2ms(v[0])).toBe(Date.UTC(v[1], 0, 1), v[0]);
});
});
it('should accept Date objects within year +/-9999', function() {
[
new Date(),
new Date(-9999, 0, 1),
new Date(9999, 11, 31, 23, 59, 59, 999),
new Date(-1, 0, 1),
new Date(323, 11, 30),
new Date(-456, 1, 2),
d1c,
new Date(2015, 8, 7, 23, 34, 45, 567)
].forEach(function(v) {
expect(Lib.dateTime2ms(v)).toBe(+v - v.getTimezoneOffset() * 60000);
});
});
it('should not accept Date objects beyond our limits', function() {
[
new Date(10000, 0, 1),
new Date(-10000, 11, 31, 23, 59, 59, 999)
].forEach(function(v) {
expect(Lib.dateTime2ms(v)).toBeUndefined(v);
});
});
it('should not accept invalid strings or other objects', function() {
[
'', 0, 1, 9, -1, -10, -99, 100, 999, -100, -999, 10000, -10000,
1.2, -1.2, 2015.1, -1023.4, NaN, null, undefined, Infinity, -Infinity,
{}, {1: '2014-01-01'}, [], [2016], ['2015-11-23'],
'123-01-01', '-756-01-01', // 3-digit year
'10000-01-01', '-10000-01-01', // 5-digit year
'2015-00-01', '2015-13-01', '2015-001-01', // bad month
'2015-01-00', '2015-01-32', '2015-02-29', '2015-04-31', '2015-01-001', // bad day (incl non-leap year)
'2015-01-01 24:00', '2015-01-01 -1:00', '2015-01-01 001:00', // bad hour
'2015-01-01 12:60', '2015-01-01 12:-1', '2015-01-01 12:001', '2015-01-01 12:1', // bad minute
'2015-01-01 12:00:60', '2015-01-01 12:00:-1', '2015-01-01 12:00:001', '2015-01-01 12:00:1', // bad second
'2015-01-01T', '2015-01-01TT12:34', // bad ISO separators
'2015-01-01Z', '2015-01-01T12Z', '2015-01-01T12:34Z05:00', '2015-01-01 12:34+500', '2015-01-01 12:34-5:00' // bad TZ info
].forEach(function(v) {
expect(Lib.dateTime2ms(v)).toBeUndefined(v);
});
});
var JULY1MS = 181 * 24 * 3600 * 1000;
it('should use UTC with no timezone offset or daylight saving time', function() {
expect(Lib.dateTime2ms('1970-01-01')).toBe(0);
// 181 days (and no DST hours) between jan 1 and july 1 in a non-leap-year
// 31 + 28 + 31 + 30 + 31 + 30
expect(Lib.dateTime2ms('1970-07-01')).toBe(JULY1MS);
});
it('should interpret JS dates by local time, not by its getTime()', function() {
// not really part of the test, just to make sure the test is meaningful
// the test should NOT be run in a UTC environment
var local0 = Number(new Date(1970, 0, 1)),
localjuly1 = Number(new Date(1970, 6, 1));
expect([local0, localjuly1]).not.toEqual([0, JULY1MS],
'test must not run in UTC');
// verify that there *is* daylight saving time in the test environment
expect(localjuly1 - local0).not.toEqual(JULY1MS - 0,
'test must run in a timezone with DST');
// now repeat the previous test and show that we throw away
// timezone info from js dates
expect(Lib.dateTime2ms(new Date(1970, 0, 1))).toBe(0);
expect(Lib.dateTime2ms(new Date(1970, 6, 1))).toBe(JULY1MS);
});
});
describe('ms2DateTime', function() {
it('should report the minimum fields with nonzero values, except minutes', function() {
[
'2016-01-01', // we'll never report less than this bcs month and day are never zero
'2016-01-01 01:00', // we won't report hours without minutes
'2016-01-01 01:01',
'2016-01-01 01:01:01',
'2016-01-01 01:01:01.1',
'2016-01-01 01:01:01.01',
'2016-01-01 01:01:01.001',
'2016-01-01 01:01:01.0001'
].forEach(function(v) {
expect(Lib.ms2DateTime(Lib.dateTime2ms(v))).toBe(v);
});
});
it('should accept Date objects within year +/-9999', function() {
[
'-9999-01-01',
'-9999-01-01 00:00:00.0001',
'9999-12-31 23:59:59.9999',
'0123-01-01',
'0042-01-01',
'-0016-01-01',
'-0016-01-01 12:34:56.7891',
'-0456-07-23 16:22'
].forEach(function(v) {
expect(Lib.ms2DateTime(Lib.dateTime2ms(v))).toBe(v);
});
});
it('should not accept Date objects beyond our limits or other objects', function() {
[
Date.UTC(10000, 0, 1),
Date.UTC(-10000, 11, 31, 23, 59, 59, 999),
'',
'2016-01-01',
'0',
[], [0], {}, {1: 2}
].forEach(function(v) {
expect(Lib.ms2DateTime(v)).toBeUndefined(v);
});
});
it('should drop the right pieces if rounding is specified', function() {
[
['2016-01-01 00:00:00.0001', 0, '2016-01-01 00:00:00.0001'],
['2016-01-01 00:00:00.0001', 299999, '2016-01-01 00:00:00.0001'],
['2016-01-01 00:00:00.0001', 300000, '2016-01-01'],
['2016-01-01 00:00:00.0001', 7776000000, '2016-01-01'],
['2016-01-01 12:34:56.7891', 0, '2016-01-01 12:34:56.7891'],
['2016-01-01 12:34:56.7891', 299999, '2016-01-01 12:34:56.7891'],
['2016-01-01 12:34:56.7891', 300000, '2016-01-01 12:34:56'],
['2016-01-01 12:34:56.7891', 10799999, '2016-01-01 12:34:56'],
['2016-01-01 12:34:56.7891', 10800000, '2016-01-01 12:34'],
['2016-01-01 12:34:56.7891', 7775999999, '2016-01-01 12:34'],
['2016-01-01 12:34:56.7891', 7776000000, '2016-01-01'],
['2016-01-01 12:34:56.7891', 1e300, '2016-01-01']
].forEach(function(v) {
expect(Lib.ms2DateTime(Lib.dateTime2ms(v[0]), v[1])).toBe(v[2], v);
});
});
it('should work right with inputs beyond our precision', function() {
for(var i = -1; i <= 1; i += 0.001) {
var tenths = Math.round(i * 10),
base = i < -0.05 ? '1969-12-31 23:59:59.99' : '1970-01-01 00:00:00.00',
expected = (base + String(tenths + 200).substr(1))
.replace(/0+$/, '')
.replace(/ 00:00:00[\.]$/, '');
expect(Lib.ms2DateTime(i)).toBe(expected, i);
}
});
});
describe('world calendar inputs', function() {
it('should give the right values near epoch zero', function() {
[
[undefined, '1970-01-01'],
['gregorian', '1970-01-01'],
['chinese', '1969-11-24'],
['coptic', '1686-04-23'],
['discworld', '1798-12-27'],
['ethiopian', '1962-04-23'],
['hebrew', '5730-10-23'],
['islamic', '1389-10-22'],
['julian', '1969-12-19'],
['mayan', '5156-07-05'],
['nanakshahi', '0501-10-19'],
['nepali', '2026-09-17'],
['persian', '1348-10-11'],
['jalali', '1348-10-11'],
['taiwan', '0059-01-01'],
['thai', '2513-01-01'],
['ummalqura', '1389-10-23']
].forEach(function(v) {
var calendar = v[0],
dateStr = v[1];
expect(Lib.ms2DateTime(0, 0, calendar)).toBe(dateStr, calendar);
expect(Lib.dateTime2ms(dateStr, calendar)).toBe(0, calendar);
var expected_p1ms = dateStr + ' 00:00:00.0001',
expected_1s = dateStr + ' 00:00:01',
expected_1m = dateStr + ' 00:01',
expected_1h = dateStr + ' 01:00',
expected_lastinstant = dateStr + ' 23:59:59.9999';
var oneSec = 1000,
oneMin = 60 * oneSec,
oneHour = 60 * oneMin,
lastInstant = 24 * oneHour - 0.1;
expect(Lib.ms2DateTime(0.1, 0, calendar)).toBe(expected_p1ms, calendar);
expect(Lib.ms2DateTime(oneSec, 0, calendar)).toBe(expected_1s, calendar);
expect(Lib.ms2DateTime(oneMin, 0, calendar)).toBe(expected_1m, calendar);
expect(Lib.ms2DateTime(oneHour, 0, calendar)).toBe(expected_1h, calendar);
expect(Lib.ms2DateTime(lastInstant, 0, calendar)).toBe(expected_lastinstant, calendar);
expect(Lib.dateTime2ms(expected_p1ms, calendar)).toBe(0.1, calendar);
expect(Lib.dateTime2ms(expected_1s, calendar)).toBe(oneSec, calendar);
expect(Lib.dateTime2ms(expected_1m, calendar)).toBe(oneMin, calendar);
expect(Lib.dateTime2ms(expected_1h, calendar)).toBe(oneHour, calendar);
expect(Lib.dateTime2ms(expected_lastinstant, calendar)).toBe(lastInstant, calendar);
});
});
it('should contain canonical ticks sundays, ranges for all calendars', function() {
var calList = Object.keys(calendars.calendars).filter(function(v) {
return v !== 'gregorian';
});
var canonicalTick = calComponent.CANONICAL_TICK,
canonicalSunday = calComponent.CANONICAL_SUNDAY,
dfltRange = calComponent.DFLTRANGE;
expect(Object.keys(canonicalTick).length).toBe(calList.length);
expect(Object.keys(canonicalSunday).length).toBe(calList.length);
expect(Object.keys(dfltRange).length).toBe(calList.length);
calList.forEach(function(calendar) {
expect(Lib.dateTime2ms(canonicalTick[calendar], calendar)).toBeDefined(calendar);
var sunday = Lib.dateTime2ms(canonicalSunday[calendar], calendar);
// convert back implicitly with gregorian calendar
expect(Lib.formatDate(sunday, '%A')).toBe('Sunday', calendar);
expect(Lib.dateTime2ms(dfltRange[calendar][0], calendar)).toBeDefined(calendar);
expect(Lib.dateTime2ms(dfltRange[calendar][1], calendar)).toBeDefined(calendar);
});
});
it('should handle Chinese intercalary months correctly', function() {
var intercalaryDates = [
'1995-08i-01',
'1995-08i-29',
'1984-10i-15',
'2023-02i-29'
];
intercalaryDates.forEach(function(v) {
var ms = Lib.dateTime2ms(v, 'chinese');
expect(Lib.ms2DateTime(ms, 0, 'chinese')).toBe(v);
// should also work without leading zeros
var vShort = v.replace(/-0/g, '-');
expect(Lib.dateTime2ms(vShort, 'chinese')).toBe(ms, vShort);
});
var badIntercalaryDates = [
'1995-07i-01',
'1995-08i-30',
'1995-09i-01'
];
badIntercalaryDates.forEach(function(v) {
expect(Lib.dateTime2ms(v, 'chinese')).toBeUndefined(v);
});
});
});
describe('cleanDate', function() {
it('should convert numbers or js Dates to strings based on local TZ', function() {
[
new Date(0),
new Date(2000),
new Date(2000, 0, 1),
new Date(),
new Date(-9999, 0, 3), // we lose one day of range +/- tzoffset this way
new Date(9999, 11, 29, 23, 59, 59, 999)
].forEach(function(v) {
var expected = Lib.ms2DateTime(Lib.dateTime2ms(v));
expect(typeof expected).toBe('string');
expect(Lib.cleanDate(v)).toBe(expected);
expect(Lib.cleanDate(+v)).toBe(expected);
expect(Lib.cleanDate(v, '2000-01-01')).toBe(expected);
});
});
it('should fail numbers & js Dates out of range, and other bad objects', function() {
[
new Date(-20000, 0, 1),
new Date(20000, 0, 1),
new Date('fail'),
undefined, null, NaN,
[], {}, [0], {1: 2}, '',
'2001-02-29' // not a leap year
].forEach(function(v) {
expect(Lib.cleanDate(v)).toBeUndefined();
if(!isNumeric(+v)) expect(Lib.cleanDate(+v)).toBeUndefined();
expect(Lib.cleanDate(v, '2000-01-01')).toBe('2000-01-01');
});
});
it('should not alter valid date strings, even to truncate them', function() {
[
'2000',
'2000-01',
'2000-01-01',
'2000-01-01 00',
'2000-01-01 00:00',
'2000-01-01 00:00:00',
'2000-01-01 00:00:00.0',
'2000-01-01 00:00:00.00',
'2000-01-01 00:00:00.000',
'2000-01-01 00:00:00.0000',
'9999-12-31 23:59:59.9999',
'-9999-01-01 00:00:00.0000',
'99-01-01',
'00-01-01'
].forEach(function(v) {
expect(Lib.cleanDate(v)).toBe(v);
});
});
});
describe('incrementMonth', function() {
it('should include Chinese intercalary months', function() {
var start = '1995-06-01';
var expected = [
'1995-07-01',
'1995-08-01',
'1995-08i-01',
'1995-09-01',
'1995-10-01',
'1995-11-01',
'1995-12-01',
'1996-01-01'
];
var tick = Lib.dateTime2ms(start, 'chinese');
expected.forEach(function(v) {
tick = Lib.incrementMonth(tick, 1, 'chinese');
expect(tick).toBe(Lib.dateTime2ms(v, 'chinese'), v);
});
});
it('should increment years even over leap years', function() {
var start = '1995-06-01';
var expected = [
'1996-06-01',
'1997-06-01',
'1998-06-01',
'1999-06-01',
'2000-06-01',
'2001-06-01',
'2002-06-01',
'2003-06-01',
'2004-06-01',
'2005-06-01',
'2006-06-01',
'2007-06-01',
'2008-06-01'
];
var tick = Lib.dateTime2ms(start, 'chinese');
expected.forEach(function(v) {
tick = Lib.incrementMonth(tick, 12, 'chinese');
expect(tick).toBe(Lib.dateTime2ms(v, 'chinese'), v);
});
});
});
describe('isJSDate', function() {
it('should return true for any Date object but not the equivalent numbers', function() {
[
new Date(),
new Date(0),
new Date(-9900, 1, 2, 3, 4, 5, 6),
new Date(9900, 1, 2, 3, 4, 5, 6),
new Date(-20000, 0, 1), new Date(20000, 0, 1), // outside our range, still true
new Date('fail') // `Invalid Date` is still a Date
].forEach(function(v) {
expect(Lib.isJSDate(v)).toBe(true);
expect(Lib.isJSDate(+v)).toBe(false);
});
});
it('should return false for anything thats not explicitly a JS Date', function() {
[
0, NaN, null, undefined, '', {}, [], [0], [2016, 0, 1],
'2016-01-01', '2016-01-01 12:34:56', '2016-01-01 12:34:56.789',
'Thu Oct 20 2016 15:35:14 GMT-0400 (EDT)',
// getting really close to a hack of our test... we look for getTime to be a function
{getTime: 4}
].forEach(function(v) {
expect(Lib.isJSDate(v)).toBe(false);
});
});
});
});