-
-
Notifications
You must be signed in to change notification settings - Fork 1.9k
/
Copy pathlib_date_test.js
661 lines (598 loc) · 28.9 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
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
var isNumeric = require('fast-isnumeric');
var Lib = require('../../../src/lib');
var Loggers = require('../../../src/lib/loggers');
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');
var utcFormat = require('d3-time-format').utcFormat;
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();
var thisYear2 = thisYear % 100;
var nowMinus70 = thisYear - 70;
var nowMinus70x2 = nowMinus70 % 100;
var nowPlus29 = thisYear + 29;
var nowPlus29x2 = nowPlus29 % 100;
function tweakedTZOffset(d) {
var tzOffset = d.getTimezoneOffset() * 60000;
var offsetTweak = (d.getUTCMinutes() - d.getMinutes()) * 60000 +
(d.getUTCSeconds() - d.getSeconds()) * 1000 +
(d.getUTCMilliseconds() - d.getMilliseconds());
if(offsetTweak) {
var comb = 3 * 60000;
var tzOffset2 = tzOffset - comb / 2 + Lib.mod(offsetTweak - tzOffset + comb / 2, comb);
// this tweak logic just copies what's in dateTime2ms to account for
// Chrome's new handling of dates before there were timezones, see
// https://github.com/plotly/plotly.js/issues/2743
// This logic has been validated manually using:
// Plotly.newPlot(gd,[{x:[new Date(1600,0,1),new Date(1600,0,1,0,1)],y:[1,2]}])
// here just check that it's only happening for years before 1884,
// and only adjusting the result less than a minute.
expect(d.getFullYear()).toBeLessThan(1884);
expect(Math.abs(tzOffset2 - tzOffset)).toBeLessThan(60000);
return tzOffset2;
}
return tzOffset;
}
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
[thisYear2 + '-05', new Date(thisYear, 4, 1)],
[nowMinus70x2 + '-10-18', new Date(nowMinus70, 9, 18)],
[nowPlus29x2 + '-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)],
// iso8601 short time offset
['2014-03-04T08:15:34-05', new Date(2014, 2, 4, 8, 15, 34)],
].forEach(function(v) {
// just for sub-millisecond precision tests, use timezoneoffset
// from the previous date object
if(v[1].getTimezoneOffset) tzOffset = tweakedTZOffset(v[1]);
var expected = +v[1] - tzOffset;
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],
[nowPlus29x2, nowPlus29],
[nowMinus70x2, 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 - tweakedTZOffset(v), v.toString());
});
});
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', '2015-01-01 12:34+5' // 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));
var 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);
var base = i < -0.05 ? '1969-12-31 23:59:59.99' : '1970-01-01 00:00:00.00';
var 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];
var dateStr = v[1];
expect(Lib.ms2DateTime(0, 0, calendar)).toBe(dateStr, calendar);
expect(Lib.dateTime2ms(dateStr, calendar)).toBe(0, calendar);
var expectedPlus1ms = dateStr + ' 00:00:00.0001';
var expected1s = dateStr + ' 00:00:01';
var expected1m = dateStr + ' 00:01';
var expected1h = dateStr + ' 01:00';
var expectedLastInstant = dateStr + ' 23:59:59.9999';
var oneSec = 1000;
var oneMin = 60 * oneSec;
var oneHour = 60 * oneMin;
var lastInstant = 24 * oneHour - 0.1;
expect(Lib.ms2DateTime(0.1, 0, calendar)).toBe(expectedPlus1ms, calendar);
expect(Lib.ms2DateTime(oneSec, 0, calendar)).toBe(expected1s, calendar);
expect(Lib.ms2DateTime(oneMin, 0, calendar)).toBe(expected1m, calendar);
expect(Lib.ms2DateTime(oneHour, 0, calendar)).toBe(expected1h, calendar);
expect(Lib.ms2DateTime(lastInstant, 0, calendar)).toBe(expectedLastInstant, calendar);
expect(Lib.dateTime2ms(expectedPlus1ms, calendar)).toBe(0.1, calendar);
expect(Lib.dateTime2ms(expected1s, calendar)).toBe(oneSec, calendar);
expect(Lib.dateTime2ms(expected1m, calendar)).toBe(oneMin, calendar);
expect(Lib.dateTime2ms(expected1h, calendar)).toBe(oneHour, calendar);
expect(Lib.dateTime2ms(expectedLastInstant, 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;
var canonicalSunday = calComponent.CANONICAL_SUNDAY;
var 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', null, utcFormat)).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() {
var errors = [];
spyOn(Loggers, 'error').and.callFake(function(msg) {
errors.push(msg);
});
var cases = [
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
];
cases.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');
});
// two errors for each case except `undefined`
expect(errors.length).toBe(2 * (cases.length - 1));
});
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);
});
});
});
describe('formatDate', function() {
function assertFormatRounds(ms, calendar, results) {
['y', 'm', 'd', 'M', 'S', 1, 2, 3, 4].forEach(function(tr, i) {
expect(Lib.formatDate(ms, '', tr, utcFormat, calendar, {
year: '%Y',
month: '%b %Y',
dayMonth: '%b %-d',
dayMonthYear: '%b %-d, %Y'
}))
.toBe(results[i], calendar);
});
}
it('should pick a format based on tickround if no format is provided', function() {
var ms = Lib.dateTime2ms('2012-08-13 06:19:34.5678');
assertFormatRounds(ms, 'gregorian', [
'2012',
'Aug 2012',
'Aug 13\n2012',
'06:19\nAug 13, 2012',
'06:19:35\nAug 13, 2012',
'06:19:34.6\nAug 13, 2012',
'06:19:34.57\nAug 13, 2012',
'06:19:34.568\nAug 13, 2012',
'06:19:34.5678\nAug 13, 2012'
]);
// and for world calendars - in coptic this is 1728-12-07 (month=Meso)
assertFormatRounds(ms, 'coptic', [
'1728',
'Meso 1728',
'Meso 7\n1728',
'06:19\nMeso 7, 1728',
'06:19:35\nMeso 7, 1728',
'06:19:34.6\nMeso 7, 1728',
'06:19:34.57\nMeso 7, 1728',
'06:19:34.568\nMeso 7, 1728',
'06:19:34.5678\nMeso 7, 1728'
]);
});
it('should accept custom formats using d3 specs even for world cals', function() {
var ms = Lib.dateTime2ms('2012-08-13 06:19:34.5678');
[
// some common formats (plotly workspace options)
['%Y-%m-%d', '2012-08-13', '1728-12-07'],
['%H:%M:%S', '06:19:34', '06:19:34'],
['%Y-%m-%e %H:%M:%S', '2012-08-13 06:19:34', '1728-12-7 06:19:34'],
['%A, %b %e', 'Monday, Aug 13', 'Pesnau, Meso 7'],
// test padding behavior
// world doesn't support space-padded (yet?)
['%Y-%_m-%_d', '2012- 8-13', '1728-12-7'],
['%Y-%-m-%-d', '2012-8-13', '1728-12-7'],
// and some strange ones to cover all fields
['%a%j!%-j', 'Mon226!226', 'Pes337!337'],
[
'%W or un or space padded-> %-W,%_W',
'33 or un or space padded-> 33,33',
'48 or un or space padded-> 48,48'
],
[
'%B \'%y WOY:%U DOW:%w',
'August \'12 WOY:33 DOW:1',
'Mesori \'28 WOY:## DOW:##' // world-cals doesn't support U or w
],
[
'%B \'%y QOY:%q WOY:%W DOW:%u',
'August \'12 QOY:3 WOY:33 DOW:1',
'Mesori \'28 QOY:3 WOY:48 DOW:1'
],
[
'seconds: %s and milliseconds: %Q since UNIX epoch',
'seconds: 1344838774 and milliseconds: 1344838774567 since UNIX epoch',
'seconds: 1344838774 and milliseconds: 1344838774567 since UNIX epoch'
],
[
'%c && %x && .%2f .%f', // %<n>f is our addition
'8/13/2012, 6:19:34 AM && 8/13/2012 && .57 .5678',
'Pes Meso 7 6:19:34 AM 1728 && 12/07/1728 && .57 .5678'
]
].forEach(function(v) {
var fmt = v[0];
var expectedGregorian = v[1];
var expectedCoptic = v[2];
// tickround is irrelevant here...
expect(Lib.formatDate(ms, fmt, 'y', utcFormat))
.toBe(expectedGregorian, fmt);
expect(Lib.formatDate(ms, fmt, 4, utcFormat, 'gregorian'))
.toBe(expectedGregorian, fmt);
expect(Lib.formatDate(ms, fmt, 'y', utcFormat, 'coptic'))
.toBe(expectedCoptic, fmt);
});
});
it('should not round up to 60 seconds', function() {
// see note in dates.js -> formatTime about this rounding
assertFormatRounds(-0.1, 'gregorian', [
'1969',
'Dec 1969',
'Dec 31\n1969',
'23:59\nDec 31, 1969',
'23:59:59\nDec 31, 1969',
'23:59:59.9\nDec 31, 1969',
'23:59:59.99\nDec 31, 1969',
'23:59:59.999\nDec 31, 1969',
'23:59:59.9999\nDec 31, 1969'
]);
// in coptic this is Koi 22, 1686
assertFormatRounds(-0.1, 'coptic', [
'1686',
'Koi 1686',
'Koi 22\n1686',
'23:59\nKoi 22, 1686',
'23:59:59\nKoi 22, 1686',
'23:59:59.9\nKoi 22, 1686',
'23:59:59.99\nKoi 22, 1686',
'23:59:59.999\nKoi 22, 1686',
'23:59:59.9999\nKoi 22, 1686'
]);
// and using the custom format machinery
expect(Lib.formatDate(-0.1, '%Y-%m-%d %H:%M:%S.%f', null, utcFormat))
.toBe('1969-12-31 23:59:59.9999');
expect(Lib.formatDate(-0.1, '%Y-%m-%d %H:%M:%S.%f', null, utcFormat, 'coptic'))
.toBe('1686-04-22 23:59:59.9999');
});
it('should remove extra fractional second zeros', function() {
var extraFormat = {
year: '%Y',
month: '%b %Y',
dayMonth: '%b %-d',
dayMonthYear: '%b %-d, %Y'
};
expect(Lib.formatDate(0.1, '', 4, utcFormat, null, extraFormat)).toBe('00:00:00.0001\nJan 1, 1970');
expect(Lib.formatDate(0.1, '', 3, utcFormat, null, extraFormat)).toBe('00:00:00\nJan 1, 1970');
expect(Lib.formatDate(0.1, '', 0, utcFormat, null, extraFormat)).toBe('00:00:00\nJan 1, 1970');
expect(Lib.formatDate(0.1, '', 'S', utcFormat, null, extraFormat)).toBe('00:00:00\nJan 1, 1970');
expect(Lib.formatDate(0.1, '', 3, utcFormat, 'coptic', extraFormat))
.toBe('00:00:00\nKoi 23, 1686');
// because the decimal point is explicitly part of the format
// string here, we can't remove it OR the very first zero after it.
expect(Lib.formatDate(0.1, '%S.%f', null, utcFormat, null, extraFormat)).toBe('00.0001');
expect(Lib.formatDate(0.1, '%S.%3f', null, utcFormat, null, extraFormat)).toBe('00.0');
});
});
});