gecko-dev/lib/xp/xp_time.c
1998-03-28 02:44:41 +00:00

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 */