/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, ", &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(¤tWithDST, &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 |