mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-31 22:25:30 +00:00
1117 lines
31 KiB
C
1117 lines
31 KiB
C
/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
|
|
*
|
|
* The contents of this file are subject to the Netscape Public License
|
|
* Version 1.0 (the "NPL"); you may not use this file except in
|
|
* compliance with the NPL. You may obtain a copy of the NPL at
|
|
* http://www.mozilla.org/NPL/
|
|
*
|
|
* Software distributed under the NPL is distributed on an "AS IS" basis,
|
|
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the NPL
|
|
* for the specific language governing rights and limitations under the
|
|
* NPL.
|
|
*
|
|
* The Initial Developer of this code under the NPL is Netscape
|
|
* Communications Corporation. Portions created by Netscape are
|
|
* Copyright (C) 1998 Netscape Communications Corporation. All Rights
|
|
* Reserved.
|
|
*/
|
|
|
|
/* *
|
|
*
|
|
*
|
|
* xp_time.c --- parsing dates and timzones and stuff
|
|
*
|
|
* Created: Jamie Zawinski <jwz@netscape.com>, 3-Aug-95
|
|
*/
|
|
|
|
#include "xp_time.h"
|
|
|
|
|
|
/* Returns the number of minutes difference between the local time and GMT.
|
|
This takes into effect daylight savings time. This is the value that
|
|
should show up in outgoing mail headers, etc.
|
|
*/
|
|
|
|
/* For speed, this function memorizes itself (the value is computed once, and
|
|
cached.) Calling time(), localtime(), and gmtime() every time we want to
|
|
parse a date is too much overhead.
|
|
|
|
However, our offset from GMT can change, each time the Daylight Savings
|
|
Time state flips (twice a year.) To avoid starting to mis-parse times if
|
|
the same process was running both before and after the flip, we discard
|
|
the cache once a minute. This is done efficiently: one minute after
|
|
the local zone offset has most recently been computed, a timer will go off,
|
|
setting a bit invalidating the cache. The next time we need the zone
|
|
offset, we will recompute it. This does NOT cause a timer to go off every
|
|
minute no matter what; it only goes off once, a minute after Netscape has
|
|
gone idle (or otherwise stopped needing to parse times.)
|
|
|
|
Making the cache lifetime be longer than a minute wouldn't be a significant
|
|
savings; it could actually be much smaller (a few seconds) without any
|
|
performance hit.
|
|
*/
|
|
|
|
void do_timer(void* closure)
|
|
{
|
|
void** timer = (void**) closure;
|
|
*timer = NULL;
|
|
}
|
|
|
|
PRIVATE int
|
|
xp_internal_zone_offset (time_t date_then)
|
|
{
|
|
struct tm local, gmt, *tm;
|
|
int zone = 0;
|
|
|
|
tm = localtime (&date_then);
|
|
if (!tm) return 0;
|
|
local = *tm;
|
|
|
|
tm = gmtime (&date_then);
|
|
if (!tm) return 0;
|
|
gmt = *tm;
|
|
|
|
/* Assume we are never more than 24 hours away. */
|
|
zone = local.tm_yday - gmt.tm_yday;
|
|
if (zone > 1)
|
|
zone = -24;
|
|
else if (zone < -1)
|
|
zone = 24;
|
|
else
|
|
zone *= 24;
|
|
|
|
/* Scale in the hours and minutes; ignore seconds. */
|
|
zone -= gmt.tm_hour - local.tm_hour;
|
|
zone *= 60;
|
|
zone -= gmt.tm_min - local.tm_min;
|
|
|
|
return zone;
|
|
}
|
|
|
|
PUBLIC int
|
|
XP_LocalZoneOffset(void)
|
|
{
|
|
static int zone = 0;
|
|
static void* timer = NULL;
|
|
|
|
/* Our "invalidation timer" is still ticking, so the results of last time
|
|
is still valid. */
|
|
if (timer) {
|
|
return zone;
|
|
}
|
|
|
|
#ifdef MOZILLA_CLIENT
|
|
timer = FE_SetTimeout(do_timer, &timer, 60L * 1000L); /* 60 seconds */
|
|
#endif /* MOZILLA_CLIENT */
|
|
|
|
zone = xp_internal_zone_offset(time(NULL));
|
|
|
|
return zone;
|
|
}
|
|
|
|
PRIVATE short monthOffset[] = {
|
|
0, /* 31 January */
|
|
31, /* 28 February */
|
|
59, /* 31 March */
|
|
90, /* 30 April */
|
|
120, /* 31 May */
|
|
151, /* 30 June */
|
|
181, /* 31 July */
|
|
212, /* 31 August */
|
|
243, /* 30 September */
|
|
273, /* 31 October */
|
|
304, /* 30 November */
|
|
334 /* 31 December */
|
|
/* 365 */
|
|
};
|
|
|
|
static time_t
|
|
xp_compute_UTC_from_GMT(const struct tm *tm)
|
|
{
|
|
int32 secs;
|
|
int32 day;
|
|
|
|
XP_ASSERT(tm->tm_mon > -1 && tm->tm_mon < 12);
|
|
|
|
day = (tm->tm_mday
|
|
+ monthOffset[tm->tm_mon]
|
|
+ ((tm->tm_year & 3) != 0
|
|
|| (tm->tm_year % 100 == 0 && (tm->tm_year + 300) % 400 != 0)
|
|
|| tm->tm_mon < 2
|
|
? -1 : 0)/* convert day-of-month to 0 based range,
|
|
* except following February in a leap year,
|
|
* in which case we skip the conversion to
|
|
* account for the extra day in February */
|
|
+ (tm->tm_year - 70) * 365L /* days per year */
|
|
+ (tm->tm_year - 69) / 4 /* plus leap days */
|
|
- (tm->tm_year - 1) / 100 /* no leap on century years */
|
|
+ (tm->tm_year + 299) / 400); /* except %400 years */
|
|
|
|
if(day < 0)
|
|
return(0);
|
|
|
|
secs = tm->tm_sec + (60L * (tm->tm_min + (60L * tm->tm_hour)));
|
|
|
|
if(day == 0 && secs < 0)
|
|
return(0);
|
|
return ((time_t) (secs + ((60L * 60L * 24L) * day)));
|
|
}
|
|
|
|
|
|
/*
|
|
I lifted this list of time zone abbreviations out of a UNIX computers setup
|
|
file (specifically, from an AT&T StarServer running UNIX System V Release 4,
|
|
in the /usr/lib/local/TZ directory).
|
|
|
|
The list is by no means complete or comprehensive, as much of it comes out
|
|
of scripts designed to adjust the time on computers when Daylight Savings
|
|
Time (DST) rolls around. Also, I would consider it at least a little
|
|
suspect. First, because it was compiled by Americans, and you know how us
|
|
Americans are with geography :). Second, the data looks to be a little old,
|
|
circa 1991 (note the reference to the "Soviet Union").
|
|
|
|
The first column is an approximate name for the time zone described, the
|
|
second column gives the time relative to GMT, the third column takes a stab
|
|
at listing the country that the time zone is in, and the final column gives
|
|
one or more abbreviations that apply to that time zone (note that
|
|
abbreviations that end with "DST" or with "S" as the middle letter indicate
|
|
Daylight Savings Time is in effect).
|
|
|
|
I've also tried to roughly divide the listings into geographical groupings.
|
|
|
|
Hope this helps,
|
|
|
|
Raymond McCauley
|
|
Texas A&M University
|
|
scooter@tamu.edu
|
|
|
|
<List follows...>
|
|
=================
|
|
|
|
Europe
|
|
|
|
Great Britain 0:00 GB-Eire GMT, BST
|
|
Western European nations +0:00 W-Eur WET, WET DST
|
|
Iceland +0:00 - WET
|
|
Middle European nations +1:00 M-Eur MET, MET DST
|
|
Poland +1:00 W-Eur MET, MET DST
|
|
Eastern European nations +2:00 E-Eur EET, EET DST
|
|
Turkey +3:00 Turkey EET, EET DST
|
|
Warsaw Pact/Soviet Union +3:00 M-Eur ????
|
|
|
|
North America
|
|
|
|
Canada/Newfoundland -3:30 Canada NST, NDT
|
|
Canada/Atlantic -4:00 Canada AST, ADT
|
|
Canada/Eastern -5:00 Canada EST, EDT
|
|
Canada/Central -6:00 Canada CST, CDT
|
|
Canada/East-Saskatchewan -6:00 Canada CST
|
|
Canada/Mountain -7:00 Canada MST, MDT
|
|
Canada/Pacific -8:00 Canada PST, PDT
|
|
Canada/Yukon -9:00 Canada YST, YDT
|
|
|
|
US/Eastern -5:00 US EST, EDT
|
|
US/Central -6:00 US CST, CDT
|
|
US/Mountain -7:00 US MST, MDT
|
|
US/Pacific -8:00 US PST, PDT
|
|
US/Yukon -9:00 US YST, YDT
|
|
US/Hawaii -10:00 US HST, PST, PDT, PPET
|
|
|
|
Mexico/BajaNorte -8:00 Mexico PST, PDT
|
|
Mexico/BajaSur -7:00 Mexico MST
|
|
Mexico/General -6:00 Mexico CST
|
|
|
|
South America
|
|
|
|
Brazil/East -3:00 Brazil EST, EDT
|
|
Brazil/West -4:00 Brazil WST, WDT
|
|
Brazil/Acre -5:00 Brazil AST, ADT
|
|
Brazil/DeNoronha -2:00 Brazil FST, FDT
|
|
|
|
Chile/Continental -4:00 Chile CST, CDT
|
|
Chile/EasterIsland -6:00 Chile EST, EDT
|
|
|
|
|
|
Asia
|
|
|
|
People's Repub. of China +8:00 PRC CST, CDT
|
|
(Yes, they really have only one time zone.)
|
|
|
|
Republic of Korea +9:00 ROK KST, KDT
|
|
|
|
Japan +9:00 Japan JST
|
|
Singapore +8:00 Singapore SST
|
|
Hongkong +8:00 U.K. HKT
|
|
ROC +8:00 - CST
|
|
|
|
Middle East
|
|
|
|
Israel +3:00 Israel IST, IDT
|
|
(This was the only listing I found)
|
|
|
|
Australia
|
|
|
|
Australia/Tasmania +10:00 Oz EST
|
|
Australia/Queensland +10:00 Oz EST
|
|
Australia/North +9:30 Oz CST
|
|
Australia/West +8:00 Oz WST
|
|
Australia/South +9:30 Oz CST
|
|
|
|
|
|
|
|
Hour TZN DZN Zone Example
|
|
0 GMT Greenwich Mean Time GMT0
|
|
0 UTC Universal Coordinated Time UTC0
|
|
2 FST FDT Fernando De Noronha Std FST2FDT
|
|
3 BST Brazil Standard Time BST3
|
|
3 EST EDT Eastern Standard (Brazil) EST3EDT
|
|
3 GST Greenland Standard Time GST3
|
|
3:30 NST NDT Newfoundland Standard Time NST3:30NDT
|
|
4 AST ADT Atlantic Standard Time AST4ADT
|
|
4 WST WDT Western Standard (Brazil) WST4WDT
|
|
5 EST EDT Eastern Standard Time EST5EDT
|
|
5 CST CDT Chile Standard Time CST5CDT
|
|
|
|
Hour TZN DZN Zone Example
|
|
5 AST ADT Acre Standard Time AST5ADT
|
|
5 CST CDT Cuba Standard Time CST5CDT
|
|
6 CST CDT Central Standard Time CST6CDT
|
|
6 EST EDT Easter Island Standard EST6EDT
|
|
7 MST MDT Mountain Standard Time MST7MDT
|
|
8 PST PDT Pacific Standard Time PST8PDT
|
|
9 AKS AKD Alaska Standard Time AKS9AKD
|
|
9 YST YDT Yukon Standard Time YST9YST
|
|
10 HST HDT Hawaii Standard Time HST10HDT
|
|
11 SST Somoa Standard Time SST11
|
|
-12 NZS NZD New Zealand Standard Time NZS-12NZD
|
|
-10 GST Guam Standard Time GST-10
|
|
-10 EAS EAD Eastern Australian Standard EAS-10EAD
|
|
-9:30 CAS CAD Central Australian Standard CAS-9:30CAD
|
|
-9 JST Japan Standard Time JST-9
|
|
-9 KST KDT Korean Standard Time KST-9KDT
|
|
-8 CCT China Coast Time CCT-8
|
|
-8 HKT Hong Kong Time HKT-8
|
|
-8 SST Singapore Standard Time SST-8
|
|
-8 WAS WAD Western Australian Standard WAS-8WAD
|
|
-7:30 JT Java Standard Time JST-7:30
|
|
-7 NST North Sumatra Time NST-7
|
|
-5:30 IST Indian Standard Time IST-5:30
|
|
-3:30 IST IDT Iran Standard Time IST-3:30IDT
|
|
-3 MSK MSD Moscow Winter Time MSK-3MSD
|
|
-2 EET Eastern Europe Time EET-2
|
|
-2 IST IDT Israel Standard Time IST-2IDT
|
|
-1 MEZ MES Middle European Time MEZ-1MES
|
|
-1 SWT SST Swedish Winter Time SWT-1SST
|
|
-1 FWT FST French Winter Time FWT-1FST
|
|
-1 CET CES Central European Time CET-1CES
|
|
-1 WAT West African Time WAT-1
|
|
*/
|
|
|
|
|
|
typedef enum
|
|
{
|
|
TT_UNKNOWN,
|
|
|
|
TT_SUN, TT_MON, TT_TUE, TT_WED, TT_THU, TT_FRI, TT_SAT,
|
|
|
|
TT_JAN, TT_FEB, TT_MAR, TT_APR, TT_MAY, TT_JUN,
|
|
TT_JUL, TT_AUG, TT_SEP, TT_OCT, TT_NOV, TT_DEC,
|
|
|
|
TT_PST, TT_PDT, TT_MST, TT_MDT, TT_CST, TT_CDT, TT_EST, TT_EDT,
|
|
TT_AST, TT_NST, TT_GMT, TT_BST, TT_MET, TT_EET, TT_JST
|
|
} TIME_TOKEN;
|
|
|
|
|
|
/* This parses a time/date string into a time_t
|
|
(seconds after "1-Jan-1970 00:00:00 GMT")
|
|
If it can't be parsed, 0 is returned.
|
|
|
|
Many formats are handled, including:
|
|
|
|
14 Apr 89 03:20:12
|
|
14 Apr 89 03:20 GMT
|
|
Fri, 17 Mar 89 4:01:33
|
|
Fri, 17 Mar 89 4:01 GMT
|
|
Mon Jan 16 16:12 PDT 1989
|
|
Mon Jan 16 16:12 +0130 1989
|
|
6 May 1992 16:41-JST (Wednesday)
|
|
22-AUG-1993 10:59:12.82
|
|
22-AUG-1993 10:59pm
|
|
22-AUG-1993 12:59am
|
|
22-AUG-1993 12:59 PM
|
|
Friday, August 04, 1995 3:54 PM
|
|
06/21/95 04:24:34 PM
|
|
20/06/95 21:07
|
|
95-06-08 19:32:48 EDT
|
|
|
|
If the input string doesn't contain a description of the timezone,
|
|
we consult the `default_to_gmt' to decide whether the string should
|
|
be interpreted relative to the local time zone (FALSE) or GMT (TRUE).
|
|
The correct value for this argument depends on what standard specified
|
|
the time string which you are parsing.
|
|
*/
|
|
time_t
|
|
XP_ParseTimeString (const char *string, XP_Bool default_to_gmt)
|
|
{
|
|
struct tm tm;
|
|
TIME_TOKEN dotw = TT_UNKNOWN;
|
|
TIME_TOKEN month = TT_UNKNOWN;
|
|
TIME_TOKEN zone = TT_UNKNOWN;
|
|
int zone_offset = -1;
|
|
int date = -1;
|
|
int32 year = -1;
|
|
int hour = -1;
|
|
int min = -1;
|
|
int sec = -1;
|
|
time_t result;
|
|
|
|
const char *rest = string;
|
|
|
|
#ifdef DEBUG
|
|
int iterations = 0;
|
|
#endif
|
|
|
|
XP_ASSERT(string);
|
|
if (!string) return 0;
|
|
|
|
while (*rest)
|
|
{
|
|
|
|
#ifdef DEBUG
|
|
if (iterations++ > 1000)
|
|
{
|
|
XP_ASSERT(0);
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
switch (*rest)
|
|
{
|
|
case 'a': case 'A':
|
|
if (month == TT_UNKNOWN &&
|
|
(rest[1] == 'p' || rest[1] == 'P') &&
|
|
(rest[2] == 'r' || rest[2] == 'R'))
|
|
month = TT_APR;
|
|
else if (zone == TT_UNKNOWN &&
|
|
(rest[1] == 's' || rest[1] == 's') &&
|
|
(rest[2] == 't' || rest[2] == 'T'))
|
|
zone = TT_AST;
|
|
else if (month == TT_UNKNOWN &&
|
|
(rest[1] == 'u' || rest[1] == 'U') &&
|
|
(rest[2] == 'g' || rest[2] == 'G'))
|
|
month = TT_AUG;
|
|
break;
|
|
case 'b': case 'B':
|
|
if (zone == TT_UNKNOWN &&
|
|
(rest[1] == 's' || rest[1] == 'S') &&
|
|
(rest[2] == 't' || rest[2] == 'T'))
|
|
zone = TT_BST;
|
|
break;
|
|
case 'c': case 'C':
|
|
if (zone == TT_UNKNOWN &&
|
|
(rest[1] == 'd' || rest[1] == 'D') &&
|
|
(rest[2] == 't' || rest[2] == 'T'))
|
|
zone = TT_CDT;
|
|
else if (zone == TT_UNKNOWN &&
|
|
(rest[1] == 's' || rest[1] == 'S') &&
|
|
(rest[2] == 't' || rest[2] == 'T'))
|
|
zone = TT_CST;
|
|
break;
|
|
case 'd': case 'D':
|
|
if (month == TT_UNKNOWN &&
|
|
(rest[1] == 'e' || rest[1] == 'E') &&
|
|
(rest[2] == 'c' || rest[2] == 'C'))
|
|
month = TT_DEC;
|
|
break;
|
|
case 'e': case 'E':
|
|
if (zone == TT_UNKNOWN &&
|
|
(rest[1] == 'd' || rest[1] == 'D') &&
|
|
(rest[2] == 't' || rest[2] == 'T'))
|
|
zone = TT_EDT;
|
|
else if (zone == TT_UNKNOWN &&
|
|
(rest[1] == 'e' || rest[1] == 'E') &&
|
|
(rest[2] == 't' || rest[2] == 'T'))
|
|
zone = TT_EET;
|
|
else if (zone == TT_UNKNOWN &&
|
|
(rest[1] == 's' || rest[1] == 'S') &&
|
|
(rest[2] == 't' || rest[2] == 'T'))
|
|
zone = TT_EST;
|
|
break;
|
|
case 'f': case 'F':
|
|
if (month == TT_UNKNOWN &&
|
|
(rest[1] == 'e' || rest[1] == 'E') &&
|
|
(rest[2] == 'b' || rest[2] == 'B'))
|
|
month = TT_FEB;
|
|
else if (dotw == TT_UNKNOWN &&
|
|
(rest[1] == 'r' || rest[1] == 'R') &&
|
|
(rest[2] == 'i' || rest[2] == 'I'))
|
|
dotw = TT_FRI;
|
|
break;
|
|
case 'g': case 'G':
|
|
if (zone == TT_UNKNOWN &&
|
|
(rest[1] == 'm' || rest[1] == 'M') &&
|
|
(rest[2] == 't' || rest[2] == 'T'))
|
|
zone = TT_GMT;
|
|
break;
|
|
case 'j': case 'J':
|
|
if (month == TT_UNKNOWN &&
|
|
(rest[1] == 'a' || rest[1] == 'A') &&
|
|
(rest[2] == 'n' || rest[2] == 'N'))
|
|
month = TT_JAN;
|
|
else if (zone == TT_UNKNOWN &&
|
|
(rest[1] == 's' || rest[1] == 'S') &&
|
|
(rest[2] == 't' || rest[2] == 'T'))
|
|
zone = TT_JST;
|
|
else if (month == TT_UNKNOWN &&
|
|
(rest[1] == 'u' || rest[1] == 'U') &&
|
|
(rest[2] == 'l' || rest[2] == 'L'))
|
|
month = TT_JUL;
|
|
else if (month == TT_UNKNOWN &&
|
|
(rest[1] == 'u' || rest[1] == 'U') &&
|
|
(rest[2] == 'n' || rest[2] == 'N'))
|
|
month = TT_JUN;
|
|
break;
|
|
case 'm': case 'M':
|
|
if (month == TT_UNKNOWN &&
|
|
(rest[1] == 'a' || rest[1] == 'A') &&
|
|
(rest[2] == 'r' || rest[2] == 'R'))
|
|
month = TT_MAR;
|
|
else if (month == TT_UNKNOWN &&
|
|
(rest[1] == 'a' || rest[1] == 'A') &&
|
|
(rest[2] == 'y' || rest[2] == 'Y'))
|
|
month = TT_MAY;
|
|
else if (zone == TT_UNKNOWN &&
|
|
(rest[1] == 'd' || rest[1] == 'D') &&
|
|
(rest[2] == 't' || rest[2] == 'T'))
|
|
zone = TT_MDT;
|
|
else if (zone == TT_UNKNOWN &&
|
|
(rest[1] == 'e' || rest[1] == 'E') &&
|
|
(rest[2] == 't' || rest[2] == 'T'))
|
|
zone = TT_MET;
|
|
else if (dotw == TT_UNKNOWN &&
|
|
(rest[1] == 'o' || rest[1] == 'O') &&
|
|
(rest[2] == 'n' || rest[2] == 'N'))
|
|
dotw = TT_MON;
|
|
else if (zone == TT_UNKNOWN &&
|
|
(rest[1] == 's' || rest[1] == 'S') &&
|
|
(rest[2] == 't' || rest[2] == 'T'))
|
|
zone = TT_MST;
|
|
break;
|
|
case 'n': case 'N':
|
|
if (month == TT_UNKNOWN &&
|
|
(rest[1] == 'o' || rest[1] == 'O') &&
|
|
(rest[2] == 'v' || rest[2] == 'V'))
|
|
month = TT_NOV;
|
|
else if (zone == TT_UNKNOWN &&
|
|
(rest[1] == 's' || rest[1] == 'S') &&
|
|
(rest[2] == 't' || rest[2] == 'T'))
|
|
zone = TT_NST;
|
|
break;
|
|
case 'o': case 'O':
|
|
if (month == TT_UNKNOWN &&
|
|
(rest[1] == 'c' || rest[1] == 'C') &&
|
|
(rest[2] == 't' || rest[2] == 'T'))
|
|
month = TT_OCT;
|
|
break;
|
|
case 'p': case 'P':
|
|
if (zone == TT_UNKNOWN &&
|
|
(rest[1] == 'd' || rest[1] == 'D') &&
|
|
(rest[2] == 't' || rest[2] == 'T'))
|
|
zone = TT_PDT;
|
|
else if (zone == TT_UNKNOWN &&
|
|
(rest[1] == 's' || rest[1] == 'S') &&
|
|
(rest[2] == 't' || rest[2] == 'T'))
|
|
zone = TT_PST;
|
|
break;
|
|
case 's': case 'S':
|
|
if (dotw == TT_UNKNOWN &&
|
|
(rest[1] == 'a' || rest[1] == 'A') &&
|
|
(rest[2] == 't' || rest[2] == 'T'))
|
|
dotw = TT_SAT;
|
|
else if (month == TT_UNKNOWN &&
|
|
(rest[1] == 'e' || rest[1] == 'E') &&
|
|
(rest[2] == 'p' || rest[2] == 'P'))
|
|
month = TT_SEP;
|
|
else if (dotw == TT_UNKNOWN &&
|
|
(rest[1] == 'u' || rest[1] == 'U') &&
|
|
(rest[2] == 'n' || rest[2] == 'N'))
|
|
dotw = TT_SUN;
|
|
break;
|
|
case 't': case 'T':
|
|
if (dotw == TT_UNKNOWN &&
|
|
(rest[1] == 'h' || rest[1] == 'H') &&
|
|
(rest[2] == 'u' || rest[2] == 'U'))
|
|
dotw = TT_THU;
|
|
else if (dotw == TT_UNKNOWN &&
|
|
(rest[1] == 'u' || rest[1] == 'U') &&
|
|
(rest[2] == 'e' || rest[2] == 'E'))
|
|
dotw = TT_TUE;
|
|
break;
|
|
case 'u': case 'U':
|
|
if (zone == TT_UNKNOWN &&
|
|
(rest[1] == 't' || rest[1] == 'T') &&
|
|
!(rest[2] >= 'A' && rest[2] <= 'Z') &&
|
|
!(rest[2] >= 'a' && rest[2] <= 'z'))
|
|
/* UT is the same as GMT but UTx is not. */
|
|
zone = TT_GMT;
|
|
break;
|
|
case 'w': case 'W':
|
|
if (dotw == TT_UNKNOWN &&
|
|
(rest[1] == 'e' || rest[1] == 'E') &&
|
|
(rest[2] == 'd' || rest[2] == 'D'))
|
|
dotw = TT_WED;
|
|
break;
|
|
|
|
case '+': case '-':
|
|
{
|
|
const char *end;
|
|
int sign;
|
|
if (zone_offset >= 0)
|
|
{
|
|
/* already got one... */
|
|
rest++;
|
|
break;
|
|
}
|
|
if (zone != TT_UNKNOWN && zone != TT_GMT)
|
|
{
|
|
/* GMT+0300 is legal, but PST+0300 is not. */
|
|
rest++;
|
|
break;
|
|
}
|
|
|
|
sign = ((*rest == '+') ? 1 : -1);
|
|
rest++; /* move over sign */
|
|
end = rest;
|
|
while (*end >= '0' && *end <= '9')
|
|
end++;
|
|
if (rest == end) /* no digits here */
|
|
break;
|
|
|
|
if ((end - rest) == 4)
|
|
/* offset in HHMM */
|
|
zone_offset = (((((rest[0]-'0')*10) + (rest[1]-'0')) * 60) +
|
|
(((rest[2]-'0')*10) + (rest[3]-'0')));
|
|
else if ((end - rest) == 2)
|
|
/* offset in hours */
|
|
zone_offset = (((rest[0]-'0')*10) + (rest[1]-'0')) * 60;
|
|
else if ((end - rest) == 1)
|
|
/* offset in hours */
|
|
zone_offset = (rest[0]-'0') * 60;
|
|
else
|
|
/* 3 or >4 */
|
|
break;
|
|
|
|
zone_offset *= sign;
|
|
zone = TT_GMT;
|
|
break;
|
|
}
|
|
|
|
case '0': case '1': case '2': case '3': case '4':
|
|
case '5': case '6': case '7': case '8': case '9':
|
|
{
|
|
int tmp_hour = -1;
|
|
int tmp_min = -1;
|
|
int tmp_sec = -1;
|
|
const char *end = rest + 1;
|
|
while (*end >= '0' && *end <= '9')
|
|
end++;
|
|
|
|
/* end is now the first character after a range of digits. */
|
|
|
|
if (*end == ':')
|
|
{
|
|
if (hour > 0 && min > 0) /* already got it */
|
|
break;
|
|
|
|
/* We have seen "[0-9]+:", so this is probably HH:MM[:SS] */
|
|
if ((end - rest) > 2)
|
|
/* it is [0-9][0-9][0-9]+: */
|
|
break;
|
|
else if (rest[1] != ':' &&
|
|
rest[2] != ':')
|
|
/* it is not [0-9]: or [0-9][0-9]: */
|
|
break;
|
|
else if ((end - rest) == 2)
|
|
tmp_hour = ((rest[0]-'0')*10 +
|
|
(rest[1]-'0'));
|
|
else
|
|
tmp_hour = (rest[0]-'0');
|
|
|
|
while (*rest && *rest != ':')
|
|
rest++;
|
|
rest++;
|
|
|
|
/* move over the colon, and parse minutes */
|
|
|
|
end = rest + 1;
|
|
while (*end >= '0' && *end <= '9')
|
|
end++;
|
|
|
|
if (end == rest)
|
|
/* no digits after first colon? */
|
|
break;
|
|
else if ((end - rest) > 2)
|
|
/* it is [0-9][0-9][0-9]+: */
|
|
break;
|
|
else if ((end - rest) == 2)
|
|
tmp_min = ((rest[0]-'0')*10 +
|
|
(rest[1]-'0'));
|
|
else
|
|
tmp_min = (rest[0]-'0');
|
|
|
|
/* now go for seconds */
|
|
rest = end;
|
|
if (*rest == ':')
|
|
rest++;
|
|
end = rest;
|
|
while (*end >= '0' && *end <= '9')
|
|
end++;
|
|
|
|
if (end == rest)
|
|
/* no digits after second colon - that's ok. */
|
|
;
|
|
else if ((end - rest) > 2)
|
|
/* it is [0-9][0-9][0-9]+: */
|
|
break;
|
|
else if ((end - rest) == 2)
|
|
tmp_sec = ((rest[0]-'0')*10 +
|
|
(rest[1]-'0'));
|
|
else
|
|
tmp_sec = (rest[0]-'0');
|
|
|
|
/* If we made it here, we've parsed hour and min,
|
|
and possibly sec, so it worked as a unit. */
|
|
|
|
/* skip over whitespace and see if there's an AM or PM
|
|
directly following the time.
|
|
*/
|
|
if (tmp_hour <= 12)
|
|
{
|
|
const char *s = end;
|
|
while (*s && (*s == ' ' || *s == '\t'))
|
|
s++;
|
|
if ((s[0] == 'p' || s[0] == 'P') &&
|
|
(s[1] == 'm' || s[1] == 'M'))
|
|
/* 10:05pm == 22:05, and 12:05pm == 12:05 */
|
|
tmp_hour = (tmp_hour == 12 ? 12 : tmp_hour + 12);
|
|
else if (tmp_hour == 12 &&
|
|
(s[0] == 'a' || s[0] == 'A') &&
|
|
(s[1] == 'm' || s[1] == 'M'))
|
|
/* 12:05am == 00:05 */
|
|
tmp_hour = 0;
|
|
}
|
|
|
|
hour = tmp_hour;
|
|
min = tmp_min;
|
|
sec = tmp_sec;
|
|
rest = end;
|
|
break;
|
|
}
|
|
else if ((*end == '/' || *end == '-') &&
|
|
end[1] >= '0' && end[1] <= '9')
|
|
{
|
|
/* Perhaps this is 6/16/95, 16/6/95, 6-16-95, or 16-6-95
|
|
or even 95-06-05...
|
|
#### But it doesn't handle 1995-06-22.
|
|
*/
|
|
int n1, n2, n3;
|
|
const char *s;
|
|
|
|
if (month != TT_UNKNOWN)
|
|
/* if we saw a month name, this can't be. */
|
|
break;
|
|
|
|
s = rest;
|
|
|
|
n1 = (*s++ - '0'); /* first 1 or 2 digits */
|
|
if (*s >= '0' && *s <= '9')
|
|
n1 = n1*10 + (*s++ - '0');
|
|
|
|
if (*s != '/' && *s != '-') /* slash */
|
|
break;
|
|
s++;
|
|
|
|
if (*s < '0' || *s > '9') /* second 1 or 2 digits */
|
|
break;
|
|
n2 = (*s++ - '0');
|
|
if (*s >= '0' && *s <= '9')
|
|
n2 = n2*10 + (*s++ - '0');
|
|
|
|
if (*s != '/' && *s != '-') /* slash */
|
|
break;
|
|
s++;
|
|
|
|
if (*s < '0' || *s > '9') /* third 1, 2, or 4 digits */
|
|
break;
|
|
n3 = (*s++ - '0');
|
|
if (*s >= '0' && *s <= '9')
|
|
n3 = n3*10 + (*s++ - '0');
|
|
|
|
if (*s >= '0' && *s <= '9') /* optional digits 3 and 4 */
|
|
{
|
|
n3 = n3*10 + (*s++ - '0');
|
|
if (*s < '0' || *s > '9')
|
|
break;
|
|
n3 = n3*10 + (*s++ - '0');
|
|
}
|
|
|
|
if ((*s >= '0' && *s <= '9') || /* followed by non-alphanum */
|
|
(*s >= 'A' && *s <= 'Z') ||
|
|
(*s >= 'a' && *s <= 'z'))
|
|
break;
|
|
|
|
/* Ok, we parsed three 1-2 digit numbers, with / or -
|
|
between them. Now decide what the hell they are
|
|
(DD/MM/YY or MM/DD/YY or YY/MM/DD.)
|
|
*/
|
|
|
|
if (n1 > 70) /* must be YY/MM/DD */
|
|
{
|
|
if (n2 > 12) break;
|
|
if (n3 > 31) break;
|
|
year = n1;
|
|
if (year < 1900) year += 1900;
|
|
month = (TIME_TOKEN)(n2 + ((int)TT_JAN) - 1);
|
|
date = n3;
|
|
rest = s;
|
|
break;
|
|
}
|
|
|
|
if (n3 < 70 || /* before epoch - can't represent it. */
|
|
(n1 > 12 && n2 > 12)) /* illegal */
|
|
{
|
|
rest = s;
|
|
break;
|
|
}
|
|
|
|
if (n3 < 1900) n3 += 1900;
|
|
|
|
if (n1 > 12) /* must be DD/MM/YY */
|
|
{
|
|
date = n1;
|
|
month = (TIME_TOKEN)(n2 + ((int)TT_JAN) - 1);
|
|
year = n3;
|
|
}
|
|
else /* assume MM/DD/YY */
|
|
{
|
|
/* #### In the ambiguous case, should we consult the
|
|
locale to find out the local default? */
|
|
month = (TIME_TOKEN)(n1 + ((int)TT_JAN) - 1);
|
|
date = n2;
|
|
year = n3;
|
|
}
|
|
rest = s;
|
|
}
|
|
else if ((*end >= 'A' && *end <= 'Z') ||
|
|
(*end >= 'a' && *end <= 'z'))
|
|
/* Digits followed by non-punctuation - what's that? */
|
|
;
|
|
else if ((end - rest) == 4) /* four digits is a year */
|
|
year = (year < 0
|
|
? ((rest[0]-'0')*1000L +
|
|
(rest[1]-'0')*100L +
|
|
(rest[2]-'0')*10L +
|
|
(rest[3]-'0'))
|
|
: year);
|
|
else if ((end - rest) == 2) /* two digits - date or year */
|
|
{
|
|
int n = ((rest[0]-'0')*10 +
|
|
(rest[1]-'0'));
|
|
/* If we don't have a date (day of the month) and we see a number
|
|
less than 32, then assume that is the date.
|
|
|
|
Otherwise, if we have a date and not a year, assume this is the
|
|
year. If it is less than 70, then assume it refers to the 21st
|
|
century. If it is two digits (>= 70), assume it refers to this
|
|
century. Otherwise, assume it refers to an unambiguous year.
|
|
|
|
The world will surely end soon.
|
|
*/
|
|
if (date < 0 && n < 32)
|
|
date = n;
|
|
else if (year < 0)
|
|
{
|
|
if (n < 70)
|
|
year = 2000 + n;
|
|
else if (n < 100)
|
|
year = 1900 + n;
|
|
else
|
|
year = n;
|
|
}
|
|
/* else what the hell is this. */
|
|
}
|
|
else if ((end - rest) == 1) /* one digit - date */
|
|
date = (date < 0 ? (rest[0]-'0') : date);
|
|
/* else, three or more than four digits - what's that? */
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Skip to the end of this token, whether we parsed it or not.
|
|
Tokens are delimited by whitespace, or ,;-/
|
|
But explicitly not :+-.
|
|
*/
|
|
while (*rest &&
|
|
*rest != ' ' && *rest != '\t' &&
|
|
*rest != ',' && *rest != ';' &&
|
|
*rest != '-' && *rest != '+' &&
|
|
*rest != '/' &&
|
|
*rest != '(' && *rest != ')' && *rest != '[' && *rest != ']')
|
|
rest++;
|
|
/* skip over uninteresting chars. */
|
|
SKIP_MORE:
|
|
while (*rest &&
|
|
(*rest == ' ' || *rest == '\t' ||
|
|
*rest == ',' || *rest == ';' || *rest == '/' ||
|
|
*rest == '(' || *rest == ')' || *rest == '[' || *rest == ']'))
|
|
rest++;
|
|
|
|
/* "-" is ignored at the beginning of a token if we have not yet
|
|
parsed a year, or if the character after the dash is not a digit. */
|
|
if (*rest == '-' && (year < 0 || rest[1] < '0' || rest[1] > '9'))
|
|
{
|
|
rest++;
|
|
goto SKIP_MORE;
|
|
}
|
|
|
|
}
|
|
|
|
if (zone != TT_UNKNOWN && zone_offset == -1)
|
|
{
|
|
switch (zone)
|
|
{
|
|
case TT_PST: zone_offset = -8 * 60; break;
|
|
case TT_PDT: zone_offset = -7 * 60; break;
|
|
case TT_MST: zone_offset = -7 * 60; break;
|
|
case TT_MDT: zone_offset = -6 * 60; break;
|
|
case TT_CST: zone_offset = -6 * 60; break;
|
|
case TT_CDT: zone_offset = -5 * 60; break;
|
|
case TT_EST: zone_offset = -5 * 60; break;
|
|
case TT_EDT: zone_offset = -4 * 60; break;
|
|
case TT_AST: zone_offset = -4 * 60; break;
|
|
case TT_NST: zone_offset = -3 * 60 - 30; break;
|
|
case TT_GMT: zone_offset = 0 * 60; break;
|
|
case TT_BST: zone_offset = 1 * 60; break;
|
|
case TT_MET: zone_offset = 1 * 60; break;
|
|
case TT_EET: zone_offset = 2 * 60; break;
|
|
case TT_JST: zone_offset = 9 * 60; break;
|
|
default:
|
|
XP_ASSERT (0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG_TIME
|
|
XP_TRACE(("%s\n"
|
|
" dotw: %s\n"
|
|
" mon: %s\n"
|
|
" date: %d\n"
|
|
" year: %d\n"
|
|
" hour: %d\n"
|
|
" min: %d\n"
|
|
" sec: %d\n"
|
|
" zone: %s / %c%02d%02d (%d)\n",
|
|
string,
|
|
(dotw == TT_SUN ? "SUN" :
|
|
dotw == TT_MON ? "MON" :
|
|
dotw == TT_TUE ? "TUE" :
|
|
dotw == TT_WED ? "WED" :
|
|
dotw == TT_THU ? "THU" :
|
|
dotw == TT_FRI ? "FRI" :
|
|
dotw == TT_SAT ? "SAT" : "???"),
|
|
(month == TT_JAN ? "JAN" :
|
|
month == TT_FEB ? "FEB" :
|
|
month == TT_MAR ? "MAR" :
|
|
month == TT_APR ? "APR" :
|
|
month == TT_MAY ? "MAY" :
|
|
month == TT_JUN ? "JUN" :
|
|
month == TT_JUL ? "JUL" :
|
|
month == TT_AUG ? "AUG" :
|
|
month == TT_SEP ? "SEP" :
|
|
month == TT_OCT ? "OCT" :
|
|
month == TT_NOV ? "NOV" :
|
|
month == TT_DEC ? "DEC" : "???"),
|
|
date, year, hour, min, sec,
|
|
(zone == TT_PST ? "PST" :
|
|
zone == TT_PDT ? "PDT" :
|
|
zone == TT_MST ? "MST" :
|
|
zone == TT_MDT ? "MDT" :
|
|
zone == TT_CST ? "CST" :
|
|
zone == TT_CDT ? "CDT" :
|
|
zone == TT_EST ? "EST" :
|
|
zone == TT_EDT ? "EDT" :
|
|
zone == TT_AST ? "AST" :
|
|
zone == TT_NST ? "NST" :
|
|
zone == TT_GMT ? "GMT" :
|
|
zone == TT_BST ? "BST" :
|
|
zone == TT_MET ? "MET" :
|
|
zone == TT_EET ? "EET" :
|
|
zone == TT_JST ? "JST" : "???"),
|
|
(zone_offset >= 0 ? '+' : '-'),
|
|
((zone_offset >= 0 ? zone_offset : -zone_offset) / 60),
|
|
((zone_offset >= 0 ? zone_offset : -zone_offset) % 60),
|
|
zone_offset));
|
|
#endif /* DEBUG_TIME */
|
|
|
|
|
|
/* If we didn't find a year, month, or day-of-the-month, we can't
|
|
possibly parse this, and in fact, mktime() will do something random
|
|
(I'm seeing it return "Tue Feb 5 06:28:16 2036", which is no doubt
|
|
a numerologically significant date... */
|
|
if (month == TT_UNKNOWN || date == -1 || year == -1)
|
|
{
|
|
result = 0;
|
|
goto FAIL;
|
|
}
|
|
|
|
XP_MEMSET (&tm, 0, sizeof(tm));
|
|
if (sec != -1)
|
|
tm.tm_sec = sec;
|
|
if (min != -1)
|
|
tm.tm_min = min;
|
|
if (hour != -1)
|
|
tm.tm_hour = hour;
|
|
if (date != -1)
|
|
tm.tm_mday = date;
|
|
if (month != TT_UNKNOWN)
|
|
tm.tm_mon = (((int)month) - ((int)TT_JAN));
|
|
if (year != -1)
|
|
tm.tm_year = year - 1900;
|
|
if (dotw != TT_UNKNOWN)
|
|
tm.tm_wday = (((int)dotw) - ((int)TT_SUN));
|
|
|
|
/* Set this to -1 to tell gmtime "I don't care". If you set it to 0 or 1,
|
|
you are making assertions about whether the date you are handing it is
|
|
in daylight savings mode or not; and if you're wrong, it will "fix" it
|
|
for you. */
|
|
tm.tm_isdst = -1;
|
|
|
|
if (zone == TT_UNKNOWN && default_to_gmt)
|
|
{
|
|
/* No zone was specified, so pretend the zone was GMT. */
|
|
zone = TT_GMT;
|
|
zone_offset = 0;
|
|
}
|
|
|
|
if (zone_offset == -1)
|
|
{
|
|
/* no zone was specified, and we're to assume that everything
|
|
is local. Add the gmt offset for the date we are parsing
|
|
to transform the date to GMT */
|
|
struct tm tmp_tm = tm;
|
|
|
|
XP_ASSERT(tm.tm_mon > -1
|
|
&& tm.tm_mday > 0
|
|
&& tm.tm_hour > -1
|
|
&& tm.tm_min > -1
|
|
&& tm.tm_sec > -1);
|
|
|
|
/* see if we are on 1-Jan-1970 or before
|
|
* if we are we need to return zero here because
|
|
* mktime will crash on win16 if we call it.
|
|
*/
|
|
if(tm.tm_year < 70)
|
|
{
|
|
/* month, day, hours, mins and secs are always non-negative
|
|
* so we dont need to worry about them.
|
|
*/
|
|
return(0); /* return the lowest possible date */
|
|
}
|
|
|
|
zone_offset = xp_internal_zone_offset(mktime(&tmp_tm));
|
|
}
|
|
|
|
/* Adjust the hours and minutes before handing them to the UTC parser.
|
|
Note that it's ok for them to be <0 or >24/60
|
|
|
|
We need to adjust the time before going in to the UTC parser
|
|
because it assumes its input is in GMT time. The zone_offset
|
|
represents the difference between the time zone parsed and GMT
|
|
|
|
*/
|
|
tm.tm_hour -= (zone_offset / 60);
|
|
tm.tm_min -= (zone_offset % 60);
|
|
|
|
#if 0
|
|
#ifdef DEBUG_TIME
|
|
XP_TRACE(("\n tm.tm_sec = %d\n tm.tm_min = %d\n tm.tm_hour = %d\n"
|
|
" tm.tm_mday = %d\n tm.tm_mon = %d\n tm.tm_year = %d\n"
|
|
" tm.tm_wday = %d\n tm.tm_yday = %d\n tm.tm_isdst = %d\n",
|
|
tm.tm_sec, tm.tm_min, tm.tm_hour, tm.tm_mday, tm.tm_mon,
|
|
tm.tm_year, tm.tm_wday, tm.tm_yday, tm.tm_isdst));
|
|
#endif /* DEBUG_TIME */
|
|
#endif /* 0 */
|
|
|
|
result = xp_compute_UTC_from_GMT (&tm);
|
|
|
|
#if 0
|
|
#ifdef DEBUG_TIME
|
|
XP_TRACE(("\n tm.tm_sec = %d\n tm.tm_min = %d\n tm.tm_hour = %d\n"
|
|
" tm.tm_mday = %d\n tm.tm_mon = %d\n tm.tm_year = %d\n"
|
|
" tm.tm_wday = %d\n tm.tm_yday = %d\n tm.tm_isdst = %d\n",
|
|
tm.tm_sec, tm.tm_min, tm.tm_hour, tm.tm_mday, tm.tm_mon,
|
|
tm.tm_year, tm.tm_wday, tm.tm_yday, tm.tm_isdst));
|
|
#endif /* DEBUG_TIME */
|
|
#endif /* 0 */
|
|
|
|
if (result == ((time_t) -1))
|
|
result = 0;
|
|
|
|
/* Bug #36544 */
|
|
if (result < 0)
|
|
result = 0;
|
|
|
|
FAIL:
|
|
#ifdef DEBUG_TIME
|
|
XP_TRACE ((" %lu: %s", result, ctime(&result)));
|
|
#endif /* DEBUG_TIME */
|
|
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
#if 0
|
|
main ()
|
|
{
|
|
FILE *f = fopen("/u/jwz/DATES", "r"); /* This is a file containing the
|
|
228128 occurences of "^Date:"
|
|
that were in our news spool in
|
|
late June 1994. */
|
|
char buf [255];
|
|
char *s;
|
|
XP_Bool flip = TRUE;
|
|
int line = 0;
|
|
int count = 0;
|
|
int failed = 0;
|
|
int dubious = 0;
|
|
while ((s = fgets (buf, sizeof(buf)-1, f)))
|
|
{
|
|
if (s[0] == 'D' || s[0] == 'd')
|
|
{
|
|
s = XP_STRCHR(s, ':');
|
|
if (s && *s)
|
|
{
|
|
time_t t = XP_ParseTimeString (++s, (flip = !flip));
|
|
count++;
|
|
if (t < 757411200L) /* "jan 1 00:00:00 1994" */
|
|
{
|
|
if (t < 1)
|
|
failed++;
|
|
else
|
|
dubious++;
|
|
fprintf (stderr, " %6d: %10lu: %s", line, t, s);
|
|
}
|
|
}
|
|
}
|
|
line++;
|
|
}
|
|
fprintf (stderr, "\ndates: %d; failed: %d; dubious = %d\n",
|
|
count, failed, dubious);
|
|
fclose(f);
|
|
}
|
|
#endif /* 0 */
|