Coverage Report

Created: 2025-01-28 06:38

/src/hermes/lib/VM/JSLib/DateUtil.cpp
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright (c) Meta Platforms, Inc. and affiliates.
3
 *
4
 * This source code is licensed under the MIT license found in the
5
 * LICENSE file in the root directory of this source tree.
6
 */
7
8
#include "hermes/VM/JSLib/DateUtil.h"
9
#include "hermes/VM/JSLib/DateCache.h"
10
11
#include "hermes/Platform/Unicode/PlatformUnicode.h"
12
#include "hermes/Support/Compiler.h"
13
#include "hermes/Support/OSCompat.h"
14
#include "hermes/VM/CallResult.h"
15
#include "hermes/VM/SmallXString.h"
16
17
#include "llvh/Support/ErrorHandling.h"
18
#include "llvh/Support/Format.h"
19
#include "llvh/Support/raw_ostream.h"
20
21
#include <cassert>
22
#include <cctype>
23
#include <cmath>
24
#include <ctime>
25
#pragma GCC diagnostic push
26
27
#ifdef HERMES_COMPILER_SUPPORTS_WSHORTEN_64_TO_32
28
#pragma GCC diagnostic ignored "-Wshorten-64-to-32"
29
#endif
30
namespace hermes {
31
namespace vm {
32
33
/// Set \p quot to the largest integral value that is smaller than or equal to
34
/// the algebraic quotient of \p x divided by \p y.
35
/// Set \p rem to the floor modulus of \p x divided by \p y.
36
0
static void floorDivMod(int64_t x, int64_t y, int64_t *quot, int64_t *rem) {
37
0
  int64_t q = x / y;
38
  // signs are different && not evenly divisable
39
0
  if ((x ^ y) < 0 && q * y != x) {
40
0
    q--;
41
0
  }
42
0
  *quot = q;
43
0
  *rem = x - y * q;
44
0
}
45
46
/// \return the floor modulus of \p x divided by \p y.
47
0
static int64_t floorMod(int64_t x, int64_t y) {
48
0
  int64_t quot, rem;
49
0
  floorDivMod(x, y, &quot, &rem);
50
0
  return rem;
51
0
}
52
53
/// Perform the fmod operation and adjusts the result so that it's not negative.
54
/// Useful in computing dates before Jan 1 1970.
55
0
static inline double posfmod(double x, double y) {
56
0
  double result = std::fmod(x, y);
57
0
  return result < 0 ? result + y : result;
58
0
}
59
60
//===----------------------------------------------------------------------===//
61
// Current time
62
63
0
std::chrono::milliseconds::rep curTime() {
64
  // Use std::chrono here because we need millisecond precision, which
65
  // std::time() fails to provide.
66
0
  return std::chrono::duration_cast<std::chrono::milliseconds>(
67
0
             std::chrono::system_clock::now().time_since_epoch())
68
0
      .count();
69
0
}
70
71
//===----------------------------------------------------------------------===//
72
// ES5.1 15.9.1.2
73
74
0
double day(double t) {
75
0
  return std::floor(t / MS_PER_DAY);
76
0
}
77
78
0
double timeWithinDay(double t) {
79
0
  return posfmod(t, MS_PER_DAY);
80
0
}
81
82
//===----------------------------------------------------------------------===//
83
// ES5.1 15.9.1.3
84
85
/// \return true if year \p y is a leap year.
86
0
static bool isLeapYear(double y) {
87
0
  if (std::fmod(y, 4) != 0) {
88
0
    return false;
89
0
  }
90
  // y % 4 == 0
91
0
  if (std::fmod(y, 100) != 0) {
92
0
    return true;
93
0
  }
94
  // y % 100 == 0
95
0
  if (std::fmod(y, 400) != 0) {
96
0
    return false;
97
0
  }
98
  // y % 400 == 0
99
0
  return true;
100
0
}
101
102
/// \return true if year \p y is a leap year.
103
0
static bool isLeapYear(int32_t y) {
104
0
  if (y % 4 != 0) {
105
0
    return false;
106
0
  }
107
  // y % 4 == 0
108
0
  if (y % 100 != 0) {
109
0
    return true;
110
0
  }
111
  // y % 100 == 0
112
0
  if (y % 400 != 0) {
113
0
    return false;
114
0
  }
115
  // y % 400 == 0
116
0
  return true;
117
0
}
118
119
0
uint32_t daysInYear(double y) {
120
0
  return isLeapYear(y) ? 366 : 365;
121
0
}
122
123
0
double dayFromYear(double y) {
124
  // Use the formula given in the spec for computing the day from year.
125
0
  return 365 * (y - 1970) + std::floor((y - 1969) / 4.0) -
126
0
      std::floor((y - 1901) / 100.0) + std::floor((y - 1601) / 400.0);
127
0
}
128
129
0
double timeFromYear(double y) {
130
0
  return MS_PER_DAY * dayFromYear(y);
131
0
}
132
133
0
double yearFromTime(double t) {
134
0
  if (!std::isfinite(t)) {
135
    // If t is infinitely in the future be done.
136
0
    return t;
137
0
  }
138
139
  // Estimate y using the average year length.
140
0
  double y = std::floor(t / (MS_PER_DAY * 365.2425)) + 1970;
141
142
  // Actual time for year y.
143
0
  double yt = timeFromYear(y);
144
145
0
  while (yt > t) {
146
    // Estimate was too high, decrement until we're correct.
147
0
    --y;
148
0
    yt = timeFromYear(y);
149
0
  }
150
0
  while (yt + daysInYear(y) * MS_PER_DAY <= t) {
151
    // t is more than a year away from the start of y.
152
    // Increment y until we're correct.
153
0
    ++y;
154
0
    yt = timeFromYear(y);
155
0
  }
156
157
0
  assert(
158
0
      timeFromYear(y) <= t && timeFromYear(y + 1) > t &&
159
0
      "yearFromTime incorrect");
160
0
  return y;
161
0
}
162
163
0
bool inLeapYear(double t) {
164
0
  return daysInYear(yearFromTime(t)) == 366;
165
0
}
166
167
//===----------------------------------------------------------------------===//
168
// ES5.1 15.9.1.4
169
170
0
uint32_t monthFromTime(double t) {
171
0
  double dayWithinYear = day(t) - dayFromYear(yearFromTime(t));
172
0
  constexpr int8_t kDaysInMonthNonLeap[11] = {
173
0
      31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30};
174
0
  double curDay = 0.0;
175
0
  for (uint32_t i = 0; i < 11; ++i) {
176
0
    curDay += (i == 1 && inLeapYear(t)) ? kDaysInMonthNonLeap[i] + 1
177
0
                                        : kDaysInMonthNonLeap[i];
178
0
    if (dayWithinYear < curDay)
179
0
      return i;
180
0
  }
181
  // Must be December.
182
0
  return 11;
183
0
}
184
185
//===----------------------------------------------------------------------===//
186
// ES5.1 15.9.1.5
187
188
/// Gives the offset of the first day in month m.
189
/// \param leap indicates if \p m falls in a leap year.
190
0
static uint32_t dayFromMonth(uint32_t m, bool leap) {
191
0
  static const uint16_t standardTable[]{
192
0
      0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
193
0
  static const uint16_t leapYearTable[]{
194
0
      0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366};
195
0
  assert(m < 12 && "invalid month supplied to dayFromMonth");
196
0
  return leap ? leapYearTable[m] : standardTable[m];
197
0
}
198
199
0
double dateFromTime(double t) {
200
0
  double dayWithinYear = day(t) - dayFromYear(yearFromTime(t));
201
0
  bool leap = inLeapYear(t);
202
0
  return dayWithinYear - dayFromMonth(monthFromTime(t), leap) + 1;
203
0
}
204
205
//===----------------------------------------------------------------------===//
206
// ES5.1 15.9.1.6
207
208
0
int32_t weekDay(double t) {
209
0
  return posfmod((day(t) + 4), 7);
210
0
}
211
212
//===----------------------------------------------------------------------===//
213
// ES5.1 15.9.1.7
214
215
160
double localTZA() {
216
#ifdef _WINDOWS
217
218
  // TODO(T173336959): We should use a thread-safe API, and also be consistent
219
  // with daylightSavingTA().
220
  _tzset();
221
222
  long gmtoff;
223
  int err = _get_timezone(&gmtoff);
224
  if (err)
225
    return 0;
226
227
  // The result of _get_timezone is negated
228
  return -gmtoff * MS_PER_SECOND;
229
230
#else
231
232
  // Get the current time in seconds (might have DST adjustment included).
233
160
  std::time_t currentWithDST = std::time(nullptr);
234
235
  // Deconstruct the time into localTime.
236
  // Note that localtime_r uses cached timezone information on Linux (glibc), so
237
  // the returned local time may not be computed using an updated timezone if
238
  // the timezone changes after this process has started.
239
160
  std::tm tm;
240
160
  std::tm *local = ::localtime_r(&currentWithDST, &tm);
241
160
  if (!local)
242
0
    return 0;
243
244
160
  long gmtoff = local->tm_gmtoff;
245
246
  // Use the gmtoff field and subtract an hour if currently in DST.
247
160
  return (gmtoff * MS_PER_SECOND) - (local->tm_isdst ? MS_PER_HOUR : 0);
248
249
160
#endif
250
160
}
251
252
//===----------------------------------------------------------------------===//
253
// ES5.1 15.9.1.8
254
255
static const int32_t DAYS_IN_1_YEAR = 365;
256
static const int32_t DAYS_IN_4_YEARS = DAYS_IN_1_YEAR * 4 + 1;
257
static const int32_t DAYS_IN_100_YEARS = DAYS_IN_4_YEARS * 25 - 1;
258
static const int32_t DAYS_IN_400_YEARS = DAYS_IN_100_YEARS * 4 + 1;
259
// ES5.1 15.9.1.1
260
// The actual range of times supported by ECMAScript Date objects is slightly
261
// smaller: exactly –100,000,000 days to 100,000,000 days measured relative to
262
// midnight at the beginning of 01 January, 1970 UTC.
263
static const int32_t BASE_YEAR = -274000;
264
static const int32_t DAYS_FROM_BASE_YEAR_TO_1970 =
265
    (-BASE_YEAR / 400) * DAYS_IN_400_YEARS + 4 * DAYS_IN_400_YEARS +
266
    3 * DAYS_IN_100_YEARS + 17 * DAYS_IN_4_YEARS + 2 * DAYS_IN_1_YEAR;
267
268
/// \p year will be set to the year the \p epochDays falls in.
269
/// \p yearAsEpochDays will be set to the number of days from 1970-01-01 to Jan
270
///    1st of \p year (e.g. 0 represents 1970, 365 represents 1971,
271
///    1096 represents 1973).
272
/// \p dayOfYear will be set to the date the \p epochDays fall on, represented
273
///    as number of days since Jan 1st (e.g. 0 represents Jan 1;
274
///    59 represents Feb 29 if \p year is a leap year, Mar 1 otherwise).
275
static void decomposeEpochDays(
276
    int32_t epochDays,
277
    int32_t *year,
278
    int32_t *yearAsEpochDays,
279
0
    int32_t *dayOfYear) {
280
0
  *year = BASE_YEAR;
281
0
  *yearAsEpochDays = -DAYS_FROM_BASE_YEAR_TO_1970;
282
0
  *dayOfYear = epochDays + DAYS_FROM_BASE_YEAR_TO_1970;
283
284
0
  int32_t countOf400Years = *dayOfYear / DAYS_IN_400_YEARS;
285
0
  *year += countOf400Years * 400;
286
0
  *yearAsEpochDays += countOf400Years * DAYS_IN_400_YEARS;
287
0
  *dayOfYear -= countOf400Years * DAYS_IN_400_YEARS;
288
289
0
  int32_t countOf100Years = *dayOfYear / DAYS_IN_100_YEARS;
290
0
  *year += countOf100Years * 100;
291
0
  *yearAsEpochDays += countOf100Years * DAYS_IN_100_YEARS;
292
0
  *dayOfYear -= countOf100Years * DAYS_IN_100_YEARS;
293
294
0
  int32_t countOf4Years = *dayOfYear / DAYS_IN_4_YEARS;
295
0
  *year += countOf4Years * 4;
296
0
  *yearAsEpochDays += countOf4Years * DAYS_IN_4_YEARS;
297
0
  *dayOfYear -= countOf4Years * DAYS_IN_4_YEARS;
298
299
0
  int32_t countOf1Year = *dayOfYear / DAYS_IN_1_YEAR;
300
0
  *year += countOf1Year * 1;
301
0
  *yearAsEpochDays += countOf1Year * DAYS_IN_1_YEAR;
302
0
  *dayOfYear -= countOf1Year * DAYS_IN_1_YEAR;
303
0
}
304
305
0
static int32_t weekDayFromEpochDays(int32_t epochDays) {
306
0
  return floorMod(epochDays + 4, 7);
307
0
}
308
309
static int32_t epochDaysForYear2006To2033[] = {
310
    13149, 13514, 13879, 14245, 14610, 14975, 15340, 15706, 16071, 16436,
311
    16801, 17167, 17532, 17897, 18262, 18628, 18993, 19358, 19723, 20089,
312
    20454, 20819, 21184, 21550, 21915, 22280, 22645, 23011};
313
314
/// Returns an equivalent year, represented as number of days since 1970-01-01,
315
/// for the purpose of determining DST using the rules in ES5.1 15.9.1.8
316
/// Daylight Saving Time Adjustment.
317
/// The returned year is guaranteed to be in range [1970, 2037].
318
/// \p yearAsEpochDays must be set to the number of days from 1970-01-01 to Jan
319
/// 1st of \p year.
320
static int32_t equivalentYearAsEpochDays(
321
    int32_t year,
322
0
    int32_t yearAsEpochDays) {
323
0
  if (year >= 1970 && year <= 2037) {
324
    // This avoids surprising results for current year and nearby years.
325
    // It also reduces overhead for the most common cases.
326
0
    return yearAsEpochDays;
327
0
  }
328
0
  int32_t wkDay = weekDayFromEpochDays(yearAsEpochDays);
329
  // * 2006-01-01 and 2012-01-01 are both Sundays.
330
  // * Starting 2006/2012, for the 40 years after it, there is a leap year
331
  //   every 4 years, with no exceptions (i.e. 100 year rules).
332
  //   This is the basis of the following two bullet points.
333
  // * any_int * 12 % 28 is guaranteed to be a multiple of 4.
334
  //   As a result, the following operations does not change
335
  //   whether a year is a leap year or not.
336
  // * Every 4 years, there is 1 leap year and 3 non-leap years.
337
  //   (365*3+366) % 7 = 5. This is the number of extra days on top of
338
  //   full weeks we get every 4 years.
339
  //   * After 28 (4 * 7) years, we get (5 * 7) % 7 = 0 day of extra day.
340
  //     This is why subtracting 28 years does not change whether a year
341
  //     is a leap year.
342
  //   * After 12 (4 * 3) years, we get (5 * 3) % 7 = 1 day of extra day.
343
  //     That's why adding 12 years increments weekday by 1.
344
0
  int32_t eqYear = (isLeapYear(year) ? 2012 : 2006) + (wkDay * 12) % 28;
345
  // Find the year in the range 2006..2033 that is equivalent mod 28.
346
  // This is to avoid anything above year 2037.
347
0
  eqYear = 2006 + (eqYear - 2006) % 28;
348
0
  return epochDaysForYear2006To2033[eqYear - 2006];
349
0
}
350
351
/// Returns an equivalent time for the purpose of determining DST using the
352
/// rules in ES5.1 15.9.1.8 Daylight Saving Time Adjustment
353
///
354
/// \p time_ms must be within the range specified by
355
/// ES5.1 15.9.1.1 Time Values and Time Range.
356
///
357
/// Some library calls doesn't work when the input date-time cannot be
358
/// represented as a 32-bit non-negative number of seconds since
359
/// 1970-01-01T00:00:00. (e.g. std::localtime on Windows)
360
///
361
/// Note: not "static" so that it can be tested directly.
362
0
int32_t detail::equivalentTime(int64_t epochSecs) {
363
  // The math behind this implementation is similar to the EquivalentTime
364
  // function in https://github.com/v8/v8/blob/master/src/date.h
365
0
  assert(epochSecs >= -TIME_RANGE_SECS && epochSecs <= TIME_RANGE_SECS);
366
0
  int64_t epochDays, secsOfDay;
367
0
  floorDivMod(epochSecs, SECONDS_PER_DAY, &epochDays, &secsOfDay);
368
0
  int32_t year, yearAsEpochDays, dayOfYear;
369
  // Narrowing of epochDays will not result in truncation
370
0
  decomposeEpochDays(epochDays, &year, &yearAsEpochDays, &dayOfYear);
371
0
  int32_t eqYearAsEpochDays = equivalentYearAsEpochDays(year, yearAsEpochDays);
372
0
  return (eqYearAsEpochDays + dayOfYear) * SECONDS_PER_DAY + secsOfDay;
373
0
}
374
375
//===----------------------------------------------------------------------===//
376
// ES5.1 15.9.1.9
377
378
/// https://tc39.es/ecma262/#sec-localtime
379
/// Conversion from UTC to local time.
380
0
double localTime(double t, LocalTimeOffsetCache &localTimeOffsetCache) {
381
  // The spec requires that localTime() accepts only finite time value, but for
382
  // simplicity, we do the check here instead of the caller site.
383
0
  if (!std::isfinite(t)) {
384
0
    return std::numeric_limits<double>::quiet_NaN();
385
0
  }
386
0
  return t + localTimeOffsetCache.getLocalTimeOffset(t, TimeType::Utc);
387
0
}
388
389
/// https://tc39.es/ecma262/#sec-utc-t
390
/// Conversion from local time to UTC.
391
///
392
/// There is time ambiguity when converting local time to UTC time. For example,
393
/// when offsets change in backward direction (transition from DST), the same
394
/// local time is repeated, and mapped to two different UTC times. When
395
/// offsets change in forward direction (transition to DST), local times are
396
/// skipped. ECMA262 requires that for both cases, time \t should be interpreted
397
/// using the time zone offset *before* the transition.
398
/// Consider time zone `America/New_York`, 1:30 AM on 5 November 2017 is
399
/// repeated twice, it should be converted to UTC epoch 1509859800000
400
/// (1:30 AM UTC-04) instead of 1509863400000 (1:30 AM UTC-05). And 2:30 AM on
401
/// 12 March 2017 is skipped, it should be interpreted as 2:30 AM UTC-05
402
/// instead of 3:30 AM UTC-04. However, in this case, both have the same UTC
403
/// epoch.
404
0
double utcTime(double t, LocalTimeOffsetCache &localTimeOffsetCache) {
405
0
  if (!std::isfinite(t)) {
406
0
    return std::numeric_limits<double>::quiet_NaN();
407
0
  }
408
0
  return t - localTimeOffsetCache.getLocalTimeOffset(t, TimeType::Local);
409
0
}
410
411
//===----------------------------------------------------------------------===//
412
// ES5.1 15.9.1.10
413
414
0
double hourFromTime(double t) {
415
0
  return posfmod(std::floor(t / MS_PER_HOUR), HOURS_PER_DAY);
416
0
}
417
418
0
double minFromTime(double t) {
419
0
  return posfmod(std::floor(t / MS_PER_MINUTE), MINUTES_PER_HOUR);
420
0
}
421
422
0
double secFromTime(double t) {
423
0
  return posfmod(std::floor(t / MS_PER_SECOND), SECONDS_PER_MINUTE);
424
0
}
425
426
0
double msFromTime(double t) {
427
0
  return posfmod(t, MS_PER_SECOND);
428
0
}
429
430
//===----------------------------------------------------------------------===//
431
// ES5.1 15.9.1.11
432
433
0
double makeTime(double hour, double min, double sec, double ms) {
434
0
  if (!std::isfinite(hour) || !std::isfinite(min) || !std::isfinite(sec) ||
435
0
      !std::isfinite(ms)) {
436
0
    return std::numeric_limits<double>::quiet_NaN();
437
0
  }
438
0
  double h = std::trunc(hour);
439
0
  double m = std::trunc(min);
440
0
  double s = std::trunc(sec);
441
0
  double milli = trunc(ms);
442
0
  return h * MS_PER_HOUR + m * MS_PER_MINUTE + s * MS_PER_SECOND + milli;
443
0
}
444
445
//===----------------------------------------------------------------------===//
446
// ES5.1 15.9.1.12
447
448
0
double makeDay(double year, double month, double date) {
449
0
  if (!std::isfinite(year) || !std::isfinite(month) || !std::isfinite(date)) {
450
0
    return std::numeric_limits<double>::quiet_NaN();
451
0
  }
452
0
  double y = std::trunc(year);
453
0
  double m = std::trunc(month);
454
0
  double dt = std::trunc(date);
455
456
  // Actual year and month, accounting for the month being greater than 11.
457
  // Need to do this because it changes the leap year calculations.
458
0
  double ym = y + std::floor(m / 12);
459
0
  double mn = posfmod(m, 12);
460
461
0
  bool leap = isLeapYear(ym);
462
463
  // Day of the first day of the year ym.
464
0
  double yd = std::floor(timeFromYear(ym) / MS_PER_DAY);
465
  // Day of the first day of the month mn.
466
0
  double md = dayFromMonth(mn, leap);
467
468
  // Final date is the first day of the year, offset by the first day of the
469
  // month, with dt - 1 to account for the day within the month.
470
0
  return yd + md + dt - 1;
471
0
}
472
473
//===----------------------------------------------------------------------===//
474
// ES5.1 15.9.1.13
475
476
0
double makeDate(double day, double t) {
477
0
  if (!std::isfinite(day) || !std::isfinite(t)) {
478
0
    return std::numeric_limits<double>::quiet_NaN();
479
0
  }
480
481
  // Some compilers may contract the multiplication and addition into a single
482
  // FMA when they are part of the same expression. This would result in
483
  // non-standard results, so to avoid it, split them into separate expressions.
484
  // Note that this applies only when compiling with -ffp-contract=on. If
485
  // -ffp-contract=fast is used, the compiler will still be permitted to emit an
486
  // FMA operation for the separate expressions.
487
0
  double dayMs = day * MS_PER_DAY;
488
0
  return dayMs + t;
489
0
}
490
491
//===----------------------------------------------------------------------===//
492
// ES5.1 15.9.1.14
493
494
0
double timeClip(double t) {
495
0
  if (!std::isfinite(t) || std::abs(t) > 8.64e15) {
496
0
    return std::numeric_limits<double>::quiet_NaN();
497
0
  }
498
499
  // Truncate and make -0 into +0.
500
0
  return std::trunc(t) + 0;
501
0
}
502
503
//===----------------------------------------------------------------------===//
504
// toString Functions
505
506
0
void dateToISOString(double t, double, llvh::SmallVectorImpl<char> &buf) {
507
0
  llvh::raw_svector_ostream os{buf};
508
509
  /// Make these ints here because we're printing and we have bounds on
510
  /// their values. Makes printing very easy.
511
0
  int32_t y = yearFromTime(t);
512
0
  int32_t m = monthFromTime(t) + 1; // monthFromTime(t) is 0-indexed.
513
0
  int32_t d = dateFromTime(t);
514
515
0
  if (y < 0 || y > 9999) {
516
    // Handle extended years.
517
0
    os << llvh::format("%+07d-%02d-%02d", y, m, d);
518
0
  } else {
519
0
    os << llvh::format("%04d-%02d-%02d", y, m, d);
520
0
  }
521
0
}
522
523
0
void timeToISOString(double t, double tza, llvh::SmallVectorImpl<char> &buf) {
524
0
  llvh::raw_svector_ostream os{buf};
525
526
  /// Make all of these ints here because we're printing and we have bounds on
527
  /// their values. Makes printing very easy.
528
0
  int32_t h = hourFromTime(t);
529
0
  int32_t min = minFromTime(t);
530
0
  int32_t s = secFromTime(t);
531
0
  int32_t ms = msFromTime(t);
532
533
0
  if (tza == 0) {
534
    // Zulu time, output Z as the time zone.
535
0
    os << llvh::format("%02d:%02d:%02d.%03dZ", h, min, s, ms);
536
0
  } else {
537
    // Calculate the +HH:mm expression for the time zone adjustment.
538
    // First account for the sign, then perform calculations on positive TZA.
539
0
    char sign = tza >= 0 ? '+' : '-';
540
0
    double tzaPos = std::abs(tza);
541
0
    int32_t tzh = hourFromTime(tzaPos);
542
0
    int32_t tzm = minFromTime(tzaPos);
543
0
    os << llvh::format(
544
0
        "%02d:%02d:%02d.%03d%c%02d:%02d", h, min, s, ms, sign, tzh, tzm);
545
0
  }
546
0
}
547
548
static void datetimeToISOString(
549
    double t,
550
    double tza,
551
    llvh::SmallVectorImpl<char> &buf,
552
0
    char separator) {
553
0
  dateToISOString(t, tza, buf);
554
0
  buf.push_back(separator);
555
0
  timeToISOString(t, tza, buf);
556
0
}
557
558
void datetimeToISOString(
559
    double t,
560
    double tza,
561
0
    llvh::SmallVectorImpl<char> &buf) {
562
0
  return datetimeToISOString(t, tza, buf, 'T');
563
0
}
564
565
0
void datetimeToLocaleString(double t, llvh::SmallVectorImpl<char16_t> &buf) {
566
0
  return platform_unicode::dateFormat(t, true, true, buf);
567
0
}
568
569
0
void dateToLocaleString(double t, llvh::SmallVectorImpl<char16_t> &buf) {
570
0
  return platform_unicode::dateFormat(t, true, false, buf);
571
0
}
572
573
0
void timeToLocaleString(double t, llvh::SmallVectorImpl<char16_t> &buf) {
574
0
  return platform_unicode::dateFormat(t, false, true, buf);
575
0
}
576
577
// ES9.0 Table 46
578
static const char *const weekdayNames[7]{
579
    "Sun",
580
    "Mon",
581
    "Tue",
582
    "Wed",
583
    "Thu",
584
    "Fri",
585
    "Sat",
586
};
587
588
// ES9.0 Table 47
589
static const char *const monthNames[12]{
590
    "Jan",
591
    "Feb",
592
    "Mar",
593
    "Apr",
594
    "May",
595
    "Jun",
596
    "Jul",
597
    "Aug",
598
    "Sep",
599
    "Oct",
600
    "Nov",
601
    "Dec",
602
};
603
604
0
void dateString(double t, double, llvh::SmallVectorImpl<char> &buf) {
605
0
  llvh::raw_svector_ostream os{buf};
606
607
  // Make these ints here because we're printing and we have bounds on
608
  // their values. Makes printing very easy.
609
0
  int32_t y = yearFromTime(t);
610
0
  int32_t m = monthFromTime(t); // monthFromTime(t) is 0-indexed.
611
0
  int32_t d = dateFromTime(t);
612
0
  int32_t wd = weekDay(t);
613
614
  // 7. Return the string-concatenation of weekday, the code unit 0x0020
615
  // (SPACE), month, the code unit 0x0020 (SPACE), day, the code unit 0x0020
616
  // (SPACE), and year.
617
  // Example: Mon Jul 22 2019
618
0
  os << llvh::format("%s %s %02d %0.4d", weekdayNames[wd], monthNames[m], d, y);
619
0
}
620
621
0
void timeString(double t, double tza, llvh::SmallVectorImpl<char> &buf) {
622
0
  llvh::raw_svector_ostream os{buf};
623
624
0
  int32_t hour = hourFromTime(t);
625
0
  int32_t minute = minFromTime(t);
626
0
  int32_t second = secFromTime(t);
627
628
  // Example: 15:50:49 GMT
629
0
  os << llvh::format("%02d:%02d:%02d GMT", hour, minute, second);
630
0
}
631
632
0
void timeZoneString(double t, double tza, llvh::SmallVectorImpl<char> &buf) {
633
0
  llvh::raw_svector_ostream os{buf};
634
635
  // We've already computed the TZA, so use that as the offset.
636
0
  double offset = tza;
637
638
  // 4. If offset >= 0, let offsetSign be "+"; otherwise, let offsetSign be "-".
639
0
  char offsetSign = offset >= 0 ? '+' : '-';
640
641
  // 5. Let offsetMin be the String representation of MinFromTime(abs(offset)),
642
  // formatted as a two-digit decimal number, padded to the left with a zero if
643
  // necessary.
644
0
  int32_t offsetMin = minFromTime(std::abs(offset));
645
646
  // 6. Let offsetHour be the String representation of
647
  // HourFromTime(abs(offset)), formatted as a two-digit decimal number, padded
648
  // to the left with a zero if necessary.
649
0
  int32_t offsetHour = hourFromTime(std::abs(offset));
650
651
  // 7. Let tzName be an implementation-defined string that is either the empty
652
  // string or the string-concatenation of the code unit 0x0020 (SPACE), the
653
  // code unit 0x0028 (LEFT PARENTHESIS), an implementation-dependent timezone
654
  // name, and the code unit 0x0029 (RIGHT PARENTHESIS).
655
  // TODO: Make this something other than empty string.
656
657
  // 8. Return the string-concatenation of offsetSign, offsetHour, offsetMin,
658
  // and tzName.
659
  // Example: -0700
660
0
  os << llvh::format("%c%02d%02d", offsetSign, offsetHour, offsetMin);
661
0
}
662
663
0
void dateTimeString(double tv, double tza, llvh::SmallVectorImpl<char> &buf) {
664
0
  llvh::raw_svector_ostream os{buf};
665
0
  dateString(tv, tza, buf);
666
  // Return the string-concatenation of DateString(t), the code unit 0x0020
667
  // (SPACE), TimeString(t), and TimeZoneString(tv).
668
  // Example: Mon Jul 22 2019 15:51:50 GMT-0700
669
0
  os << " ";
670
0
  timeString(tv, tza, buf);
671
0
  timeZoneString(tv, tza, buf);
672
0
}
673
674
void dateTimeUTCString(
675
    double tv,
676
    double tza,
677
0
    llvh::SmallVectorImpl<char> &buf) {
678
0
  llvh::raw_svector_ostream os{buf};
679
680
  // Make these ints here because we're printing and we have bounds on
681
  // their values. Makes printing very easy.
682
0
  int32_t y = yearFromTime(tv);
683
0
  int32_t m = monthFromTime(tv); // monthFromTime(t) is 0-indexed.
684
0
  int32_t d = dateFromTime(tv);
685
0
  int32_t wd = weekDay(tv);
686
687
  // 8. Return the string-concatenation of weekday, ",", the code unit 0x0020
688
  // (SPACE), day, the code unit 0x0020 (SPACE), month, the code unit 0x0020
689
  // (SPACE), year, the code unit 0x0020 (SPACE), and TimeString(tv).
690
  // Example: Mon Jul 22 2019 15:51:50 GMT
691
0
  os << llvh::format(
692
0
      "%s, %02d %s %0.4d ", weekdayNames[wd], d, monthNames[m], y);
693
0
  timeString(tv, tza, buf);
694
0
}
695
696
0
void timeTZString(double tv, double tza, llvh::SmallVectorImpl<char> &buf) {
697
  // Return the string-concatenation of TimeString(t) and TimeZoneString(tv).
698
  // Example: 15:51:50 GMT-0700
699
0
  timeString(tv, tza, buf);
700
0
  timeZoneString(tv, tza, buf);
701
0
}
702
703
//===----------------------------------------------------------------------===//
704
// Date parsing
705
706
/// \return true if c represents a digit between 0 and 9.
707
0
static inline bool isDigit(char16_t c) {
708
0
  return u'0' <= c && c <= u'9';
709
0
}
710
711
/// \return true if c represents an alphabet letter.
712
0
static inline bool isAlpha(char16_t c) {
713
0
  c |= 'a' ^ 'A'; // Lowercase
714
0
  return 'a' <= c && c <= 'z';
715
0
}
716
717
/// Read a number from the iterator at \p it into \p x.
718
/// Can read integers that consist entirely of digits.
719
/// \param[in,out] it is modified to the new start point of the scan if
720
/// successful.
721
/// \param end the end of the string.
722
/// \param[out] x modified to contain the scanned integer.
723
/// \return true if successful, false if failed.
724
template <class InputIter>
725
0
static bool scanInt(InputIter &it, const InputIter end, int32_t &x) {
726
0
  llvh::SmallString<16> str{};
727
0
  if (it == end) {
728
0
    return false;
729
0
  }
730
0
  for (; it != end && isDigit(*it); ++it) {
731
0
    str += static_cast<char>(*it);
732
0
  }
733
0
  llvh::StringRef ref{str};
734
  // getAsInteger returns false to signify success.
735
0
  return !ref.getAsInteger(10, x);
736
0
}
737
738
static double parseISODate(
739
    StringView u16str,
740
0
    LocalTimeOffsetCache &localTimeOffsetCache) {
741
0
  constexpr double nan = std::numeric_limits<double>::quiet_NaN();
742
743
0
  auto it = u16str.begin();
744
0
  auto end = u16str.end();
745
746
  // Used to indicate the negation multiplier on an integer.
747
  // 1 for positive, -1 for negative.
748
0
  double sign;
749
750
  // Initialize these fields to their defaults.
751
0
  int32_t y, m{1}, d{1}, h{0}, min{0}, s{0}, ms{0}, tzh{0}, tzm{0};
752
753
0
  auto consume = [&](char16_t ch) {
754
0
    if (it != end && *it == ch) {
755
0
      ++it;
756
0
      return true;
757
0
    }
758
0
    return false;
759
0
  };
760
761
  // Must read the year.
762
0
  sign = 1;
763
0
  if (consume(u'+')) {
764
0
    sign = 1;
765
0
  } else if (consume(u'-')) {
766
0
    sign = -1;
767
0
  }
768
0
  if (!scanInt(it, end, y)) {
769
0
    return nan;
770
0
  }
771
0
  y *= sign;
772
0
  if (consume(u'-')) {
773
    // Try to read the month.
774
0
    if (!scanInt(it, end, m)) {
775
0
      return nan;
776
0
    }
777
0
    if (consume(u'-')) {
778
      // Try to read the date.
779
0
      if (!scanInt(it, end, d)) {
780
0
        return nan;
781
0
      }
782
0
    }
783
0
  }
784
785
  // See if there's a time.
786
0
  if (consume(u'T') || consume(u' ')) {
787
    // Hours and minutes must exist.
788
0
    if (!scanInt(it, end, h)) {
789
0
      return nan;
790
0
    }
791
0
    if (!consume(u':')) {
792
0
      return nan;
793
0
    }
794
0
    if (!scanInt(it, end, min)) {
795
0
      return nan;
796
0
    }
797
0
    if (consume(u':')) {
798
      // Try to read seconds.
799
0
      if (!scanInt(it, end, s)) {
800
0
        return nan;
801
0
      }
802
0
      if (consume(u'.')) {
803
        // Try to read fraction of a second.
804
0
        if (it == end || !isDigit(*it)) {
805
0
          return nan;
806
0
        }
807
808
        // Position of the milliseconds counter.
809
        // Start at the 100s place and discard anything after the third digit by
810
        // dividing by 10 every iteration.
811
0
        int32_t pos = 100;
812
813
0
        for (; it != end && isDigit(*it); ++it) {
814
0
          ms += pos * (*it - '0');
815
0
          pos /= 10;
816
0
        }
817
0
      }
818
0
    }
819
820
0
    if (it == end) {
821
      // ES12 21.4.3.2: When the UTC offset representation is absent, date-only
822
      // forms are interpreted as a UTC time and date-time forms are interpreted
823
      // as a local time.
824
0
      double t = makeDate(makeDay(y, m - 1, d), makeTime(h, min, s, ms));
825
0
      t = utcTime(t, localTimeOffsetCache);
826
0
      return t;
827
0
    }
828
829
    // Try to parse a timezone.
830
0
    if (consume(u'Z')) {
831
0
      tzh = 0;
832
0
      tzm = 0;
833
0
    } else {
834
      // Try to parse the fully specified timezone: [+/-]HH:mm.
835
0
      if (consume(u'+')) {
836
0
        sign = 1;
837
0
      } else if (consume(u'-')) {
838
0
        sign = -1;
839
0
      } else {
840
        // Need a + or a -.
841
0
        return nan;
842
0
      }
843
0
      if (it > end - 2) {
844
0
        return nan;
845
0
      }
846
0
      if (!scanInt(it, it + 2, tzh)) {
847
0
        return nan;
848
0
      }
849
0
      tzh *= sign;
850
0
      consume(u':');
851
0
      if (it > end - 2) {
852
0
        return nan;
853
0
      }
854
0
      if (!scanInt(it, it + 2, tzm)) {
855
0
        return nan;
856
0
      }
857
0
      tzm *= sign;
858
0
    }
859
0
  }
860
861
0
  if (it != end) {
862
    // Should be done parsing.
863
0
    return nan;
864
0
  }
865
866
  // Account for the fact that m was 1-indexed and the timezone offset.
867
0
  return makeDate(makeDay(y, m - 1, d), makeTime(h - tzh, min - tzm, s, ms));
868
0
}
869
870
static double parseESDate(
871
    StringView str,
872
0
    LocalTimeOffsetCache &localTimeOffsetCache) {
873
0
  constexpr double nan = std::numeric_limits<double>::quiet_NaN();
874
0
  StringView tok = str;
875
876
  // Initialize these fields to their defaults.
877
0
  int32_t y, m{1}, d{1}, h{0}, min{0}, s{0}, ms{0}, tzh{0}, tzm{0};
878
0
  double sign = 1;
879
880
  // Example strings to parse:
881
  // Mon Jul 15 2019 14:33:22 GMT-0700 (PDT)
882
  // Mon, 15 Jul 2019 14:33:22 GMT
883
  // The comma, time zone adjustment, and description are optional,
884
885
  // Current index we are parsing.
886
0
  auto it = str.begin();
887
0
  auto end = str.end();
888
889
  /// Read a string starting at `it` into `tok`.
890
  /// \p len the number of characters to scan in the string.
891
  /// \return true if successful, false if failed.
892
0
  auto scanStr = [&str, &tok, &it](int32_t len) -> bool {
893
0
    if (it + len > str.end()) {
894
0
      return false;
895
0
    }
896
0
    tok = str.slice(it, it + len);
897
0
    it += len;
898
0
    return true;
899
0
  };
900
901
  /// Reads the next \p len characters into `tok`,
902
  /// but instead of consuming \p len chars, it consumes a single word
903
  /// whatever how long it is (i.e. until a space or dash is encountered).
904
  /// e.g.
905
  ///     &str ="Garbage G MayG"
906
  ///     scanStrAndSkipWord(3); consumeSpaces();  // &str="G MayG", &tok="Gar"
907
  ///     scanStrAndSkipWord(3); consumeSpaces();  // &str="MayG"  , &tok="G M"
908
  ///     scanStrAndSkipWord(3); consumeSpaces();  // &str=""      , &tok="May"
909
  ///     scanStrAndSkipWord(3);                   // -> false
910
  /// \return true if successful, false if failed.
911
0
  auto scanStrAndSkipWord = [&str, &tok, &it](int32_t len) -> bool {
912
0
    if (it + len > str.end())
913
0
      return false;
914
0
    tok = str.slice(it, it + len);
915
0
    while (it != str.end() && !std::isspace(*it) && *it != '-')
916
0
      it++;
917
0
    return true;
918
0
  };
919
920
0
  auto consume = [&](char16_t ch) {
921
0
    if (it != str.end() && *it == ch) {
922
0
      ++it;
923
0
      return true;
924
0
    }
925
0
    return false;
926
0
  };
927
928
0
  auto consumeSpaces = [&]() {
929
0
    while (it != str.end() && std::isspace(*it))
930
0
      ++it;
931
0
  };
932
933
  /// Only one dash is ever allowed, and the dash must come first.
934
  /// There is no limit to the number of spaces.
935
  /// This is in line with V8's behavior.
936
0
  auto consumeSpacesOrDash = [&]() {
937
0
    auto first = true;
938
0
    while (it != str.end()) {
939
0
      if (std::isspace(*it) || (first && *it == '-')) {
940
0
        ++it;
941
0
      } else {
942
0
        return;
943
0
      }
944
0
      first = false;
945
0
    }
946
0
  };
947
948
  // Weekday
949
0
  if (!scanStr(3))
950
0
    return nan;
951
0
  bool foundWeekday = false;
952
0
  for (const char *name : weekdayNames) {
953
0
    if (tok.equals(llvh::arrayRefFromStringRef(name))) {
954
0
      foundWeekday = true;
955
0
      break;
956
0
    }
957
0
  }
958
0
  if (!foundWeekday)
959
0
    return nan;
960
961
  /// If we found a valid Month string from the current `tok`.
962
0
  auto tokIsMonth = [&]() -> bool {
963
0
    for (uint32_t i = 0; i < sizeof(monthNames) / sizeof(monthNames[0]); ++i) {
964
0
      if (tok.equals(llvh::arrayRefFromStringRef(monthNames[i]))) {
965
        // m is 1-indexed.
966
0
        m = i + 1;
967
0
        return true;
968
0
      }
969
0
    }
970
0
    return false;
971
0
  };
972
973
  // Day Month Year
974
  // or
975
  // Month Day Year
976
0
  while (it != str.end()) {
977
0
    if (isDigit(*it)) {
978
      // Day
979
0
      scanInt(it, end, d);
980
      // Month
981
0
      consumeSpacesOrDash();
982
      // e.g. `Janwhatever` will get read as `Jan`
983
0
      if (!scanStrAndSkipWord(3))
984
0
        return nan;
985
      // `tok` is now set to the Month candidate.
986
0
      if (!tokIsMonth())
987
0
        return nan;
988
0
      break;
989
0
    }
990
0
    if (isAlpha(*it)) {
991
      // try Month
992
0
      if (!scanStrAndSkipWord(3))
993
0
        return nan;
994
      // `tok` is now set to the Month candidate.
995
0
      if (tokIsMonth()) {
996
        // Day
997
0
        consumeSpacesOrDash();
998
0
        if (!scanInt(it, end, d))
999
0
          return nan;
1000
0
        break;
1001
0
      }
1002
      // Continue scanning for Month.
1003
0
      continue;
1004
0
    }
1005
    // Ignore any garbage.
1006
0
    ++it;
1007
0
  }
1008
1009
  // Year
1010
0
  consumeSpacesOrDash();
1011
0
  if (!scanInt(it, end, y))
1012
0
    return nan;
1013
1014
  // Hour:minute:second.
1015
0
  consumeSpacesOrDash();
1016
1017
0
  if (it != end) {
1018
0
    if (!scanInt(it, end, h))
1019
0
      return nan;
1020
0
    if (!consume(':'))
1021
0
      return nan;
1022
0
    if (!scanInt(it, end, min))
1023
0
      return nan;
1024
0
    if (!consume(':'))
1025
0
      return nan;
1026
0
    if (!scanInt(it, end, s))
1027
0
      return nan;
1028
0
  }
1029
1030
  // Space and time zone.
1031
0
  consumeSpaces();
1032
1033
0
  if (it == end) {
1034
    // Default to local time zone if no time zone provided
1035
0
    double t = makeDate(makeDay(y, m - 1, d), makeTime(h, min, s, ms));
1036
0
    t = utcTime(t, localTimeOffsetCache);
1037
0
    return t;
1038
0
  }
1039
1040
0
  struct KnownTZ {
1041
0
    const char *tz;
1042
0
    int32_t tzh;
1043
0
  };
1044
1045
  // Known time zones per RFC 2822.
1046
  // All other obsolete time zones that aren't in this array treated as +00:00.
1047
0
  static constexpr KnownTZ knownTZs[] = {
1048
0
      {"GMT", 0},
1049
0
      {"EDT", -4},
1050
0
      {"EST", -5},
1051
0
      {"CDT", -5},
1052
0
      {"CST", -6},
1053
0
      {"MDT", -6},
1054
0
      {"MST", -7},
1055
0
      {"PDT", -7},
1056
0
      {"PST", -8},
1057
0
  };
1058
1059
  // TZ name is optional, but if there is a letter, it is the only option.
1060
0
  if ('A' <= *it && *it <= 'Z') {
1061
0
    if (!scanStr(3))
1062
0
      return nan;
1063
0
    for (const KnownTZ &knownTZ : knownTZs) {
1064
0
      if (tok.equals(llvh::arrayRefFromStringRef(knownTZ.tz))) {
1065
0
        tzh = knownTZ.tzh;
1066
0
        break;
1067
0
      }
1068
0
    }
1069
0
  }
1070
1071
0
  if (it == end)
1072
0
    goto complete;
1073
1074
  // Prevent "CDT+0700", for example.
1075
0
  if (tzh != 0 && it != end)
1076
0
    return nan;
1077
1078
  // Sign of the timezone adjustment.
1079
0
  if (consume('+'))
1080
0
    sign = 1;
1081
0
  else if (consume('-'))
1082
0
    sign = -1;
1083
0
  else
1084
0
    return nan;
1085
1086
  // Hour and minute of timezone adjustment.
1087
0
  if (it > end - 4)
1088
0
    return nan;
1089
0
  if (!scanInt(it, it + 2, tzh))
1090
0
    return nan;
1091
0
  tzh *= sign;
1092
0
  if (!scanInt(it, it + 2, tzm))
1093
0
    return nan;
1094
0
  tzm *= sign;
1095
1096
0
  if (it != end) {
1097
    // Optional parenthesized description of timezone (must be at the end).
1098
0
    if (!consume(' '))
1099
0
      return nan;
1100
0
    if (!consume('('))
1101
0
      return nan;
1102
0
    while (it != end && *it != ')')
1103
0
      ++it;
1104
0
    if (!consume(')'))
1105
0
      return nan;
1106
0
  }
1107
1108
0
  if (it != end)
1109
0
    return nan;
1110
1111
0
complete:
1112
  // Account for the fact that m was 1-indexed and the timezone offset.
1113
0
  return makeDate(makeDay(y, m - 1, d), makeTime(h - tzh, min - tzm, s, ms));
1114
0
}
1115
1116
0
double parseDate(StringView str, LocalTimeOffsetCache &localTimeOffsetCache) {
1117
0
  double result = parseISODate(str, localTimeOffsetCache);
1118
0
  if (!std::isnan(result)) {
1119
0
    return result;
1120
0
  }
1121
1122
0
  return parseESDate(str, localTimeOffsetCache);
1123
0
}
1124
1125
} // namespace vm
1126
} // namespace hermes