/******************************************************************************* * c0xtimezone.c * Proposed ISO/IEC C 201X timezone types and functions. * * This source file implements several proposed timezone functions as a * proof of concept; most of the algorithms used are for illustration * purposes only and are not meant for production-quality systems. * * Notes * Compile with macro 'TEST' or 'TIMEZONE_TEST' defined to a true * (non-zero) value to get a main() test driver. * Compile with macro 'DEBUG' defined to a true (non-zero) value to get * code that produces verbose debugging output. * * Functions * inittimezone() * mktimezonename() * timezonedst() * timezoneoffset() * * Author * This code was written by David R. Tribble (david@tribble.com), Jul 2004. * It is hereby placed into the public domain, and may be used for all * purposes, both commercial and private. No warranty is provided for this * source code, and the author cannot be held liable for its use in any * way. * * References * * * * See also * c0xtimezone.h * c0xcalendar.h * * Since * 2001-12-27 */ /* Identification */ static const char REV[] = "@(#)drt/text/std/c0xtimezone.c $Revision: 1.10 $ $Date: 2009/10/10 15:37:54 $"; /* Standard includes */ #include #define _stdc_ctype_h 1 #include #define _stdc_iso646_h 1 #include #define _stdc_stddef_h 1 #include #define _stdc_stdlib_h 1 #include #define _stdc_string_h 1 #ifdef TIMEZONE_TEST #ifndef TEST #define TEST TIMEZONE_TEST #endif #endif #if DEBUG || TEST #include #define _stdc_stdio_h 1 #endif /* Local includes */ #include "c0xcalendar.h" #include "c0xtimezone.h" /******************************************************************************* * Public variables *******************************************************************************/ /*------------------------------------------------------------------------------ * _c0x_timezone_utc * UTC (Z) timezone. * * Notes * This (constant) variable is not part of the proposed standard library, * but is meant to be used only by this particular implementation. */ #define TZONE_ENT(std, dst, hr, min, dstadj) \ {\ _C0X_TIMEZONE_VS, /* tz__vers */\ (hr*60 + min)*60*1000L, /* tz_offset */\ { /* tz_z */\ { std, 0 }, /* tz_z[0] */\ { dst, dstadj*60*60 }, /* tz_z[1] */\ }\ } const struct timezone _c0x_timezone_utc = TZONE_ENT("UTC", "UTC", +0, 0, 0); /******************************************************************************* * Private constants *******************************************************************************/ #define WDAY_SAT 6 #define MON_APR 4 #define MON_OCT 10 static const char *const TZ_ENV = /* Timezone environ var */ "TZ"; /*------------------------------------------------------------------------------ * pzones[] * List of known named timezones. * * Notes * This is not meant to be an exhaustive list, but contains enough entries * to illustrate the capabilities of the 'struct timezone' type. * In a real implementation, this table would probably be loaded from an * external file or database. * Entries with interesting UTC offsets include "India", "Nepal", * "Chatham", et al. */ #define PZone_VS 20040821 struct PZone { const char * pz_name; /* Full name */ struct timezone pz_zone; /* Timezone setting */ }; #define PZONE_ENT(name, std, dst, hr, min, dstadj) \ {\ name, /* pz_name */\ TZONE_ENT(std, dst, hr, min, dstadj)\ } static const struct PZone pzones[] = { #if _C0X_TIMEZONE_VS > 20091008 #error Struct timezone has changed #endif /* Entries are in east-to-west order */ PZONE_ENT(":Tonga", "???", "???", +13, 0, 0), PZONE_ENT(":Chatham", "???", "???", +12, +45, 0), PZONE_ENT(":New Zealand", "NZT", "NZD", +12, 0, +1), PZONE_ENT(":Old New Zealand", "OZT", "OZD", +11, +30, +1), PZONE_ENT(":Russia/10", "Z11", "11D", +11, 0, +1), PZONE_ENT(":Guam", "GST", "GSD", +10, 0, +1), PZONE_ENT(":Australian/Eastern", "???", "???", +10, 0, +1), /*?*/ PZONE_ENT(":Australian/South", "SAT", "SAD", +9, +30, +1), PZONE_ENT(":Japan", "JST", "JSD", +9, 0, +1), PZONE_ENT(":Australian/Western", "???", "???", +8, 0, +1), /*?*/ PZONE_ENT(":China", "CCT", "CCD", +8, 0, +1), PZONE_ENT(":Java", "JVT", "JVD", +7, +30, +1), PZONE_ENT(":Sumatra/South", "SST", "SSD", +7, 0, +1), PZONE_ENT(":Sumatra/North", "NST", "NSD", +6, +30, +1), PZONE_ENT(":Burma", "???", "???", +6, +30, +1), PZONE_ENT(":Russia/5", "R5T", "R5D", +6, 0, +1), PZONE_ENT(":Nepal", "???", "???", +5, +40, +1), PZONE_ENT(":India", "INT", "IND", +5, +30, +1), PZONE_ENT(":Russia/4", "R4T", "R4D", +5, 0, +1), PZONE_ENT(":Afghanistan", "???", "???", +4, +30, +1), PZONE_ENT(":Russia/3", "R3T", "R3D", +4, 0, +1), PZONE_ENT(":Iran", "IRT", "IRD", +3, +30, +1), PZONE_ENT(":Iraq/Baghdad", "BGT", "BGD", +3, 0, +1), PZONE_ENT(":Kenya", "KET", "KED", +2, +30, +1), PZONE_ENT(":European/Eastern", "EET", "EED", +2, 0, +1), PZONE_ENT(":European/Central", "CET", "CED", +1, 0, +1), PZONE_ENT(":Zulu", "UTC", "UTC", +0, 0, 0), PZONE_ENT(":Greenwich", "GMT", "GMD", +0, 0, +1), PZONE_ENT(":UK", "GMT", "GMD", +0, 0, +1), PZONE_ENT(":Africa/West", "WAT", "WAD", -1, 0, +1), PZONE_ENT(":Azores", "AZT", "AZD", -2, 0, +1), /*-1?*/ PZONE_ENT(":Brazil/2", "BZT", "BZD", -3, 0, +1), PZONE_ENT(":Greenland", "???", "???", -3, 0, +1), PZONE_ENT(":US/Newfoundland", "NFT", "NFD", -3, -30, +1), PZONE_ENT(":US/Atlantic", "AST", "ADT", -4, 0, +1), PZONE_ENT(":US/Eastern", "EST", "EDT", -5, 0, +1), PZONE_ENT(":US/Central", "CST", "CDT", -6, 0, +1), PZONE_ENT(":US/Mountain", "MST", "MDT", -7, 0, +1), PZONE_ENT(":US/Pacific", "PST", "PDT", -8, 0, +1), PZONE_ENT(":US/Nevada", "PST", "PST", -8, 0, 0), PZONE_ENT(":Pitcairn", "???", "???", -8, -30, 0), PZONE_ENT(":Yukon", "YST", "YDT", -9, 0, +1), PZONE_ENT(":Marquesas", "???", "???", -9, -30, 0), PZONE_ENT(":US/Alaska", "AHT", "AHD", -10, 0, +1), PZONE_ENT(":US/Hawaii", "HST", "HSD", -10, -30, +1), PZONE_ENT(":US/Nome", "NAT", "NAD", -11, 0, +1), PZONE_ENT(":International Date Line", "IDL", "IDL", -12, 0, 0), }; static const int npzones = sizeof(pzones)/sizeof(pzones[0]); /******************************************************************************* * Public functions *******************************************************************************/ /*------------------------------------------------------------------------------ * inittimezone() * Find a supported timezone with a name matching a given string. * * Parameter zone * Pointer to a timezone object, which is filled (initialized) with the * appropriate information for a timezone matching the name 'name'. * * Parameter name * The name or abbreviated name of a timezone. If this is empty ("") or * null, the local timezone is used (as determined by the value of the "TZ" * environment variable). * * If the name is "Z", a timezone with zero offset from UTC and no DST * adjustments is used. * * The timezone name can be of the form "XXX[[s]n[YYY[[s]m]]", * where: * XXX is the abbreviated non-DST timezone name; * s is an optional "+" (west) or "-" (east) character; * n is a number specifying the hours offset from UTC; * YYY is the abbreviated DST timezone name; * s is an optional "+" (west) or "-" (east) character; * m is a number specifying the DST hours offset from UTC. * * The timezone name can also be of the form "N[D]", where N and D are of * the form "[s][h]h[[:]mm[[:]ss]]", and where: * s is an optional "+" (east) or "-" (west) sign character; * hh is the number of hours offset from UTC; * mm is the optional number of minutes offset from UTC; * ss is the optional number of seconds offset from UTC. * * Thus the names "CST6" and "-0600" specify the same timezone, having a * 6 hour offset west of UTC and no DST adjustments. The name "CST6CDT" * specifies a timezone named "CST" having a 6 hour offset west of UTC * and a one-hour DST-adjusted (5 hour offset) name of "CDT". * * Returns * Zero on success, otherwise -1 on error. * * Since * 1.8, 2004-08-21 */ int inittimezone(struct timezone *zone, const char *name) { #if _C0X_TIMEZONE_VS > 20091008 #error struct timezone has changed #endif #if DEBUG printf("$ inittimezone(zone=%08p, name=%08p=\"%.200s\")\n", zone, name, (name != NULL ? name : "")); fflush(stdout); #endif /* Check args */ if (zone == NULL) goto fail; if (name == NULL) name = ""; /* Check for a local timezone request */ if (name[0] == '\0') { /* Use the local timezone */ name = getenv(TZ_ENV); #if DEBUG printf("$ env TZ=\"%.200s\"\n", name); fflush(stdout); #endif if (name == NULL or name[0] == '\0') goto fail; } /* Determine the format of the zone name */ if (strcmp(name, "Z") == 0) { /* Use UTC (Zulu) timezone */ #if DEBUG printf("$ Z/UTC timezone\n"); fflush(stdout); #endif *zone = _c0x_timezone_utc; } else if (name[0] == ':') { int i; /* Format: ':xxx' */ /* Look up the zone name in a table of known names */ for (i = 0; i < npzones; i++) { if (strcmp(name, pzones[i].pz_name) == 0) { /* Found a matching zone name */ #if DEBUG printf("$ found pzones[%d]\n", i); fflush(stdout); #endif *zone = pzones[i].pz_zone; return 0; } } /* No matching zone name found */ goto fail; } else if (isalpha(name[0])) { char name1[20+1]; char name2[20+1]; int off1; int off2; char buf[20+1]; int i, j; /* Format (POSIX): 'XXX[s]n[YYY[[s]m]]' */ /* Extract the components of the name */ i = j = 0; while (isalpha(name[i]) and j < sizeof(name1)) name1[j++] = name[i++]; name1[j] = '\0'; j = 0; if (name[i] == '+' or name[i] == '-') buf[j++] = name[i++]; while (isdigit(name[i]) and j < sizeof(buf)) buf[j++] = name[i++]; buf[j] = '\0'; off1 = 0; if (buf[0] != '\0') off1 = atoi(buf); if (name[i] != '\0') { j = 0; while (isalpha(name[i]) and j < sizeof(name2)) name2[j++] = name[i++]; name2[j] = '\0'; j = 0; if (name[i] == '+' or name[i] == '-') buf[j++] = name[i++]; while (isdigit(name[i]) and j < sizeof(buf)) buf[j++] = name[i++]; buf[j] = '\0'; off2 = off1 - 1; if (buf[0] != '\0') off2 = atoi(buf); } else { strcpy(name2, name1); off2 = off1; } #if DEBUG printf("$ name1='%s' off1=%+d\n", name1, off1); printf("$ name2='%s' off2=%+d\n", name2, off2); fflush(stdout); #endif /* Init the timezone object from the name components */ zone->tz__vers = _C0X_TIMEZONE_VS; zone->tz_offset = -off1 * 60L*60*1000; strncpy(zone->tz_z[0].z_name, name1, sizeof(zone->tz_z[0].z_name)); zone->tz_z[0].z_name[sizeof(zone->tz_z[0].z_name)-1] = '\0'; zone->tz_z[0].z_dst = 0; strncpy(zone->tz_z[1].z_name, name2, sizeof(zone->tz_z[1].z_name)); zone->tz_z[1].z_name[sizeof(zone->tz_z[1].z_name)-1] = '\0'; zone->tz_z[1].z_dst = (off1 - off2) * 60*60; } else { char buf[20+1]; int i; int j; /* Check for format (ISO 8601, RFC 2822): 'N[D]' */ i = j = 0; if (name[0] == '+' or name[0] == '-') i++; if (isdigit(name[i])) { long hhmmss1 = 0; long hhmmss2 = 0; long off1 = 0; long off2 = 0; /* Format: '[s][h]h[[:]mm[[:]ss]][[s][h]h[[:]mm[[:]ss]]]' */ while (isdigit(name[i])) { hhmmss1 = hhmmss1*10 + name[i++]-'0'; if (name[i] == ':') i++; } if (hhmmss1 < 100) hhmmss1 *= 100; /* hh -> hh00 */ if (hhmmss1 < 10000) hhmmss1 *= 100; /* hhmm -> hhmm00 */ off1 = (hhmmss1/10000 * 60*60) + (hhmmss1%10000/100 * 60) + (hhmmss1%100); if (name[j] == '-') off1 = -off1; j = i; if (name[i] == '+' or name[i] == '-') i++; if (isdigit(name[i])) { while (isdigit(name[i])) { hhmmss2 = hhmmss2*10 + name[i++]-'0'; if (name[i] == ':') i++; } if (hhmmss2 < 100) hhmmss2 *= 100; /* hh -> hh00 */ if (hhmmss2 < 10000) hhmmss2 *= 100; /* hhmm -> hhmm00 */ off2 = (hhmmss2/10000 * 60*60) + (hhmmss2%10000/100 * 60) + (hhmmss2%100); if (name[j] == '-') off2 = -off2; } /* Init the timezone object from the name components */ zone->tz__vers = _C0X_TIMEZONE_VS; zone->tz_offset = off1 * 1000L; if (hhmmss1%100 > 0) { sprintf(zone->tz_z[0].z_name, "%c%02ld%02ld%02ld", (off1 < 0 ? '-' : '+'), hhmmss1/10000, hhmmss1%10000/100, hhmmss1%100); } else { sprintf(zone->tz_z[0].z_name, "%c%02ld%02ld", (off1 < 0 ? '-' : '+'), hhmmss1/10000, hhmmss1%10000/100); } zone->tz_z[0].z_dst = 0; if (hhmmss2 > 0) { if (hhmmss2%100 > 0) { sprintf(zone->tz_z[1].z_name, "%c%02ld%02ld%02ld", (off2 < 0 ? '-' : '+'), hhmmss2/10000, hhmmss2%10000/100, hhmmss2%100); } else { sprintf(zone->tz_z[1].z_name, "%c%02ld%02ld", (off2 < 0 ? '-' : '+'), hhmmss2/10000, hhmmss2%10000/100); } zone->tz_z[1].z_dst = off1 - off2; } else { strcpy(zone->tz_z[1].z_name, zone->tz_z[0].z_name); zone->tz_z[1].z_dst = 0; } #if DEBUG printf("$ name1='%s' off1=%+ld\n", zone->tz_z[0].z_name, off1); printf("$ name2='%s' off2=%+ld\n", zone->tz_z[1].z_name, off2); fflush(stdout); #endif } else { /* Unknown timezone name format */ goto fail; } } /* Success */ return 0; fail: /* Failure */ #if DEBUG printf("$ no matching timezone found: '%s'\n", (name != NULL ? name : "")); fflush(stdout); #endif if (zone != NULL) zone->tz_offset = _TZ_ERROR; return -1; } /*------------------------------------------------------------------------------ * mktimezonename() * Construct a compound timezone name from a timezone object. * * Parameter buf * Points to a character array that will be filled with at most 'max' * characters, representing the constructed compound timezone name. * This pointer cannot be null. * * Parameter max * Specifies the maximum number of characters, including a terminating null * character, that can be placed into the character array pointed to by * 'buf'. * * Parameter zone * Pointer to a timezone object. * * Returns * The number of characters, not including the terminating null character, * written into the array pointed to by 'buf' on success (which will not be * greater than 'max'), or -1 if an error occurs. * * Since * 1.8, 2004-08-22 */ int mktimezonename(char *buf, size_t max, const struct timezone *zone) { #if _C0X_TIMEZONE_VS > 20091008 #error struct timezone has changed #endif char name[200+1]; int i, j; /* Check args */ if (buf == NULL and max > 0) return -1; /* Handle special case */ if (zone == NULL) { /* Return "Z" (UTC) */ if (max < 1+1) return -(1+1); strcpy(buf, "Z"); return 1+1; } if (zone->tz__vers != _C0X_TIMEZONE_VS) return -1; /* Construct a name for the timezone combination */ i = j = 0; while (zone->tz_z[0].z_name[j] != '\0') name[i++] = zone->tz_z[0].z_name[j++]; if ((zone->tz_offset != 0 or zone->tz_z[1].z_dst != 0) and zone->tz_z[1].z_name[0] != '\0') { long off; char num[20]; /* Append the hour offset of the zone */ off = -zone->tz_offset; if (zone->tz_z[0].z_name[0] != '+' and zone->tz_z[0].z_name[0] != '-') { sprintf(num, "%ld", off/(60*60*1000)); j = 0; while (num[j] != '\0') name[i++] = num[j++]; } if (zone->tz_z[1].z_dst != 0) { /* Append the DST variant of the zone */ j = 0; while (zone->tz_z[1].z_name[j] != '\0') name[i++] = zone->tz_z[1].z_name[j++]; if (zone->tz_z[1].z_dst != +1*60*60) { off = (-zone->tz_offset - zone->tz_z[1].z_dst*1000L); sprintf(num, "%ld", off/(60*60*1000)); j = 0; while (num[j] != '\0') name[i++] = num[j++]; } } } /* Done */ name[i] = '\0'; if (i > max) return -(i+1); memcpy(buf, name, i+1); return i; } /*------------------------------------------------------------------------------ * timezonedst() * Determine the DST adjustment to make to a specified calendar date for a * specified timezone. * * Parameter date * Points to a calendar date object. * * Parameter zone * Points to a timezone object. * This can be null, in which case zero is returned. * * Returns * The index of the DST adjustment (.tz_z[i]) to apply, or -1 on error. * * Since * 1.8, 2004-08-22 */ int timezonedst(const struct calendar *date, const struct timezone *zone) { #if _C0X_TIMEZONE_VS > 20091008 #error struct timezone has changed #endif #if _C0X_CALENDAR_VS > 20060311 #error struct calendar has changed #endif int mon; /* Check args */ if (date == NULL) return -1; if (date->cal_year == _CAL_YR_ERROR) return -1; if (date->cal_type != _CAL_TYPE_GREGORIAN) return -1; if (zone == NULL) return 0; if (zone->tz__vers != _C0X_TIMEZONE_VS) return -1; /* Determine the DST adjustment for the date in the given zone */ /* Note: Assume simplified American DST rules for simplicity; * i.e., DST occurs between {1st Sat in Apr} and {1st Sat in Oct} * at 02:00 LOC. * In a real implementation, these rules would be loaded from an external * file or database. */ mon = date->cal_mon; if (mon < MON_APR or mon > MON_OCT) return 0; if (mon > MON_APR and mon < MON_OCT) return 1; if (mon == MON_APR) { if (date->cal_mday > 7) return 1; if (date->cal_wday < WDAY_SAT) return 0; if (date->cal_hour >= 2) return 1; } if (mon == MON_OCT) { if (date->cal_mday > 7) return 0; if (date->cal_wday < WDAY_SAT) return 1; if (date->cal_hour >= 2) return 0; } return 0; } /*------------------------------------------------------------------------------ * timezoneoffset() * Determine the offset in milliseconds east of UTC of the time represented * by a timezone+DST combination for a given calendar date. * * Parameter date * Points to a calendar date object. * * Parameter zone * Points to a timezone object. * This can be null, in which case zero is returned. * * Returns * The number of milliseconds offset east of UTC of the calendar date * object, i.e., the time that must be added to the specified UTC calendar * time to properly reflect the specified timezone. * Returns '_TZ_ERROR' on error. * * Since * 1.8, 2004-08-22 */ long int timezoneoffset(const struct calendar *date, const struct timezone *zone) { #if _C0X_TIMEZONE_VS > 20091008 #error struct timezone has changed #endif #if _C0X_CALENDAR_VS > 20060311 #error struct calendar has changed #endif int dsti; long int off; /* Check args */ if (date == NULL) return _TZ_ERROR; if (date->cal_year == _CAL_YR_ERROR) return _TZ_ERROR; if (date->cal_type != _CAL_TYPE_GREGORIAN) return _TZ_ERROR; if (zone == NULL) return 0; if (zone->tz__vers != _C0X_TIMEZONE_VS) return _TZ_ERROR; /* Determine the offset from UTC for the date in the given zone */ dsti = date->cal_dsti; if (dsti < 0 or dsti > 1) return _TZ_ERROR; off = zone->tz_offset - zone->tz_z[dsti].z_dst; return off; } /******************************************************************************/ /******************************************************************************/ /******************************************************************************/ #if TEST /*------------------------------------------------------------------------------ * main() * Test driver. * * Since * 1.8, 2004-07-29 */ int main(int argc, const char *const *argv) { #if _C0X_TIMEZONE_VS > 20091008 #error struct timezone has changed #endif const char * prog; int i; /* Check args */ prog = argv[0]; if (argc < 2) { int o; usage: /* Display a program usage message */ printf("[%s]\n", REV); printf("\n"); printf("usage: %s timezone...\n", prog); printf("\n"); printf("A timezone can be specified in one of these formats:\n"); printf(" [+|-][h]h[[:]mm[[:]ss] (e.g., '+0600')\n"); printf(" XXX[+|-]nn[YYY[[+|-]mm] (e.g., 'CST6CDT5')\n"); printf(" :Name (see list below)\n"); printf(" Z (same as 'UTC0UTC0')\n"); printf("\n"); printf("Supported zones:\n"); o = (npzones+3-1)/3; for (i = 0; i < (npzones+3-1)/3; i++) { printf(" %-24s%-24s%s\n", pzones[0*o+i].pz_name, (1*o+i < npzones ? pzones[1*o+i].pz_name : ""), (2*o+i < npzones ? pzones[2*o+i].pz_name : "")); } /* Punt */ return 255; } /* Construct one or more timezones */ for (i = 1; i < argc; i++) { struct timezone zone; const char * name; /* Construct a timezone from a name */ name = argv[i]; if (inittimezone(&zone, name) >= 0) { int j; long off; char buf[80+1]; /* Dump the contents of the timezone object */ printf("'%s'\n", name); fflush(stdout); off = zone.tz_offset/1000; off = (off < 0 ? -off : off); printf(" tz_offset: " "%+ld, %+ld.%03lds (%c%02ld:%02ld:%02ld) east of UTC\n", zone.tz_offset, zone.tz_offset/1000, zone.tz_offset%1000, (zone.tz_offset < 0 ? '-' : '+'), off/(60*60), off/60%60, off%60); fflush(stdout); for (j = 0; j < sizeof(zone.tz_z)/sizeof(zone.tz_z[0]); j++) { printf(" tz_z[%d]:\n", j); printf(" z_name: '%s'\n", zone.tz_z[j].z_name); printf(" z_dst: %+ds\n", zone.tz_z[j].z_dst); fflush(stdout); } /* Construct a compound name for the zone */ if (mktimezonename(buf, sizeof(buf), &zone) <= 0) strcpy(buf, "*** ERROR ***"); printf(" => '%s'\n", buf); printf("\n"); fflush(stdout); } else { /* Can't initialize the timezone */ printf("Malformed timezone: '%s'\n", name); fflush(stdout); } } /* Success */ return 0; } #endif /* TEST */ /* End c0xtimezone.c */