1 | /* |
2 | * Copyright (C) 1999-2000 Harri Porten ([email protected]) |
3 | * Copyright (C) 2006-2019 Apple Inc. All rights reserved. |
4 | * Copyright (C) 2009 Google Inc. All rights reserved. |
5 | * Copyright (C) 2007-2009 Torch Mobile, Inc. |
6 | * Copyright (C) 2010 &yet, LLC. ([email protected]) |
7 | * |
8 | * The Original Code is Mozilla Communicator client code, released |
9 | * March 31, 1998. |
10 | * |
11 | * The Initial Developer of the Original Code is |
12 | * Netscape Communications Corporation. |
13 | * Portions created by the Initial Developer are Copyright (C) 1998 |
14 | * the Initial Developer. All Rights Reserved. |
15 | * |
16 | * This library is free software; you can redistribute it and/or |
17 | * modify it under the terms of the GNU Lesser General Public |
18 | * License as published by the Free Software Foundation; either |
19 | * version 2.1 of the License, or (at your option) any later version. |
20 | * |
21 | * This library is distributed in the hope that it will be useful, |
22 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
23 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
24 | * Lesser General Public License for more details. |
25 | * |
26 | * You should have received a copy of the GNU Lesser General Public |
27 | * License along with this library; if not, write to the Free Software |
28 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
29 | * |
30 | * Alternatively, the contents of this file may be used under the terms |
31 | * of either the Mozilla Public License Version 1.1, found at |
32 | * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public |
33 | * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html |
34 | * (the "GPL"), in which case the provisions of the MPL or the GPL are |
35 | * applicable instead of those above. If you wish to allow use of your |
36 | * version of this file only under the terms of one of those two |
37 | * licenses (the MPL or the GPL) and not to allow others to use your |
38 | * version of this file under the LGPL, indicate your decision by |
39 | * deletingthe provisions above and replace them with the notice and |
40 | * other provisions required by the MPL or the GPL, as the case may be. |
41 | * If you do not delete the provisions above, a recipient may use your |
42 | * version of this file under any of the LGPL, the MPL or the GPL. |
43 | |
44 | * Copyright 2006-2008 the V8 project authors. All rights reserved. |
45 | * Redistribution and use in source and binary forms, with or without |
46 | * modification, are permitted provided that the following conditions are |
47 | * met: |
48 | * |
49 | * * Redistributions of source code must retain the above copyright |
50 | * notice, this list of conditions and the following disclaimer. |
51 | * * Redistributions in binary form must reproduce the above |
52 | * copyright notice, this list of conditions and the following |
53 | * disclaimer in the documentation and/or other materials provided |
54 | * with the distribution. |
55 | * * Neither the name of Google Inc. nor the names of its |
56 | * contributors may be used to endorse or promote products derived |
57 | * from this software without specific prior written permission. |
58 | * |
59 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
60 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
61 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
62 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
63 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
64 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
65 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
66 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
67 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
68 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
69 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
70 | */ |
71 | |
72 | #include "config.h" |
73 | #include <wtf/DateMath.h> |
74 | |
75 | #include <algorithm> |
76 | #include <limits.h> |
77 | #include <limits> |
78 | #include <stdint.h> |
79 | #include <time.h> |
80 | #include <wtf/Assertions.h> |
81 | #include <wtf/ASCIICType.h> |
82 | #include <wtf/MathExtras.h> |
83 | #include <wtf/StdLibExtras.h> |
84 | #include <wtf/text/StringBuilder.h> |
85 | |
86 | #if OS(WINDOWS) |
87 | #include <windows.h> |
88 | #endif |
89 | |
90 | #if HAVE(ERRNO_H) |
91 | #include <errno.h> |
92 | #endif |
93 | |
94 | #if HAVE(SYS_TIME_H) |
95 | #include <sys/time.h> |
96 | #endif |
97 | |
98 | #if HAVE(SYS_TIMEB_H) |
99 | #include <sys/timeb.h> |
100 | #endif |
101 | |
102 | namespace WTF { |
103 | |
104 | // FIXME: Should this function go into StringCommon.h or some other header? |
105 | template<unsigned length> inline bool startsWithLettersIgnoringASCIICase(const char* string, const char (&lowercaseLetters)[length]) |
106 | { |
107 | return equalLettersIgnoringASCIICase(string, lowercaseLetters, length - 1); |
108 | } |
109 | |
110 | /* Constants */ |
111 | |
112 | const char* const weekdayName[7] = { "Mon" , "Tue" , "Wed" , "Thu" , "Fri" , "Sat" , "Sun" }; |
113 | const char* const monthName[12] = { "Jan" , "Feb" , "Mar" , "Apr" , "May" , "Jun" , "Jul" , "Aug" , "Sep" , "Oct" , "Nov" , "Dec" }; |
114 | const char* const monthFullName[12] = { "January" , "February" , "March" , "April" , "May" , "June" , "July" , "August" , "September" , "October" , "November" , "December" }; |
115 | |
116 | // Day of year for the first day of each month, where index 0 is January, and day 0 is January 1. |
117 | // First for non-leap years, then for leap years. |
118 | const int firstDayOfMonth[2][12] = { |
119 | {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}, |
120 | {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335} |
121 | }; |
122 | |
123 | #if !OS(WINDOWS) || HAVE(TM_GMTOFF) |
124 | static inline void getLocalTime(const time_t* localTime, struct tm* localTM) |
125 | { |
126 | #if HAVE(LOCALTIME_R) |
127 | localtime_r(localTime, localTM); |
128 | #else |
129 | localtime_s(localTime, localTM); |
130 | #endif |
131 | } |
132 | #endif |
133 | |
134 | static void appendTwoDigitNumber(StringBuilder& builder, int number) |
135 | { |
136 | ASSERT(number >= 0); |
137 | ASSERT(number < 100); |
138 | builder.append(static_cast<LChar>('0' + number / 10)); |
139 | builder.append(static_cast<LChar>('0' + number % 10)); |
140 | } |
141 | |
142 | static inline double msToMilliseconds(double ms) |
143 | { |
144 | double result = fmod(ms, msPerDay); |
145 | if (result < 0) |
146 | result += msPerDay; |
147 | return result; |
148 | } |
149 | |
150 | // There is a hard limit at 2038 that we currently do not have a workaround |
151 | // for (rdar://problem/5052975). |
152 | static inline int maximumYearForDST() |
153 | { |
154 | return 2037; |
155 | } |
156 | |
157 | static inline int minimumYearForDST() |
158 | { |
159 | // Because of the 2038 issue (see maximumYearForDST) if the current year is |
160 | // greater than the max year minus 27 (2010), we want to use the max year |
161 | // minus 27 instead, to ensure there is a range of 28 years that all years |
162 | // can map to. |
163 | return std::min(msToYear(jsCurrentTime()), maximumYearForDST() - 27) ; |
164 | } |
165 | |
166 | /* |
167 | * Find an equivalent year for the one given, where equivalence is deterined by |
168 | * the two years having the same leapness and the first day of the year, falling |
169 | * on the same day of the week. |
170 | * |
171 | * This function returns a year between this current year and 2037, however this |
172 | * function will potentially return incorrect results if the current year is after |
173 | * 2010, (rdar://problem/5052975), if the year passed in is before 1900 or after |
174 | * 2100, (rdar://problem/5055038). |
175 | */ |
176 | int equivalentYearForDST(int year) |
177 | { |
178 | // It is ok if the cached year is not the current year as long as the rules |
179 | // for DST did not change between the two years; if they did the app would need |
180 | // to be restarted. |
181 | static int minYear = minimumYearForDST(); |
182 | int maxYear = maximumYearForDST(); |
183 | |
184 | int difference; |
185 | if (year > maxYear) |
186 | difference = minYear - year; |
187 | else if (year < minYear) |
188 | difference = maxYear - year; |
189 | else |
190 | return year; |
191 | |
192 | int quotient = difference / 28; |
193 | int product = (quotient) * 28; |
194 | |
195 | year += product; |
196 | return year; |
197 | } |
198 | |
199 | #if OS(WINDOWS) |
200 | typedef BOOL(WINAPI* callGetTimeZoneInformationForYear_t)(USHORT, PDYNAMIC_TIME_ZONE_INFORMATION, LPTIME_ZONE_INFORMATION); |
201 | |
202 | static callGetTimeZoneInformationForYear_t timeZoneInformationForYearFunction() |
203 | { |
204 | static callGetTimeZoneInformationForYear_t getTimeZoneInformationForYear = nullptr; |
205 | |
206 | if (getTimeZoneInformationForYear) |
207 | return getTimeZoneInformationForYear; |
208 | |
209 | HMODULE module = ::GetModuleHandleW(L"kernel32.dll" ); |
210 | if (!module) |
211 | return nullptr; |
212 | |
213 | getTimeZoneInformationForYear = reinterpret_cast<callGetTimeZoneInformationForYear_t>(::GetProcAddress(module, "GetTimeZoneInformationForYear" )); |
214 | |
215 | return getTimeZoneInformationForYear; |
216 | } |
217 | #endif |
218 | |
219 | static int32_t calculateUTCOffset() |
220 | { |
221 | #if OS(WINDOWS) |
222 | TIME_ZONE_INFORMATION timeZoneInformation; |
223 | DWORD rc = 0; |
224 | |
225 | if (callGetTimeZoneInformationForYear_t timeZoneFunction = timeZoneInformationForYearFunction()) { |
226 | // If available, use the Windows API call that takes into account the varying DST from |
227 | // year to year. |
228 | SYSTEMTIME systemTime; |
229 | ::GetSystemTime(&systemTime); |
230 | rc = timeZoneFunction(systemTime.wYear, nullptr, &timeZoneInformation); |
231 | if (rc == TIME_ZONE_ID_INVALID) |
232 | return 0; |
233 | } else { |
234 | rc = ::GetTimeZoneInformation(&timeZoneInformation); |
235 | if (rc == TIME_ZONE_ID_INVALID) |
236 | return 0; |
237 | } |
238 | |
239 | int32_t bias = timeZoneInformation.Bias; |
240 | |
241 | if (rc == TIME_ZONE_ID_DAYLIGHT) |
242 | bias += timeZoneInformation.DaylightBias; |
243 | else if (rc == TIME_ZONE_ID_STANDARD || rc == TIME_ZONE_ID_UNKNOWN) |
244 | bias += timeZoneInformation.StandardBias; |
245 | |
246 | return -bias * 60 * 1000; |
247 | #else |
248 | time_t localTime = time(0); |
249 | tm localt; |
250 | getLocalTime(&localTime, &localt); |
251 | |
252 | // Get the difference between this time zone and UTC on the 1st of January of this year. |
253 | localt.tm_sec = 0; |
254 | localt.tm_min = 0; |
255 | localt.tm_hour = 0; |
256 | localt.tm_mday = 1; |
257 | localt.tm_mon = 0; |
258 | // Not setting localt.tm_year! |
259 | localt.tm_wday = 0; |
260 | localt.tm_yday = 0; |
261 | localt.tm_isdst = 0; |
262 | #if HAVE(TM_GMTOFF) |
263 | localt.tm_gmtoff = 0; |
264 | #endif |
265 | #if HAVE(TM_ZONE) |
266 | localt.tm_zone = 0; |
267 | #endif |
268 | |
269 | #if HAVE(TIMEGM) |
270 | time_t utcOffset = timegm(&localt) - mktime(&localt); |
271 | #else |
272 | // Using a canned date of 01/01/2019 on platforms with weaker date-handling foo. |
273 | localt.tm_year = 119; |
274 | time_t utcOffset = 1546300800 - mktime(&localt); |
275 | #endif |
276 | |
277 | return static_cast<int32_t>(utcOffset * 1000); |
278 | #endif |
279 | } |
280 | |
281 | #if !HAVE(TM_GMTOFF) |
282 | |
283 | #if OS(WINDOWS) |
284 | // Code taken from http://support.microsoft.com/kb/167296 |
285 | static void UnixTimeToFileTime(time_t t, LPFILETIME pft) |
286 | { |
287 | // Note that LONGLONG is a 64-bit value |
288 | LONGLONG ll; |
289 | |
290 | ll = Int32x32To64(t, 10000000) + 116444736000000000; |
291 | pft->dwLowDateTime = (DWORD)ll; |
292 | pft->dwHighDateTime = ll >> 32; |
293 | } |
294 | #endif |
295 | |
296 | /* |
297 | * Get the DST offset for the time passed in. |
298 | */ |
299 | static double calculateDSTOffset(time_t localTime, double utcOffset) |
300 | { |
301 | // input is UTC so we have to shift back to local time to determine DST thus the + getUTCOffset() |
302 | double offsetTime = (localTime * msPerSecond) + utcOffset; |
303 | |
304 | // Offset from UTC but doesn't include DST obviously |
305 | int offsetHour = msToHours(offsetTime); |
306 | int offsetMinute = msToMinutes(offsetTime); |
307 | |
308 | #if OS(WINDOWS) |
309 | FILETIME utcFileTime; |
310 | UnixTimeToFileTime(localTime, &utcFileTime); |
311 | SYSTEMTIME utcSystemTime, localSystemTime; |
312 | if (!::FileTimeToSystemTime(&utcFileTime, &utcSystemTime)) |
313 | return 0; |
314 | if (!::SystemTimeToTzSpecificLocalTime(nullptr, &utcSystemTime, &localSystemTime)) |
315 | return 0; |
316 | |
317 | double diff = ((localSystemTime.wHour - offsetHour) * secondsPerHour) + ((localSystemTime.wMinute - offsetMinute) * 60); |
318 | #else |
319 | tm localTM; |
320 | getLocalTime(&localTime, &localTM); |
321 | |
322 | double diff = ((localTM.tm_hour - offsetHour) * secondsPerHour) + ((localTM.tm_min - offsetMinute) * 60); |
323 | #endif |
324 | |
325 | if (diff < 0) |
326 | diff += secondsPerDay; |
327 | |
328 | return (diff * msPerSecond); |
329 | } |
330 | |
331 | #endif |
332 | |
333 | // Returns combined offset in millisecond (UTC + DST). |
334 | LocalTimeOffset calculateLocalTimeOffset(double ms, TimeType inputTimeType) |
335 | { |
336 | #if HAVE(TM_GMTOFF) |
337 | double localToUTCTimeOffset = inputTimeType == LocalTime ? calculateUTCOffset() : 0; |
338 | #else |
339 | double localToUTCTimeOffset = calculateUTCOffset(); |
340 | #endif |
341 | if (inputTimeType == LocalTime) |
342 | ms -= localToUTCTimeOffset; |
343 | |
344 | // On Mac OS X, the call to localtime (see calculateDSTOffset) will return historically accurate |
345 | // DST information (e.g. New Zealand did not have DST from 1946 to 1974) however the JavaScript |
346 | // standard explicitly dictates that historical information should not be considered when |
347 | // determining DST. For this reason we shift away from years that localtime can handle but would |
348 | // return historically accurate information. |
349 | int year = msToYear(ms); |
350 | int equivalentYear = equivalentYearForDST(year); |
351 | if (year != equivalentYear) { |
352 | bool leapYear = isLeapYear(year); |
353 | int dayInYearLocal = dayInYear(ms, year); |
354 | int dayInMonth = dayInMonthFromDayInYear(dayInYearLocal, leapYear); |
355 | int month = monthFromDayInYear(dayInYearLocal, leapYear); |
356 | double day = dateToDaysFrom1970(equivalentYear, month, dayInMonth); |
357 | ms = (day * msPerDay) + msToMilliseconds(ms); |
358 | } |
359 | |
360 | double localTimeSeconds = ms / msPerSecond; |
361 | if (localTimeSeconds > maxUnixTime) |
362 | localTimeSeconds = maxUnixTime; |
363 | else if (localTimeSeconds < 0) // Go ahead a day to make localtime work (does not work with 0). |
364 | localTimeSeconds += secondsPerDay; |
365 | // FIXME: time_t has a potential problem in 2038. |
366 | time_t localTime = static_cast<time_t>(localTimeSeconds); |
367 | |
368 | #if HAVE(TM_GMTOFF) |
369 | tm localTM; |
370 | getLocalTime(&localTime, &localTM); |
371 | return LocalTimeOffset(localTM.tm_isdst, localTM.tm_gmtoff * msPerSecond); |
372 | #else |
373 | double dstOffset = calculateDSTOffset(localTime, localToUTCTimeOffset); |
374 | return LocalTimeOffset(dstOffset, localToUTCTimeOffset + dstOffset); |
375 | #endif |
376 | } |
377 | |
378 | void initializeDates() |
379 | { |
380 | #if !ASSERT_DISABLED |
381 | static bool alreadyInitialized; |
382 | ASSERT(!alreadyInitialized); |
383 | alreadyInitialized = true; |
384 | #endif |
385 | |
386 | equivalentYearForDST(2000); // Need to call once to initialize a static used in this function. |
387 | } |
388 | |
389 | static inline double ymdhmsToSeconds(int year, long mon, long day, long hour, long minute, double second) |
390 | { |
391 | int mday = firstDayOfMonth[isLeapYear(year)][mon - 1]; |
392 | double ydays = daysFrom1970ToYear(year); |
393 | |
394 | double dateSeconds = second + minute * secondsPerMinute + hour * secondsPerHour + (mday + day - 1 + ydays) * secondsPerDay; |
395 | |
396 | // Clamp to EcmaScript standard (ecma262/#sec-time-values-and-time-range) of |
397 | // +/- 100,000,000 days from 01 January, 1970. |
398 | if (dateSeconds < -8640000000000.0 || dateSeconds > 8640000000000.0) |
399 | return std::numeric_limits<double>::quiet_NaN(); |
400 | |
401 | return dateSeconds; |
402 | } |
403 | |
404 | // We follow the recommendation of RFC 2822 to consider all |
405 | // obsolete time zones not listed here equivalent to "-0000". |
406 | static const struct KnownZone { |
407 | #if !OS(WINDOWS) |
408 | const |
409 | #endif |
410 | char tzName[4]; |
411 | int tzOffset; |
412 | } knownZones[] = { |
413 | { "ut" , 0 }, |
414 | { "gmt" , 0 }, |
415 | { "est" , -300 }, |
416 | { "edt" , -240 }, |
417 | { "cst" , -360 }, |
418 | { "cdt" , -300 }, |
419 | { "mst" , -420 }, |
420 | { "mdt" , -360 }, |
421 | { "pst" , -480 }, |
422 | { "pdt" , -420 } |
423 | }; |
424 | |
425 | inline static void skipSpacesAndComments(const char*& s) |
426 | { |
427 | int nesting = 0; |
428 | char ch; |
429 | while ((ch = *s)) { |
430 | if (!isASCIISpace(ch)) { |
431 | if (ch == '(') |
432 | nesting++; |
433 | else if (ch == ')' && nesting > 0) |
434 | nesting--; |
435 | else if (nesting == 0) |
436 | break; |
437 | } |
438 | s++; |
439 | } |
440 | } |
441 | |
442 | // returns 0-11 (Jan-Dec); -1 on failure |
443 | static int findMonth(const char* monthStr) |
444 | { |
445 | ASSERT(monthStr); |
446 | char needle[4]; |
447 | for (int i = 0; i < 3; ++i) { |
448 | if (!*monthStr) |
449 | return -1; |
450 | needle[i] = static_cast<char>(toASCIILower(*monthStr++)); |
451 | } |
452 | needle[3] = '\0'; |
453 | const char *haystack = "janfebmaraprmayjunjulaugsepoctnovdec" ; |
454 | const char *str = strstr(haystack, needle); |
455 | if (str) { |
456 | int position = static_cast<int>(str - haystack); |
457 | if (position % 3 == 0) |
458 | return position / 3; |
459 | } |
460 | return -1; |
461 | } |
462 | |
463 | static bool parseInt(const char* string, char** stopPosition, int base, int* result) |
464 | { |
465 | long longResult = strtol(string, stopPosition, base); |
466 | // Avoid the use of errno as it is not available on Windows CE |
467 | if (string == *stopPosition || longResult <= std::numeric_limits<int>::min() || longResult >= std::numeric_limits<int>::max()) |
468 | return false; |
469 | *result = static_cast<int>(longResult); |
470 | return true; |
471 | } |
472 | |
473 | static bool parseLong(const char* string, char** stopPosition, int base, long* result) |
474 | { |
475 | *result = strtol(string, stopPosition, base); |
476 | // Avoid the use of errno as it is not available on Windows CE |
477 | if (string == *stopPosition || *result == std::numeric_limits<long>::min() || *result == std::numeric_limits<long>::max()) |
478 | return false; |
479 | return true; |
480 | } |
481 | |
482 | // Parses a date with the format YYYY[-MM[-DD]]. |
483 | // Year parsing is lenient, allows any number of digits, and +/-. |
484 | // Returns 0 if a parse error occurs, else returns the end of the parsed portion of the string. |
485 | static char* parseES5DatePortion(const char* currentPosition, int& year, long& month, long& day) |
486 | { |
487 | char* postParsePosition; |
488 | |
489 | // This is a bit more lenient on the year string than ES5 specifies: |
490 | // instead of restricting to 4 digits (or 6 digits with mandatory +/-), |
491 | // it accepts any integer value. Consider this an implementation fallback. |
492 | if (!parseInt(currentPosition, &postParsePosition, 10, &year)) |
493 | return 0; |
494 | |
495 | // Check for presence of -MM portion. |
496 | if (*postParsePosition != '-') |
497 | return postParsePosition; |
498 | currentPosition = postParsePosition + 1; |
499 | |
500 | if (!isASCIIDigit(*currentPosition)) |
501 | return 0; |
502 | if (!parseLong(currentPosition, &postParsePosition, 10, &month)) |
503 | return 0; |
504 | if ((postParsePosition - currentPosition) != 2) |
505 | return 0; |
506 | |
507 | // Check for presence of -DD portion. |
508 | if (*postParsePosition != '-') |
509 | return postParsePosition; |
510 | currentPosition = postParsePosition + 1; |
511 | |
512 | if (!isASCIIDigit(*currentPosition)) |
513 | return 0; |
514 | if (!parseLong(currentPosition, &postParsePosition, 10, &day)) |
515 | return 0; |
516 | if ((postParsePosition - currentPosition) != 2) |
517 | return 0; |
518 | return postParsePosition; |
519 | } |
520 | |
521 | // Parses a time with the format HH:mm[:ss[.sss]][Z|(+|-)00:00]. |
522 | // Fractional seconds parsing is lenient, allows any number of digits. |
523 | // Returns 0 if a parse error occurs, else returns the end of the parsed portion of the string. |
524 | static char* parseES5TimePortion(char* currentPosition, long& hours, long& minutes, double& seconds, long& timeZoneSeconds) |
525 | { |
526 | char* postParsePosition; |
527 | if (!isASCIIDigit(*currentPosition)) |
528 | return 0; |
529 | if (!parseLong(currentPosition, &postParsePosition, 10, &hours)) |
530 | return 0; |
531 | if (*postParsePosition != ':' || (postParsePosition - currentPosition) != 2) |
532 | return 0; |
533 | currentPosition = postParsePosition + 1; |
534 | |
535 | if (!isASCIIDigit(*currentPosition)) |
536 | return 0; |
537 | if (!parseLong(currentPosition, &postParsePosition, 10, &minutes)) |
538 | return 0; |
539 | if ((postParsePosition - currentPosition) != 2) |
540 | return 0; |
541 | currentPosition = postParsePosition; |
542 | |
543 | // Seconds are optional. |
544 | if (*currentPosition == ':') { |
545 | ++currentPosition; |
546 | |
547 | long intSeconds; |
548 | if (!isASCIIDigit(*currentPosition)) |
549 | return 0; |
550 | if (!parseLong(currentPosition, &postParsePosition, 10, &intSeconds)) |
551 | return 0; |
552 | if ((postParsePosition - currentPosition) != 2) |
553 | return 0; |
554 | seconds = intSeconds; |
555 | if (*postParsePosition == '.') { |
556 | currentPosition = postParsePosition + 1; |
557 | |
558 | // In ECMA-262-5 it's a bit unclear if '.' can be present without milliseconds, but |
559 | // a reasonable interpretation guided by the given examples and RFC 3339 says "no". |
560 | // We check the next character to avoid reading +/- timezone hours after an invalid decimal. |
561 | if (!isASCIIDigit(*currentPosition)) |
562 | return 0; |
563 | |
564 | // We are more lenient than ES5 by accepting more or less than 3 fraction digits. |
565 | long fracSeconds; |
566 | if (!parseLong(currentPosition, &postParsePosition, 10, &fracSeconds)) |
567 | return 0; |
568 | |
569 | long numFracDigits = postParsePosition - currentPosition; |
570 | seconds += fracSeconds * pow(10.0, static_cast<double>(-numFracDigits)); |
571 | } |
572 | currentPosition = postParsePosition; |
573 | } |
574 | |
575 | if (*currentPosition == 'Z') |
576 | return currentPosition + 1; |
577 | |
578 | bool tzNegative; |
579 | if (*currentPosition == '-') |
580 | tzNegative = true; |
581 | else if (*currentPosition == '+') |
582 | tzNegative = false; |
583 | else |
584 | return currentPosition; // no timezone |
585 | ++currentPosition; |
586 | |
587 | long tzHours; |
588 | long tzHoursAbs; |
589 | long tzMinutes; |
590 | |
591 | if (!isASCIIDigit(*currentPosition)) |
592 | return 0; |
593 | if (!parseLong(currentPosition, &postParsePosition, 10, &tzHours)) |
594 | return 0; |
595 | if (*postParsePosition != ':' || (postParsePosition - currentPosition) != 2) |
596 | return 0; |
597 | tzHoursAbs = labs(tzHours); |
598 | currentPosition = postParsePosition + 1; |
599 | |
600 | if (!isASCIIDigit(*currentPosition)) |
601 | return 0; |
602 | if (!parseLong(currentPosition, &postParsePosition, 10, &tzMinutes)) |
603 | return 0; |
604 | if ((postParsePosition - currentPosition) != 2) |
605 | return 0; |
606 | currentPosition = postParsePosition; |
607 | |
608 | if (tzHoursAbs > 24) |
609 | return 0; |
610 | if (tzMinutes < 0 || tzMinutes > 59) |
611 | return 0; |
612 | |
613 | timeZoneSeconds = 60 * (tzMinutes + (60 * tzHoursAbs)); |
614 | if (tzNegative) |
615 | timeZoneSeconds = -timeZoneSeconds; |
616 | |
617 | return currentPosition; |
618 | } |
619 | |
620 | double parseES5DateFromNullTerminatedCharacters(const char* dateString) |
621 | { |
622 | // This parses a date of the form defined in ecma262/#sec-date-time-string-format |
623 | // (similar to RFC 3339 / ISO 8601: YYYY-MM-DDTHH:mm:ss[.sss]Z). |
624 | // In most cases it is intentionally strict (e.g. correct field widths, no stray whitespace). |
625 | |
626 | static const long daysPerMonth[12] = { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; |
627 | |
628 | // The year must be present, but the other fields may be omitted - see ES5.1 15.9.1.15. |
629 | int year = 0; |
630 | long month = 1; |
631 | long day = 1; |
632 | long hours = 0; |
633 | long minutes = 0; |
634 | double seconds = 0; |
635 | long timeZoneSeconds = 0; |
636 | |
637 | // Parse the date YYYY[-MM[-DD]] |
638 | char* currentPosition = parseES5DatePortion(dateString, year, month, day); |
639 | if (!currentPosition) |
640 | return std::numeric_limits<double>::quiet_NaN(); |
641 | // Look for a time portion. |
642 | if (*currentPosition == 'T') { |
643 | // Parse the time HH:mm[:ss[.sss]][Z|(+|-)00:00] |
644 | currentPosition = parseES5TimePortion(currentPosition + 1, hours, minutes, seconds, timeZoneSeconds); |
645 | if (!currentPosition) |
646 | return std::numeric_limits<double>::quiet_NaN(); |
647 | } |
648 | // Check that we have parsed all characters in the string. |
649 | if (*currentPosition) |
650 | return std::numeric_limits<double>::quiet_NaN(); |
651 | |
652 | // A few of these checks could be done inline above, but since many of them are interrelated |
653 | // we would be sacrificing readability to "optimize" the (presumably less common) failure path. |
654 | if (month < 1 || month > 12) |
655 | return std::numeric_limits<double>::quiet_NaN(); |
656 | if (day < 1 || day > daysPerMonth[month - 1]) |
657 | return std::numeric_limits<double>::quiet_NaN(); |
658 | if (month == 2 && day > 28 && !isLeapYear(year)) |
659 | return std::numeric_limits<double>::quiet_NaN(); |
660 | if (hours < 0 || hours > 24) |
661 | return std::numeric_limits<double>::quiet_NaN(); |
662 | if (hours == 24 && (minutes || seconds)) |
663 | return std::numeric_limits<double>::quiet_NaN(); |
664 | if (minutes < 0 || minutes > 59) |
665 | return std::numeric_limits<double>::quiet_NaN(); |
666 | if (seconds < 0 || seconds >= 61) |
667 | return std::numeric_limits<double>::quiet_NaN(); |
668 | if (seconds > 60) { |
669 | // Discard leap seconds by clamping to the end of a minute. |
670 | seconds = 60; |
671 | } |
672 | |
673 | double dateSeconds = ymdhmsToSeconds(year, month, day, hours, minutes, seconds) - timeZoneSeconds; |
674 | return dateSeconds * msPerSecond; |
675 | } |
676 | |
677 | // Odd case where 'exec' is allowed to be 0, to accomodate a caller in WebCore. |
678 | double parseDateFromNullTerminatedCharacters(const char* dateString, bool& haveTZ, int& offset) |
679 | { |
680 | haveTZ = false; |
681 | offset = 0; |
682 | |
683 | // This parses a date in the form: |
684 | // Tuesday, 09-Nov-99 23:12:40 GMT |
685 | // or |
686 | // Sat, 01-Jan-2000 08:00:00 GMT |
687 | // or |
688 | // Sat, 01 Jan 2000 08:00:00 GMT |
689 | // or |
690 | // 01 Jan 99 22:00 +0100 (exceptions in rfc822/rfc2822) |
691 | // ### non RFC formats, added for Javascript: |
692 | // [Wednesday] January 09 1999 23:12:40 GMT |
693 | // [Wednesday] January 09 23:12:40 GMT 1999 |
694 | // |
695 | // We ignore the weekday. |
696 | |
697 | // Skip leading space |
698 | skipSpacesAndComments(dateString); |
699 | |
700 | long month = -1; |
701 | const char *wordStart = dateString; |
702 | // Check contents of first words if not number |
703 | while (*dateString && !isASCIIDigit(*dateString)) { |
704 | if (isASCIISpace(*dateString) || *dateString == '(') { |
705 | if (dateString - wordStart >= 3) |
706 | month = findMonth(wordStart); |
707 | skipSpacesAndComments(dateString); |
708 | wordStart = dateString; |
709 | } else |
710 | dateString++; |
711 | } |
712 | |
713 | // Missing delimiter between month and day (like "January29")? |
714 | if (month == -1 && wordStart != dateString) |
715 | month = findMonth(wordStart); |
716 | |
717 | skipSpacesAndComments(dateString); |
718 | |
719 | if (!*dateString) |
720 | return std::numeric_limits<double>::quiet_NaN(); |
721 | |
722 | // ' 09-Nov-99 23:12:40 GMT' |
723 | char* newPosStr; |
724 | long day; |
725 | if (!parseLong(dateString, &newPosStr, 10, &day)) |
726 | return std::numeric_limits<double>::quiet_NaN(); |
727 | dateString = newPosStr; |
728 | |
729 | if (day < 0) |
730 | return std::numeric_limits<double>::quiet_NaN(); |
731 | |
732 | Optional<int> year; |
733 | if (day > 31) { |
734 | // ### where is the boundary and what happens below? |
735 | if (*dateString != '/') |
736 | return std::numeric_limits<double>::quiet_NaN(); |
737 | // looks like a YYYY/MM/DD date |
738 | if (!*++dateString) |
739 | return std::numeric_limits<double>::quiet_NaN(); |
740 | if (day <= std::numeric_limits<int>::min() || day >= std::numeric_limits<int>::max()) |
741 | return std::numeric_limits<double>::quiet_NaN(); |
742 | year = static_cast<int>(day); |
743 | if (!parseLong(dateString, &newPosStr, 10, &month)) |
744 | return std::numeric_limits<double>::quiet_NaN(); |
745 | month -= 1; |
746 | dateString = newPosStr; |
747 | if (*dateString++ != '/' || !*dateString) |
748 | return std::numeric_limits<double>::quiet_NaN(); |
749 | if (!parseLong(dateString, &newPosStr, 10, &day)) |
750 | return std::numeric_limits<double>::quiet_NaN(); |
751 | dateString = newPosStr; |
752 | } else if (*dateString == '/' && month == -1) { |
753 | dateString++; |
754 | // This looks like a MM/DD/YYYY date, not an RFC date. |
755 | month = day - 1; // 0-based |
756 | if (!parseLong(dateString, &newPosStr, 10, &day)) |
757 | return std::numeric_limits<double>::quiet_NaN(); |
758 | if (day < 1 || day > 31) |
759 | return std::numeric_limits<double>::quiet_NaN(); |
760 | dateString = newPosStr; |
761 | if (*dateString == '/') |
762 | dateString++; |
763 | if (!*dateString) |
764 | return std::numeric_limits<double>::quiet_NaN(); |
765 | } else { |
766 | if (*dateString == '-') |
767 | dateString++; |
768 | |
769 | skipSpacesAndComments(dateString); |
770 | |
771 | if (*dateString == ',') |
772 | dateString++; |
773 | |
774 | if (month == -1) { // not found yet |
775 | month = findMonth(dateString); |
776 | if (month == -1) |
777 | return std::numeric_limits<double>::quiet_NaN(); |
778 | |
779 | while (*dateString && *dateString != '-' && *dateString != ',' && !isASCIISpace(*dateString)) |
780 | dateString++; |
781 | |
782 | if (!*dateString) |
783 | return std::numeric_limits<double>::quiet_NaN(); |
784 | |
785 | // '-99 23:12:40 GMT' |
786 | if (*dateString != '-' && *dateString != '/' && *dateString != ',' && !isASCIISpace(*dateString)) |
787 | return std::numeric_limits<double>::quiet_NaN(); |
788 | dateString++; |
789 | } |
790 | } |
791 | |
792 | if (month < 0 || month > 11) |
793 | return std::numeric_limits<double>::quiet_NaN(); |
794 | |
795 | // '99 23:12:40 GMT' |
796 | if (*dateString && !year) { |
797 | int result = 0; |
798 | if (!parseInt(dateString, &newPosStr, 10, &result)) |
799 | return std::numeric_limits<double>::quiet_NaN(); |
800 | year = result; |
801 | } |
802 | |
803 | // Don't fail if the time is missing. |
804 | long hour = 0; |
805 | long minute = 0; |
806 | long second = 0; |
807 | if (!*newPosStr) |
808 | dateString = newPosStr; |
809 | else { |
810 | // ' 23:12:40 GMT' |
811 | if (!(isASCIISpace(*newPosStr) || *newPosStr == ',')) { |
812 | if (*newPosStr != ':') |
813 | return std::numeric_limits<double>::quiet_NaN(); |
814 | // There was no year; the number was the hour. |
815 | year = WTF::nullopt; |
816 | } else { |
817 | // in the normal case (we parsed the year), advance to the next number |
818 | dateString = ++newPosStr; |
819 | skipSpacesAndComments(dateString); |
820 | } |
821 | |
822 | parseLong(dateString, &newPosStr, 10, &hour); |
823 | // Do not check for errno here since we want to continue |
824 | // even if errno was set becasue we are still looking |
825 | // for the timezone! |
826 | |
827 | // Read a number? If not, this might be a timezone name. |
828 | if (newPosStr != dateString) { |
829 | dateString = newPosStr; |
830 | |
831 | if (hour < 0 || hour > 23) |
832 | return std::numeric_limits<double>::quiet_NaN(); |
833 | |
834 | if (!*dateString) |
835 | return std::numeric_limits<double>::quiet_NaN(); |
836 | |
837 | // ':12:40 GMT' |
838 | if (*dateString++ != ':') |
839 | return std::numeric_limits<double>::quiet_NaN(); |
840 | |
841 | if (!parseLong(dateString, &newPosStr, 10, &minute)) |
842 | return std::numeric_limits<double>::quiet_NaN(); |
843 | dateString = newPosStr; |
844 | |
845 | if (minute < 0 || minute > 59) |
846 | return std::numeric_limits<double>::quiet_NaN(); |
847 | |
848 | // ':40 GMT' |
849 | if (*dateString && *dateString != ':' && !isASCIISpace(*dateString)) |
850 | return std::numeric_limits<double>::quiet_NaN(); |
851 | |
852 | // seconds are optional in rfc822 + rfc2822 |
853 | if (*dateString ==':') { |
854 | dateString++; |
855 | |
856 | if (!parseLong(dateString, &newPosStr, 10, &second)) |
857 | return std::numeric_limits<double>::quiet_NaN(); |
858 | dateString = newPosStr; |
859 | |
860 | if (second < 0 || second > 59) |
861 | return std::numeric_limits<double>::quiet_NaN(); |
862 | } |
863 | |
864 | skipSpacesAndComments(dateString); |
865 | |
866 | if (startsWithLettersIgnoringASCIICase(dateString, "am" )) { |
867 | if (hour > 12) |
868 | return std::numeric_limits<double>::quiet_NaN(); |
869 | if (hour == 12) |
870 | hour = 0; |
871 | dateString += 2; |
872 | skipSpacesAndComments(dateString); |
873 | } else if (startsWithLettersIgnoringASCIICase(dateString, "pm" )) { |
874 | if (hour > 12) |
875 | return std::numeric_limits<double>::quiet_NaN(); |
876 | if (hour != 12) |
877 | hour += 12; |
878 | dateString += 2; |
879 | skipSpacesAndComments(dateString); |
880 | } |
881 | } |
882 | } |
883 | |
884 | // The year may be after the time but before the time zone. |
885 | if (isASCIIDigit(*dateString) && !year) { |
886 | int result = 0; |
887 | if (!parseInt(dateString, &newPosStr, 10, &result)) |
888 | return std::numeric_limits<double>::quiet_NaN(); |
889 | year = result; |
890 | dateString = newPosStr; |
891 | skipSpacesAndComments(dateString); |
892 | } |
893 | |
894 | // Don't fail if the time zone is missing. |
895 | // Some websites omit the time zone (4275206). |
896 | if (*dateString) { |
897 | if (startsWithLettersIgnoringASCIICase(dateString, "gmt" ) || startsWithLettersIgnoringASCIICase(dateString, "utc" )) { |
898 | dateString += 3; |
899 | haveTZ = true; |
900 | } |
901 | |
902 | if (*dateString == '+' || *dateString == '-') { |
903 | int o; |
904 | if (!parseInt(dateString, &newPosStr, 10, &o)) |
905 | return std::numeric_limits<double>::quiet_NaN(); |
906 | dateString = newPosStr; |
907 | |
908 | if (o < -9959 || o > 9959) |
909 | return std::numeric_limits<double>::quiet_NaN(); |
910 | |
911 | int sgn = (o < 0) ? -1 : 1; |
912 | o = abs(o); |
913 | if (*dateString != ':') { |
914 | if (o >= 24) |
915 | offset = ((o / 100) * 60 + (o % 100)) * sgn; |
916 | else |
917 | offset = o * 60 * sgn; |
918 | } else { // GMT+05:00 |
919 | ++dateString; // skip the ':' |
920 | int o2; |
921 | if (!parseInt(dateString, &newPosStr, 10, &o2)) |
922 | return std::numeric_limits<double>::quiet_NaN(); |
923 | dateString = newPosStr; |
924 | offset = (o * 60 + o2) * sgn; |
925 | } |
926 | haveTZ = true; |
927 | } else { |
928 | for (auto& knownZone : knownZones) { |
929 | // Since the passed-in length is used for both strings, the following checks that |
930 | // dateString has the time zone name as a prefix, not that it is equal. |
931 | auto length = strlen(knownZone.tzName); |
932 | if (equalLettersIgnoringASCIICase(dateString, knownZone.tzName, length)) { |
933 | offset = knownZone.tzOffset; |
934 | dateString += length; |
935 | haveTZ = true; |
936 | break; |
937 | } |
938 | } |
939 | } |
940 | } |
941 | |
942 | skipSpacesAndComments(dateString); |
943 | |
944 | if (*dateString && !year) { |
945 | int result = 0; |
946 | if (!parseInt(dateString, &newPosStr, 10, &result)) |
947 | return std::numeric_limits<double>::quiet_NaN(); |
948 | year = result; |
949 | dateString = newPosStr; |
950 | skipSpacesAndComments(dateString); |
951 | } |
952 | |
953 | // Trailing garbage |
954 | if (*dateString) |
955 | return std::numeric_limits<double>::quiet_NaN(); |
956 | |
957 | // Y2K: Handle 2 digit years. |
958 | if (year) { |
959 | int yearValue = year.value(); |
960 | if (yearValue >= 0 && yearValue < 100) { |
961 | if (yearValue < 50) |
962 | yearValue += 2000; |
963 | else |
964 | yearValue += 1900; |
965 | } |
966 | year = yearValue; |
967 | } else { |
968 | // We select 2000 as default value. This is because of the following reasons. |
969 | // 1. Year 2000 was used for the initial value of the variable `year`. While it won't be posed to users in WebKit, |
970 | // V8 used this 2000 as its default value. (As of April 2017, V8 is using the year 2001 and Spider Monkey is |
971 | // not doing this kind of fallback.) |
972 | // 2. It is a leap year. When using `new Date("Feb 29")`, we assume that people want to save month and day. |
973 | // Leap year can save user inputs if they is valid. If we use the current year instead, the current year |
974 | // may not be a leap year. In that case, `new Date("Feb 29").getMonth()` becomes 2 (March). |
975 | year = 2000; |
976 | } |
977 | ASSERT(year); |
978 | |
979 | return ymdhmsToSeconds(year.value(), month + 1, day, hour, minute, second) * msPerSecond; |
980 | } |
981 | |
982 | double parseDateFromNullTerminatedCharacters(const char* dateString) |
983 | { |
984 | bool haveTZ; |
985 | int offset; |
986 | double ms = parseDateFromNullTerminatedCharacters(dateString, haveTZ, offset); |
987 | if (std::isnan(ms)) |
988 | return std::numeric_limits<double>::quiet_NaN(); |
989 | |
990 | // fall back to local timezone |
991 | if (!haveTZ) |
992 | offset = calculateLocalTimeOffset(ms, LocalTime).offset / msPerMinute; // ms value is in local time milliseconds. |
993 | |
994 | return ms - (offset * msPerMinute); |
995 | } |
996 | |
997 | // See http://tools.ietf.org/html/rfc2822#section-3.3 for more information. |
998 | String makeRFC2822DateString(unsigned dayOfWeek, unsigned day, unsigned month, unsigned year, unsigned hours, unsigned minutes, unsigned seconds, int utcOffset) |
999 | { |
1000 | StringBuilder stringBuilder; |
1001 | stringBuilder.append(weekdayName[dayOfWeek], ", " , day, ' ', monthName[month], ' ', year, ' '); |
1002 | |
1003 | appendTwoDigitNumber(stringBuilder, hours); |
1004 | stringBuilder.append(':'); |
1005 | appendTwoDigitNumber(stringBuilder, minutes); |
1006 | stringBuilder.append(':'); |
1007 | appendTwoDigitNumber(stringBuilder, seconds); |
1008 | stringBuilder.append(' '); |
1009 | |
1010 | stringBuilder.append(utcOffset > 0 ? '+' : '-'); |
1011 | int absoluteUTCOffset = abs(utcOffset); |
1012 | appendTwoDigitNumber(stringBuilder, absoluteUTCOffset / 60); |
1013 | appendTwoDigitNumber(stringBuilder, absoluteUTCOffset % 60); |
1014 | |
1015 | return stringBuilder.toString(); |
1016 | } |
1017 | |
1018 | } // namespace WTF |
1019 | |