2 * Copyright (C) 1999-2000 Harri Porten (porten@kde.org)
3 * Copyright (C) 2006, 2007 Apple Inc. All rights reserved.
5 * The Original Code is Mozilla Communicator client code, released
8 * The Initial Developer of the Original Code is
9 * Netscape Communications Corporation.
10 * Portions created by the Initial Developer are Copyright (C) 1998
11 * the Initial Developer. All Rights Reserved.
13 * This library is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU Lesser General Public
15 * License as published by the Free Software Foundation; either
16 * version 2.1 of the License, or (at your option) any later version.
18 * This library is distributed in the hope that it will be useful,
19 * but WITHOUT ANY WARRANTY; without even the implied warranty of
20 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 * Lesser General Public License for more details.
23 * You should have received a copy of the GNU Lesser General Public
24 * License along with this library; if not, write to the Free Software
25 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
27 * Alternatively, the contents of this file may be used under the terms
28 * of either the Mozilla Public License Version 1.1, found at
29 * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public
30 * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html
31 * (the "GPL"), in which case the provisions of the MPL or the GPL are
32 * applicable instead of those above. If you wish to allow use of your
33 * version of this file only under the terms of one of those two
34 * licenses (the MPL or the GPL) and not to allow others to use your
35 * version of this file under the LGPL, indicate your decision by
36 * deletingthe provisions above and replace them with the notice and
37 * other provisions required by the MPL or the GPL, as the case may be.
38 * If you do not delete the provisions above, a recipient may use your
39 * version of this file under any of the LGPL, the MPL or the GPL.
45 #include "JSNumberCell.h"
49 #include <wtf/ASCIICType.h>
50 #include <wtf/Assertions.h>
51 #include <wtf/CurrentTime.h>
52 #include <wtf/MathExtras.h>
53 #include <wtf/StringExtras.h>
68 #include <sys/timeb.h>
81 static const double minutesPerDay
= 24.0 * 60.0;
82 static const double secondsPerDay
= 24.0 * 60.0 * 60.0;
83 static const double secondsPerYear
= 24.0 * 60.0 * 60.0 * 365.0;
85 static const double usecPerSec
= 1000000.0;
87 static const double maxUnixTime
= 2145859200.0; // 12/31/2037
89 // Day of year for the first day of each month, where index 0 is January, and day 0 is January 1.
90 // First for non-leap years, then for leap years.
91 static const int firstDayOfMonth
[2][12] = {
92 {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334},
93 {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}
96 static inline bool isLeapYear(int year
)
107 static inline int daysInYear(int year
)
109 return 365 + isLeapYear(year
);
112 static inline double daysFrom1970ToYear(int year
)
114 // The Gregorian Calendar rules for leap years:
115 // Every fourth year is a leap year. 2004, 2008, and 2012 are leap years.
116 // However, every hundredth year is not a leap year. 1900 and 2100 are not leap years.
117 // Every four hundred years, there's a leap year after all. 2000 and 2400 are leap years.
119 static const int leapDaysBefore1971By4Rule
= 1970 / 4;
120 static const int excludedLeapDaysBefore1971By100Rule
= 1970 / 100;
121 static const int leapDaysBefore1971By400Rule
= 1970 / 400;
123 const double yearMinusOne
= year
- 1;
124 const double yearsToAddBy4Rule
= floor(yearMinusOne
/ 4.0) - leapDaysBefore1971By4Rule
;
125 const double yearsToExcludeBy100Rule
= floor(yearMinusOne
/ 100.0) - excludedLeapDaysBefore1971By100Rule
;
126 const double yearsToAddBy400Rule
= floor(yearMinusOne
/ 400.0) - leapDaysBefore1971By400Rule
;
128 return 365.0 * (year
- 1970) + yearsToAddBy4Rule
- yearsToExcludeBy100Rule
+ yearsToAddBy400Rule
;
131 static inline double msToDays(double ms
)
133 return floor(ms
/ msPerDay
);
136 static inline int msToYear(double ms
)
138 int approxYear
= static_cast<int>(floor(ms
/ (msPerDay
* 365.2425)) + 1970);
139 double msFromApproxYearTo1970
= msPerDay
* daysFrom1970ToYear(approxYear
);
140 if (msFromApproxYearTo1970
> ms
)
141 return approxYear
- 1;
142 if (msFromApproxYearTo1970
+ msPerDay
* daysInYear(approxYear
) <= ms
)
143 return approxYear
+ 1;
147 static inline int dayInYear(double ms
, int year
)
149 return static_cast<int>(msToDays(ms
) - daysFrom1970ToYear(year
));
152 static inline double msToMilliseconds(double ms
)
154 double result
= fmod(ms
, msPerDay
);
160 // 0: Sunday, 1: Monday, etc.
161 static inline int msToWeekDay(double ms
)
163 int wd
= (static_cast<int>(msToDays(ms
)) + 4) % 7;
169 static inline int msToSeconds(double ms
)
171 double result
= fmod(floor(ms
/ msPerSecond
), secondsPerMinute
);
173 result
+= secondsPerMinute
;
174 return static_cast<int>(result
);
177 static inline int msToMinutes(double ms
)
179 double result
= fmod(floor(ms
/ msPerMinute
), minutesPerHour
);
181 result
+= minutesPerHour
;
182 return static_cast<int>(result
);
185 static inline int msToHours(double ms
)
187 double result
= fmod(floor(ms
/msPerHour
), hoursPerDay
);
189 result
+= hoursPerDay
;
190 return static_cast<int>(result
);
193 static inline int monthFromDayInYear(int dayInYear
, bool leapYear
)
195 const int d
= dayInYear
;
200 step
+= (leapYear
? 29 : 28);
203 if (d
< (step
+= 31))
205 if (d
< (step
+= 30))
207 if (d
< (step
+= 31))
209 if (d
< (step
+= 30))
211 if (d
< (step
+= 31))
213 if (d
< (step
+= 31))
215 if (d
< (step
+= 30))
217 if (d
< (step
+= 31))
219 if (d
< (step
+= 30))
224 static inline bool checkMonth(int dayInYear
, int& startDayOfThisMonth
, int& startDayOfNextMonth
, int daysInThisMonth
)
226 startDayOfThisMonth
= startDayOfNextMonth
;
227 startDayOfNextMonth
+= daysInThisMonth
;
228 return (dayInYear
<= startDayOfNextMonth
);
231 static inline int dayInMonthFromDayInYear(int dayInYear
, bool leapYear
)
233 const int d
= dayInYear
;
239 const int daysInFeb
= (leapYear
? 29 : 28);
240 if (checkMonth(d
, step
, next
, daysInFeb
))
242 if (checkMonth(d
, step
, next
, 31))
244 if (checkMonth(d
, step
, next
, 30))
246 if (checkMonth(d
, step
, next
, 31))
248 if (checkMonth(d
, step
, next
, 30))
250 if (checkMonth(d
, step
, next
, 31))
252 if (checkMonth(d
, step
, next
, 31))
254 if (checkMonth(d
, step
, next
, 30))
256 if (checkMonth(d
, step
, next
, 31))
258 if (checkMonth(d
, step
, next
, 30))
264 static inline int monthToDayInYear(int month
, bool isLeapYear
)
266 return firstDayOfMonth
[isLeapYear
][month
];
269 static inline double timeToMS(double hour
, double min
, double sec
, double ms
)
271 return (((hour
* minutesPerHour
+ min
) * secondsPerMinute
+ sec
) * msPerSecond
+ ms
);
274 static int dateToDayInYear(int year
, int month
, int day
)
284 int yearday
= static_cast<int>(floor(daysFrom1970ToYear(year
)));
285 int monthday
= monthToDayInYear(month
, isLeapYear(year
));
287 return yearday
+ monthday
+ day
- 1;
290 double getCurrentUTCTime()
292 return floor(getCurrentUTCTimeWithMicroseconds());
295 // Returns current time in milliseconds since 1 Jan 1970.
296 double getCurrentUTCTimeWithMicroseconds()
298 return currentTime() * 1000.0;
301 void getLocalTime(const time_t* localTime
, struct tm
* localTM
)
303 #if COMPILER(MSVC7) || COMPILER(MINGW) || PLATFORM(WIN_CE)
304 *localTM
= *localtime(localTime
);
306 localtime_s(localTM
, localTime
);
308 localtime_r(localTime
, localTM
);
312 // There is a hard limit at 2038 that we currently do not have a workaround
313 // for (rdar://problem/5052975).
314 static inline int maximumYearForDST()
319 static inline int minimumYearForDST()
321 // Because of the 2038 issue (see maximumYearForDST) if the current year is
322 // greater than the max year minus 27 (2010), we want to use the max year
323 // minus 27 instead, to ensure there is a range of 28 years that all years
325 return std::min(msToYear(getCurrentUTCTime()), maximumYearForDST() - 27) ;
329 * Find an equivalent year for the one given, where equivalence is deterined by
330 * the two years having the same leapness and the first day of the year, falling
331 * on the same day of the week.
333 * This function returns a year between this current year and 2037, however this
334 * function will potentially return incorrect results if the current year is after
335 * 2010, (rdar://problem/5052975), if the year passed in is before 1900 or after
336 * 2100, (rdar://problem/5055038).
338 int equivalentYearForDST(int year
)
340 // It is ok if the cached year is not the current year as long as the rules
341 // for DST did not change between the two years; if they did the app would need
343 static int minYear
= minimumYearForDST();
344 int maxYear
= maximumYearForDST();
348 difference
= minYear
- year
;
349 else if (year
< minYear
)
350 difference
= maxYear
- year
;
354 int quotient
= difference
/ 28;
355 int product
= (quotient
) * 28;
358 ASSERT((year
>= minYear
&& year
<= maxYear
) || (product
- year
== static_cast<int>(NaN
)));
362 static int32_t calculateUTCOffset()
365 memset(&localt
, 0, sizeof(localt
));
367 // get the difference between this time zone and UTC on Jan 01, 2000 12:00:00 AM
369 localt
.tm_year
= 100;
370 time_t utcOffset
= 946684800 - mktime(&localt
);
372 return static_cast<int32_t>(utcOffset
* 1000);
376 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.
377 static bool s_haveCachedUTCOffset
;
378 static int s_notificationToken
;
382 * Get the difference in milliseconds between this time zone and UTC (GMT)
385 double getUTCOffset()
388 if (s_haveCachedUTCOffset
) {
390 uint32_t status
= notify_check(s_notificationToken
, ¬ified
);
391 if (status
== NOTIFY_STATUS_OK
&& !notified
)
392 return s_cachedUTCOffset
;
396 int32_t utcOffset
= calculateUTCOffset();
399 // Theoretically, it is possible that several threads will be executing this code at once, in which case we will have a race condition,
400 // and a newer value may be overwritten. In practice, time zones don't change that often.
401 s_cachedUTCOffset
= utcOffset
;
408 * Get the DST offset for the time passed in. Takes
409 * seconds (not milliseconds) and cannot handle dates before 1970
412 static double getDSTOffsetSimple(double localTimeSeconds
, double utcOffset
)
414 if (localTimeSeconds
> maxUnixTime
)
415 localTimeSeconds
= maxUnixTime
;
416 else if (localTimeSeconds
< 0) // Go ahead a day to make localtime work (does not work with 0)
417 localTimeSeconds
+= secondsPerDay
;
419 //input is UTC so we have to shift back to local time to determine DST thus the + getUTCOffset()
420 double offsetTime
= (localTimeSeconds
* msPerSecond
) + utcOffset
;
422 // Offset from UTC but doesn't include DST obviously
423 int offsetHour
= msToHours(offsetTime
);
424 int offsetMinute
= msToMinutes(offsetTime
);
426 // FIXME: time_t has a potential problem in 2038
427 time_t localTime
= static_cast<time_t>(localTimeSeconds
);
430 getLocalTime(&localTime
, &localTM
);
432 double diff
= ((localTM
.tm_hour
- offsetHour
) * secondsPerHour
) + ((localTM
.tm_min
- offsetMinute
) * 60);
435 diff
+= secondsPerDay
;
437 return (diff
* msPerSecond
);
440 // Get the DST offset, given a time in UTC
441 static double getDSTOffset(double ms
, double utcOffset
)
443 // On Mac OS X, the call to localtime (see getDSTOffsetSimple) will return historically accurate
444 // DST information (e.g. New Zealand did not have DST from 1946 to 1974) however the JavaScript
445 // standard explicitly dictates that historical information should not be considered when
446 // determining DST. For this reason we shift away from years that localtime can handle but would
447 // return historically accurate information.
448 int year
= msToYear(ms
);
449 int equivalentYear
= equivalentYearForDST(year
);
450 if (year
!= equivalentYear
) {
451 bool leapYear
= isLeapYear(year
);
452 int dayInYearLocal
= dayInYear(ms
, year
);
453 int dayInMonth
= dayInMonthFromDayInYear(dayInYearLocal
, leapYear
);
454 int month
= monthFromDayInYear(dayInYearLocal
, leapYear
);
455 int day
= dateToDayInYear(equivalentYear
, month
, dayInMonth
);
456 ms
= (day
* msPerDay
) + msToMilliseconds(ms
);
459 return getDSTOffsetSimple(ms
/ msPerSecond
, utcOffset
);
462 double gregorianDateTimeToMS(const GregorianDateTime
& t
, double milliSeconds
, bool inputIsUTC
)
464 int day
= dateToDayInYear(t
.year
+ 1900, t
.month
, t
.monthDay
);
465 double ms
= timeToMS(t
.hour
, t
.minute
, t
.second
, milliSeconds
);
466 double result
= (day
* msPerDay
) + ms
;
468 if (!inputIsUTC
) { // convert to UTC
469 double utcOffset
= getUTCOffset();
471 result
-= getDSTOffset(result
, utcOffset
);
477 void msToGregorianDateTime(double ms
, bool outputIsUTC
, GregorianDateTime
& tm
)
481 const double utcOff
= getUTCOffset();
483 if (!outputIsUTC
) { // convert to local time
484 dstOff
= getDSTOffset(ms
, utcOff
);
485 ms
+= dstOff
+ utcOff
;
488 const int year
= msToYear(ms
);
489 tm
.second
= msToSeconds(ms
);
490 tm
.minute
= msToMinutes(ms
);
491 tm
.hour
= msToHours(ms
);
492 tm
.weekDay
= msToWeekDay(ms
);
493 tm
.yearDay
= dayInYear(ms
, year
);
494 tm
.monthDay
= dayInMonthFromDayInYear(tm
.yearDay
, isLeapYear(year
));
495 tm
.month
= monthFromDayInYear(tm
.yearDay
, isLeapYear(year
));
496 tm
.year
= year
- 1900;
497 tm
.isDST
= dstOff
!= 0.0;
499 tm
.utcOffset
= static_cast<long>((dstOff
+ utcOff
) / msPerSecond
);
506 static bool alreadyInitialized
;
507 ASSERT(!alreadyInitialized
++);
510 equivalentYearForDST(2000); // Need to call once to initialize a static used in this function.
512 // Register for a notification whenever the time zone changes.
513 uint32_t status
= notify_register_check("com.apple.system.timezone", &s_notificationToken
);
514 if (status
== NOTIFY_STATUS_OK
) {
515 s_cachedUTCOffset
= calculateUTCOffset();
516 s_haveCachedUTCOffset
= true;
521 static inline double ymdhmsToSeconds(long year
, int mon
, int day
, int hour
, int minute
, int second
)
523 double days
= (day
- 32075)
524 + floor(1461 * (year
+ 4800.0 + (mon
- 14) / 12) / 4)
525 + 367 * (mon
- 2 - (mon
- 14) / 12 * 12) / 12
526 - floor(3 * ((year
+ 4900.0 + (mon
- 14) / 12) / 100) / 4)
528 return ((days
* hoursPerDay
+ hour
) * minutesPerHour
+ minute
) * secondsPerMinute
+ second
;
531 // We follow the recommendation of RFC 2822 to consider all
532 // obsolete time zones not listed here equivalent to "-0000".
533 static const struct KnownZone
{
534 #if !PLATFORM(WIN_OS)
552 inline static void skipSpacesAndComments(const char*& s
)
557 if (!isASCIISpace(ch
)) {
560 else if (ch
== ')' && nesting
> 0)
562 else if (nesting
== 0)
569 // returns 0-11 (Jan-Dec); -1 on failure
570 static int findMonth(const char* monthStr
)
574 for (int i
= 0; i
< 3; ++i
) {
577 needle
[i
] = static_cast<char>(toASCIILower(*monthStr
++));
580 const char *haystack
= "janfebmaraprmayjunjulaugsepoctnovdec";
581 const char *str
= strstr(haystack
, needle
);
583 int position
= static_cast<int>(str
- haystack
);
584 if (position
% 3 == 0)
590 static bool parseLong(const char* string
, char** stopPosition
, int base
, long* result
)
592 *result
= strtol(string
, stopPosition
, base
);
593 // Avoid the use of errno as it is not available on Windows CE
594 if (string
== *stopPosition
|| *result
== LONG_MIN
|| *result
== LONG_MAX
)
599 double parseDate(const UString
&date
)
601 // This parses a date in the form:
602 // Tuesday, 09-Nov-99 23:12:40 GMT
604 // Sat, 01-Jan-2000 08:00:00 GMT
606 // Sat, 01 Jan 2000 08:00:00 GMT
608 // 01 Jan 99 22:00 +0100 (exceptions in rfc822/rfc2822)
609 // ### non RFC formats, added for Javascript:
610 // [Wednesday] January 09 1999 23:12:40 GMT
611 // [Wednesday] January 09 23:12:40 GMT 1999
613 // We ignore the weekday.
615 CString dateCString
= date
.UTF8String();
616 const char *dateString
= dateCString
.c_str();
618 // Skip leading space
619 skipSpacesAndComments(dateString
);
622 const char *wordStart
= dateString
;
623 // Check contents of first words if not number
624 while (*dateString
&& !isASCIIDigit(*dateString
)) {
625 if (isASCIISpace(*dateString
) || *dateString
== '(') {
626 if (dateString
- wordStart
>= 3)
627 month
= findMonth(wordStart
);
628 skipSpacesAndComments(dateString
);
629 wordStart
= dateString
;
634 // Missing delimiter between month and day (like "January29")?
635 if (month
== -1 && wordStart
!= dateString
)
636 month
= findMonth(wordStart
);
638 skipSpacesAndComments(dateString
);
643 // ' 09-Nov-99 23:12:40 GMT'
646 if (!parseLong(dateString
, &newPosStr
, 10, &day
))
648 dateString
= newPosStr
;
658 // ### where is the boundary and what happens below?
659 if (*dateString
!= '/')
661 // looks like a YYYY/MM/DD date
665 if (!parseLong(dateString
, &newPosStr
, 10, &month
))
668 dateString
= newPosStr
;
669 if (*dateString
++ != '/' || !*dateString
)
671 if (!parseLong(dateString
, &newPosStr
, 10, &day
))
673 dateString
= newPosStr
;
674 } else if (*dateString
== '/' && month
== -1) {
676 // This looks like a MM/DD/YYYY date, not an RFC date.
677 month
= day
- 1; // 0-based
678 if (!parseLong(dateString
, &newPosStr
, 10, &day
))
680 if (day
< 1 || day
> 31)
682 dateString
= newPosStr
;
683 if (*dateString
== '/')
688 if (*dateString
== '-')
691 skipSpacesAndComments(dateString
);
693 if (*dateString
== ',')
696 if (month
== -1) { // not found yet
697 month
= findMonth(dateString
);
701 while (*dateString
&& *dateString
!= '-' && *dateString
!= ',' && !isASCIISpace(*dateString
))
707 // '-99 23:12:40 GMT'
708 if (*dateString
!= '-' && *dateString
!= '/' && *dateString
!= ',' && !isASCIISpace(*dateString
))
714 if (month
< 0 || month
> 11)
718 if (year
<= 0 && *dateString
) {
719 if (!parseLong(dateString
, &newPosStr
, 10, &year
))
723 // Don't fail if the time is missing.
728 dateString
= newPosStr
;
731 if (!(isASCIISpace(*newPosStr
) || *newPosStr
== ',')) {
732 if (*newPosStr
!= ':')
734 // There was no year; the number was the hour.
737 // in the normal case (we parsed the year), advance to the next number
738 dateString
= ++newPosStr
;
739 skipSpacesAndComments(dateString
);
742 parseLong(dateString
, &newPosStr
, 10, &hour
);
743 // Do not check for errno here since we want to continue
744 // even if errno was set becasue we are still looking
747 // Read a number? If not, this might be a timezone name.
748 if (newPosStr
!= dateString
) {
749 dateString
= newPosStr
;
751 if (hour
< 0 || hour
> 23)
758 if (*dateString
++ != ':')
761 if (!parseLong(dateString
, &newPosStr
, 10, &minute
))
763 dateString
= newPosStr
;
765 if (minute
< 0 || minute
> 59)
769 if (*dateString
&& *dateString
!= ':' && !isASCIISpace(*dateString
))
772 // seconds are optional in rfc822 + rfc2822
773 if (*dateString
==':') {
776 if (!parseLong(dateString
, &newPosStr
, 10, &second
))
778 dateString
= newPosStr
;
780 if (second
< 0 || second
> 59)
784 skipSpacesAndComments(dateString
);
786 if (strncasecmp(dateString
, "AM", 2) == 0) {
792 skipSpacesAndComments(dateString
);
793 } else if (strncasecmp(dateString
, "PM", 2) == 0) {
799 skipSpacesAndComments(dateString
);
807 // Don't fail if the time zone is missing.
808 // Some websites omit the time zone (4275206).
810 if (strncasecmp(dateString
, "GMT", 3) == 0 || strncasecmp(dateString
, "UTC", 3) == 0) {
815 if (*dateString
== '+' || *dateString
== '-') {
817 if (!parseLong(dateString
, &newPosStr
, 10, &o
))
819 dateString
= newPosStr
;
821 if (o
< -9959 || o
> 9959)
824 int sgn
= (o
< 0) ? -1 : 1;
826 if (*dateString
!= ':') {
827 offset
= ((o
/ 100) * 60 + (o
% 100)) * sgn
;
828 } else { // GMT+05:00
830 if (!parseLong(dateString
, &newPosStr
, 10, &o2
))
832 dateString
= newPosStr
;
833 offset
= (o
* 60 + o2
) * sgn
;
837 for (int i
= 0; i
< int(sizeof(known_zones
) / sizeof(KnownZone
)); i
++) {
838 if (0 == strncasecmp(dateString
, known_zones
[i
].tzName
, strlen(known_zones
[i
].tzName
))) {
839 offset
= known_zones
[i
].tzOffset
;
840 dateString
+= strlen(known_zones
[i
].tzName
);
848 skipSpacesAndComments(dateString
);
850 if (*dateString
&& year
== -1) {
851 if (!parseLong(dateString
, &newPosStr
, 10, &year
))
853 dateString
= newPosStr
;
856 skipSpacesAndComments(dateString
);
862 // Y2K: Handle 2 digit years.
863 if (year
>= 0 && year
< 100) {
870 // fall back to local timezone
875 t
.year
= year
- 1900;
881 // Use our gregorianDateTimeToMS() rather than mktime() as the latter can't handle the full year range.
882 return gregorianDateTimeToMS(t
, 0, false);
885 return (ymdhmsToSeconds(year
, month
+ 1, day
, hour
, minute
, second
) - (offset
* 60.0)) * msPerSecond
;
888 double timeClip(double t
)
892 if (fabs(t
) > 8.64E15
)
897 UString
formatDate(const GregorianDateTime
&t
)
900 snprintf(buffer
, sizeof(buffer
), "%s %s %02d %04d",
901 weekdayName
[(t
.weekDay
+ 6) % 7],
902 monthName
[t
.month
], t
.monthDay
, t
.year
+ 1900);
906 UString
formatDateUTCVariant(const GregorianDateTime
&t
)
909 snprintf(buffer
, sizeof(buffer
), "%s, %02d %s %04d",
910 weekdayName
[(t
.weekDay
+ 6) % 7],
911 t
.monthDay
, monthName
[t
.month
], t
.year
+ 1900);
915 UString
formatTime(const GregorianDateTime
&t
, bool utc
)
919 snprintf(buffer
, sizeof(buffer
), "%02d:%02d:%02d GMT", t
.hour
, t
.minute
, t
.second
);
921 int offset
= abs(gmtoffset(t
));
924 strftime(tzname
, sizeof(tzname
), "%Z", >m
);
927 snprintf(buffer
, sizeof(buffer
), "%02d:%02d:%02d GMT%c%02d%02d (%s)",
928 t
.hour
, t
.minute
, t
.second
,
929 gmtoffset(t
) < 0 ? '-' : '+', offset
/ (60*60), (offset
/ 60) % 60, tzname
);
931 snprintf(buffer
, sizeof(buffer
), "%02d:%02d:%02d GMT%c%02d%02d",
932 t
.hour
, t
.minute
, t
.second
,
933 gmtoffset(t
) < 0 ? '-' : '+', offset
/ (60*60), (offset
/ 60) % 60);
936 return UString(buffer
);