Source code

Revision control

Other Tools

1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2
* vim: set ts=8 sts=2 et sw=2 tw=80:
3
* This Source Code Form is subject to the terms of the Mozilla Public
4
* License, v. 2.0. If a copy of the MPL was not distributed with this
5
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6
7
/*
8
* JS date methods.
9
*
10
* "For example, OS/360 devotes 26 bytes of the permanently
11
* resident date-turnover routine to the proper handling of
12
* December 31 on leap years (when it is Day 366). That
13
* might have been left to the operator."
14
*
15
* Frederick Brooks, 'The Second-System Effect'.
16
*/
17
18
#include "jsdate.h"
19
20
#include "mozilla/ArrayUtils.h"
21
#include "mozilla/Atomics.h"
22
#include "mozilla/Casting.h"
23
#include "mozilla/FloatingPoint.h"
24
#include "mozilla/Sprintf.h"
25
#include "mozilla/TextUtils.h"
26
27
#include <algorithm>
28
#include <math.h>
29
#include <string.h>
30
31
#include "jsapi.h"
32
#include "jsfriendapi.h"
33
#include "jsnum.h"
34
#include "jstypes.h"
35
36
#include "js/Conversions.h"
37
#include "js/Date.h"
38
#include "js/LocaleSensitive.h"
39
#include "js/PropertySpec.h"
40
#include "js/Wrapper.h"
41
#include "util/StringBuffer.h"
42
#include "util/Text.h"
43
#include "vm/DateObject.h"
44
#include "vm/DateTime.h"
45
#include "vm/GlobalObject.h"
46
#include "vm/Interpreter.h"
47
#include "vm/JSContext.h"
48
#include "vm/JSObject.h"
49
#include "vm/StringType.h"
50
#include "vm/Time.h"
51
52
#include "vm/JSObject-inl.h"
53
54
using namespace js;
55
56
using mozilla::ArrayLength;
57
using mozilla::Atomic;
58
using mozilla::BitwiseCast;
59
using mozilla::IsAsciiAlpha;
60
using mozilla::IsAsciiDigit;
61
using mozilla::IsFinite;
62
using mozilla::IsNaN;
63
using mozilla::NumbersAreIdentical;
64
using mozilla::Relaxed;
65
66
using JS::AutoCheckCannotGC;
67
using JS::ClippedTime;
68
using JS::GenericNaN;
69
using JS::TimeClip;
70
using JS::ToInteger;
71
72
// When this value is non-zero, we'll round the time by this resolution.
73
static Atomic<uint32_t, Relaxed> sResolutionUsec;
74
// This is not implemented yet, but we will use this to know to jitter the time
75
// in the JS shell
76
static Atomic<bool, Relaxed> sJitter;
77
// The callback we will use for the Gecko implementation of Timer
78
// Clamping/Jittering
79
static Atomic<JS::ReduceMicrosecondTimePrecisionCallback, Relaxed>
80
sReduceMicrosecondTimePrecisionCallback;
81
82
/*
83
* The JS 'Date' object is patterned after the Java 'Date' object.
84
* Here is a script:
85
*
86
* today = new Date();
87
*
88
* print(today.toLocaleString());
89
*
90
* weekDay = today.getDay();
91
*
92
*
93
* These Java (and ECMA-262) methods are supported:
94
*
95
* UTC
96
* getDate (getUTCDate)
97
* getDay (getUTCDay)
98
* getHours (getUTCHours)
99
* getMinutes (getUTCMinutes)
100
* getMonth (getUTCMonth)
101
* getSeconds (getUTCSeconds)
102
* getMilliseconds (getUTCMilliseconds)
103
* getTime
104
* getTimezoneOffset
105
* getYear
106
* getFullYear (getUTCFullYear)
107
* parse
108
* setDate (setUTCDate)
109
* setHours (setUTCHours)
110
* setMinutes (setUTCMinutes)
111
* setMonth (setUTCMonth)
112
* setSeconds (setUTCSeconds)
113
* setMilliseconds (setUTCMilliseconds)
114
* setTime
115
* setYear (setFullYear, setUTCFullYear)
116
* toGMTString (toUTCString)
117
* toLocaleString
118
* toString
119
*
120
*
121
* These Java methods are not supported
122
*
123
* setDay
124
* before
125
* after
126
* equals
127
* hashCode
128
*/
129
130
namespace {
131
132
class DateTimeHelper {
133
private:
134
#if ENABLE_INTL_API && !MOZ_SYSTEM_ICU
135
static double localTZA(double t, DateTimeInfo::TimeZoneOffset offset);
136
#else
137
static int equivalentYearForDST(int year);
138
static bool isRepresentableAsTime32(double t);
139
static double daylightSavingTA(double t);
140
static double adjustTime(double date);
141
static PRMJTime toPRMJTime(double localTime, double utcTime);
142
#endif
143
144
public:
145
static double localTime(double t);
146
static double UTC(double t);
147
static JSString* timeZoneComment(JSContext* cx, double utcTime,
148
double localTime);
149
#if !ENABLE_INTL_API || MOZ_SYSTEM_ICU
150
static size_t formatTime(char* buf, size_t buflen, const char* fmt,
151
double utcTime, double localTime);
152
#endif
153
};
154
155
} // namespace
156
157
// ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
158
// 5.2.5 Mathematical Operations
159
static inline double PositiveModulo(double dividend, double divisor) {
160
MOZ_ASSERT(divisor > 0);
161
MOZ_ASSERT(IsFinite(divisor));
162
163
double result = fmod(dividend, divisor);
164
if (result < 0) {
165
result += divisor;
166
}
167
return result + (+0.0);
168
}
169
170
static inline double Day(double t) { return floor(t / msPerDay); }
171
172
static double TimeWithinDay(double t) { return PositiveModulo(t, msPerDay); }
173
174
/* ES5 15.9.1.3. */
175
static inline bool IsLeapYear(double year) {
176
MOZ_ASSERT(ToInteger(year) == year);
177
return fmod(year, 4) == 0 && (fmod(year, 100) != 0 || fmod(year, 400) == 0);
178
}
179
180
static inline double DaysInYear(double year) {
181
if (!IsFinite(year)) {
182
return GenericNaN();
183
}
184
return IsLeapYear(year) ? 366 : 365;
185
}
186
187
static inline double DayFromYear(double y) {
188
return 365 * (y - 1970) + floor((y - 1969) / 4.0) -
189
floor((y - 1901) / 100.0) + floor((y - 1601) / 400.0);
190
}
191
192
static inline double TimeFromYear(double y) {
193
return DayFromYear(y) * msPerDay;
194
}
195
196
static double YearFromTime(double t) {
197
if (!IsFinite(t)) {
198
return GenericNaN();
199
}
200
201
MOZ_ASSERT(ToInteger(t) == t);
202
203
double y = floor(t / (msPerDay * 365.2425)) + 1970;
204
double t2 = TimeFromYear(y);
205
206
/*
207
* Adjust the year if the approximation was wrong. Since the year was
208
* computed using the average number of ms per year, it will usually
209
* be wrong for dates within several hours of a year transition.
210
*/
211
if (t2 > t) {
212
y--;
213
} else {
214
if (t2 + msPerDay * DaysInYear(y) <= t) {
215
y++;
216
}
217
}
218
return y;
219
}
220
221
static inline int DaysInFebruary(double year) {
222
return IsLeapYear(year) ? 29 : 28;
223
}
224
225
/* ES5 15.9.1.4. */
226
static inline double DayWithinYear(double t, double year) {
227
MOZ_ASSERT_IF(IsFinite(t), YearFromTime(t) == year);
228
return Day(t) - DayFromYear(year);
229
}
230
231
static double MonthFromTime(double t) {
232
if (!IsFinite(t)) {
233
return GenericNaN();
234
}
235
236
double year = YearFromTime(t);
237
double d = DayWithinYear(t, year);
238
239
int step;
240
if (d < (step = 31)) {
241
return 0;
242
}
243
if (d < (step += DaysInFebruary(year))) {
244
return 1;
245
}
246
if (d < (step += 31)) {
247
return 2;
248
}
249
if (d < (step += 30)) {
250
return 3;
251
}
252
if (d < (step += 31)) {
253
return 4;
254
}
255
if (d < (step += 30)) {
256
return 5;
257
}
258
if (d < (step += 31)) {
259
return 6;
260
}
261
if (d < (step += 31)) {
262
return 7;
263
}
264
if (d < (step += 30)) {
265
return 8;
266
}
267
if (d < (step += 31)) {
268
return 9;
269
}
270
if (d < (step += 30)) {
271
return 10;
272
}
273
return 11;
274
}
275
276
/* ES5 15.9.1.5. */
277
static double DateFromTime(double t) {
278
if (!IsFinite(t)) {
279
return GenericNaN();
280
}
281
282
double year = YearFromTime(t);
283
double d = DayWithinYear(t, year);
284
285
int next;
286
if (d <= (next = 30)) {
287
return d + 1;
288
}
289
int step = next;
290
if (d <= (next += DaysInFebruary(year))) {
291
return d - step;
292
}
293
step = next;
294
if (d <= (next += 31)) {
295
return d - step;
296
}
297
step = next;
298
if (d <= (next += 30)) {
299
return d - step;
300
}
301
step = next;
302
if (d <= (next += 31)) {
303
return d - step;
304
}
305
step = next;
306
if (d <= (next += 30)) {
307
return d - step;
308
}
309
step = next;
310
if (d <= (next += 31)) {
311
return d - step;
312
}
313
step = next;
314
if (d <= (next += 31)) {
315
return d - step;
316
}
317
step = next;
318
if (d <= (next += 30)) {
319
return d - step;
320
}
321
step = next;
322
if (d <= (next += 31)) {
323
return d - step;
324
}
325
step = next;
326
if (d <= (next += 30)) {
327
return d - step;
328
}
329
step = next;
330
return d - step;
331
}
332
333
/* ES5 15.9.1.6. */
334
static int WeekDay(double t) {
335
/*
336
* We can't assert TimeClip(t) == t because we call this function with
337
* local times, which can be offset outside TimeClip's permitted range.
338
*/
339
MOZ_ASSERT(ToInteger(t) == t);
340
int result = (int(Day(t)) + 4) % 7;
341
if (result < 0) {
342
result += 7;
343
}
344
return result;
345
}
346
347
static inline int DayFromMonth(int month, bool isLeapYear) {
348
/*
349
* The following array contains the day of year for the first day of
350
* each month, where index 0 is January, and day 0 is January 1.
351
*/
352
static const int firstDayOfMonth[2][13] = {
353
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
354
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}};
355
356
MOZ_ASSERT(0 <= month && month <= 12);
357
return firstDayOfMonth[isLeapYear][month];
358
}
359
360
template <typename T>
361
static inline int DayFromMonth(T month, bool isLeapYear) = delete;
362
363
/* ES5 15.9.1.12 (out of order to accommodate DaylightSavingTA). */
364
static double MakeDay(double year, double month, double date) {
365
/* Step 1. */
366
if (!IsFinite(year) || !IsFinite(month) || !IsFinite(date)) {
367
return GenericNaN();
368
}
369
370
/* Steps 2-4. */
371
double y = ToInteger(year);
372
double m = ToInteger(month);
373
double dt = ToInteger(date);
374
375
/* Step 5. */
376
double ym = y + floor(m / 12);
377
378
/* Step 6. */
379
int mn = int(PositiveModulo(m, 12));
380
381
/* Steps 7-8. */
382
bool leap = IsLeapYear(ym);
383
384
double yearday = floor(TimeFromYear(ym) / msPerDay);
385
double monthday = DayFromMonth(mn, leap);
386
387
return yearday + monthday + dt - 1;
388
}
389
390
/* ES5 15.9.1.13 (out of order to accommodate DaylightSavingTA). */
391
static inline double MakeDate(double day, double time) {
392
/* Step 1. */
393
if (!IsFinite(day) || !IsFinite(time)) {
394
return GenericNaN();
395
}
396
397
/* Step 2. */
398
return day * msPerDay + time;
399
}
400
401
JS_PUBLIC_API double JS::MakeDate(double year, unsigned month, unsigned day) {
402
MOZ_ASSERT(month <= 11);
403
MOZ_ASSERT(day >= 1 && day <= 31);
404
405
return ::MakeDate(MakeDay(year, month, day), 0);
406
}
407
408
JS_PUBLIC_API double JS::MakeDate(double year, unsigned month, unsigned day,
409
double time) {
410
MOZ_ASSERT(month <= 11);
411
MOZ_ASSERT(day >= 1 && day <= 31);
412
413
return ::MakeDate(MakeDay(year, month, day), time);
414
}
415
416
JS_PUBLIC_API double JS::YearFromTime(double time) {
417
return ::YearFromTime(time);
418
}
419
420
JS_PUBLIC_API double JS::MonthFromTime(double time) {
421
return ::MonthFromTime(time);
422
}
423
424
JS_PUBLIC_API double JS::DayFromTime(double time) { return DateFromTime(time); }
425
426
JS_PUBLIC_API double JS::DayFromYear(double year) {
427
return ::DayFromYear(year);
428
}
429
430
JS_PUBLIC_API double JS::DayWithinYear(double time, double year) {
431
return ::DayWithinYear(time, year);
432
}
433
434
JS_PUBLIC_API void JS::SetReduceMicrosecondTimePrecisionCallback(
435
JS::ReduceMicrosecondTimePrecisionCallback callback) {
436
sReduceMicrosecondTimePrecisionCallback = callback;
437
}
438
439
JS_PUBLIC_API void JS::SetTimeResolutionUsec(uint32_t resolution, bool jitter) {
440
sResolutionUsec = resolution;
441
sJitter = jitter;
442
}
443
444
#if ENABLE_INTL_API && !MOZ_SYSTEM_ICU
445
// ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
446
// 20.3.1.7 LocalTZA ( t, isUTC )
447
double DateTimeHelper::localTZA(double t, DateTimeInfo::TimeZoneOffset offset) {
448
MOZ_ASSERT(IsFinite(t));
449
450
int64_t milliseconds = static_cast<int64_t>(t);
451
int32_t offsetMilliseconds =
452
DateTimeInfo::getOffsetMilliseconds(milliseconds, offset);
453
return static_cast<double>(offsetMilliseconds);
454
}
455
456
// ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
457
// 20.3.1.8 LocalTime ( t )
458
double DateTimeHelper::localTime(double t) {
459
if (!IsFinite(t)) {
460
return GenericNaN();
461
}
462
463
MOZ_ASSERT(StartOfTime <= t && t <= EndOfTime);
464
return t + localTZA(t, DateTimeInfo::TimeZoneOffset::UTC);
465
}
466
467
// ES2019 draft rev 0ceb728a1adbffe42b26972a6541fd7f398b1557
468
// 20.3.1.9 UTC ( t )
469
double DateTimeHelper::UTC(double t) {
470
if (!IsFinite(t)) {
471
return GenericNaN();
472
}
473
474
if (t < (StartOfTime - msPerDay) || t > (EndOfTime + msPerDay)) {
475
return GenericNaN();
476
}
477
478
return t - localTZA(t, DateTimeInfo::TimeZoneOffset::Local);
479
}
480
#else
481
/*
482
* Find a year for which any given date will fall on the same weekday.
483
*
484
* This function should be used with caution when used other than
485
* for determining DST; it hasn't been proven not to produce an
486
* incorrect year for times near year boundaries.
487
*/
488
int DateTimeHelper::equivalentYearForDST(int year) {
489
/*
490
* Years and leap years on which Jan 1 is a Sunday, Monday, etc.
491
*
492
* yearStartingWith[0][i] is an example non-leap year where
493
* Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc.
494
*
495
* yearStartingWith[1][i] is an example leap year where
496
* Jan 1 appears on Sunday (i == 0), Monday (i == 1), etc.
497
*
498
* Keep two different mappings, one for past years (< 1970), and a
499
* different one for future years (> 2037).
500
*/
501
static const int pastYearStartingWith[2][7] = {
502
{1978, 1973, 1974, 1975, 1981, 1971, 1977},
503
{1984, 1996, 1980, 1992, 1976, 1988, 1972}};
504
static const int futureYearStartingWith[2][7] = {
505
{2034, 2035, 2030, 2031, 2037, 2027, 2033},
506
{2012, 2024, 2036, 2020, 2032, 2016, 2028}};
507
508
int day = int(DayFromYear(year) + 4) % 7;
509
if (day < 0) {
510
day += 7;
511
}
512
513
const auto& yearStartingWith =
514
year < 1970 ? pastYearStartingWith : futureYearStartingWith;
515
return yearStartingWith[IsLeapYear(year)][day];
516
}
517
518
// Return true if |t| is representable as a 32-bit time_t variable, that means
519
// the year is in [1970, 2038).
520
bool DateTimeHelper::isRepresentableAsTime32(double t) {
521
return 0.0 <= t && t < 2145916800000.0;
522
}
523
524
/* ES5 15.9.1.8. */
525
double DateTimeHelper::daylightSavingTA(double t) {
526
if (!IsFinite(t)) {
527
return GenericNaN();
528
}
529
530
/*
531
* If earlier than 1970 or after 2038, potentially beyond the ken of
532
* many OSes, map it to an equivalent year before asking.
533
*/
534
if (!isRepresentableAsTime32(t)) {
535
int year = equivalentYearForDST(int(YearFromTime(t)));
536
double day = MakeDay(year, MonthFromTime(t), DateFromTime(t));
537
t = MakeDate(day, TimeWithinDay(t));
538
}
539
540
int64_t utcMilliseconds = static_cast<int64_t>(t);
541
int32_t offsetMilliseconds =
542
DateTimeInfo::getDSTOffsetMilliseconds(utcMilliseconds);
543
return static_cast<double>(offsetMilliseconds);
544
}
545
546
double DateTimeHelper::adjustTime(double date) {
547
double localTZA = DateTimeInfo::localTZA();
548
double t = daylightSavingTA(date) + localTZA;
549
t = (localTZA >= 0) ? fmod(t, msPerDay) : -fmod(msPerDay - t, msPerDay);
550
return t;
551
}
552
553
/* ES5 15.9.1.9. */
554
double DateTimeHelper::localTime(double t) { return t + adjustTime(t); }
555
556
double DateTimeHelper::UTC(double t) {
557
// Following the ES2017 specification creates undesirable results at DST
558
// transitions. For example when transitioning from PST to PDT,
559
// |new Date(2016,2,13,2,0,0).toTimeString()| returns the string value
560
// "01:00:00 GMT-0800 (PST)" instead of "03:00:00 GMT-0700 (PDT)". Follow
561
// V8 and subtract one hour before computing the offset.
563
564
return t - adjustTime(t - DateTimeInfo::localTZA() - msPerHour);
565
}
566
#endif /* ENABLE_INTL_API && !MOZ_SYSTEM_ICU */
567
568
static double LocalTime(double t) { return DateTimeHelper::localTime(t); }
569
570
static double UTC(double t) { return DateTimeHelper::UTC(t); }
571
572
/* ES5 15.9.1.10. */
573
static double HourFromTime(double t) {
574
return PositiveModulo(floor(t / msPerHour), HoursPerDay);
575
}
576
577
static double MinFromTime(double t) {
578
return PositiveModulo(floor(t / msPerMinute), MinutesPerHour);
579
}
580
581
static double SecFromTime(double t) {
582
return PositiveModulo(floor(t / msPerSecond), SecondsPerMinute);
583
}
584
585
static double msFromTime(double t) { return PositiveModulo(t, msPerSecond); }
586
587
/* ES5 15.9.1.11. */
588
static double MakeTime(double hour, double min, double sec, double ms) {
589
/* Step 1. */
590
if (!IsFinite(hour) || !IsFinite(min) || !IsFinite(sec) || !IsFinite(ms)) {
591
return GenericNaN();
592
}
593
594
/* Step 2. */
595
double h = ToInteger(hour);
596
597
/* Step 3. */
598
double m = ToInteger(min);
599
600
/* Step 4. */
601
double s = ToInteger(sec);
602
603
/* Step 5. */
604
double milli = ToInteger(ms);
605
606
/* Steps 6-7. */
607
return h * msPerHour + m * msPerMinute + s * msPerSecond + milli;
608
}
609
610
/**
611
* end of ECMA 'support' functions
612
*/
613
614
/* for use by date_parse */
615
616
static const char* const wtb[] = {
617
// clang-format off
618
"am", "pm",
619
"monday", "tuesday", "wednesday", "thursday", "friday",
620
"saturday", "sunday",
621
"january", "february", "march", "april", "may", "june",
622
"july", "august", "september", "october", "november", "december",
623
"gmt", "ut", "utc",
624
"est", "edt",
625
"cst", "cdt",
626
"mst", "mdt",
627
"pst", "pdt"
628
/* time zone table needs to be expanded */
629
// clang-format on
630
};
631
632
static const int ttb[] = {
633
// clang-format off
634
-1, -2, 0, 0, 0, 0, 0, 0, 0, /* AM/PM */
635
2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
636
10000 + 0, 10000 + 0, 10000 + 0, /* GMT/UT/UTC */
637
10000 + 5 * 60, 10000 + 4 * 60, /* EST/EDT */
638
10000 + 6 * 60, 10000 + 5 * 60, /* CST/CDT */
639
10000 + 7 * 60, 10000 + 6 * 60, /* MST/MDT */
640
10000 + 8 * 60, 10000 + 7 * 60 /* PST/PDT */
641
// clang-format on
642
};
643
644
template <typename CharT>
645
static bool RegionMatches(const char* s1, int s1off, const CharT* s2, int s2off,
646
int count) {
647
while (count > 0 && s1[s1off] && s2[s2off]) {
648
if (unicode::ToLowerCase(s1[s1off]) != unicode::ToLowerCase(s2[s2off])) {
649
break;
650
}
651
652
s1off++;
653
s2off++;
654
count--;
655
}
656
657
return count == 0;
658
}
659
660
// ES2017 draft rev (TODO: Add git hash when PR 642 is merged.)
661
// 20.3.3.4
662
// Date.UTC(year [, month [, date [, hours [, minutes [, seconds [, ms]]]]]])
663
static bool date_UTC(JSContext* cx, unsigned argc, Value* vp) {
664
CallArgs args = CallArgsFromVp(argc, vp);
665
666
// Step 1.
667
double y;
668
if (!ToNumber(cx, args.get(0), &y)) {
669
return false;
670
}
671
672
// Step 2.
673
double m;
674
if (args.length() >= 2) {
675
if (!ToNumber(cx, args[1], &m)) {
676
return false;
677
}
678
} else {
679
m = 0;
680
}
681
682
// Step 3.
683
double dt;
684
if (args.length() >= 3) {
685
if (!ToNumber(cx, args[2], &dt)) {
686
return false;
687
}
688
} else {
689
dt = 1;
690
}
691
692
// Step 4.
693
double h;
694
if (args.length() >= 4) {
695
if (!ToNumber(cx, args[3], &h)) {
696
return false;
697
}
698
} else {
699
h = 0;
700
}
701
702
// Step 5.
703
double min;
704
if (args.length() >= 5) {
705
if (!ToNumber(cx, args[4], &min)) {
706
return false;
707
}
708
} else {
709
min = 0;
710
}
711
712
// Step 6.
713
double s;
714
if (args.length() >= 6) {
715
if (!ToNumber(cx, args[5], &s)) {
716
return false;
717
}
718
} else {
719
s = 0;
720
}
721
722
// Step 7.
723
double milli;
724
if (args.length() >= 7) {
725
if (!ToNumber(cx, args[6], &milli)) {
726
return false;
727
}
728
} else {
729
milli = 0;
730
}
731
732
// Step 8.
733
double yr = y;
734
if (!IsNaN(y)) {
735
double yint = ToInteger(y);
736
if (0 <= yint && yint <= 99) {
737
yr = 1900 + yint;
738
}
739
}
740
741
// Step 9.
742
ClippedTime time =
743
TimeClip(MakeDate(MakeDay(yr, m, dt), MakeTime(h, min, s, milli)));
744
args.rval().set(TimeValue(time));
745
return true;
746
}
747
748
/*
749
* Read and convert decimal digits from s[*i] into *result
750
* while *i < limit.
751
*
752
* Succeed if any digits are converted. Advance *i only
753
* as digits are consumed.
754
*/
755
template <typename CharT>
756
static bool ParseDigits(size_t* result, const CharT* s, size_t* i,
757
size_t limit) {
758
size_t init = *i;
759
*result = 0;
760
while (*i < limit && ('0' <= s[*i] && s[*i] <= '9')) {
761
*result *= 10;
762
*result += (s[*i] - '0');
763
++(*i);
764
}
765
return *i != init;
766
}
767
768
/*
769
* Read and convert decimal digits to the right of a decimal point,
770
* representing a fractional integer, from s[*i] into *result
771
* while *i < limit.
772
*
773
* Succeed if any digits are converted. Advance *i only
774
* as digits are consumed.
775
*/
776
template <typename CharT>
777
static bool ParseFractional(double* result, const CharT* s, size_t* i,
778
size_t limit) {
779
double factor = 0.1;
780
size_t init = *i;
781
*result = 0.0;
782
while (*i < limit && ('0' <= s[*i] && s[*i] <= '9')) {
783
*result += (s[*i] - '0') * factor;
784
factor *= 0.1;
785
++(*i);
786
}
787
return *i != init;
788
}
789
790
/*
791
* Read and convert exactly n decimal digits from s[*i]
792
* to s[min(*i+n,limit)] into *result.
793
*
794
* Succeed if exactly n digits are converted. Advance *i only
795
* on success.
796
*/
797
template <typename CharT>
798
static bool ParseDigitsN(size_t n, size_t* result, const CharT* s, size_t* i,
799
size_t limit) {
800
size_t init = *i;
801
802
if (ParseDigits(result, s, i, std::min(limit, init + n))) {
803
return (*i - init) == n;
804
}
805
806
*i = init;
807
return false;
808
}
809
810
/*
811
* Read and convert n or less decimal digits from s[*i]
812
* to s[min(*i+n,limit)] into *result.
813
*
814
* Succeed only if greater than zero but less than or equal to n digits are
815
* converted. Advance *i only on success.
816
*/
817
template <typename CharT>
818
static bool ParseDigitsNOrLess(size_t n, size_t* result, const CharT* s,
819
size_t* i, size_t limit) {
820
size_t init = *i;
821
822
if (ParseDigits(result, s, i, std::min(limit, init + n))) {
823
return ((*i - init) > 0) && ((*i - init) <= n);
824
}
825
826
*i = init;
827
return false;
828
}
829
830
static int DaysInMonth(int year, int month) {
831
bool leap = IsLeapYear(year);
832
int result = int(DayFromMonth(month, leap) - DayFromMonth(month - 1, leap));
833
return result;
834
}
835
836
/*
837
* Parse a string according to the formats specified in section 20.3.1.16
838
* of the ECMAScript standard. These formats are based upon a simplification
839
* of the ISO 8601 Extended Format. As per the spec omitted month and day
840
* values are defaulted to '01', omitted HH:mm:ss values are defaulted to '00'
841
* and an omitted sss field is defaulted to '000'.
842
*
843
* For cross compatibility we allow the following extensions.
844
*
845
* These are:
846
*
847
* Standalone time part:
848
* Any of the time formats below can be parsed without a date part.
849
* E.g. "T19:00:00Z" will parse successfully. The date part will then
850
* default to 1970-01-01.
851
*
852
* 'T' from the time part may be replaced with a space character:
853
* "1970-01-01 12:00:00Z" will parse successfully. Note that only a single
854
* space is permitted and this is not permitted in the standalone
855
* version above.
856
*
857
* One or more decimal digits for milliseconds:
858
* The specification requires exactly three decimal digits for
859
* the fractional part but we allow for one or more digits.
860
*
861
* Time zone specifier without ':':
862
* We allow the time zone to be specified without a ':' character.
863
* E.g. "T19:00:00+0700" is equivalent to "T19:00:00+07:00".
864
*
865
* One or two digits for months, days, hours, minutes and seconds:
866
* The specification requires exactly two decimal digits for the fields
867
* above. We allow for one or two decimal digits. I.e. "1970-1-1" is
868
* equivalent to "1970-01-01".
869
*
870
* Date part:
871
*
872
* Year:
873
* YYYY (eg 1997)
874
*
875
* Year and month:
876
* YYYY-MM (eg 1997-07)
877
*
878
* Complete date:
879
* YYYY-MM-DD (eg 1997-07-16)
880
*
881
* Time part:
882
*
883
* Hours and minutes:
884
* Thh:mmTZD (eg T19:20+01:00)
885
*
886
* Hours, minutes and seconds:
887
* Thh:mm:ssTZD (eg T19:20:30+01:00)
888
*
889
* Hours, minutes, seconds and a decimal fraction of a second:
890
* Thh:mm:ss.sTZD (eg T19:20:30.45+01:00)
891
*
892
* where:
893
*
894
* YYYY = four-digit year or six digit year as +YYYYYY or -YYYYYY
895
* MM = one or two-digit month (01=January, etc.)
896
* DD = one or two-digit day of month (01 through 31)
897
* hh = one or two digits of hour (00 through 23) (am/pm NOT allowed)
898
* mm = one or two digits of minute (00 through 59)
899
* ss = one or two digits of second (00 through 59)
900
* sss = one or more digits representing a decimal fraction of a second
901
* TZD = time zone designator (Z or +hh:mm or -hh:mm or missing for local)
902
*/
903
template <typename CharT>
904
static bool ParseISOStyleDate(const CharT* s, size_t length,
905
ClippedTime* result) {
906
size_t i = 0;
907
size_t pre = 0;
908
int tzMul = 1;
909
int dateMul = 1;
910
size_t year = 1970;
911
size_t month = 1;
912
size_t day = 1;
913
size_t hour = 0;
914
size_t min = 0;
915
size_t sec = 0;
916
double frac = 0;
917
bool isLocalTime = false;
918
size_t tzHour = 0;
919
size_t tzMin = 0;
920
bool isPermissive = false;
921
bool isStrict = false;
922
923
#define PEEK(ch) (i < length && s[i] == ch)
924
925
#define NEED(ch) \
926
if (i >= length || s[i] != ch) { \
927
return false; \
928
} else { \
929
++i; \
930
}
931
932
#define DONE_DATE_UNLESS(ch) \
933
if (i >= length || s[i] != ch) { \
934
goto done_date; \
935
} else { \
936
++i; \
937
}
938
939
#define DONE_UNLESS(ch) \
940
if (i >= length || s[i] != ch) { \
941
goto done; \
942
} else { \
943
++i; \
944
}
945
946
#define NEED_NDIGITS(n, field) \
947
if (!ParseDigitsN(n, &field, s, &i, length)) { \
948
return false; \
949
}
950
951
#define NEED_NDIGITS_OR_LESS(n, field) \
952
pre = i; \
953
if (!ParseDigitsNOrLess(n, &field, s, &i, length)) { \
954
return false; \
955
} \
956
if (i < pre + (n)) { \
957
if (isStrict) { \
958
return false; \
959
} else { \
960
isPermissive = true; \
961
} \
962
}
963
964
if (PEEK('+') || PEEK('-')) {
965
if (PEEK('-')) {
966
dateMul = -1;
967
}
968
++i;
969
NEED_NDIGITS(6, year);
970
} else {
971
NEED_NDIGITS(4, year);
972
}
973
DONE_DATE_UNLESS('-');
974
NEED_NDIGITS_OR_LESS(2, month);
975
DONE_DATE_UNLESS('-');
976
NEED_NDIGITS_OR_LESS(2, day);
977
978
done_date:
979
if (PEEK('T')) {
980
if (isPermissive) {
981
// Require standard format "[+00]1970-01-01" if a time part marker "T"
982
// exists
983
return false;
984
}
985
isStrict = true;
986
i++;
987
} else if (PEEK(' ')) {
988
i++;
989
} else {
990
goto done;
991
}
992
993
NEED_NDIGITS_OR_LESS(2, hour);
994
NEED(':');
995
NEED_NDIGITS_OR_LESS(2, min);
996
997
if (PEEK(':')) {
998
++i;
999
NEED_NDIGITS_OR_LESS(2, sec);
1000
if (PEEK('.')) {
1001
++i;
1002
if (!ParseFractional(&frac, s, &i, length)) {
1003
return false;
1004
}
1005
}
1006
}
1007
1008
if (PEEK('Z')) {
1009
++i;
1010
} else if (PEEK('+') || PEEK('-')) {
1011
if (PEEK('-')) {
1012
tzMul = -1;
1013
}
1014
++i;
1015
NEED_NDIGITS(2, tzHour);
1016
/*
1017
* Non-standard extension to the ISO date format (permitted by ES5):
1018
* allow "-0700" as a time zone offset, not just "-07:00".
1019
*/
1020
if (PEEK(':')) {
1021
++i;
1022
}
1023
NEED_NDIGITS(2, tzMin);
1024
} else {
1025
isLocalTime = true;
1026
}
1027
1028
done:
1029
if (year > 275943 // ceil(1e8/365) + 1970
1030
|| (month == 0 || month > 12) ||
1031
(day == 0 || day > size_t(DaysInMonth(year, month))) || hour > 24 ||
1032
((hour == 24) && (min > 0 || sec > 0 || frac > 0)) || min > 59 ||
1033
sec > 59 || tzHour > 23 || tzMin > 59) {
1034
return false;
1035
}
1036
1037
if (i != length) {
1038
return false;
1039
}
1040
1041
month -= 1; /* convert month to 0-based */
1042
1043
double msec = MakeDate(MakeDay(dateMul * double(year), month, day),
1044
MakeTime(hour, min, sec, frac * 1000.0));
1045
1046
if (isLocalTime) {
1047
msec = UTC(msec);
1048
} else {
1049
msec -= tzMul * (tzHour * msPerHour + tzMin * msPerMinute);
1050
}
1051
1052
*result = TimeClip(msec);
1053
return NumbersAreIdentical(msec, result->toDouble());
1054
1055
#undef PEEK
1056
#undef NEED
1057
#undef DONE_UNLESS
1058
#undef NEED_NDIGITS
1059
#undef NEED_NDIGITS_OR_LESS
1060
}
1061
1062
template <typename CharT>
1063
static bool ParseDate(const CharT* s, size_t length, ClippedTime* result) {
1064
if (ParseISOStyleDate(s, length, result)) {
1065
return true;
1066
}
1067
1068
if (length == 0) {
1069
return false;
1070
}
1071
1072
int year = -1;
1073
int mon = -1;
1074
int mday = -1;
1075
int hour = -1;
1076
int min = -1;
1077
int sec = -1;
1078
int tzOffset = -1;
1079
1080
// One of '+', '-', ':', '/', or 0 (the default value).
1081
int prevc = 0;
1082
1083
bool seenPlusMinus = false;
1084
bool seenMonthName = false;
1085
bool seenFullYear = false;
1086
bool negativeYear = false;
1087
1088
size_t i = 0;
1089
while (i < length) {
1090
int c = s[i];
1091
i++;
1092
if (c <= ' ' || c == ',' || c == '-') {
1093
if (c == '-' && '0' <= s[i] && s[i] <= '9') {
1094
prevc = c;
1095
}
1096
continue;
1097
}
1098
if (c == '(') { /* comments) */
1099
int depth = 1;
1100
while (i < length) {
1101
c = s[i];
1102
i++;
1103
if (c == '(') {
1104
depth++;
1105
} else if (c == ')') {
1106
if (--depth <= 0) {
1107
break;
1108
}
1109
}
1110
}
1111
continue;
1112
}
1113
if ('0' <= c && c <= '9') {
1114
size_t partStart = i - 1;
1115
uint32_t u = c - '0';
1116
while (i < length && '0' <= (c = s[i]) && c <= '9') {
1117
u = u * 10 + (c - '0');
1118
i++;
1119
}
1120
size_t partLength = i - partStart;
1121
1122
int n = int(u);
1123
1124
/*
1125
* Allow TZA before the year, so 'Wed Nov 05 21:49:11 GMT-0800 1997'
1126
* works.
1127
*
1128
* Uses of seenPlusMinus allow ':' in TZA, so Java no-timezone style
1129
* of GMT+4:30 works.
1130
*/
1131
1132
if (prevc == '-' && (tzOffset != 0 || seenPlusMinus) && partLength >= 4 &&
1133
year < 0) {
1134
// Parse as a negative, possibly zero-padded year if
1135
// 1. the preceding character is '-',
1136
// 2. the TZA is not 'GMT' (tested by |tzOffset != 0|),
1137
// 3. or a TZA was already parsed |seenPlusMinus == true|,
1138
// 4. the part length is at least 4 (to parse '-08' as a TZA),
1139
// 5. and we did not already parse a year |year < 0|.
1140
year = n;
1141
seenFullYear = true;
1142
negativeYear = true;
1143
} else if ((prevc == '+' || prevc == '-') /* && year>=0 */) {
1144
/* Make ':' case below change tzOffset. */
1145
seenPlusMinus = true;
1146
1147
/* offset */
1148
if (n < 24) {
1149
n = n * 60; /* EG. "GMT-3" */
1150
} else {
1151
n = n % 100 + n / 100 * 60; /* eg "GMT-0430" */
1152
}
1153
1154
if (prevc == '+') /* plus means east of GMT */
1155
n = -n;
1156
1157
// Reject if not preceded by 'GMT' or if a time zone offset
1158
// was already parsed.
1159
if (tzOffset != 0 && tzOffset != -1) {
1160
return false;
1161
}
1162
1163
tzOffset = n;
1164
} else if (prevc == '/' && mon >= 0 && mday >= 0 && year < 0) {
1165
if (c <= ' ' || c == ',' || c == '/' || i >= length) {
1166
year = n;
1167
} else {
1168
return false;
1169
}
1170
} else if (c == ':') {
1171
if (hour < 0) {
1172
hour = /*byte*/ n;
1173
} else if (min < 0) {
1174
min = /*byte*/ n;
1175
} else {
1176
return false;
1177
}
1178
} else if (c == '/') {
1179
/*
1180
* Until it is determined that mon is the actual month, keep
1181
* it as 1-based rather than 0-based.
1182
*/
1183
if (mon < 0) {
1184
mon = /*byte*/ n;
1185
} else if (mday < 0) {
1186
mday = /*byte*/ n;
1187
} else {
1188
return false;
1189
}
1190
} else if (i < length && c != ',' && c > ' ' && c != '-' && c != '(') {
1191
return false;
1192
} else if (seenPlusMinus && n < 60) { /* handle GMT-3:30 */
1193
if (tzOffset < 0) {
1194
tzOffset -= n;
1195
} else {
1196
tzOffset += n;
1197
}
1198
} else if (hour >= 0 && min < 0) {
1199
min = /*byte*/ n;
1200
} else if (prevc == ':' && min >= 0 && sec < 0) {
1201
sec = /*byte*/ n;
1202
} else if (mon < 0) {
1203
mon = /*byte*/ n;
1204
} else if (mon >= 0 && mday < 0) {
1205
mday = /*byte*/ n;
1206
} else if (mon >= 0 && mday >= 0 && year < 0) {
1207
year = n;
1208
seenFullYear = partLength >= 4;
1209
} else {
1210
return false;
1211
}
1212
prevc = 0;
1213
} else if (c == '/' || c == ':' || c == '+' || c == '-') {
1214
prevc = c;
1215
} else {
1216
size_t st = i - 1;
1217
while (i < length) {
1218
c = s[i];
1219
if (!IsAsciiAlpha(c)) {
1220
break;
1221
}
1222
i++;
1223
}
1224
1225
if (i <= st + 1) {
1226
return false;
1227
}
1228
1229
int k;
1230
for (k = ArrayLength(wtb); --k >= 0;) {
1231
if (RegionMatches(wtb[k], 0, s, st, i - st)) {
1232
int action = ttb[k];
1233
if (action != 0) {
1234
if (action < 0) {
1235
/*
1236
* AM/PM. Count 12:30 AM as 00:30, 12:30 PM as
1237
* 12:30, instead of blindly adding 12 if PM.
1238
*/
1239
MOZ_ASSERT(action == -1 || action == -2);
1240
if (hour > 12 || hour < 0) {
1241
return false;
1242
}
1243
1244
if (action == -1 && hour == 12) /* am */
1245
hour = 0;
1246
else if (action == -2 && hour != 12) /* pm */
1247
hour += 12;
1248
} else if (action <= 13) { /* month! */
1249
/*
1250
* Adjust mon to be 1-based until the final values
1251
* for mon, mday and year are adjusted below.
1252
*/
1253
if (seenMonthName) {
1254
return false;
1255
}
1256
1257
seenMonthName = true;
1258
int temp = /*byte*/ (action - 2) + 1;
1259
1260
if (mon < 0) {
1261
mon = temp;
1262
} else if (mday < 0) {
1263
mday = mon;
1264
mon = temp;
1265
} else if (year < 0) {
1266
year = mon;
1267
mon = temp;
1268
} else {
1269
return false;
1270
}
1271
} else {
1272
tzOffset = action - 10000;
1273
}
1274
}
1275
break;
1276
}
1277
}
1278
1279
if (k < 0) {
1280
return false;
1281
}
1282
1283
prevc = 0;
1284
}
1285
}
1286
1287
if (year < 0 || mon < 0 || mday < 0) {
1288
return false;
1289
}
1290
1291
/*
1292
* Case 1. The input string contains an English month name.
1293
* The form of the string can be month f l, or f month l, or
1294
* f l month which each evaluate to the same date.
1295
* If f and l are both greater than or equal to 100 the date
1296
* is invalid.
1297
*
1298
* The year is taken to be either the greater of the values f, l or
1299
* whichever is set to zero.
1300
*
1301
* Case 2. The input string is of the form "f/m/l" where f, m and l are
1302
* integers, e.g. 7/16/45. mon, mday and year values are adjusted
1303
* to achieve Chrome compatibility.
1304
*
1305
* a. If 0 < f <= 12 and 0 < l <= 31, f/m/l is interpreted as
1306
* month/day/year.
1307
* b. If 31 < f and 0 < m <= 12 and 0 < l <= 31 f/m/l is
1308
* interpreted as year/month/day
1309
*/
1310
if (seenMonthName) {
1311
if (mday >= 100 && mon >= 100) {
1312
return false;
1313
}
1314
1315
if (year > 0 && (mday == 0 || mday > year) && !seenFullYear) {
1316
int temp = year;
1317
year = mday;
1318
mday = temp;
1319
}
1320
1321
if (mday <= 0 || mday > 31) {
1322
return false;
1323
}
1324
1325
} else if (0 < mon && mon <= 12 && 0 < mday && mday <= 31) {
1326
/* (a) month/day/year */
1327
} else {
1328
/* (b) year/month/day */
1329
if (mon > 31 && mday <= 12 && year <= 31 && !seenFullYear) {
1330
int temp = year;
1331
year = mon;
1332
mon = mday;
1333
mday = temp;
1334
} else {
1335
return false;
1336
}
1337
}
1338
1339
// If the year is greater than or equal to 50 and less than 100, it is
1340
// considered to be the number of years after 1900. If the year is less
1341
// than 50 it is considered to be the number of years after 2000,
1342
// otherwise it is considered to be the number of years after 0.
1343
if (!seenFullYear) {
1344
if (year < 50) {
1345
year += 2000;
1346
} else if (year >= 50 && year < 100) {
1347
year += 1900;
1348
}
1349
}
1350
1351
if (negativeYear) {
1352
year = -year;
1353
}
1354
1355
mon -= 1; /* convert month to 0-based */
1356
if (sec < 0) {
1357
sec = 0;
1358
}
1359
if (min < 0) {
1360
min = 0;
1361
}
1362
if (hour < 0) {
1363
hour = 0;
1364
}
1365
1366
double msec = MakeDate(MakeDay(year, mon, mday), MakeTime(hour, min, sec, 0));
1367
1368
if (tzOffset == -1) { /* no time zone specified, have to use local */
1369
msec = UTC(msec);
1370
} else {
1371
msec += tzOffset * msPerMinute;
1372
}
1373
1374
*result = TimeClip(msec);
1375
return true;
1376
}
1377
1378
static bool ParseDate(JSLinearString* s, ClippedTime* result) {
1379
AutoCheckCannotGC nogc;
1380
return s->hasLatin1Chars()
1381
? ParseDate(s->latin1Chars(nogc), s->length(), result)
1382
: ParseDate(s->twoByteChars(nogc), s->length(), result);
1383
}
1384
1385
static bool date_parse(JSContext* cx, unsigned argc, Value* vp) {
1386
CallArgs args = CallArgsFromVp(argc, vp);
1387
if (args.length() == 0) {
1388
args.rval().setNaN();
1389
return true;
1390
}
1391
1392
JSString* str = ToString<CanGC>(cx, args[0]);
1393
if (!str) {
1394
return false;
1395
}
1396
1397
JSLinearString* linearStr = str->ensureLinear(cx);
1398
if (!linearStr) {
1399
return false;
1400
}
1401
1402
ClippedTime result;
1403
if (!ParseDate(linearStr, &result)) {
1404
args.rval().setNaN();
1405
return true;
1406
}
1407
1408
args.rval().set(TimeValue(result));
1409
return true;
1410
}
1411
1412
static ClippedTime NowAsMillis(JSContext* cx) {
1413
double now = PRMJ_Now();
1414
bool clampAndJitter = cx->realm()->behaviors().clampAndJitterTime();
1415
if (clampAndJitter && sReduceMicrosecondTimePrecisionCallback) {
1416
now = sReduceMicrosecondTimePrecisionCallback(now);
1417
} else if (clampAndJitter && sResolutionUsec) {
1418
double clamped = floor(now / sResolutionUsec) * sResolutionUsec;
1419
1420
if (sJitter) {
1421
// Calculate a random midpoint for jittering. In the browser, we are
1422
// adversarial: Web Content may try to calculate the midpoint themselves
1423
// and use that to bypass it's security. In the JS Shell, we are not
1424
// adversarial, we want to jitter the time to recreate the operating
1425
// environment, but we do not concern ourselves with trying to prevent an
1426
// attacker from calculating the midpoint themselves. So we use a very
1427
// simple, very fast CRC with a hardcoded seed.
1428
1429
uint64_t midpoint = BitwiseCast<uint64_t>(clamped);
1430
midpoint ^= 0x0F00DD1E2BAD2DED; // XOR in a 'secret'
1431
// MurmurHash3 internal component from
1433
midpoint ^= midpoint >> 33;
1434
midpoint *= uint64_t{0xFF51AFD7ED558CCD};
1435
midpoint ^= midpoint >> 33;
1436
midpoint *= uint64_t{0xC4CEB9FE1A85EC53};
1437
midpoint ^= midpoint >> 33;
1438
midpoint %= sResolutionUsec;
1439
1440
if (now > clamped + midpoint) { // We're jittering up to the next step
1441
now = clamped + sResolutionUsec;
1442
} else { // We're staying at the clamped value
1443
now = clamped;
1444
}
1445
} else { // No jitter, only clamping
1446
now = clamped;
1447
}
1448
}
1449
1450
return TimeClip(now / PRMJ_USEC_PER_MSEC);
1451
}
1452
1453
bool js::date_now(JSContext* cx, unsigned argc, Value* vp) {
1454
CallArgs args = CallArgsFromVp(argc, vp);
1455
args.rval().set(TimeValue(NowAsMillis(cx)));
1456
return true;
1457
}
1458
1459
void DateObject::setUTCTime(ClippedTime t) {
1460
for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++) {
1461
setReservedSlot(ind, UndefinedValue());
1462
}
1463
1464
setFixedSlot(UTC_TIME_SLOT, TimeValue(t));
1465
}
1466
1467
void DateObject::setUTCTime(ClippedTime t, MutableHandleValue vp) {
1468
setUTCTime(t);
1469
vp.set(TimeValue(t));
1470
}
1471
1472
void DateObject::fillLocalTimeSlots() {
1473
const int32_t utcTZOffset = DateTimeInfo::utcToLocalStandardOffsetSeconds();
1474
1475
/* Check if the cache is already populated. */
1476
if (!getReservedSlot(LOCAL_TIME_SLOT).isUndefined() &&
1477
getReservedSlot(UTC_TIME_ZONE_OFFSET_SLOT).toInt32() == utcTZOffset) {
1478
return;
1479
}
1480
1481
/* Remember time zone used to generate the local cache. */
1482
setReservedSlot(UTC_TIME_ZONE_OFFSET_SLOT, Int32Value(utcTZOffset));
1483
1484
double utcTime = UTCTime().toNumber();
1485
1486
if (!IsFinite(utcTime)) {
1487
for (size_t ind = COMPONENTS_START_SLOT; ind < RESERVED_SLOTS; ind++) {
1488
setReservedSlot(ind, DoubleValue(utcTime));
1489
}
1490
return;
1491
}
1492
1493
double localTime = LocalTime(utcTime);
1494
1495
setReservedSlot(LOCAL_TIME_SLOT, DoubleValue(localTime));
1496
1497
int year = (int)floor(localTime / (msPerDay * 365.2425)) + 1970;
1498
double yearStartTime = TimeFromYear(year);
1499
1500
/* Adjust the year in case the approximation was wrong, as in YearFromTime. */
1501
int yearDays;
1502
if (yearStartTime > localTime) {
1503
year--;
1504
yearStartTime -= (msPerDay * DaysInYear(year));
1505
yearDays = DaysInYear(year);
1506
} else {
1507
yearDays = DaysInYear(year);
1508
double nextStart = yearStartTime + (msPerDay * yearDays);
1509
if (nextStart <= localTime) {
1510
year++;
1511
yearStartTime = nextStart;
1512
yearDays = DaysInYear(year);
1513
}
1514
}
1515
1516
setReservedSlot(LOCAL_YEAR_SLOT, Int32Value(year));
1517
1518
uint64_t yearTime = uint64_t(localTime - yearStartTime);
1519
int yearSeconds = uint32_t(yearTime / 1000);
1520
1521
int day = yearSeconds / int(SecondsPerDay);
1522
1523
int step = -1, next = 30;
1524
int month;
1525
1526
do {
1527
if (day <= next) {
1528
month = 0;
1529
break;
1530
}
1531
step = next;
1532
next += ((yearDays == 366) ? 29 : 28);
1533
if (day <= next) {
1534
month = 1;
1535
break;
1536
}
1537
step = next;
1538
if (day <= (next += 31)) {
1539
month = 2;
1540
break;
1541
}
1542
step = next;
1543
if (day <= (next += 30)) {
1544
month = 3;
1545
break;
1546
}
1547
step = next;
1548
if (day <= (next += 31)) {
1549
month = 4;
1550
break;
1551
}
1552
step = next;
1553
if (day <= (next += 30)) {
1554
month = 5;
1555
break;
1556
}
1557
step = next;
1558
if (day <= (next += 31)) {
1559
month = 6;
1560
break;
1561
}
1562
step = next;
1563
if (day <= (next += 31)) {
1564
month = 7;
1565
break;
1566
}
1567
step = next;
1568
if (day <= (next += 30)) {
1569
month = 8;
1570
break;
1571
}
1572
step = next;
1573
if (day <= (next += 31)) {
1574
month = 9;
1575
break;
1576
}
1577
step = next;
1578
if (day <= (next += 30)) {
1579
month = 10;
1580
break;
1581
}
1582
step = next;
1583
month = 11;
1584
} while (0);
1585
1586
setReservedSlot(LOCAL_MONTH_SLOT, Int32Value(month));
1587
setReservedSlot(LOCAL_DATE_SLOT, Int32Value(day - step));
1588
1589
int weekday = WeekDay(localTime);
1590
setReservedSlot(LOCAL_DAY_SLOT, Int32Value(weekday));
1591
1592
setReservedSlot(LOCAL_SECONDS_INTO_YEAR_SLOT, Int32Value(yearSeconds));
1593
}
1594
1595
inline double DateObject::cachedLocalTime() {
1596
fillLocalTimeSlots();
1597
return getReservedSlot(LOCAL_TIME_SLOT).toDouble();
1598
}
1599
1600
MOZ_ALWAYS_INLINE bool IsDate(HandleValue v) {
1601
return v.isObject() && v.toObject().is<DateObject>();
1602
}
1603
1604
/*
1605
* See ECMA 15.9.5.4 thru 15.9.5.23
1606
*/
1607
/* static */ MOZ_ALWAYS_INLINE bool DateObject::getTime_impl(
1608
JSContext* cx, const CallArgs& args) {
1609
args.rval().set(args.thisv().toObject().as<DateObject>().UTCTime());
1610
return true;
1611
}
1612
1613
static bool date_getTime(JSContext* cx, unsigned argc, Value* vp) {
1614
CallArgs args = CallArgsFromVp(argc, vp);
1615
return CallNonGenericMethod<IsDate, DateObject::getTime_impl>(cx, args);
1616
}
1617
1618
/* static */ MOZ_ALWAYS_INLINE bool DateObject::getYear_impl(
1619
JSContext* cx, const CallArgs& args) {
1620
DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
1621
dateObj->fillLocalTimeSlots();
1622
1623
Value yearVal = dateObj->getReservedSlot(LOCAL_YEAR_SLOT);
1624
if (yearVal.isInt32()) {
1625
/* Follow ECMA-262 to the letter, contrary to IE JScript. */
1626
int year = yearVal.toInt32() - 1900;
1627
args.rval().setInt32(year);
1628
} else {
1629
args.rval().set(yearVal);
1630
}
1631
1632
return true;
1633
}
1634
1635
static bool date_getYear(JSContext* cx, unsigned argc, Value* vp) {
1636
CallArgs args = CallArgsFromVp(argc, vp);
1637
return CallNonGenericMethod<IsDate, DateObject::getYear_impl>(cx, args);
1638
}
1639
1640
/* static */ MOZ_ALWAYS_INLINE bool DateObject::getFullYear_impl(
1641
JSContext* cx, const CallArgs& args) {
1642
DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
1643
dateObj->fillLocalTimeSlots();
1644
1645
args.rval().set(dateObj->getReservedSlot(LOCAL_YEAR_SLOT));
1646
return true;
1647
}
1648
1649
static bool date_getFullYear(JSContext* cx, unsigned argc, Value* vp) {
1650
CallArgs args = CallArgsFromVp(argc, vp);
1651
return CallNonGenericMethod<IsDate, DateObject::getFullYear_impl>(cx, args);
1652
}
1653
1654
/* static */ MOZ_ALWAYS_INLINE bool DateObject::getUTCFullYear_impl(
1655
JSContext* cx, const CallArgs& args) {
1656
double result = args.thisv().toObject().as<DateObject>().UTCTime().toNumber();
1657
if (IsFinite(result)) {
1658
result = YearFromTime(result);
1659
}
1660
1661
args.rval().setNumber(result);
1662
return true;
1663
}
1664
1665
static bool date_getUTCFullYear(JSContext* cx, unsigned argc, Value* vp) {
1666
CallArgs args = CallArgsFromVp(argc, vp);
1667
return CallNonGenericMethod<IsDate, DateObject::getUTCFullYear_impl>(cx,
1668
args);
1669
}
1670
1671
/* static */ MOZ_ALWAYS_INLINE bool DateObject::getMonth_impl(
1672
JSContext* cx, const CallArgs& args) {
1673
DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
1674
dateObj->fillLocalTimeSlots();
1675
1676
args.rval().set(dateObj->getReservedSlot(LOCAL_MONTH_SLOT));
1677
return true;
1678
}
1679
1680
static bool date_getMonth(JSContext* cx, unsigned argc, Value* vp) {
1681
CallArgs args = CallArgsFromVp(argc, vp);
1682
return CallNonGenericMethod<IsDate, DateObject::getMonth_impl>(cx, args);
1683
}
1684
1685
/* static */ MOZ_ALWAYS_INLINE bool DateObject::getUTCMonth_impl(
1686
JSContext* cx, const CallArgs& args) {
1687
double d = args.thisv().toObject().as<DateObject>().UTCTime().toNumber();
1688
args.rval().setNumber(MonthFromTime(d));
1689
return true;
1690
}
1691
1692
static bool date_getUTCMonth(JSContext* cx, unsigned argc, Value* vp) {
1693
CallArgs args = CallArgsFromVp(argc, vp);
1694
return CallNonGenericMethod<IsDate, DateObject::getUTCMonth_impl>(cx, args);
1695
}
1696
1697
/* static */ MOZ_ALWAYS_INLINE bool DateObject::getDate_impl(
1698
JSContext* cx, const CallArgs& args) {
1699
DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
1700
dateObj->fillLocalTimeSlots();
1701
1702
args.rval().set(dateObj->getReservedSlot(LOCAL_DATE_SLOT));
1703
return true;
1704
}
1705
1706
static bool date_getDate(JSContext* cx, unsigned argc, Value* vp) {
1707
CallArgs args = CallArgsFromVp(argc, vp);
1708
return CallNonGenericMethod<IsDate, DateObject::getDate_impl>(cx, args);
1709
}
1710
1711
/* static */ MOZ_ALWAYS_INLINE bool DateObject::getUTCDate_impl(
1712
JSContext* cx, const CallArgs& args) {
1713
double result = args.thisv().toObject().as<DateObject>().UTCTime().toNumber();
1714
if (IsFinite(result)) {
1715
result = DateFromTime(result);
1716
}
1717
1718
args.rval().setNumber(result);
1719
return true;
1720
}
1721
1722
static bool date_getUTCDate(JSContext* cx, unsigned argc, Value* vp) {
1723
CallArgs args = CallArgsFromVp(argc, vp);
1724
return CallNonGenericMethod<IsDate, DateObject::getUTCDate_impl>(cx, args);
1725
}
1726
1727
/* static */ MOZ_ALWAYS_INLINE bool DateObject::getDay_impl(
1728
JSContext* cx, const CallArgs& args) {
1729
DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
1730
dateObj->fillLocalTimeSlots();
1731
1732
args.rval().set(dateObj->getReservedSlot(LOCAL_DAY_SLOT));
1733
return true;
1734
}
1735
1736
static bool date_getDay(JSContext* cx, unsigned argc, Value* vp) {
1737
CallArgs args = CallArgsFromVp(argc, vp);
1738
return CallNonGenericMethod<IsDate, DateObject::getDay_impl>(cx, args);
1739
}
1740
1741
/* static */ MOZ_ALWAYS_INLINE bool DateObject::getUTCDay_impl(
1742
JSContext* cx, const CallArgs& args) {
1743
double result = args.thisv().toObject().as<DateObject>().UTCTime().toNumber();
1744
if (IsFinite(result)) {
1745
result = WeekDay(result);
1746
}
1747
1748
args.rval().setNumber(result);
1749
return true;
1750
}
1751
1752
static bool date_getUTCDay(JSContext* cx, unsigned argc, Value* vp) {
1753
CallArgs args = CallArgsFromVp(argc, vp);
1754
return CallNonGenericMethod<IsDate, DateObject::getUTCDay_impl>(cx, args);
1755
}
1756
1757
/* static */ MOZ_ALWAYS_INLINE bool DateObject::getHours_impl(
1758
JSContext* cx, const CallArgs& args) {
1759
DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
1760
dateObj->fillLocalTimeSlots();
1761
1762
// Note: LOCAL_SECONDS_INTO_YEAR_SLOT is guaranteed to contain an
1763
// int32 or NaN after the call to fillLocalTimeSlots.
1764
Value yearSeconds = dateObj->getReservedSlot(LOCAL_SECONDS_INTO_YEAR_SLOT);
1765
if (yearSeconds.isDouble()) {
1766
MOZ_ASSERT(IsNaN(yearSeconds.toDouble()));
1767
args.rval().set(yearSeconds);
1768
} else {
1769
args.rval().setInt32((yearSeconds.toInt32() / int(SecondsPerHour)) %
1770
int(HoursPerDay));
1771
}
1772
return true;
1773
}
1774
1775
static bool date_getHours(JSContext* cx, unsigned argc, Value* vp) {
1776
CallArgs args = CallArgsFromVp(argc, vp);
1777
return CallNonGenericMethod<IsDate, DateObject::getHours_impl>(cx, args);
1778
}
1779
1780
/* static */ MOZ_ALWAYS_INLINE bool DateObject::getUTCHours_impl(
1781
JSContext* cx, const CallArgs& args) {
1782
double result = args.thisv().toObject().as<DateObject>().UTCTime().toNumber();
1783
if (IsFinite(result)) {
1784
result = HourFromTime(result);
1785
}
1786
1787
args.rval().setNumber(result);
1788
return true;
1789
}
1790
1791
static bool date_getUTCHours(JSContext* cx, unsigned argc, Value* vp) {
1792
CallArgs args = CallArgsFromVp(argc, vp);
1793
return CallNonGenericMethod<IsDate, DateObject::getUTCHours_impl>(cx, args);
1794
}
1795
1796
/* static */ MOZ_ALWAYS_INLINE bool DateObject::getMinutes_impl(
1797
JSContext* cx, const CallArgs& args) {
1798
DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
1799
dateObj->fillLocalTimeSlots();
1800
1801
// Note: LOCAL_SECONDS_INTO_YEAR_SLOT is guaranteed to contain an
1802
// int32 or NaN after the call to fillLocalTimeSlots.
1803
Value yearSeconds = dateObj->getReservedSlot(LOCAL_SECONDS_INTO_YEAR_SLOT);
1804
if (yearSeconds.isDouble()) {
1805
MOZ_ASSERT(IsNaN(yearSeconds.toDouble()));
1806
args.rval().set(yearSeconds);
1807
} else {
1808
args.rval().setInt32((yearSeconds.toInt32() / int(SecondsPerMinute)) %
1809
int(MinutesPerHour));
1810
}
1811
return true;
1812
}
1813
1814
static bool date_getMinutes(JSContext* cx, unsigned argc, Value* vp) {
1815
CallArgs args = CallArgsFromVp(argc, vp);
1816
return CallNonGenericMethod<IsDate, DateObject::getMinutes_impl>(cx, args);
1817
}
1818
1819
/* static */ MOZ_ALWAYS_INLINE bool DateObject::getUTCMinutes_impl(
1820
JSContext* cx, const CallArgs& args) {
1821
double result = args.thisv().toObject().as<DateObject>().UTCTime().toNumber();
1822
if (IsFinite(result)) {
1823
result = MinFromTime(result);
1824
}
1825
1826
args.rval().setNumber(result);
1827
return true;
1828
}
1829
1830
static bool date_getUTCMinutes(JSContext* cx, unsigned argc, Value* vp) {
1831
CallArgs args = CallArgsFromVp(argc, vp);
1832
return CallNonGenericMethod<IsDate, DateObject::getUTCMinutes_impl>(cx, args);
1833
}
1834
1835
/* static */ MOZ_ALWAYS_INLINE bool DateObject::getSeconds_impl(
1836
JSContext* cx, const CallArgs& args) {
1837
DateObject* dateObj = &args.thisv().toObject().as<DateObject>();
1838
dateObj->fillLocalTimeSlots();
1839
1840
// Note: LOCAL_SECONDS_INTO_YEAR_SLOT is guaranteed to contain an
1841
// int32 or NaN after the call to fillLocalTimeSlots.
1842
Value yearSeconds = dateObj->getReservedSlot(LOCAL_SECONDS_INTO_YEAR_SLOT);
1843
if (yearSeconds.isDouble()) {
1844
MOZ_ASSERT(IsNaN(yearSeconds.toDouble()));
1845
args.rval().set(yearSeconds);
1846
} else {
1847
args.rval().setInt32(yearSeconds.toInt32() % int(SecondsPerMinute));
1848
}
1849
return true;
1850
}
1851