1 | /* |
2 | * Copyright (C) 1999-2000 Harri Porten (porten@kde.org) |
3 | * Copyright (C) 2006, 2007 Apple Inc. All rights reserved. |
4 | * Copyright (C) 2009 Google Inc. All rights reserved. |
5 | * Copyright (C) 2007-2009 Torch Mobile, Inc. |
6 | * |
7 | * The Original Code is Mozilla Communicator client code, released |
8 | * March 31, 1998. |
9 | * |
10 | * The Initial Developer of the Original Code is |
11 | * Netscape Communications Corporation. |
12 | * Portions created by the Initial Developer are Copyright (C) 1998 |
13 | * the Initial Developer. All Rights Reserved. |
14 | * |
15 | * This library is free software; you can redistribute it and/or |
16 | * modify it under the terms of the GNU Lesser General Public |
17 | * License as published by the Free Software Foundation; either |
18 | * version 2.1 of the License, or (at your option) any later version. |
19 | * |
20 | * This library is distributed in the hope that it will be useful, |
21 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
22 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
23 | * Lesser General Public License for more details. |
24 | * |
25 | * You should have received a copy of the GNU Lesser General Public |
26 | * License along with this library; if not, write to the Free Software |
27 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA |
28 | * |
29 | * Alternatively, the contents of this file may be used under the terms |
30 | * of either the Mozilla Public License Version 1.1, found at |
31 | * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public |
32 | * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html |
33 | * (the "GPL"), in which case the provisions of the MPL or the GPL are |
34 | * applicable instead of those above. If you wish to allow use of your |
35 | * version of this file only under the terms of one of those two |
36 | * licenses (the MPL or the GPL) and not to allow others to use your |
37 | * version of this file under the LGPL, indicate your decision by |
38 | * deletingthe provisions above and replace them with the notice and |
39 | * other provisions required by the MPL or the GPL, as the case may be. |
40 | * If you do not delete the provisions above, a recipient may use your |
41 | * version of this file under any of the LGPL, the MPL or the GPL. |
42 | |
43 | * Copyright 2006-2008 the V8 project authors. All rights reserved. |
44 | * Redistribution and use in source and binary forms, with or without |
45 | * modification, are permitted provided that the following conditions are |
46 | * met: |
47 | * |
48 | * * Redistributions of source code must retain the above copyright |
49 | * notice, this list of conditions and the following disclaimer. |
50 | * * Redistributions in binary form must reproduce the above |
51 | * copyright notice, this list of conditions and the following |
52 | * disclaimer in the documentation and/or other materials provided |
53 | * with the distribution. |
54 | * * Neither the name of Google Inc. nor the names of its |
55 | * contributors may be used to endorse or promote products derived |
56 | * from this software without specific prior written permission. |
57 | * |
58 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
59 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
60 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
61 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
62 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
63 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
64 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
65 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
66 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
67 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
68 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
69 | */ |
70 | |
71 | #include "config.h" |
72 | #include "DateMath.h" |
73 | |
74 | #include "Assertions.h" |
75 | #include "ASCIICType.h" |
76 | #include "CurrentTime.h" |
77 | #include "StringExtras.h" |
78 | |
79 | #include <algorithm> |
80 | #include <limits.h> |
81 | #include <limits> |
82 | #include <stdint.h> |
83 | #include <time.h> |
84 | |
85 | |
86 | #if HAVE(ERRNO_H) |
87 | #include <errno.h> |
88 | #endif |
89 | |
90 | #if OS(WINCE) |
91 | extern "C" size_t strftime(char * const s, const size_t maxsize, const char * const format, const struct tm * const t); |
92 | extern "C" struct tm * localtime(const time_t *timer); |
93 | #endif |
94 | |
95 | #if HAVE(SYS_TIME_H) |
96 | #include <sys/time.h> |
97 | #endif |
98 | |
99 | #if HAVE(SYS_TIMEB_H) |
100 | #include <sys/timeb.h> |
101 | #endif |
102 | |
103 | #if USE(JSC) |
104 | #include "CallFrame.h" |
105 | #endif |
106 | |
107 | #include "MathExtras.h" |
108 | |
109 | #define NaN std::numeric_limits<double>::quiet_NaN() |
110 | |
111 | using namespace WTF; |
112 | |
113 | namespace WTF { |
114 | |
115 | /* Constants */ |
116 | |
117 | static const double minutesPerDay = 24.0 * 60.0; |
118 | static const double secondsPerDay = 24.0 * 60.0 * 60.0; |
119 | static const double secondsPerYear = 24.0 * 60.0 * 60.0 * 365.0; |
120 | |
121 | static const double usecPerSec = 1000000.0; |
122 | |
123 | static const double maxUnixTime = 2145859200.0; // 12/31/2037 |
124 | // ECMAScript asks not to support for a date of which total |
125 | // millisecond value is larger than the following value. |
126 | // See 15.9.1.14 of ECMA-262 5th edition. |
127 | static const double maxECMAScriptTime = 8.64E15; |
128 | |
129 | // Day of year for the first day of each month, where index 0 is January, and day 0 is January 1. |
130 | // First for non-leap years, then for leap years. |
131 | static const int firstDayOfMonth[2][12] = { |
132 | {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}, |
133 | {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335} |
134 | }; |
135 | |
136 | static inline bool isLeapYear(int year) |
137 | { |
138 | if (year % 4 != 0) |
139 | return false; |
140 | if (year % 400 == 0) |
141 | return true; |
142 | if (year % 100 == 0) |
143 | return false; |
144 | return true; |
145 | } |
146 | |
147 | static inline int daysInYear(int year) |
148 | { |
149 | return 365 + isLeapYear(year); |
150 | } |
151 | |
152 | static inline double daysFrom1970ToYear(int year) |
153 | { |
154 | // The Gregorian Calendar rules for leap years: |
155 | // Every fourth year is a leap year. 2004, 2008, and 2012 are leap years. |
156 | // However, every hundredth year is not a leap year. 1900 and 2100 are not leap years. |
157 | // Every four hundred years, there's a leap year after all. 2000 and 2400 are leap years. |
158 | |
159 | static const int leapDaysBefore1971By4Rule = 1970 / 4; |
160 | static const int excludedLeapDaysBefore1971By100Rule = 1970 / 100; |
161 | static const int leapDaysBefore1971By400Rule = 1970 / 400; |
162 | |
163 | const double yearMinusOne = year - 1; |
164 | const double yearsToAddBy4Rule = floor(x: yearMinusOne / 4.0) - leapDaysBefore1971By4Rule; |
165 | const double yearsToExcludeBy100Rule = floor(x: yearMinusOne / 100.0) - excludedLeapDaysBefore1971By100Rule; |
166 | const double yearsToAddBy400Rule = floor(x: yearMinusOne / 400.0) - leapDaysBefore1971By400Rule; |
167 | |
168 | return 365.0 * (year - 1970) + yearsToAddBy4Rule - yearsToExcludeBy100Rule + yearsToAddBy400Rule; |
169 | } |
170 | |
171 | static inline double msToDays(double ms) |
172 | { |
173 | return floor(x: ms / msPerDay); |
174 | } |
175 | |
176 | int msToYear(double ms) |
177 | { |
178 | int approxYear = static_cast<int>(floor(x: ms / (msPerDay * 365.2425)) + 1970); |
179 | double msFromApproxYearTo1970 = msPerDay * daysFrom1970ToYear(year: approxYear); |
180 | if (msFromApproxYearTo1970 > ms) |
181 | return approxYear - 1; |
182 | if (msFromApproxYearTo1970 + msPerDay * daysInYear(year: approxYear) <= ms) |
183 | return approxYear + 1; |
184 | return approxYear; |
185 | } |
186 | |
187 | int dayInYear(double ms, int year) |
188 | { |
189 | return static_cast<int>(msToDays(ms) - daysFrom1970ToYear(year)); |
190 | } |
191 | |
192 | static inline double msToMilliseconds(double ms) |
193 | { |
194 | double result = fmod(x: ms, y: msPerDay); |
195 | if (result < 0) |
196 | result += msPerDay; |
197 | return result; |
198 | } |
199 | |
200 | // 0: Sunday, 1: Monday, etc. |
201 | static inline int msToWeekDay(double ms) |
202 | { |
203 | int wd = (static_cast<int>(msToDays(ms)) + 4) % 7; |
204 | if (wd < 0) |
205 | wd += 7; |
206 | return wd; |
207 | } |
208 | |
209 | static inline int msToSeconds(double ms) |
210 | { |
211 | double result = fmod(x: floor(x: ms / msPerSecond), y: secondsPerMinute); |
212 | if (result < 0) |
213 | result += secondsPerMinute; |
214 | return static_cast<int>(result); |
215 | } |
216 | |
217 | static inline int msToMinutes(double ms) |
218 | { |
219 | double result = fmod(x: floor(x: ms / msPerMinute), y: minutesPerHour); |
220 | if (result < 0) |
221 | result += minutesPerHour; |
222 | return static_cast<int>(result); |
223 | } |
224 | |
225 | static inline int msToHours(double ms) |
226 | { |
227 | double result = fmod(x: floor(x: ms/msPerHour), y: hoursPerDay); |
228 | if (result < 0) |
229 | result += hoursPerDay; |
230 | return static_cast<int>(result); |
231 | } |
232 | |
233 | int monthFromDayInYear(int dayInYear, bool leapYear) |
234 | { |
235 | const int d = dayInYear; |
236 | int step; |
237 | |
238 | if (d < (step = 31)) |
239 | return 0; |
240 | step += (leapYear ? 29 : 28); |
241 | if (d < step) |
242 | return 1; |
243 | if (d < (step += 31)) |
244 | return 2; |
245 | if (d < (step += 30)) |
246 | return 3; |
247 | if (d < (step += 31)) |
248 | return 4; |
249 | if (d < (step += 30)) |
250 | return 5; |
251 | if (d < (step += 31)) |
252 | return 6; |
253 | if (d < (step += 31)) |
254 | return 7; |
255 | if (d < (step += 30)) |
256 | return 8; |
257 | if (d < (step += 31)) |
258 | return 9; |
259 | if (d < (step += 30)) |
260 | return 10; |
261 | return 11; |
262 | } |
263 | |
264 | static inline bool checkMonth(int dayInYear, int& startDayOfThisMonth, int& startDayOfNextMonth, int daysInThisMonth) |
265 | { |
266 | startDayOfThisMonth = startDayOfNextMonth; |
267 | startDayOfNextMonth += daysInThisMonth; |
268 | return (dayInYear <= startDayOfNextMonth); |
269 | } |
270 | |
271 | int dayInMonthFromDayInYear(int dayInYear, bool leapYear) |
272 | { |
273 | const int d = dayInYear; |
274 | int step; |
275 | int next = 30; |
276 | |
277 | if (d <= next) |
278 | return d + 1; |
279 | const int daysInFeb = (leapYear ? 29 : 28); |
280 | if (checkMonth(dayInYear: d, startDayOfThisMonth&: step, startDayOfNextMonth&: next, daysInThisMonth: daysInFeb)) |
281 | return d - step; |
282 | if (checkMonth(dayInYear: d, startDayOfThisMonth&: step, startDayOfNextMonth&: next, daysInThisMonth: 31)) |
283 | return d - step; |
284 | if (checkMonth(dayInYear: d, startDayOfThisMonth&: step, startDayOfNextMonth&: next, daysInThisMonth: 30)) |
285 | return d - step; |
286 | if (checkMonth(dayInYear: d, startDayOfThisMonth&: step, startDayOfNextMonth&: next, daysInThisMonth: 31)) |
287 | return d - step; |
288 | if (checkMonth(dayInYear: d, startDayOfThisMonth&: step, startDayOfNextMonth&: next, daysInThisMonth: 30)) |
289 | return d - step; |
290 | if (checkMonth(dayInYear: d, startDayOfThisMonth&: step, startDayOfNextMonth&: next, daysInThisMonth: 31)) |
291 | return d - step; |
292 | if (checkMonth(dayInYear: d, startDayOfThisMonth&: step, startDayOfNextMonth&: next, daysInThisMonth: 31)) |
293 | return d - step; |
294 | if (checkMonth(dayInYear: d, startDayOfThisMonth&: step, startDayOfNextMonth&: next, daysInThisMonth: 30)) |
295 | return d - step; |
296 | if (checkMonth(dayInYear: d, startDayOfThisMonth&: step, startDayOfNextMonth&: next, daysInThisMonth: 31)) |
297 | return d - step; |
298 | if (checkMonth(dayInYear: d, startDayOfThisMonth&: step, startDayOfNextMonth&: next, daysInThisMonth: 30)) |
299 | return d - step; |
300 | step = next; |
301 | return d - step; |
302 | } |
303 | |
304 | static inline int monthToDayInYear(int month, bool isLeapYear) |
305 | { |
306 | return firstDayOfMonth[isLeapYear][month]; |
307 | } |
308 | |
309 | static inline double timeToMS(double hour, double min, double sec, double ms) |
310 | { |
311 | return (((hour * minutesPerHour + min) * secondsPerMinute + sec) * msPerSecond + ms); |
312 | } |
313 | |
314 | double dateToDaysFrom1970(int year, int month, int day) |
315 | { |
316 | year += month / 12; |
317 | |
318 | month %= 12; |
319 | if (month < 0) { |
320 | month += 12; |
321 | --year; |
322 | } |
323 | |
324 | double yearday = floor(x: daysFrom1970ToYear(year)); |
325 | ASSERT((year >= 1970 && yearday >= 0) || (year < 1970 && yearday < 0)); |
326 | int monthday = monthToDayInYear(month, isLeapYear: isLeapYear(year)); |
327 | |
328 | return yearday + monthday + day - 1; |
329 | } |
330 | |
331 | // There is a hard limit at 2038 that we currently do not have a workaround |
332 | // for (rdar://problem/5052975). |
333 | static inline int maximumYearForDST() |
334 | { |
335 | return 2037; |
336 | } |
337 | |
338 | static inline int minimumYearForDST() |
339 | { |
340 | // Because of the 2038 issue (see maximumYearForDST) if the current year is |
341 | // greater than the max year minus 27 (2010), we want to use the max year |
342 | // minus 27 instead, to ensure there is a range of 28 years that all years |
343 | // can map to. |
344 | return std::min(a: msToYear(ms: jsCurrentTime()), b: maximumYearForDST() - 27) ; |
345 | } |
346 | |
347 | /* |
348 | * Find an equivalent year for the one given, where equivalence is deterined by |
349 | * the two years having the same leapness and the first day of the year, falling |
350 | * on the same day of the week. |
351 | * |
352 | * This function returns a year between this current year and 2037, however this |
353 | * function will potentially return incorrect results if the current year is after |
354 | * 2010, (rdar://problem/5052975), if the year passed in is before 1900 or after |
355 | * 2100, (rdar://problem/5055038). |
356 | */ |
357 | int equivalentYearForDST(int year) |
358 | { |
359 | // It is ok if the cached year is not the current year as long as the rules |
360 | // for DST did not change between the two years; if they did the app would need |
361 | // to be restarted. |
362 | static int minYear = minimumYearForDST(); |
363 | int maxYear = maximumYearForDST(); |
364 | |
365 | int difference; |
366 | if (year > maxYear) |
367 | difference = minYear - year; |
368 | else if (year < minYear) |
369 | difference = maxYear - year; |
370 | else |
371 | return year; |
372 | |
373 | int quotient = difference / 28; |
374 | int product = (quotient) * 28; |
375 | |
376 | year += product; |
377 | ASSERT((year >= minYear && year <= maxYear) || (product - year == static_cast<int>(NaN))); |
378 | return year; |
379 | } |
380 | |
381 | #if !HAVE(TM_GMTOFF) |
382 | |
383 | static int32_t calculateUTCOffset() |
384 | { |
385 | #if PLATFORM(BREWMP) |
386 | time_t localTime = static_cast<time_t>(currentTime()); |
387 | #else |
388 | time_t localTime = time(0); |
389 | #endif |
390 | tm localt; |
391 | getLocalTime(&localTime, &localt); |
392 | |
393 | // Get the difference between this time zone and UTC on the 1st of January of this year. |
394 | localt.tm_sec = 0; |
395 | localt.tm_min = 0; |
396 | localt.tm_hour = 0; |
397 | localt.tm_mday = 1; |
398 | localt.tm_mon = 0; |
399 | // Not setting localt.tm_year! |
400 | localt.tm_wday = 0; |
401 | localt.tm_yday = 0; |
402 | localt.tm_isdst = 0; |
403 | #if HAVE(TM_GMTOFF) |
404 | localt.tm_gmtoff = 0; |
405 | #endif |
406 | #if HAVE(TM_ZONE) |
407 | localt.tm_zone = 0; |
408 | #endif |
409 | |
410 | #if HAVE(TIMEGM) |
411 | time_t utcOffset = timegm(&localt) - mktime(&localt); |
412 | #else |
413 | // Using a canned date of 01/01/2009 on platforms with weaker date-handling foo. |
414 | localt.tm_year = 109; |
415 | time_t utcOffset = 1230768000 - mktime(&localt); |
416 | #endif |
417 | |
418 | return static_cast<int32_t>(utcOffset * 1000); |
419 | } |
420 | |
421 | /* |
422 | * Get the DST offset for the time passed in. |
423 | */ |
424 | static double calculateDSTOffset(time_t localTime, double utcOffset) |
425 | { |
426 | //input is UTC so we have to shift back to local time to determine DST thus the + getUTCOffset() |
427 | double offsetTime = (localTime * msPerSecond) + utcOffset; |
428 | |
429 | // Offset from UTC but doesn't include DST obviously |
430 | int offsetHour = msToHours(offsetTime); |
431 | int offsetMinute = msToMinutes(offsetTime); |
432 | |
433 | tm localTM; |
434 | getLocalTime(&localTime, &localTM); |
435 | |
436 | double diff = ((localTM.tm_hour - offsetHour) * secondsPerHour) + ((localTM.tm_min - offsetMinute) * 60); |
437 | |
438 | if (diff < 0) |
439 | diff += secondsPerDay; |
440 | |
441 | return (diff * msPerSecond); |
442 | } |
443 | |
444 | #endif |
445 | |
446 | // Returns combined offset in millisecond (UTC + DST). |
447 | LocalTimeOffset calculateLocalTimeOffset(double ms) |
448 | { |
449 | // On Mac OS X, the call to localtime (see calculateDSTOffset) will return historically accurate |
450 | // DST information (e.g. New Zealand did not have DST from 1946 to 1974) however the JavaScript |
451 | // standard explicitly dictates that historical information should not be considered when |
452 | // determining DST. For this reason we shift away from years that localtime can handle but would |
453 | // return historically accurate information. |
454 | int year = msToYear(ms); |
455 | int equivalentYear = equivalentYearForDST(year); |
456 | if (year != equivalentYear) { |
457 | bool leapYear = isLeapYear(year); |
458 | int dayInYearLocal = dayInYear(ms, year); |
459 | int dayInMonth = dayInMonthFromDayInYear(dayInYear: dayInYearLocal, leapYear); |
460 | int month = monthFromDayInYear(dayInYear: dayInYearLocal, leapYear); |
461 | double day = dateToDaysFrom1970(year: equivalentYear, month, day: dayInMonth); |
462 | ms = (day * msPerDay) + msToMilliseconds(ms); |
463 | } |
464 | |
465 | double localTimeSeconds = ms / msPerSecond; |
466 | if (localTimeSeconds > maxUnixTime) |
467 | localTimeSeconds = maxUnixTime; |
468 | else if (localTimeSeconds < 0) // Go ahead a day to make localtime work (does not work with 0). |
469 | localTimeSeconds += secondsPerDay; |
470 | // FIXME: time_t has a potential problem in 2038. |
471 | time_t localTime = static_cast<time_t>(localTimeSeconds); |
472 | |
473 | #if HAVE(TM_GMTOFF) |
474 | tm localTM; |
475 | getLocalTime(localTime: &localTime, localTM: &localTM); |
476 | return LocalTimeOffset(localTM.tm_isdst, localTM.tm_gmtoff * msPerSecond); |
477 | #else |
478 | double utcOffset = calculateUTCOffset(); |
479 | double dstOffset = calculateDSTOffset(localTime, utcOffset); |
480 | return LocalTimeOffset(dstOffset, utcOffset + dstOffset); |
481 | #endif |
482 | } |
483 | |
484 | void initializeDates() |
485 | { |
486 | #ifndef NDEBUG |
487 | static bool alreadyInitialized; |
488 | ASSERT(!alreadyInitialized); |
489 | alreadyInitialized = true; |
490 | #endif |
491 | |
492 | equivalentYearForDST(year: 2000); // Need to call once to initialize a static used in this function. |
493 | } |
494 | |
495 | static inline double ymdhmsToSeconds(long year, int mon, int day, int hour, int minute, int second) |
496 | { |
497 | double days = (day - 32075) |
498 | + floor(x: 1461 * (year + 4800.0 + (mon - 14) / 12) / 4) |
499 | + 367 * (mon - 2 - (mon - 14) / 12 * 12) / 12 |
500 | - floor(x: 3 * ((year + 4900.0 + (mon - 14) / 12) / 100) / 4) |
501 | - 2440588; |
502 | return ((days * hoursPerDay + hour) * minutesPerHour + minute) * secondsPerMinute + second; |
503 | } |
504 | |
505 | // We follow the recommendation of RFC 2822 to consider all |
506 | // obsolete time zones not listed here equivalent to "-0000". |
507 | static const struct KnownZone { |
508 | #if !OS(WINDOWS) |
509 | const |
510 | #endif |
511 | char tzName[4]; |
512 | int tzOffset; |
513 | } known_zones[] = { |
514 | { .tzName: "UT" , .tzOffset: 0 }, |
515 | { .tzName: "GMT" , .tzOffset: 0 }, |
516 | { .tzName: "EST" , .tzOffset: -300 }, |
517 | { .tzName: "EDT" , .tzOffset: -240 }, |
518 | { .tzName: "CST" , .tzOffset: -360 }, |
519 | { .tzName: "CDT" , .tzOffset: -300 }, |
520 | { .tzName: "MST" , .tzOffset: -420 }, |
521 | { .tzName: "MDT" , .tzOffset: -360 }, |
522 | { .tzName: "PST" , .tzOffset: -480 }, |
523 | { .tzName: "PDT" , .tzOffset: -420 } |
524 | }; |
525 | |
526 | inline static void skipSpacesAndComments(const char*& s) |
527 | { |
528 | int nesting = 0; |
529 | char ch; |
530 | while ((ch = *s)) { |
531 | if (!isASCIISpace(c: ch)) { |
532 | if (ch == '(') |
533 | nesting++; |
534 | else if (ch == ')' && nesting > 0) |
535 | nesting--; |
536 | else if (nesting == 0) |
537 | break; |
538 | } |
539 | s++; |
540 | } |
541 | } |
542 | |
543 | // returns 0-11 (Jan-Dec); -1 on failure |
544 | static int findMonth(const char* monthStr) |
545 | { |
546 | ASSERT(monthStr); |
547 | char needle[4]; |
548 | for (int i = 0; i < 3; ++i) { |
549 | if (!*monthStr) |
550 | return -1; |
551 | needle[i] = static_cast<char>(toASCIILower(c: *monthStr++)); |
552 | } |
553 | needle[3] = '\0'; |
554 | const char *haystack = "janfebmaraprmayjunjulaugsepoctnovdec" ; |
555 | const char *str = strstr(haystack: haystack, needle: needle); |
556 | if (str) { |
557 | int position = static_cast<int>(str - haystack); |
558 | if (position % 3 == 0) |
559 | return position / 3; |
560 | } |
561 | return -1; |
562 | } |
563 | |
564 | static bool parseLong(const char* string, char** stopPosition, int base, long* result) |
565 | { |
566 | *result = strtol(nptr: string, endptr: stopPosition, base: base); |
567 | // Avoid the use of errno as it is not available on Windows CE |
568 | if (string == *stopPosition || *result == LONG_MIN || *result == LONG_MAX) |
569 | return false; |
570 | return true; |
571 | } |
572 | |
573 | // Odd case where 'exec' is allowed to be 0, to accomodate a caller in WebCore. |
574 | static double parseDateFromNullTerminatedCharacters(const char* dateString, bool& haveTZ, int& offset) |
575 | { |
576 | haveTZ = false; |
577 | offset = 0; |
578 | |
579 | // This parses a date in the form: |
580 | // Tuesday, 09-Nov-99 23:12:40 GMT |
581 | // or |
582 | // Sat, 01-Jan-2000 08:00:00 GMT |
583 | // or |
584 | // Sat, 01 Jan 2000 08:00:00 GMT |
585 | // or |
586 | // 01 Jan 99 22:00 +0100 (exceptions in rfc822/rfc2822) |
587 | // ### non RFC formats, added for Javascript: |
588 | // [Wednesday] January 09 1999 23:12:40 GMT |
589 | // [Wednesday] January 09 23:12:40 GMT 1999 |
590 | // |
591 | // We ignore the weekday. |
592 | |
593 | // Skip leading space |
594 | skipSpacesAndComments(s&: dateString); |
595 | |
596 | long month = -1; |
597 | const char *wordStart = dateString; |
598 | // Check contents of first words if not number |
599 | while (*dateString && !isASCIIDigit(c: *dateString)) { |
600 | if (isASCIISpace(c: *dateString) || *dateString == '(') { |
601 | if (dateString - wordStart >= 3) |
602 | month = findMonth(monthStr: wordStart); |
603 | skipSpacesAndComments(s&: dateString); |
604 | wordStart = dateString; |
605 | } else |
606 | dateString++; |
607 | } |
608 | |
609 | // Missing delimiter between month and day (like "January29")? |
610 | if (month == -1 && wordStart != dateString) |
611 | month = findMonth(monthStr: wordStart); |
612 | |
613 | skipSpacesAndComments(s&: dateString); |
614 | |
615 | if (!*dateString) |
616 | return NaN; |
617 | |
618 | // ' 09-Nov-99 23:12:40 GMT' |
619 | char* newPosStr; |
620 | long day; |
621 | if (!parseLong(string: dateString, stopPosition: &newPosStr, base: 10, result: &day)) |
622 | return NaN; |
623 | dateString = newPosStr; |
624 | |
625 | if (!*dateString) |
626 | return NaN; |
627 | |
628 | if (day < 0) |
629 | return NaN; |
630 | |
631 | long year = 0; |
632 | if (day > 31) { |
633 | // ### where is the boundary and what happens below? |
634 | if (*dateString != '/') |
635 | return NaN; |
636 | // looks like a YYYY/MM/DD date |
637 | if (!*++dateString) |
638 | return NaN; |
639 | year = day; |
640 | if (!parseLong(string: dateString, stopPosition: &newPosStr, base: 10, result: &month)) |
641 | return NaN; |
642 | month -= 1; |
643 | dateString = newPosStr; |
644 | if (*dateString++ != '/' || !*dateString) |
645 | return NaN; |
646 | if (!parseLong(string: dateString, stopPosition: &newPosStr, base: 10, result: &day)) |
647 | return NaN; |
648 | dateString = newPosStr; |
649 | } else if (*dateString == '/' && month == -1) { |
650 | dateString++; |
651 | // This looks like a MM/DD/YYYY date, not an RFC date. |
652 | month = day - 1; // 0-based |
653 | if (!parseLong(string: dateString, stopPosition: &newPosStr, base: 10, result: &day)) |
654 | return NaN; |
655 | if (day < 1 || day > 31) |
656 | return NaN; |
657 | dateString = newPosStr; |
658 | if (*dateString == '/') |
659 | dateString++; |
660 | if (!*dateString) |
661 | return NaN; |
662 | } else { |
663 | if (*dateString == '-') |
664 | dateString++; |
665 | |
666 | skipSpacesAndComments(s&: dateString); |
667 | |
668 | if (*dateString == ',') |
669 | dateString++; |
670 | |
671 | if (month == -1) { // not found yet |
672 | month = findMonth(monthStr: dateString); |
673 | if (month == -1) |
674 | return NaN; |
675 | |
676 | while (*dateString && *dateString != '-' && *dateString != ',' && !isASCIISpace(c: *dateString)) |
677 | dateString++; |
678 | |
679 | if (!*dateString) |
680 | return NaN; |
681 | |
682 | // '-99 23:12:40 GMT' |
683 | if (*dateString != '-' && *dateString != '/' && *dateString != ',' && !isASCIISpace(c: *dateString)) |
684 | return NaN; |
685 | dateString++; |
686 | } |
687 | } |
688 | |
689 | if (month < 0 || month > 11) |
690 | return NaN; |
691 | |
692 | // '99 23:12:40 GMT' |
693 | if (year <= 0 && *dateString) { |
694 | if (!parseLong(string: dateString, stopPosition: &newPosStr, base: 10, result: &year)) |
695 | return NaN; |
696 | } |
697 | |
698 | // Don't fail if the time is missing. |
699 | long hour = 0; |
700 | long minute = 0; |
701 | long second = 0; |
702 | if (!*newPosStr) |
703 | dateString = newPosStr; |
704 | else { |
705 | // ' 23:12:40 GMT' |
706 | if (!(isASCIISpace(c: *newPosStr) || *newPosStr == ',')) { |
707 | if (*newPosStr != ':') |
708 | return NaN; |
709 | // There was no year; the number was the hour. |
710 | year = -1; |
711 | } else { |
712 | // in the normal case (we parsed the year), advance to the next number |
713 | dateString = ++newPosStr; |
714 | skipSpacesAndComments(s&: dateString); |
715 | } |
716 | |
717 | parseLong(string: dateString, stopPosition: &newPosStr, base: 10, result: &hour); |
718 | // Do not check for errno here since we want to continue |
719 | // even if errno was set becasue we are still looking |
720 | // for the timezone! |
721 | |
722 | // Read a number? If not, this might be a timezone name. |
723 | if (newPosStr != dateString) { |
724 | dateString = newPosStr; |
725 | |
726 | if (hour < 0 || hour > 23) |
727 | return NaN; |
728 | |
729 | if (!*dateString) |
730 | return NaN; |
731 | |
732 | // ':12:40 GMT' |
733 | if (*dateString++ != ':') |
734 | return NaN; |
735 | |
736 | if (!parseLong(string: dateString, stopPosition: &newPosStr, base: 10, result: &minute)) |
737 | return NaN; |
738 | dateString = newPosStr; |
739 | |
740 | if (minute < 0 || minute > 59) |
741 | return NaN; |
742 | |
743 | // ':40 GMT' |
744 | if (*dateString && *dateString != ':' && !isASCIISpace(c: *dateString)) |
745 | return NaN; |
746 | |
747 | // seconds are optional in rfc822 + rfc2822 |
748 | if (*dateString ==':') { |
749 | dateString++; |
750 | |
751 | if (!parseLong(string: dateString, stopPosition: &newPosStr, base: 10, result: &second)) |
752 | return NaN; |
753 | dateString = newPosStr; |
754 | |
755 | if (second < 0 || second > 59) |
756 | return NaN; |
757 | } |
758 | |
759 | skipSpacesAndComments(s&: dateString); |
760 | |
761 | if (strncasecmp(s1: dateString, s2: "AM" , n: 2) == 0) { |
762 | if (hour > 12) |
763 | return NaN; |
764 | if (hour == 12) |
765 | hour = 0; |
766 | dateString += 2; |
767 | skipSpacesAndComments(s&: dateString); |
768 | } else if (strncasecmp(s1: dateString, s2: "PM" , n: 2) == 0) { |
769 | if (hour > 12) |
770 | return NaN; |
771 | if (hour != 12) |
772 | hour += 12; |
773 | dateString += 2; |
774 | skipSpacesAndComments(s&: dateString); |
775 | } |
776 | } |
777 | } |
778 | |
779 | // Don't fail if the time zone is missing. |
780 | // Some websites omit the time zone (4275206). |
781 | if (*dateString) { |
782 | if (strncasecmp(s1: dateString, s2: "GMT" , n: 3) == 0 || strncasecmp(s1: dateString, s2: "UTC" , n: 3) == 0) { |
783 | dateString += 3; |
784 | haveTZ = true; |
785 | } |
786 | |
787 | if (*dateString == '+' || *dateString == '-') { |
788 | long o; |
789 | if (!parseLong(string: dateString, stopPosition: &newPosStr, base: 10, result: &o)) |
790 | return NaN; |
791 | dateString = newPosStr; |
792 | |
793 | if (o < -9959 || o > 9959) |
794 | return NaN; |
795 | |
796 | int sgn = (o < 0) ? -1 : 1; |
797 | o = labs(x: o); |
798 | if (*dateString != ':') { |
799 | offset = ((o / 100) * 60 + (o % 100)) * sgn; |
800 | } else { // GMT+05:00 |
801 | long o2; |
802 | if (!parseLong(string: dateString, stopPosition: &newPosStr, base: 10, result: &o2)) |
803 | return NaN; |
804 | dateString = newPosStr; |
805 | offset = (o * 60 + o2) * sgn; |
806 | } |
807 | haveTZ = true; |
808 | } else { |
809 | for (int i = 0; i < int(sizeof(known_zones) / sizeof(KnownZone)); i++) { |
810 | if (0 == strncasecmp(s1: dateString, s2: known_zones[i].tzName, n: strlen(s: known_zones[i].tzName))) { |
811 | offset = known_zones[i].tzOffset; |
812 | dateString += strlen(s: known_zones[i].tzName); |
813 | haveTZ = true; |
814 | break; |
815 | } |
816 | } |
817 | } |
818 | } |
819 | |
820 | skipSpacesAndComments(s&: dateString); |
821 | |
822 | if (*dateString && year == -1) { |
823 | if (!parseLong(string: dateString, stopPosition: &newPosStr, base: 10, result: &year)) |
824 | return NaN; |
825 | dateString = newPosStr; |
826 | } |
827 | |
828 | skipSpacesAndComments(s&: dateString); |
829 | |
830 | // Trailing garbage |
831 | if (*dateString) |
832 | return NaN; |
833 | |
834 | // Y2K: Handle 2 digit years. |
835 | if (year >= 0 && year < 100) { |
836 | if (year < 50) |
837 | year += 2000; |
838 | else |
839 | year += 1900; |
840 | } |
841 | |
842 | return ymdhmsToSeconds(year, mon: month + 1, day, hour, minute, second) * msPerSecond; |
843 | } |
844 | |
845 | double parseDateFromNullTerminatedCharacters(const char* dateString) |
846 | { |
847 | bool haveTZ; |
848 | int offset; |
849 | double ms = parseDateFromNullTerminatedCharacters(dateString, haveTZ, offset); |
850 | if (std::isnan(x: ms)) |
851 | return NaN; |
852 | |
853 | // fall back to local timezone |
854 | if (!haveTZ) |
855 | offset = calculateLocalTimeOffset(ms).offset / msPerMinute; |
856 | |
857 | return ms - (offset * msPerMinute); |
858 | } |
859 | |
860 | double timeClip(double t) |
861 | { |
862 | if (!std::isfinite(x: t)) |
863 | return NaN; |
864 | if (fabs(x: t) > maxECMAScriptTime) |
865 | return NaN; |
866 | return trunc(x: t); |
867 | } |
868 | } // namespace WTF |
869 | |
870 | #if USE(JSC) |
871 | namespace JSC { |
872 | |
873 | // Get the combined UTC + DST offset for the time passed in. |
874 | // |
875 | // NOTE: The implementation relies on the fact that no time zones have |
876 | // more than one daylight savings offset change per month. |
877 | // If this function is called with NaN it returns NaN. |
878 | static LocalTimeOffset localTimeOffset(ExecState* exec, double ms) |
879 | { |
880 | LocalTimeOffsetCache& cache = exec->globalData().localTimeOffsetCache; |
881 | double start = cache.start; |
882 | double end = cache.end; |
883 | |
884 | if (start <= ms) { |
885 | // If the time fits in the cached interval, return the cached offset. |
886 | if (ms <= end) return cache.offset; |
887 | |
888 | // Compute a possible new interval end. |
889 | double newEnd = end + cache.increment; |
890 | |
891 | if (ms <= newEnd) { |
892 | LocalTimeOffset endOffset = calculateLocalTimeOffset(ms: newEnd); |
893 | if (cache.offset == endOffset) { |
894 | // If the offset at the end of the new interval still matches |
895 | // the offset in the cache, we grow the cached time interval |
896 | // and return the offset. |
897 | cache.end = newEnd; |
898 | cache.increment = msPerMonth; |
899 | return endOffset; |
900 | } |
901 | LocalTimeOffset offset = calculateLocalTimeOffset(ms); |
902 | if (offset == endOffset) { |
903 | // The offset at the given time is equal to the offset at the |
904 | // new end of the interval, so that means that we've just skipped |
905 | // the point in time where the DST offset change occurred. Updated |
906 | // the interval to reflect this and reset the increment. |
907 | cache.start = ms; |
908 | cache.end = newEnd; |
909 | cache.increment = msPerMonth; |
910 | } else { |
911 | // The interval contains a DST offset change and the given time is |
912 | // before it. Adjust the increment to avoid a linear search for |
913 | // the offset change point and change the end of the interval. |
914 | cache.increment /= 3; |
915 | cache.end = ms; |
916 | } |
917 | // Update the offset in the cache and return it. |
918 | cache.offset = offset; |
919 | return offset; |
920 | } |
921 | } |
922 | |
923 | // Compute the DST offset for the time and shrink the cache interval |
924 | // to only contain the time. This allows fast repeated DST offset |
925 | // computations for the same time. |
926 | LocalTimeOffset offset = calculateLocalTimeOffset(ms); |
927 | cache.offset = offset; |
928 | cache.start = ms; |
929 | cache.end = ms; |
930 | cache.increment = msPerMonth; |
931 | return offset; |
932 | } |
933 | |
934 | double gregorianDateTimeToMS(ExecState* exec, const GregorianDateTime& t, double milliSeconds, bool inputIsUTC) |
935 | { |
936 | double day = dateToDaysFrom1970(year: t.year + 1900, month: t.month, day: t.monthDay); |
937 | double ms = timeToMS(hour: t.hour, min: t.minute, sec: t.second, ms: milliSeconds); |
938 | double result = (day * WTF::msPerDay) + ms; |
939 | |
940 | if (!inputIsUTC) |
941 | result -= localTimeOffset(exec, ms: result).offset; |
942 | |
943 | return result; |
944 | } |
945 | |
946 | // input is UTC |
947 | void msToGregorianDateTime(ExecState* exec, double ms, bool outputIsUTC, GregorianDateTime& tm) |
948 | { |
949 | LocalTimeOffset localTime(false, 0); |
950 | if (!outputIsUTC) { |
951 | localTime = localTimeOffset(exec, ms); |
952 | ms += localTime.offset; |
953 | } |
954 | |
955 | const int year = msToYear(ms); |
956 | tm.second = msToSeconds(ms); |
957 | tm.minute = msToMinutes(ms); |
958 | tm.hour = msToHours(ms); |
959 | tm.weekDay = msToWeekDay(ms); |
960 | tm.yearDay = dayInYear(ms, year); |
961 | tm.monthDay = dayInMonthFromDayInYear(dayInYear: tm.yearDay, leapYear: isLeapYear(year)); |
962 | tm.month = monthFromDayInYear(dayInYear: tm.yearDay, leapYear: isLeapYear(year)); |
963 | tm.year = year - 1900; |
964 | tm.isDST = localTime.isDST; |
965 | tm.utcOffset = localTime.offset / WTF::msPerSecond; |
966 | tm.timeZone = NULL; |
967 | } |
968 | |
969 | double parseDateFromNullTerminatedCharacters(ExecState* exec, const char* dateString) |
970 | { |
971 | ASSERT(exec); |
972 | bool haveTZ; |
973 | int offset; |
974 | double ms = WTF::parseDateFromNullTerminatedCharacters(dateString, haveTZ, offset); |
975 | if (std::isnan(x: ms)) |
976 | return NaN; |
977 | |
978 | // fall back to local timezone |
979 | if (!haveTZ) |
980 | offset = calculateLocalTimeOffset(ms).offset / msPerMinute; |
981 | |
982 | return ms - (offset * WTF::msPerMinute); |
983 | } |
984 | |
985 | } // namespace JSC |
986 | #endif // USE(JSC) |
987 | |