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