///+INCOMPLETE /******************************************************************************* * c0xtimezone.c * Proposed ISO/IEC C 200X 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' defined to a non-zero value to get a main() * test driver. * * 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 * 2004-07-12 */ /* Identification */ static const char REV[] = "@(#)drt/text/stdc/c0xtimezone.c $Revision: 1.8+ $ $Date: 2004-08-21 $"; /* 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 #if TEST #include #define _stdc_stdio_h 1 #endif /* Local includes */ #include "c0xcalendar.h" #include "c0xtimezone.h" /******************************************************************************* * Private constants *******************************************************************************/ 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 only enough * entries to illustrate the capabilities of the 'struct timezone' type. * Interesting entries include "India", "Nepal", "Chatham", etc. */ #define PZone_VS 20040821 struct PZone { const char * pz_name; /* Full name */ struct timezone pz_zone; /* Timezone setting */ }; static const struct PZone pzones[] = { #if _C0X_TIMEZONE_VS > 20040821 #error Struct timezone has changed #endif /* Entries are in east-to-west order */ { ":Zulu", /* pz_name */ { /* pz_zone */ _C0X_TIMEZONE_VS, /* pz_zone[0].tz__vers */ +(0*60 + 0)*60*1000L, /* pz_zone[0].tz_offset */ { { "GMT", 0 }, /* pz_zone[0].tz_z[0] */ { "GMT", 0 }, /* pz_zone[0].tz_z[1] */ } }, }, { ":UK/London", { _C0X_TIMEZONE_VS, +(0*60 + 0)*60*1000L, { { "GMT", 0 }, { "BST", 1 * 60*60 }, } }, }, { ":Azores", { _C0X_TIMEZONE_VS, +(1*60 + 0)*60*1000L, { { "Azores", 0 }, { "Azores", 0 }, } }, }, { ":Greenland", { _C0X_TIMEZONE_VS, +(3*60 + 0)*60*1000L, { { "Green", 0 }, { "Green", 0 }, } }, }, { ":US/Newfoundland", { _C0X_TIMEZONE_VS, +(3*60 + 30)*60*1000L, { { "Newf", 0 }, { "NewfD", 1 * 60*60 }, } }, }, { ":US/Atlantic", { _C0X_TIMEZONE_VS, +(4*60 + 0)*60*1000L, { { "AST", 0 }, { "ADT", 1 * 60*60 }, } }, }, { ":US/Eastern", { _C0X_TIMEZONE_VS, +(5*60 + 0)*60*1000L, { { "EST", 0 }, { "EDT", 1 * 60*60 }, } }, }, { ":US/Central", { _C0X_TIMEZONE_VS, +(6*60 + 0)*60*1000L, { { "CST", 0 }, { "CDT", 1 * 60*60 }, } }, }, { ":US/Mountain", { _C0X_TIMEZONE_VS, +(7*60 + 0)*60*1000L, { { "MST", 0 }, { "MDT", 1 * 60*60 }, } }, }, { ":US/Pacific", { _C0X_TIMEZONE_VS, +(8*60 + 0)*60*1000L, { { "PST", 0 }, { "PDT", 1 * 60*60 }, } }, }, { ":US/Nevada", { _C0X_TIMEZONE_VS, +(8*60 + 0)*60*1000L, { { "PST", 0 }, { "PST", 0 }, } }, }, { ":Pitcairn", { _C0X_TIMEZONE_VS, +(8*60 + 30)*60*1000L, { { "Pitcairn", 0 }, { "Pitcairn", 0 }, } }, }, { ":US/Alaska", { _C0X_TIMEZONE_VS, +(9*60 + 0)*60*1000L, { { "?", 0 }, { "?", 1 * 60*60 }, } }, }, { ":Marquesas", { _C0X_TIMEZONE_VS, +(9*60 + 30)*60*1000L, { { "Marquesas", 0 }, { "Marquesas", 0 }, } }, }, { ":US/Hawaii", { _C0X_TIMEZONE_VS, +(10*60 + 0)*60*1000L, { { "HST", 0 }, { "HDT", 1 * 60*60 }, } }, }, { ":Tonga", { _C0X_TIMEZONE_VS, -(13*60 + 0)*60*1000L, { { "Tonga", 0 }, { "Tonga", 0 }, } }, }, { ":Chatham", { _C0X_TIMEZONE_VS, -(12*60 + 45)*60*1000L, { { "Chatham", 0 }, { "Chatham", 0 }, } }, }, { ":New Zealand", { _C0X_TIMEZONE_VS, -(12*60 + 0)*60*1000L, { { "NZT?", 0 }, { "NZD?", 1 * 60*60 }, } }, }, { ":Australia/Eastern", { _C0X_TIMEZONE_VS, -(10*60 + 0)*60*1000L, { { "EST", 0 }, { "EDT", 1 * 60*60 }, } }, }, { ":Australia/Central", { _C0X_TIMEZONE_VS, -(9*60 + 30)*60*1000L, { { "CST", 0 }, { "CDT", 1 * 60*60 }, } }, }, { ":Japan", { _C0X_TIMEZONE_VS, -(9*60 + 0)*60*1000L, { { "Japan", 0 }, { "Japan", 0 }, } }, }, { ":Australia/Western", { _C0X_TIMEZONE_VS, -(8*60 + 0)*60*1000L, { { "WST", 0 }, { "WDT", 1 * 60*60 }, } }, }, { ":China", { _C0X_TIMEZONE_VS, -(8*60 + 0)*60*1000L, { { "China", 0 }, { "China", 0 }, } }, }, { ":Burma", { _C0X_TIMEZONE_VS, -(6*60 + 30)*60*1000L, { { "Burma", 0 }, { "Burma", 0 }, } }, }, { ":Nepal", { _C0X_TIMEZONE_VS, -(5*60 + 40)*60*1000L, { { "Nepal", 0 }, { "Nepal", 0 }, } }, }, { ":India", { _C0X_TIMEZONE_VS, -(5*60 + 30)*60*1000L, { { "India", 0 }, { "India", 0 }, } }, }, { ":Afghanistan", { _C0X_TIMEZONE_VS, -(4*60 + 30)*60*1000L, { { "Afghan", 0 }, { "Afghan", 0 }, } }, }, { ":Iran", { _C0X_TIMEZONE_VS, -(3*60 + 30)*60*1000L, { { "Iran", 0 }, { "Iran", 0 }, } }, }, { ":Turkey", { _C0X_TIMEZONE_VS, -(3*60 + 0)*60*1000L, { { "Turkey", 0 }, { "Turkey", 0 }, } }, }, { ":Europe", { _C0X_TIMEZONE_VS, -(1*60 + 0)*60*1000L, { { "Europe", 0 }, { "Europe", 0 }, } }, }, ///+INCOMPLETE ///..., }; 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 'tzname'. * * 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 GMT 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 GMT; * 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 GMT. * * The timezone name can also be of the form "[s][h]h[[:]mm[[:]ss]]", * where: * s is an optional "+" (west) or "-" (east) sign character; * hh is the number of hours offset from GMT; * mm is the optional number of minutes offset from GMT; * ss is the optional number of seconds offset from GMT. * * Thus the names "CST6" and "+0600" specify the same timezone, having a +6 * hour offset (west) from GMT and no DST adjustments. The name "CST6CDT" * specifies a timezone named "CST" having a +6 hour offset (west) from GMT * and a one-hour DST-adjusted name of "CDT". * * Returns * Zero on success, otherwise -1 on error. * * Since * 1.9, 2004-08-21 */ int inittimezone(struct timezone *zone, const char *name) { #if _C0X_TIMEZONE_VS > 20040821 #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("$ TZ=\"%.200s\"\n", name); fflush(stdout); #endif if (name == NULL or name[0] == '\0') goto fail; } else if (strcmp(name, "Z") == 0) { name = "GMT0"; } /* Determine the format of the zone name */ 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 */ *zone = pzones[i].pz_zone; zone->tz__vers = _C0X_TIMEZONE_VS; #if DEBUG printf("$ found pzones[%d]\n", i); fflush(stdout); #endif 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: '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; i = j = 0; if (name[0] == '+' or name[0] == '-') i++; if (isdigit(name[i])) { long hhmmss; long off; /* Format: '[s][h]h[[:]mm[[:]ss]]' */ hhmmss = 0; while (isdigit(name[i])) { hhmmss = hhmmss*10 + name[i++]-'0'; if (name[i] == ':') i++; } if (hhmmss < 100) hhmmss *= 100; /* hh -> hh00 */ if (hhmmss < 10000) hhmmss *= 100; /* hhmm -> hhmm00 */ off = (hhmmss/10000 * 60*60) + (hhmmss%10000/100 * 60) + (hhmmss%100); if (name[0] == '-') off = -off; /* Init the timezone object from the name components */ zone->tz__vers = _C0X_TIMEZONE_VS; zone->tz_offset = off * 1000L; if (hhmmss%100 > 0) { sprintf(zone->tz_z[0].z_name, "%c%02ld%02ld%02ld", (off < 0 ? '-' : '+'), hhmmss/10000, hhmmss%10000/100, hhmmss%100); } else { sprintf(zone->tz_z[0].z_name, "%c%02ld%02ld", (off < 0 ? '-' : '+'), hhmmss/10000, hhmmss%10000/100); } zone->tz_z[0].z_dst = 0; zone->tz_z[1].z_name[0] = '\0'; zone->tz_z[1].z_dst = 0; } else { /* Unknown zone name format */ goto fail; } } /* Success */ return (0); fail: /* Failure */ 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.9, 2004-08-22 */ int mktimezonename(char *buf, size_t max, const struct timezone *zone) { #if _C0X_TIMEZONE_VS > 20040822 #error struct timezone has changed #endif int i, j; long off; char num[20]; /* Check args */ if (buf == NULL and max > 0) return (-1); /* Handle special case */ if (zone == NULL) { /* Return "Z" (GMT) */ if (max < 1+1) return (-(1+1)); strcpy(buf, "Z"); return (1+1); } /* Construct a name for the timezone combination */ i = j = 0; while (zone->tz_z[0].z_name[j] != '\0') buf[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') { /* Append the hour offset of the zone */ off = zone->tz_offset; sprintf(num, "%ld", off/(60*60*1000)); j = 0; while (num[j] != '\0') buf[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') buf[i++] = zone->tz_z[1].z_name[j++]; if (zone->tz_z[1].z_dst != 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') buf[i++] = num[j++]; } } } // Done buf[i] = '\0'; return (i); } /*------------------------------------------------------------------------------ * timezonedst() * Determine the DST adjustment to make for a timezone and a given * calendar date. * * Parameter date * Points to a calendar date object. * * Parameter zone * Points to a timezone object. * * Returns * The index of the DST adjustment (.tz_z[i]) to apply, or -1 on error. * * Since * 1.9, 2004-08-22 */ int timezonedst(const struct calendar *date, const struct timezone *zone) { /* Check args */ if (date == NULL) return (-1); if (date->cal_year == _CAL_YR_ERROR) return (-1); if (zone == NULL) return (0); /* Determine the DST adjustment for the date in the given zone */ /* Note: American DST rules assumed for simplicity */ #if __INCOMPLETE_CODE__ +INCOMPLETE ... #endif // __INCOMPLETE_CODE__ return (-1); } /*------------------------------------------------------------------------------ * timezoneoffset() * Determine the offset in milliseconds from GMT 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. * * Returns * The number of milliseconds offset (west) from GMT of the calendar date * object, or '_TZ_ERROR' on error. * * Since * 1.9, 2004-08-22 */ long int timezoneoffset(const struct calendar *date, const struct timezone *zone) { /* Check args */ if (date == NULL) return (_TZ_ERROR); if (date->cal_year == _CAL_YR_ERROR) return (_TZ_ERROR); if (zone == NULL) return (0); /* Determine the offset from GMT for the date in the given zone */ #if __INCOMPLETE_CODE__ +INCOMPLETE ... #endif // __INCOMPLETE_CODE__ return (_TZ_ERROR); } /******************************************************************************/ /******************************************************************************/ /******************************************************************************/ #if TEST /*------------------------------------------------------------------------------ * main() * Test driver. * * Since * 1.9, 2004-07-29 */ int main(int argc, const char *const *argv) { #if _C0X_TIMEZONE_VS > 20040821 #error struct timezone has changed #endif const char * prog; int i; /* Check args */ prog = argv[0]; if (argc < 2) { int o; usage: 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 'GMT0GMT0')\n"); printf("\n"); printf("Supported zones:\n"); o = npzones/3; for (i = 0; i < npzones/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 : "")); } 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.%03ld sec (%c%02ld:%02ld:%02ld) " "west of GMT\n", 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: %+d sec\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); } } return (0); } #endif /* TEST */ /* End c0xtimezone.c */