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.
7 * The Original Code is Mozilla Communicator client code, released
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.
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.
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.
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
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.
47 #include "Assertions.h"
48 #include "ASCIICType.h"
49 #include "CurrentTime.h"
50 #include "MathExtras.h"
51 #include "StringExtras.h"
73 #include <sys/timeb.h>
76 #define NaN std::numeric_limits<double>::quiet_NaN()
82 static const double minutesPerDay
= 24.0 * 60.0;
83 static const double secondsPerDay
= 24.0 * 60.0 * 60.0;
84 static const double secondsPerYear
= 24.0 * 60.0 * 60.0 * 365.0;
86 static const double usecPerSec
= 1000000.0;
88 static const double maxUnixTime
= 2145859200.0; // 12/31/2037
90 // Day of year for the first day of each month, where index 0 is January, and day 0 is January 1.
91 // First for non-leap years, then for leap years.
92 static const int firstDayOfMonth
[2][12] = {
93 {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
94 {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}
97 static inline bool isLeapYear(int year
)
108 static inline int daysInYear(int year
)
110 return 365 + isLeapYear(year
);
113 static inline double daysFrom1970ToYear(int year
)
115 // The Gregorian Calendar rules for leap years:
116 // Every fourth year is a leap year. 2004, 2008, and 2012 are leap years.
117 // However, every hundredth year is not a leap year. 1900 and 2100 are not leap years.
118 // Every four hundred years, there's a leap year after all. 2000 and 2400 are leap years.
120 static const int leapDaysBefore1971By4Rule
= 1970 / 4;
121 static const int excludedLeapDaysBefore1971By100Rule
= 1970 / 100;
122 static const int leapDaysBefore1971By400Rule
= 1970 / 400;
124 const double yearMinusOne
= year
- 1;
125 const double yearsToAddBy4Rule
= floor(yearMinusOne
/ 4.0) - leapDaysBefore1971By4Rule
;
126 const double yearsToExcludeBy100Rule
= floor(yearMinusOne
/ 100.0) - excludedLeapDaysBefore1971By100Rule
;
127 const double yearsToAddBy400Rule
= floor(yearMinusOne
/ 400.0) - leapDaysBefore1971By400Rule
;
129 return 365.0 * (year
- 1970) + yearsToAddBy4Rule
- yearsToExcludeBy100Rule
+ yearsToAddBy400Rule
;
132 static inline double msToDays(double ms
)
134 return floor(ms
/ msPerDay
);
137 static inline int msToYear(double ms
)
139 int approxYear
= static_cast<int>(floor(ms
/ (msPerDay
* 365.2425)) + 1970);
140 double msFromApproxYearTo1970
= msPerDay
* daysFrom1970ToYear(approxYear
);
141 if (msFromApproxYearTo1970
> ms
)
142 return approxYear
- 1;
143 if (msFromApproxYearTo1970
+ msPerDay
* daysInYear(approxYear
) <= ms
)
144 return approxYear
+ 1;
148 static inline int dayInYear(double ms
, int year
)
150 return static_cast<int>(msToDays(ms
) - daysFrom1970ToYear(year
));
153 static inline double msToMilliseconds(double ms
)
155 double result
= fmod(ms
, msPerDay
);
161 // 0: Sunday, 1: Monday, etc.
162 static inline int msToWeekDay(double ms
)
164 int wd
= (static_cast<int>(msToDays(ms
)) + 4) % 7;
170 static inline int msToSeconds(double ms
)
172 double result
= fmod(floor(ms
/ msPerSecond
), secondsPerMinute
);
174 result
+= secondsPerMinute
;
175 return static_cast<int>(result
);
178 static inline int msToMinutes(double ms
)
180 double result
= fmod(floor(ms
/ msPerMinute
), minutesPerHour
);
182 result
+= minutesPerHour
;
183 return static_cast<int>(result
);
186 static inline int msToHours(double ms
)
188 double result
= fmod(floor(ms
/msPerHour
), hoursPerDay
);
190 result
+= hoursPerDay
;
191 return static_cast<int>(result
);
194 static inline int monthFromDayInYear(int dayInYear
, bool leapYear
)
196 const int d
= dayInYear
;
201 step
+= (leapYear
? 29 : 28);
204 if (d
< (step
+= 31))
206 if (d
< (step
+= 30))
208 if (d
< (step
+= 31))
210 if (d
< (step
+= 30))
212 if (d
< (step
+= 31))
214 if (d
< (step
+= 31))
216 if (d
< (step
+= 30))
218 if (d
< (step
+= 31))
220 if (d
< (step
+= 30))
225 static inline bool checkMonth(int dayInYear
, int& startDayOfThisMonth
, int& startDayOfNextMonth
, int daysInThisMonth
)
227 startDayOfThisMonth
= startDayOfNextMonth
;
228 startDayOfNextMonth
+= daysInThisMonth
;
229 return (dayInYear
<= startDayOfNextMonth
);
232 static inline int dayInMonthFromDayInYear(int dayInYear
, bool leapYear
)
234 const int d
= dayInYear
;
240 const int daysInFeb
= (leapYear
? 29 : 28);
241 if (checkMonth(d
, step
, next
, daysInFeb
))
243 if (checkMonth(d
, step
, next
, 31))
245 if (checkMonth(d
, step
, next
, 30))
247 if (checkMonth(d
, step
, next
, 31))
249 if (checkMonth(d
, step
, next
, 30))
251 if (checkMonth(d
, step
, next
, 31))
253 if (checkMonth(d
, step
, next
, 31))
255 if (checkMonth(d
, step
, next
, 30))
257 if (checkMonth(d
, step
, next
, 31))
259 if (checkMonth(d
, step
, next
, 30))
265 static inline int monthToDayInYear(int month
, bool isLeapYear
)
267 return firstDayOfMonth
[isLeapYear
][month
];
270 static inline double timeToMS(double hour
, double min
, double sec
, double ms
)
272 return (((hour
* minutesPerHour
+ min
) * secondsPerMinute
+ sec
) * msPerSecond
+ ms
);
275 static int dateToDayInYear(int year
, int month
, int day
)
285 int yearday
= static_cast<int>(floor(daysFrom1970ToYear(year
)));
286 int monthday
= monthToDayInYear(month
, isLeapYear(year
));
288 return yearday
+ monthday
+ day
- 1;
291 double getCurrentUTCTime()
293 return floor(getCurrentUTCTimeWithMicroseconds());
296 // Returns current time in milliseconds since 1 Jan 1970.
297 double getCurrentUTCTimeWithMicroseconds()
299 return currentTime() * 1000.0;
302 void getLocalTime(const time_t* localTime
, struct tm
* localTM
)
304 #if COMPILER(MSVC7) || COMPILER(MINGW) || PLATFORM(WINCE)
305 *localTM
= *localtime(localTime
);
307 localtime_s(localTM
, localTime
);
309 localtime_r(localTime
, localTM
);
313 // There is a hard limit at 2038 that we currently do not have a workaround
314 // for (rdar://problem/5052975).
315 static inline int maximumYearForDST()
320 static inline int minimumYearForDST()
322 // Because of the 2038 issue (see maximumYearForDST) if the current year is
323 // greater than the max year minus 27 (2010), we want to use the max year
324 // minus 27 instead, to ensure there is a range of 28 years that all years
326 return std::min(msToYear(getCurrentUTCTime()), maximumYearForDST() - 27) ;
330 * Find an equivalent year for the one given, where equivalence is deterined by
331 * the two years having the same leapness and the first day of the year, falling
332 * on the same day of the week.
334 * This function returns a year between this current year and 2037, however this
335 * function will potentially return incorrect results if the current year is after
336 * 2010, (rdar://problem/5052975), if the year passed in is before 1900 or after
337 * 2100, (rdar://problem/5055038).
339 int equivalentYearForDST(int year
)
341 // It is ok if the cached year is not the current year as long as the rules
342 // for DST did not change between the two years; if they did the app would need
344 static int minYear
= minimumYearForDST();
345 int maxYear
= maximumYearForDST();
349 difference
= minYear
- year
;
350 else if (year
< minYear
)
351 difference
= maxYear
- year
;
355 int quotient
= difference
/ 28;
356 int product
= (quotient
) * 28;
359 ASSERT((year
>= minYear
&& year
<= maxYear
) || (product
- year
== static_cast<int>(NaN
)));
363 static int32_t calculateUTCOffset()
365 time_t localTime
= time(0);
367 getLocalTime(&localTime
, &localt
);
369 // Get the difference between this time zone and UTC on the 1st of January of this year.
375 // Not setting localt.tm_year!
379 #if PLATFORM(WIN_OS) || PLATFORM(SOLARIS) || COMPILER(RVCT)
380 // Using a canned date of 01/01/2009 on platforms with weaker date-handling foo.
381 localt
.tm_year
= 109;
382 time_t utcOffset
= 1230768000 - mktime(&localt
);
385 localt
.tm_gmtoff
= 0;
386 time_t utcOffset
= timegm(&localt
) - mktime(&localt
);
389 return static_cast<int32_t>(utcOffset
* 1000);
393 static int32_t s_cachedUTCOffset
; // In milliseconds. An assumption here is that access to an int32_t variable is atomic on platforms that take this code path.
394 static bool s_haveCachedUTCOffset
;
395 static int s_notificationToken
;
399 * Get the difference in milliseconds between this time zone and UTC (GMT)
402 double getUTCOffset()
405 if (s_haveCachedUTCOffset
) {
407 uint32_t status
= notify_check(s_notificationToken
, ¬ified
);
408 if (status
== NOTIFY_STATUS_OK
&& !notified
)
409 return s_cachedUTCOffset
;
413 int32_t utcOffset
= calculateUTCOffset();
416 // Theoretically, it is possible that several threads will be executing this code at once, in which case we will have a race condition,
417 // and a newer value may be overwritten. In practice, time zones don't change that often.
418 s_cachedUTCOffset
= utcOffset
;
425 * Get the DST offset for the time passed in. Takes
426 * seconds (not milliseconds) and cannot handle dates before 1970
429 static double getDSTOffsetSimple(double localTimeSeconds
, double utcOffset
)
431 if (localTimeSeconds
> maxUnixTime
)
432 localTimeSeconds
= maxUnixTime
;
433 else if (localTimeSeconds
< 0) // Go ahead a day to make localtime work (does not work with 0)
434 localTimeSeconds
+= secondsPerDay
;
436 //input is UTC so we have to shift back to local time to determine DST thus the + getUTCOffset()
437 double offsetTime
= (localTimeSeconds
* msPerSecond
) + utcOffset
;
439 // Offset from UTC but doesn't include DST obviously
440 int offsetHour
= msToHours(offsetTime
);
441 int offsetMinute
= msToMinutes(offsetTime
);
443 // FIXME: time_t has a potential problem in 2038
444 time_t localTime
= static_cast<time_t>(localTimeSeconds
);
447 getLocalTime(&localTime
, &localTM
);
449 double diff
= ((localTM
.tm_hour
- offsetHour
) * secondsPerHour
) + ((localTM
.tm_min
- offsetMinute
) * 60);
452 diff
+= secondsPerDay
;
454 return (diff
* msPerSecond
);
457 // Get the DST offset, given a time in UTC
458 static double getDSTOffset(double ms
, double utcOffset
)
460 // On Mac OS X, the call to localtime (see getDSTOffsetSimple) will return historically accurate
461 // DST information (e.g. New Zealand did not have DST from 1946 to 1974) however the JavaScript
462 // standard explicitly dictates that historical information should not be considered when
463 // determining DST. For this reason we shift away from years that localtime can handle but would
464 // return historically accurate information.
465 int year
= msToYear(ms
);
466 int equivalentYear
= equivalentYearForDST(year
);
467 if (year
!= equivalentYear
) {
468 bool leapYear
= isLeapYear(year
);
469 int dayInYearLocal
= dayInYear(ms
, year
);
470 int dayInMonth
= dayInMonthFromDayInYear(dayInYearLocal
, leapYear
);
471 int month
= monthFromDayInYear(dayInYearLocal
, leapYear
);
472 int day
= dateToDayInYear(equivalentYear
, month
, dayInMonth
);
473 ms
= (day
* msPerDay
) + msToMilliseconds(ms
);
476 return getDSTOffsetSimple(ms
/ msPerSecond
, utcOffset
);
479 double gregorianDateTimeToMS(const GregorianDateTime
& t
, double milliSeconds
, bool inputIsUTC
)
481 int day
= dateToDayInYear(t
.year
+ 1900, t
.month
, t
.monthDay
);
482 double ms
= timeToMS(t
.hour
, t
.minute
, t
.second
, milliSeconds
);
483 double result
= (day
* msPerDay
) + ms
;
485 if (!inputIsUTC
) { // convert to UTC
486 double utcOffset
= getUTCOffset();
488 result
-= getDSTOffset(result
, utcOffset
);
494 void msToGregorianDateTime(double ms
, bool outputIsUTC
, GregorianDateTime
& tm
)
498 const double utcOff
= getUTCOffset();
500 if (!outputIsUTC
) { // convert to local time
501 dstOff
= getDSTOffset(ms
, utcOff
);
502 ms
+= dstOff
+ utcOff
;
505 const int year
= msToYear(ms
);
506 tm
.second
= msToSeconds(ms
);
507 tm
.minute
= msToMinutes(ms
);
508 tm
.hour
= msToHours(ms
);
509 tm
.weekDay
= msToWeekDay(ms
);
510 tm
.yearDay
= dayInYear(ms
, year
);
511 tm
.monthDay
= dayInMonthFromDayInYear(tm
.yearDay
, isLeapYear(year
));
512 tm
.month
= monthFromDayInYear(tm
.yearDay
, isLeapYear(year
));
513 tm
.year
= year
- 1900;
514 tm
.isDST
= dstOff
!= 0.0;
516 tm
.utcOffset
= outputIsUTC
? 0 : static_cast<long>((dstOff
+ utcOff
) / msPerSecond
);
520 void initializeDates()
523 static bool alreadyInitialized
;
524 ASSERT(!alreadyInitialized
++);
527 equivalentYearForDST(2000); // Need to call once to initialize a static used in this function.
529 // Register for a notification whenever the time zone changes.
530 uint32_t status
= notify_register_check("com.apple.system.timezone", &s_notificationToken
);
531 if (status
== NOTIFY_STATUS_OK
) {
532 s_cachedUTCOffset
= calculateUTCOffset();
533 s_haveCachedUTCOffset
= true;
538 static inline double ymdhmsToSeconds(long year
, int mon
, int day
, int hour
, int minute
, int second
)
540 double days
= (day
- 32075)
541 + floor(1461 * (year
+ 4800.0 + (mon
- 14) / 12) / 4)
542 + 367 * (mon
- 2 - (mon
- 14) / 12 * 12) / 12
543 - floor(3 * ((year
+ 4900.0 + (mon
- 14) / 12) / 100) / 4)
545 return ((days
* hoursPerDay
+ hour
) * minutesPerHour
+ minute
) * secondsPerMinute
+ second
;
548 // We follow the recommendation of RFC 2822 to consider all
549 // obsolete time zones not listed here equivalent to "-0000".
550 static const struct KnownZone
{
551 #if !PLATFORM(WIN_OS)
569 inline static void skipSpacesAndComments(const char*& s
)
574 if (!isASCIISpace(ch
)) {
577 else if (ch
== ')' && nesting
> 0)
579 else if (nesting
== 0)
586 // returns 0-11 (Jan-Dec); -1 on failure
587 static int findMonth(const char* monthStr
)
591 for (int i
= 0; i
< 3; ++i
) {
594 needle
[i
] = static_cast<char>(toASCIILower(*monthStr
++));
597 const char *haystack
= "janfebmaraprmayjunjulaugsepoctnovdec";
598 const char *str
= strstr(haystack
, needle
);
600 int position
= static_cast<int>(str
- haystack
);
601 if (position
% 3 == 0)
607 static bool parseLong(const char* string
, char** stopPosition
, int base
, long* result
)
609 *result
= strtol(string
, stopPosition
, base
);
610 // Avoid the use of errno as it is not available on Windows CE
611 if (string
== *stopPosition
|| *result
== LONG_MIN
|| *result
== LONG_MAX
)
616 double parseDateFromNullTerminatedCharacters(const char* dateString
)
618 // This parses a date in the form:
619 // Tuesday, 09-Nov-99 23:12:40 GMT
621 // Sat, 01-Jan-2000 08:00:00 GMT
623 // Sat, 01 Jan 2000 08:00:00 GMT
625 // 01 Jan 99 22:00 +0100 (exceptions in rfc822/rfc2822)
626 // ### non RFC formats, added for Javascript:
627 // [Wednesday] January 09 1999 23:12:40 GMT
628 // [Wednesday] January 09 23:12:40 GMT 1999
630 // We ignore the weekday.
632 // Skip leading space
633 skipSpacesAndComments(dateString
);
636 const char *wordStart
= dateString
;
637 // Check contents of first words if not number
638 while (*dateString
&& !isASCIIDigit(*dateString
)) {
639 if (isASCIISpace(*dateString
) || *dateString
== '(') {
640 if (dateString
- wordStart
>= 3)
641 month
= findMonth(wordStart
);
642 skipSpacesAndComments(dateString
);
643 wordStart
= dateString
;
648 // Missing delimiter between month and day (like "January29")?
649 if (month
== -1 && wordStart
!= dateString
)
650 month
= findMonth(wordStart
);
652 skipSpacesAndComments(dateString
);
657 // ' 09-Nov-99 23:12:40 GMT'
660 if (!parseLong(dateString
, &newPosStr
, 10, &day
))
662 dateString
= newPosStr
;
672 // ### where is the boundary and what happens below?
673 if (*dateString
!= '/')
675 // looks like a YYYY/MM/DD date
679 if (!parseLong(dateString
, &newPosStr
, 10, &month
))
682 dateString
= newPosStr
;
683 if (*dateString
++ != '/' || !*dateString
)
685 if (!parseLong(dateString
, &newPosStr
, 10, &day
))
687 dateString
= newPosStr
;
688 } else if (*dateString
== '/' && month
== -1) {
690 // This looks like a MM/DD/YYYY date, not an RFC date.
691 month
= day
- 1; // 0-based
692 if (!parseLong(dateString
, &newPosStr
, 10, &day
))
694 if (day
< 1 || day
> 31)
696 dateString
= newPosStr
;
697 if (*dateString
== '/')
702 if (*dateString
== '-')
705 skipSpacesAndComments(dateString
);
707 if (*dateString
== ',')
710 if (month
== -1) { // not found yet
711 month
= findMonth(dateString
);
715 while (*dateString
&& *dateString
!= '-' && *dateString
!= ',' && !isASCIISpace(*dateString
))
721 // '-99 23:12:40 GMT'
722 if (*dateString
!= '-' && *dateString
!= '/' && *dateString
!= ',' && !isASCIISpace(*dateString
))
728 if (month
< 0 || month
> 11)
732 if (year
<= 0 && *dateString
) {
733 if (!parseLong(dateString
, &newPosStr
, 10, &year
))
737 // Don't fail if the time is missing.
742 dateString
= newPosStr
;
745 if (!(isASCIISpace(*newPosStr
) || *newPosStr
== ',')) {
746 if (*newPosStr
!= ':')
748 // There was no year; the number was the hour.
751 // in the normal case (we parsed the year), advance to the next number
752 dateString
= ++newPosStr
;
753 skipSpacesAndComments(dateString
);
756 parseLong(dateString
, &newPosStr
, 10, &hour
);
757 // Do not check for errno here since we want to continue
758 // even if errno was set becasue we are still looking
761 // Read a number? If not, this might be a timezone name.
762 if (newPosStr
!= dateString
) {
763 dateString
= newPosStr
;
765 if (hour
< 0 || hour
> 23)
772 if (*dateString
++ != ':')
775 if (!parseLong(dateString
, &newPosStr
, 10, &minute
))
777 dateString
= newPosStr
;
779 if (minute
< 0 || minute
> 59)
783 if (*dateString
&& *dateString
!= ':' && !isASCIISpace(*dateString
))
786 // seconds are optional in rfc822 + rfc2822
787 if (*dateString
==':') {
790 if (!parseLong(dateString
, &newPosStr
, 10, &second
))
792 dateString
= newPosStr
;
794 if (second
< 0 || second
> 59)
798 skipSpacesAndComments(dateString
);
800 if (strncasecmp(dateString
, "AM", 2) == 0) {
806 skipSpacesAndComments(dateString
);
807 } else if (strncasecmp(dateString
, "PM", 2) == 0) {
813 skipSpacesAndComments(dateString
);
821 // Don't fail if the time zone is missing.
822 // Some websites omit the time zone (4275206).
824 if (strncasecmp(dateString
, "GMT", 3) == 0 || strncasecmp(dateString
, "UTC", 3) == 0) {
829 if (*dateString
== '+' || *dateString
== '-') {
831 if (!parseLong(dateString
, &newPosStr
, 10, &o
))
833 dateString
= newPosStr
;
835 if (o
< -9959 || o
> 9959)
838 int sgn
= (o
< 0) ? -1 : 1;
840 if (*dateString
!= ':') {
841 offset
= ((o
/ 100) * 60 + (o
% 100)) * sgn
;
842 } else { // GMT+05:00
844 if (!parseLong(dateString
, &newPosStr
, 10, &o2
))
846 dateString
= newPosStr
;
847 offset
= (o
* 60 + o2
) * sgn
;
851 for (int i
= 0; i
< int(sizeof(known_zones
) / sizeof(KnownZone
)); i
++) {
852 if (0 == strncasecmp(dateString
, known_zones
[i
].tzName
, strlen(known_zones
[i
].tzName
))) {
853 offset
= known_zones
[i
].tzOffset
;
854 dateString
+= strlen(known_zones
[i
].tzName
);
862 skipSpacesAndComments(dateString
);
864 if (*dateString
&& year
== -1) {
865 if (!parseLong(dateString
, &newPosStr
, 10, &year
))
867 dateString
= newPosStr
;
870 skipSpacesAndComments(dateString
);
876 // Y2K: Handle 2 digit years.
877 if (year
>= 0 && year
< 100) {
884 // fall back to local timezone
889 t
.year
= year
- 1900;
895 // Use our gregorianDateTimeToMS() rather than mktime() as the latter can't handle the full year range.
896 return gregorianDateTimeToMS(t
, 0, false);
899 return (ymdhmsToSeconds(year
, month
+ 1, day
, hour
, minute
, second
) - (offset
* 60.0)) * msPerSecond
;
902 double timeClip(double t
)
906 if (fabs(t
) > 8.64E15
)