commit 4872ce12519edfd154167590c7a98f287c4d671c Author: wollman Date: Tue Sep 13 03:29:24 1994 +0000 The latest and greatest timezone code from Arthur David Olson, part 1. This import comprises the `zic' and `zdump' programs. Obtained from: Arthur David Olson, ftp://elsie.nci.nih.gov/pub/tzcode94g diff --git a/usr.sbin/zic/README b/usr.sbin/zic/README new file mode 100644 index 000000000000..5eea1a2bcc96 --- /dev/null +++ b/usr.sbin/zic/README @@ -0,0 +1,72 @@ +@(#)README 7.5 + +"What time is it?" -- Richard Deacon as The King +"Any time you want it to be." -- Frank Baxter as The Scientist + (from the Bell System film on time) + +The 1989 update of the time zone package featured + +* POSIXization (including interpretation of POSIX-style TZ environment + variables, provided by Guy Harris), +* ANSIfication (including versions of "mktime" and "difftime"), +* SVIDulation (an "altzone" variable) +* MACHination (the "gtime" function) +* corrections to some time zone data (including corrections to the rules + for Great Britain and New Zealand) +* reference data from the United States Naval Observatory for folks who + want to do additional time zones +* and the 1989 data for Saudi Arabia. + +(Since this code will be treated as "part of the implementation" in some places +and as "part of the application" in others, there's no good way to name +functions, such as timegm, that are not part of the proposed ANSI C standard; +such functions have kept their old, underscore-free names in this update.) + +Support for the tz_abbr variable has been eliminated from this version +(to forestall "kitchen sink" complaints from certain quarters :-). + +Support for Turbo C compilation has also been eliminated; it was present to +allow checking in an ANSI-style environment, and such checking is now done with +gcc. + +And the "dysize" function has disappeared; it was present to allow compilation +of the "date" command on old BSD systems, and a version of "date" is now +provided in the package. The "date" command is not created when you "make all" +since it may lack options provided by the version distributed with your +operating system, or may not interact with the system in the same way the +native version does. + +Since POSIX frowns on correct leap second handling, the default behavior of +the "zic" command (in the absence of a "-L" option) has been changed to omit +leap second information from its output files. + +Be sure to read the comments in "Makefile" and make any changes +needed to make things right for your system. + +To use the new functions, use a "-lz" option when compiling or linking. + +Historical local time information has been included here not because it +is particularly useful, but rather to: + +* give an idea of the variety of local time rules that have + existed in the past and thus an idea of the variety that may be + expected in the future; + +* provide a test of the generality of the local time rule description + system. + +The information in the time zone data files is by no means authoritative; +if you know that the rules are different from those in a file, by all means +feel free to change file (and please send the changed version to +tz@elsie.nci.nih.gov for use in the future). Europeans take note! + +Thanks to these Timezone Caballeros who've made major contributions to the +time conversion package: Keith Bostic; Bob Devine; Paul Eggert; Robert Elz; +Guy Harris; Mark Horton; John Mackin; and Bradley White. Thanks also to +Michael Bloom, Art Neilson, Stephen Prince, John Sovereign, and Frank Wales +for testing work. None of them are responsible for remaining errors. + +Look in the ~ftp/pub directory of elsie.nci.nih.gov (128.231.16.1) +for updated versions of these files. + +Please send comments or information to ado@elsie.nci.nih.gov. diff --git a/usr.sbin/zic/Theory b/usr.sbin/zic/Theory new file mode 100644 index 000000000000..93a07c0f7da4 --- /dev/null +++ b/usr.sbin/zic/Theory @@ -0,0 +1,120 @@ +@(#)Theory 7.2 + +These time and date functions are much like the System V Release 2.0 (SVR2) +time and date functions; there are a few additions and changes to extend +the usefulness of the SVR2 functions: + +* In SVR2, time display in a process is controlled by the environment + variable TZ, which "must be a three-letter time zone name, followed + by a number representing the difference between local time and + Greenwich Mean Time in hours, followed by an optional three-letter + name for a daylight time zone;" when the optional daylight time zone is + present, "standard U.S.A. Daylight Savings Time conversion is applied." + This means that SVR2 can't deal with other (for example, Australian) + daylight savings time rules, or situations where more than two + time zone abbreviations are used in an area. + +* In SVR2, time conversion information is compiled into each program + that does time conversion. This means that when time conversion + rules change (as in the United States in 1987), all programs that + do time conversion must be recompiled to ensure proper results. + +* In SVR2, time conversion fails for near-minimum or near-maximum + time_t values when doing conversions for places that don't use GMT. + +* In SVR2, there's no tamper-proof way for a process to learn the + system's best idea of local wall clock. (This is important for + applications that an administrator wants used only at certain times-- + without regard to whether the user has fiddled the "TZ" environment + variable. While an administrator can "do everything in GMT" to get + around the problem, doing so is inconvenient and precludes handling + daylight savings time shifts--as might be required to limit phone + calls to off-peak hours.) + +* These functions can account for leap seconds, thanks to Bradley White + (bww@k.cs.cmu.edu). + +These are the changes that have been made to the SVR2 functions: + +* The "TZ" environment variable is used in generating the name of a file + from which time zone information is read (or is interpreted a la + POSIX); "TZ" is no longer constrained to be a three-letter time zone + name followed by a number of hours and an optional three-letter + daylight time zone name. The daylight saving time rules to be used + for a particular time zone are encoded in the time zone file; + the format of the file allows U.S., Australian, and other rules to be + encoded, and allows for situations where more than two time zone + abbreviations are used. + + It was recognized that allowing the "TZ" environment variable to + take on values such as "US/Eastern" might cause "old" programs + (that expect "TZ" to have a certain form) to operate incorrectly; + consideration was given to using some other environment variable + (for example, "TIMEZONE") to hold the string used to generate the + time zone information file name. In the end, however, it was decided + to continue using "TZ": it is widely used for time zone purposes; + separately maintaining both "TZ" and "TIMEZONE" seemed a nuisance; + and systems where "new" forms of "TZ" might cause problems can simply + use TZ values such as "EST5EDT" which can be used both by + "new" programs (a la POSIX) and "old" programs (as zone names and + offsets). + +* To handle places where more than two time zone abbreviations are used, + the functions "localtime" and "gmtime" set tzname[tmp->tm_isdst] + (where "tmp" is the value the function returns) to the time zone + abbreviation to be used. This differs from SVR2, where the elements + of tzname are only changed as a result of calls to tzset. + +* Since the "TZ" environment variable can now be used to control time + conversion, the "daylight" and "timezone" variables are no longer + needed or supported. (You can use a compile-time option to cause + these variables to be defined and to be set by "tzset"; however, their + values will not be used by "localtime.") + +* The "localtime" function has been set up to deliver correct results + for near-minimum or near-maximum time_t values. (A comment in the + source code tells how to get compatibly wrong results). + +* A function "tzsetwall" has been added to arrange for the system's + best approximation to local wall clock time to be delivered by + subsequent calls to "localtime." Source code for portable + applications that "must" run on local wall clock time should call + "tzsetwall();" if such code is moved to "old" systems that don't provide + tzsetwall, you won't be able to generate an executable program. + (These time zone functions also arrange for local wall clock time to be + used if tzset is called--directly or indirectly--and there's no "TZ" + environment variable; portable applications should not, however, rely + on this behavior since it's not the way SVR2 systems behave.) + +Points of interest to folks with Version 7 or BSD systems: + +* The BSD "timezone" function is not present in this package; + it's impossible to reliably map timezone's arguments (a "minutes west + of GMT" value and a "daylight saving time in effect" flag) to a + time zone abbreviation, and we refuse to guess. + Programs that in the past used the timezone function may now examine + tzname[localtime(&clock)->tm_isdst] to learn the correct time + zone abbreviation to use. Alternatively, use localtime(&clock)->tm_zone + if this has been enabled. + +* The BSD gettimeofday function is not used in this package; + this lets users control the time zone used in doing time conversions. + Users who don't try to control things (that is, users who do not set + the environment variable TZ) get the time conversion specified in the + file "/etc/zoneinfo/localtime"; see the time zone compiler writeup for + information on how to initialize this file. + +The functions that are conditionally compiled if STD_INSPIRED is defined should, +at this point, be looked on primarily as food for thought. They are not in +any sense "standard compatible"--some are not, in fact, specified in *any* +standard. They do, however, represent responses of various authors to +standardization proposals. + +Other time conversion proposals, in particular the one developed by folks at +Hewlett Packard, offer a wider selection of functions that provide capabilities +beyond those provided here. The absence of such functions from this package +is not meant to discourage the development, standardization, or use of such +functions. Rather, their absence reflects the decision to make this package +close to SVR2 (with the exceptions outlined above) to ensure its broad +acceptability. If more powerful time conversion functions can be standardized, +so much the better. diff --git a/usr.sbin/zic/ialloc.c b/usr.sbin/zic/ialloc.c new file mode 100644 index 000000000000..7dc2f2b494ac --- /dev/null +++ b/usr.sbin/zic/ialloc.c @@ -0,0 +1,103 @@ +#ifndef lint +#ifndef NOID +static char elsieid[] = "@(#)ialloc.c 8.21"; +#endif /* !defined NOID */ +#endif /* !defined lint */ + +/*LINTLIBRARY*/ + +#include "private.h" + +#ifdef MAL +#define NULLMAL(x) ((x) == NULL || (x) == MAL) +#endif /* defined MAL */ +#ifndef MAL +#define NULLMAL(x) ((x) == NULL) +#endif /* !defined MAL */ + +#define nonzero(n) (((n) == 0) ? 1 : (n)) + +char * icalloc P((int nelem, int elsize)); +char * icatalloc P((char * old, const char * new)); +char * icpyalloc P((const char * string)); +char * imalloc P((int n)); +char * irealloc P((char * pointer, int size)); +void ifree P((char * pointer)); + +char * +imalloc(n) +const int n; +{ +#ifdef MAL + register char * result; + + result = malloc((alloc_size_t) nonzero(n)); + return NULLMAL(result) ? NULL : result; +#endif /* defined MAL */ +#ifndef MAL + return malloc((alloc_size_t) nonzero(n)); +#endif /* !defined MAL */ +} + +char * +icalloc(nelem, elsize) +int nelem; +int elsize; +{ + if (nelem == 0 || elsize == 0) + nelem = elsize = 1; + return calloc((alloc_size_t) nelem, (alloc_size_t) elsize); +} + +char * +irealloc(pointer, size) +char * const pointer; +const int size; +{ + if (NULLMAL(pointer)) + return imalloc(size); + return realloc((genericptr_t) pointer, (alloc_size_t) nonzero(size)); +} + +char * +icatalloc(old, new) +char * const old; +const char * const new; +{ + register char * result; + register int oldsize, newsize; + + newsize = NULLMAL(new) ? 0 : strlen(new); + if (NULLMAL(old)) + oldsize = 0; + else if (newsize == 0) + return old; + else oldsize = strlen(old); + if ((result = irealloc(old, oldsize + newsize + 1)) != NULL) + if (!NULLMAL(new)) + (void) strcpy(result + oldsize, new); + return result; +} + +char * +icpyalloc(string) +const char * const string; +{ + return icatalloc((char *) NULL, string); +} + +void +ifree(p) +char * const p; +{ + if (!NULLMAL(p)) + (void) free(p); +} + +void +icfree(p) +char * const p; +{ + if (!NULLMAL(p)) + (void) free(p); +} diff --git a/usr.sbin/zic/scheck.c b/usr.sbin/zic/scheck.c new file mode 100644 index 000000000000..4d9616d645df --- /dev/null +++ b/usr.sbin/zic/scheck.c @@ -0,0 +1,62 @@ +#ifndef lint +#ifndef NOID +static char elsieid[] = "@(#)scheck.c 8.11"; +#endif /* !defined lint */ +#endif /* !defined NOID */ + +/*LINTLIBRARY*/ + +#include "private.h" + +extern char * imalloc P((int n)); +extern void ifree P((char * p)); + +char * +scheck(string, format) +const char * const string; +const char * const format; +{ + register char * fbuf; + register const char * fp; + register char * tp; + register int c; + register char * result; + char dummy; + static char nada[1]; + + result = nada; + if (string == NULL || format == NULL) + return result; + fbuf = imalloc(2 * strlen(format) + 4); + if (fbuf == NULL) + return result; + fp = format; + tp = fbuf; + while ((*tp++ = c = *fp++) != '\0') { + if (c != '%') + continue; + if (*fp == '%') { + *tp++ = *fp++; + continue; + } + *tp++ = '*'; + if (*fp == '*') + ++fp; + while (isascii(*fp) && isdigit(*fp)) + *tp++ = *fp++; + if (*fp == 'l' || *fp == 'h') + *tp++ = *fp++; + else if (*fp == '[') + do *tp++ = *fp++; + while (*fp != '\0' && *fp != ']'); + if ((*tp++ = *fp++) == '\0') + break; + } + *(tp - 1) = '%'; + *tp++ = 'c'; + *tp = '\0'; + if (sscanf(string, fbuf, &dummy) != 1) + result = (char *) format; + ifree(fbuf); + return result; +} diff --git a/usr.sbin/zic/zdump.8 b/usr.sbin/zic/zdump.8 new file mode 100644 index 000000000000..23f49461c78a --- /dev/null +++ b/usr.sbin/zic/zdump.8 @@ -0,0 +1,40 @@ +.TH ZDUMP 8 +.SH NAME +zdump \- time zone dumper +.SH SYNOPSIS +.B zdump +[ +.B \-v +] [ +.B \-c +cutoffyear ] [ zonename ... ] +.SH DESCRIPTION +.I Zdump +prints the current time in each +.I zonename +named on the command line. +.PP +These options are available: +.TP +.B \-v +For each +.I zonename +on the command line, +print the current time, +the time at the lowest possible time value, +the time one day after the lowest possible time value, +the times both one second before and exactly at +each detected time discontinuity, +the time at one day less than the highest possible time value, +and the time at the highest possible time value, +Each line ends with +.B isdst=1 +if the given time is Daylight Saving Time or +.B isdst=0 +otherwise. +.TP +.BI "\-c " cutoffyear +Cut off the verbose output near the start of the given year. +.SH "SEE ALSO" +newctime(3), tzfile(5), zic(8) +.\" @(#)zdump.8 7.2 diff --git a/usr.sbin/zic/zdump.c b/usr.sbin/zic/zdump.c new file mode 100644 index 000000000000..1e44d4a7974a --- /dev/null +++ b/usr.sbin/zic/zdump.c @@ -0,0 +1,307 @@ +#ifndef lint +#ifndef NOID +static char elsieid[] = "@(#)zdump.c 7.10"; +#endif /* !defined NOID */ +#endif /* !defined lint */ + +/* +** This code has been made independent of the rest of the time +** conversion package to increase confidence in the verification it provides. +** You can use this code to help in verifying other implementations. +*/ + +#include "stdio.h" /* for stdout, stderr */ +#include "string.h" /* for strcpy */ +#include "sys/types.h" /* for time_t */ +#include "time.h" /* for struct tm */ + +#ifndef MAX_STRING_LENGTH +#define MAX_STRING_LENGTH 1024 +#endif /* !defined MAX_STRING_LENGTH */ + +#ifndef TRUE +#define TRUE 1 +#endif /* !defined TRUE */ + +#ifndef FALSE +#define FALSE 0 +#endif /* !defined FALSE */ + +#ifndef EXIT_SUCCESS +#define EXIT_SUCCESS 0 +#endif /* !defined EXIT_SUCCESS */ + +#ifndef EXIT_FAILURE +#define EXIT_FAILURE 1 +#endif /* !defined EXIT_FAILURE */ + +#ifndef SECSPERMIN +#define SECSPERMIN 60 +#endif /* !defined SECSPERMIN */ + +#ifndef MINSPERHOUR +#define MINSPERHOUR 60 +#endif /* !defined MINSPERHOUR */ + +#ifndef SECSPERHOUR +#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) +#endif /* !defined SECSPERHOUR */ + +#ifndef HOURSPERDAY +#define HOURSPERDAY 24 +#endif /* !defined HOURSPERDAY */ + +#ifndef EPOCH_YEAR +#define EPOCH_YEAR 1970 +#endif /* !defined EPOCH_YEAR */ + +#ifndef TM_YEAR_BASE +#define TM_YEAR_BASE 1900 +#endif /* !defined TM_YEAR_BASE */ + +#ifndef DAYSPERNYEAR +#define DAYSPERNYEAR 365 +#endif /* !defined DAYSPERNYEAR */ + +#ifndef isleap +#define isleap(y) ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0) +#endif /* !defined isleap */ + +extern char ** environ; +extern int getopt(); +extern char * optarg; +extern int optind; +extern time_t time(); +extern char * tzname[2]; +extern void tzset(); + +#ifdef USG +extern void exit(); +extern void perror(); +#endif /* defined USG */ + +static char * abbr(); +static long delta(); +static time_t hunt(); +static int longest; +static char * progname; +static void show(); + +int +main(argc, argv) +int argc; +char * argv[]; +{ + register int i, c; + register int vflag; + register char * cutoff; + register int cutyear; + register long cuttime; + time_t now; + time_t t, newt; + time_t hibit; + struct tm tm, newtm; + + progname = argv[0]; + vflag = 0; + cutoff = NULL; + while ((c = getopt(argc, argv, "c:v")) == 'c' || c == 'v') + if (c == 'v') + vflag = 1; + else cutoff = optarg; + if (c != EOF || + (optind == argc - 1 && strcmp(argv[optind], "=") == 0)) { + (void) fprintf(stderr, +"%s: usage is %s [ -v ] [ -c cutoff ] zonename ...\n", + argv[0], argv[0]); + (void) exit(EXIT_FAILURE); + } + if (cutoff != NULL) { + int y; + + cutyear = atoi(cutoff); + cuttime = 0; + for (y = EPOCH_YEAR; y < cutyear; ++y) + cuttime += DAYSPERNYEAR + isleap(y); + cuttime *= SECSPERHOUR * HOURSPERDAY; + } + (void) time(&now); + longest = 0; + for (i = optind; i < argc; ++i) + if (strlen(argv[i]) > longest) + longest = strlen(argv[i]); + for (hibit = 1; (hibit << 1) != 0; hibit <<= 1) + continue; + for (i = optind; i < argc; ++i) { + register char ** saveenv; + static char buf[MAX_STRING_LENGTH]; + char * fakeenv[2]; + + if (strlen(argv[i]) + 4 > sizeof buf) { + (void) fflush(stdout); + (void) fprintf(stderr, "%s: argument too long -- %s\n", + progname, argv[i]); + (void) exit(EXIT_FAILURE); + } + (void) strcpy(buf, "TZ="); + (void) strcat(buf, argv[i]); + fakeenv[0] = buf; + fakeenv[1] = NULL; + saveenv = environ; + environ = fakeenv; + (void) tzset(); + environ = saveenv; + show(argv[i], now, FALSE); + if (!vflag) + continue; + /* + ** Get lowest value of t. + */ + t = hibit; + if (t > 0) /* time_t is unsigned */ + t = 0; + show(argv[i], t, TRUE); + t += SECSPERHOUR * HOURSPERDAY; + show(argv[i], t, TRUE); + tm = *localtime(&t); + (void) strncpy(buf, abbr(&tm), (sizeof buf) - 1); + for ( ; ; ) { + if (cutoff != NULL && t >= cuttime) + break; + newt = t + SECSPERHOUR * 12; + if (cutoff != NULL && newt >= cuttime) + break; + if (newt <= t) + break; + newtm = *localtime(&newt); + if (delta(&newtm, &tm) != (newt - t) || + newtm.tm_isdst != tm.tm_isdst || + strcmp(abbr(&newtm), buf) != 0) { + newt = hunt(argv[i], t, newt); + newtm = *localtime(&newt); + (void) strncpy(buf, abbr(&newtm), + (sizeof buf) - 1); + } + t = newt; + tm = newtm; + } + /* + ** Get highest value of t. + */ + t = ~((time_t) 0); + if (t < 0) /* time_t is signed */ + t &= ~hibit; + t -= SECSPERHOUR * HOURSPERDAY; + show(argv[i], t, TRUE); + t += SECSPERHOUR * HOURSPERDAY; + show(argv[i], t, TRUE); + } + if (fflush(stdout) || ferror(stdout)) { + (void) fprintf(stderr, "%s: Error writing standard output ", + argv[0]); + (void) perror("standard output"); + (void) exit(EXIT_FAILURE); + } + exit(EXIT_SUCCESS); + + /* gcc -Wall pacifier */ + for ( ; ; ) + continue; +} + +static time_t +hunt(name, lot, hit) +char * name; +time_t lot; +time_t hit; +{ + time_t t; + struct tm lotm; + struct tm tm; + static char loab[MAX_STRING_LENGTH]; + + lotm = *localtime(&lot); + (void) strncpy(loab, abbr(&lotm), (sizeof loab) - 1); + while ((hit - lot) >= 2) { + t = lot / 2 + hit / 2; + if (t <= lot) + ++t; + else if (t >= hit) + --t; + tm = *localtime(&t); + if (delta(&tm, &lotm) == (t - lot) && + tm.tm_isdst == lotm.tm_isdst && + strcmp(abbr(&tm), loab) == 0) { + lot = t; + lotm = tm; + } else hit = t; + } + show(name, lot, TRUE); + show(name, hit, TRUE); + return hit; +} + +/* +** Thanks to Paul Eggert (eggert@twinsun.com) for logic used in delta. +*/ + +static long +delta(newp, oldp) +struct tm * newp; +struct tm * oldp; +{ + long result; + int tmy; + + if (newp->tm_year < oldp->tm_year) + return -delta(oldp, newp); + result = 0; + for (tmy = oldp->tm_year; tmy < newp->tm_year; ++tmy) + result += DAYSPERNYEAR + isleap(tmy + TM_YEAR_BASE); + result += newp->tm_yday - oldp->tm_yday; + result *= HOURSPERDAY; + result += newp->tm_hour - oldp->tm_hour; + result *= MINSPERHOUR; + result += newp->tm_min - oldp->tm_min; + result *= SECSPERMIN; + result += newp->tm_sec - oldp->tm_sec; + return result; +} + +static void +show(zone, t, v) +char * zone; +time_t t; +int v; +{ + struct tm * tmp; + extern struct tm * localtime(); + + (void) printf("%-*s ", longest, zone); + if (v) + (void) printf("%.24s GMT = ", asctime(gmtime(&t))); + tmp = localtime(&t); + (void) printf("%.24s", asctime(tmp)); + if (*abbr(tmp) != '\0') + (void) printf(" %s", abbr(tmp)); + if (v) { + (void) printf(" isdst=%d", tmp->tm_isdst); +#ifdef TM_GMTOFF + (void) printf(" gmtoff=%ld", tmp->TM_GMTOFF); +#endif /* defined TM_GMTOFF */ + } + (void) printf("\n"); +} + +static char * +abbr(tmp) +struct tm * tmp; +{ + register char * result; + static char nada[1]; + + if (tmp->tm_isdst != 0 && tmp->tm_isdst != 1) + return nada; + result = tzname[tmp->tm_isdst]; + return (result == NULL) ? nada : result; +} diff --git a/usr.sbin/zic/zic.8 b/usr.sbin/zic/zic.8 new file mode 100644 index 000000000000..6972d0e38b33 --- /dev/null +++ b/usr.sbin/zic/zic.8 @@ -0,0 +1,412 @@ +.TH ZIC 8 +.SH NAME +zic \- time zone compiler +.SH SYNOPSIS +.B zic +[ +.B \-v +] [ +.B \-d +.I directory +] [ +.B \-l +.I localtime +] [ +.B \-p +.I posixrules +] [ +.B \-L +.I leapsecondfilename +] [ +.B \-s +] [ +.B \-y +.I command +] [ +.I filename +\&... ] +.SH DESCRIPTION +.if t .ds lq `` +.if t .ds rq '' +.if n .ds lq \&"\" +.if n .ds rq \&"\" +.de q +\\$3\*(lq\\$1\*(rq\\$2 +.. +.I Zic +reads text from the file(s) named on the command line +and creates the time conversion information files specified in this input. +If a +.I filename +is +.BR \- , +the standard input is read. +.PP +These options are available: +.TP +.BI "\-d " directory +Create time conversion information files in the named directory rather than +in the standard directory named below. +.TP +.BI "\-l " timezone +Use the given time zone as local time. +.I Zic +will act as if the input contained a link line of the form +.sp +.ti +.5i +Link \fItimezone\fP localtime +.TP +.BI "\-p " timezone +Use the given time zone's rules when handling POSIX-format +time zone environment variables. +.I Zic +will act as if the input contained a link line of the form +.sp +.ti +.5i +Link \fItimezone\fP posixrules +.TP +.BI "\-L " leapsecondfilename +Read leap second information from the file with the given name. +If this option is not used, +no leap second information appears in output files. +.TP +.B \-v +Complain if a year that appears in a data file is outside the range +of years representable by +.IR time (2) +values. +.TP +.B \-s +Limit time values stored in output files to values that are the same +whether they're taken to be signed or unsigned. +You can use this option to generate SVVS-compatible files. +.TP +.BI "\-y " command +Use the given +.I command +rather than +.B yearistype +when checking year types (see below). +.PP +Input lines are made up of fields. +Fields are separated from one another by any number of white space characters. +Leading and trailing white space on input lines is ignored. +An unquoted sharp character (#) in the input introduces a comment which extends +to the end of the line the sharp character appears on. +White space characters and sharp characters may be enclosed in double quotes +(") if they're to be used as part of a field. +Any line that is blank (after comment stripping) is ignored. +Non-blank lines are expected to be of one of three types: +rule lines, zone lines, and link lines. +.PP +A rule line has the form +.nf +.ti +.5i +.ta \w'Rule\0\0'u +\w'NAME\0\0'u +\w'FROM\0\0'u +\w'1973\0\0'u +\w'TYPE\0\0'u +\w'Apr\0\0'u +\w'lastSun\0\0'u +\w'2:00\0\0'u +\w'SAVE\0\0'u +.sp +Rule NAME FROM TO TYPE IN ON AT SAVE LETTER/S +.sp +For example: +.ti +.5i +.sp +Rule US 1967 1973 \- Apr lastSun 2:00 1:00 D +.sp +.fi +The fields that make up a rule line are: +.TP "\w'LETTER/S'u" +.B NAME +Gives the (arbitrary) name of the set of rules this rule is part of. +.TP +.B FROM +Gives the first year in which the rule applies. +Any integer year can be supplied; the Gregorian calendar is assumed. +The word +.B minimum +(or an abbreviation) means the minimum year representable as an integer. +The word +.B maximum +(or an abbreviation) means the maximum year representable as an integer. +Rules can describe times that are not representable as time values, +with the unrepresentable times ignored; this allows rules to be portable +among hosts with differing time value types. +.TP +.B TO +Gives the final year in which the rule applies. +In addition to +.B minimum +and +.B maximum +(as above), +the word +.B only +(or an abbreviation) +may be used to repeat the value of the +.B FROM +field. +.TP +.B TYPE +Gives the type of year in which the rule applies. +If +.B TYPE +is +.B \- +then the rule applies in all years between +.B FROM +and +.B TO +inclusive; +if +.B TYPE +is +.BR uspres , +the rule applies in U.S. Presidential election years; +if +.B TYPE +is +.BR nonpres , +the rule applies in years other than U.S. Presidential election years. +If +.B TYPE +is something else, then +.I zic +executes the command +.ti +.5i +\fByearistype\fP \fIyear\fP \fItype\fP +.br +to check the type of a year: +an exit status of zero is taken to mean that the year is of the given type; +an exit status of one is taken to mean that the year is not of the given type. +.TP +.B IN +Names the month in which the rule takes effect. +Month names may be abbreviated. +.TP +.B ON +Gives the day on which the rule takes effect. +Recognized forms include: +.nf +.in +.5i +.sp +.ta \w'Sun<=25\0\0'u +5 the fifth of the month +lastSun the last Sunday in the month +lastMon the last Monday in the month +Sun>=8 first Sunday on or after the eighth +Sun<=25 last Sunday on or before the 25th +.fi +.in -.5i +.sp +Names of days of the week may be abbreviated or spelled out in full. +Note that there must be no spaces within the +.B ON +field. +.TP +.B AT +Gives the time of day at which the rule takes effect. +Recognized forms include: +.nf +.in +.5i +.sp +.ta \w'1:28:13\0\0'u +2 time in hours +2:00 time in hours and minutes +15:00 24-hour format time (for times after noon) +1:28:14 time in hours, minutes, and seconds +.fi +.in -.5i +.sp +Any of these forms may be followed by the letter +.B w +if the given time is local +.q "wall clock" +time or +.B s +if the given time is local +.q standard +time; in the absence of +.B w +or +.BR s , +wall clock time is assumed. +.TP +.B SAVE +Gives the amount of time to be added to local standard time when the rule is in +effect. +This field has the same format as the +.B AT +field +(although, of course, the +.B w +and +.B s +suffixes are not used). +.TP +.B LETTER/S +Gives the +.q "variable part" +(for example, the +.q S +or +.q D +in +.q EST +or +.q EDT ) +of time zone abbreviations to be used when this rule is in effect. +If this field is +.BR \- , +the variable part is null. +.PP +A zone line has the form +.sp +.nf +.ti +.5i +.ta \w'Zone\0\0'u +\w'Australia/Adelaide\0\0'u +\w'GMTOFF\0\0'u +\w'RULES/SAVE\0\0'u +\w'FORMAT\0\0'u +Zone NAME GMTOFF RULES/SAVE FORMAT [UNTIL] +.sp +For example: +.sp +.ti +.5i +Zone Australia/Adelaide 9:30 Aus CST 1971 Oct 31 2:00 +.sp +.fi +The fields that make up a zone line are: +.TP "\w'GMTOFF'u" +.B NAME +The name of the time zone. +This is the name used in creating the time conversion information file for the +zone. +.TP +.B GMTOFF +The amount of time to add to GMT to get standard time in this zone. +This field has the same format as the +.B AT +and +.B SAVE +fields of rule lines; +begin the field with a minus sign if time must be subtracted from GMT. +.TP +.B RULES/SAVE +The name of the rule(s) that apply in the time zone or, +alternately, an amount of time to add to local standard time. +If this field is +.B \- +then standard time always applies in the time zone. +.TP +.B FORMAT +The format for time zone abbreviations in this time zone. +The pair of characters +.B %s +is used to show where the +.q "variable part" +of the time zone abbreviation goes. +.TP +.B UNTIL +The time at which the GMT offset or the rule(s) change for a location. +It is specified as a year, a month, a day, and a time of day. +If this is specified, +the time zone information is generated from the given GMT offset +and rule change until the time specified. +.IP +The next line must be a +.q continuation +line; this has the same form as a zone line except that the +string +.q Zone +and the name are omitted, as the continuation line will +place information starting at the time specified as the +.B UNTIL +field in the previous line in the file used by the previous line. +Continuation lines may contain an +.B UNTIL +field, just as zone lines do, indicating that the next line is a further +continuation. +.PP +A link line has the form +.sp +.nf +.ti +.5i +.if t .ta \w'Link\0\0'u +\w'LINK-FROM\0\0'u +.if n .ta \w'Link\0\0'u +\w'US/Eastern\0\0'u +Link LINK-FROM LINK-TO +.sp +For example: +.sp +.ti +.5i +Link US/Eastern EST5EDT +.sp +.fi +The +.B LINK-FROM +field should appear as the +.B NAME +field in some zone line; +the +.B LINK-TO +field is used as an alternate name for that zone. +.PP +Except for continuation lines, +lines may appear in any order in the input. +.PP +Lines in the file that describes leap seconds have the following form: +.nf +.ti +.5i +.ta \w'Leap\0\0'u +\w'YEAR\0\0'u +\w'MONTH\0\0'u +\w'DAY\0\0'u +\w'HH:MM:SS\0\0'u +\w'CORR\0\0'u +.sp +Leap YEAR MONTH DAY HH:MM:SS CORR R/S +.sp +For example: +.ti +.5i +.sp +Leap 1974 Dec 31 23:59:60 + S +.sp +.fi +The +.BR YEAR , +.BR MONTH , +.BR DAY , +and +.B HH:MM:SS +fields tell when the leap second happened. +The +.B CORR +field +should be +.q + +if a second was added +or +.q - +if a second was skipped. +.\" There's no need to document the following, since it's impossible for more +.\" than one leap second to be inserted or deleted at a time. +.\" The C Standard is in error in suggesting the possibility. +.\" See Terry J Quinn, The BIPM and the accurate measure of time, +.\" Proc IEEE 79, 7 (July 1991), 894-905. +.\" or +.\" .q ++ +.\" if two seconds were added +.\" or +.\" .q -- +.\" if two seconds were skipped. +The +.B R/S +field +should be (an abbreviation of) +.q Stationary +if the leap second time given by the other fields should be interpreted as GMT +or +(an abbreviation of) +.q Rolling +if the leap second time given by the other fields should be interpreted as +local wall clock time. +.SH NOTE +For areas with more than two types of local time, +you may need to use local standard time in the +.B AT +field of the earliest transition time's rule to ensure that +the earliest transition time recorded in the compiled file is correct. +.SH FILE +/usr/local/etc/zoneinfo standard directory used for created files +.SH "SEE ALSO" +newctime(3), tzfile(5), zdump(8) +.\" @(#)zic.8 7.7 diff --git a/usr.sbin/zic/zic.c b/usr.sbin/zic/zic.c new file mode 100644 index 000000000000..a0dc5f6bbdc3 --- /dev/null +++ b/usr.sbin/zic/zic.c @@ -0,0 +1,1939 @@ +#ifndef lint +#ifndef NOID +static char elsieid[] = "@(#)zic.c 7.22"; +#endif /* !defined NOID */ +#endif /* !defined lint */ + +#include "private.h" +#include "tzfile.h" + +struct rule { + const char * r_filename; + int r_linenum; + const char * r_name; + + int r_loyear; /* for example, 1986 */ + int r_hiyear; /* for example, 1986 */ + const char * r_yrtype; + + int r_month; /* 0..11 */ + + int r_dycode; /* see below */ + int r_dayofmonth; + int r_wday; + + long r_tod; /* time from midnight */ + int r_todisstd; /* above is standard time if TRUE */ + /* or wall clock time if FALSE */ + long r_stdoff; /* offset from standard time */ + const char * r_abbrvar; /* variable part of abbreviation */ + + int r_todo; /* a rule to do (used in outzone) */ + time_t r_temp; /* used in outzone */ +}; + +/* +** r_dycode r_dayofmonth r_wday +*/ + +#define DC_DOM 0 /* 1..31 */ /* unused */ +#define DC_DOWGEQ 1 /* 1..31 */ /* 0..6 (Sun..Sat) */ +#define DC_DOWLEQ 2 /* 1..31 */ /* 0..6 (Sun..Sat) */ + +struct zone { + const char * z_filename; + int z_linenum; + + const char * z_name; + long z_gmtoff; + const char * z_rule; + const char * z_format; + + long z_stdoff; + + struct rule * z_rules; + int z_nrules; + + struct rule z_untilrule; + time_t z_untiltime; +}; + +extern int emkdir P((const char * name, int mode)); +extern int getopt P((int argc, char * argv[], const char * options)); +extern char * icatalloc P((char * old, const char * new)); +extern char * icpyalloc P((const char * string)); +extern void ifree P((char * p)); +extern char * imalloc P((int n)); +extern char * irealloc P((char * old, int n)); +extern int link P((const char * fromname, const char * toname)); +extern char * optarg; +extern int optind; +extern char * scheck P((const char * string, const char * format)); + +static void addtt P((time_t starttime, int type)); +static int addtype P((long gmtoff, const char * abbr, int isdst, + int ttisstd)); +static void leapadd P((time_t t, int positive, int rolling, int count)); +static void adjleap P((void)); +static void associate P((void)); +static int ciequal P((const char * ap, const char * bp)); +static void convert P((long val, char * buf)); +static void dolink P((const char * fromfile, const char * tofile)); +static void eat P((const char * name, int num)); +static void eats P((const char * name, int num, + const char * rname, int rnum)); +static long eitol P((int i)); +static void error P((const char * message)); +static char ** getfields P((char * buf)); +static long gethms P((const char * string, const char * errstrng, + int signable)); +static void infile P((const char * filename)); +static void inleap P((char ** fields, int nfields)); +static void inlink P((char ** fields, int nfields)); +static void inrule P((char ** fields, int nfields)); +static int inzcont P((char ** fields, int nfields)); +static int inzone P((char ** fields, int nfields)); +static int inzsub P((char ** fields, int nfields, int iscont)); +static int itsabbr P((const char * abbr, const char * word)); +static int itsdir P((const char * name)); +static int lowerit P((int c)); +static char * memcheck P((char * tocheck)); +static int mkdirs P((char * filename)); +static void newabbr P((const char * abbr)); +static long oadd P((long t1, long t2)); +static void outzone P((const struct zone * zp, int ntzones)); +static void puttzcode P((long code, FILE * fp)); +static int rcomp P((const genericptr_t leftp, const genericptr_t rightp)); +static time_t rpytime P((const struct rule * rp, int wantedy)); +static void rulesub P((struct rule * rp, + char * loyearp, char * hiyearp, + char * typep, char * monthp, + char * dayp, char * timep)); +static void setboundaries P((void)); +static time_t tadd P((time_t t1, long t2)); +static void usage P((void)); +static void writezone P((const char * name)); +static int yearistype P((int year, const char * type)); + +static int charcnt; +static int errors; +static const char * filename; +static int leapcnt; +static int linenum; +static int max_int; +static time_t max_time; +static int max_year; +static int min_int; +static time_t min_time; +static int min_year; +static int noise; +static const char * rfilename; +static int rlinenum; +static const char * progname; +static int timecnt; +static int typecnt; +static int tt_signed; + +/* +** Line codes. +*/ + +#define LC_RULE 0 +#define LC_ZONE 1 +#define LC_LINK 2 +#define LC_LEAP 3 + +/* +** Which fields are which on a Zone line. +*/ + +#define ZF_NAME 1 +#define ZF_GMTOFF 2 +#define ZF_RULE 3 +#define ZF_FORMAT 4 +#define ZF_TILYEAR 5 +#define ZF_TILMONTH 6 +#define ZF_TILDAY 7 +#define ZF_TILTIME 8 +#define ZONE_MINFIELDS 5 +#define ZONE_MAXFIELDS 9 + +/* +** Which fields are which on a Zone continuation line. +*/ + +#define ZFC_GMTOFF 0 +#define ZFC_RULE 1 +#define ZFC_FORMAT 2 +#define ZFC_TILYEAR 3 +#define ZFC_TILMONTH 4 +#define ZFC_TILDAY 5 +#define ZFC_TILTIME 6 +#define ZONEC_MINFIELDS 3 +#define ZONEC_MAXFIELDS 7 + +/* +** Which files are which on a Rule line. +*/ + +#define RF_NAME 1 +#define RF_LOYEAR 2 +#define RF_HIYEAR 3 +#define RF_COMMAND 4 +#define RF_MONTH 5 +#define RF_DAY 6 +#define RF_TOD 7 +#define RF_STDOFF 8 +#define RF_ABBRVAR 9 +#define RULE_FIELDS 10 + +/* +** Which fields are which on a Link line. +*/ + +#define LF_FROM 1 +#define LF_TO 2 +#define LINK_FIELDS 3 + +/* +** Which fields are which on a Leap line. +*/ + +#define LP_YEAR 1 +#define LP_MONTH 2 +#define LP_DAY 3 +#define LP_TIME 4 +#define LP_CORR 5 +#define LP_ROLL 6 +#define LEAP_FIELDS 7 + +/* +** Year synonyms. +*/ + +#define YR_MINIMUM 0 +#define YR_MAXIMUM 1 +#define YR_ONLY 2 + +static struct rule * rules; +static int nrules; /* number of rules */ + +static struct zone * zones; +static int nzones; /* number of zones */ + +struct link { + const char * l_filename; + int l_linenum; + const char * l_from; + const char * l_to; +}; + +static struct link * links; +static int nlinks; + +struct lookup { + const char * l_word; + const int l_value; +}; + +static struct lookup const * byword P((const char * string, + const struct lookup * lp)); + +static struct lookup const line_codes[] = { + { "Rule", LC_RULE }, + { "Zone", LC_ZONE }, + { "Link", LC_LINK }, + { "Leap", LC_LEAP }, + { NULL, 0} +}; + +static struct lookup const mon_names[] = { + { "January", TM_JANUARY }, + { "February", TM_FEBRUARY }, + { "March", TM_MARCH }, + { "April", TM_APRIL }, + { "May", TM_MAY }, + { "June", TM_JUNE }, + { "July", TM_JULY }, + { "August", TM_AUGUST }, + { "September", TM_SEPTEMBER }, + { "October", TM_OCTOBER }, + { "November", TM_NOVEMBER }, + { "December", TM_DECEMBER }, + { NULL, 0 } +}; + +static struct lookup const wday_names[] = { + { "Sunday", TM_SUNDAY }, + { "Monday", TM_MONDAY }, + { "Tuesday", TM_TUESDAY }, + { "Wednesday", TM_WEDNESDAY }, + { "Thursday", TM_THURSDAY }, + { "Friday", TM_FRIDAY }, + { "Saturday", TM_SATURDAY }, + { NULL, 0 } +}; + +static struct lookup const lasts[] = { + { "last-Sunday", TM_SUNDAY }, + { "last-Monday", TM_MONDAY }, + { "last-Tuesday", TM_TUESDAY }, + { "last-Wednesday", TM_WEDNESDAY }, + { "last-Thursday", TM_THURSDAY }, + { "last-Friday", TM_FRIDAY }, + { "last-Saturday", TM_SATURDAY }, + { NULL, 0 } +}; + +static struct lookup const begin_years[] = { + { "minimum", YR_MINIMUM }, + { "maximum", YR_MAXIMUM }, + { NULL, 0 } +}; + +static struct lookup const end_years[] = { + { "minimum", YR_MINIMUM }, + { "maximum", YR_MAXIMUM }, + { "only", YR_ONLY }, + { NULL, 0 } +}; + +static struct lookup const leap_types[] = { + { "Rolling", TRUE }, + { "Stationary", FALSE }, + { NULL, 0 } +}; + +static const int len_months[2][MONSPERYEAR] = { + { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, + { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } +}; + +static const int len_years[2] = { + DAYSPERNYEAR, DAYSPERLYEAR +}; + +static time_t ats[TZ_MAX_TIMES]; +static unsigned char types[TZ_MAX_TIMES]; +static long gmtoffs[TZ_MAX_TYPES]; +static char isdsts[TZ_MAX_TYPES]; +static unsigned char abbrinds[TZ_MAX_TYPES]; +static char ttisstds[TZ_MAX_TYPES]; +static char chars[TZ_MAX_CHARS]; +static time_t trans[TZ_MAX_LEAPS]; +static long corr[TZ_MAX_LEAPS]; +static char roll[TZ_MAX_LEAPS]; + +/* +** Memory allocation. +*/ + +static char * +memcheck(ptr) +char * const ptr; +{ + if (ptr == NULL) { + (void) perror(progname); + (void) exit(EXIT_FAILURE); + } + return ptr; +} + +#define emalloc(size) memcheck(imalloc(size)) +#define erealloc(ptr, size) memcheck(irealloc(ptr, size)) +#define ecpyalloc(ptr) memcheck(icpyalloc(ptr)) +#define ecatalloc(oldp, newp) memcheck(icatalloc(oldp, newp)) + +/* +** Error handling. +*/ + +static void +eats(name, num, rname, rnum) +const char * const name; +const int num; +const char * const rname; +const int rnum; +{ + filename = name; + linenum = num; + rfilename = rname; + rlinenum = rnum; +} + +static void +eat(name, num) +const char * const name; +const int num; +{ + eats(name, num, (char *) NULL, -1); +} + +static void +error(string) +const char * const string; +{ + /* + ** Match the format of "cc" to allow sh users to + ** zic ... 2>&1 | error -t "*" -v + ** on BSD systems. + */ + (void) fprintf(stderr, "\"%s\", line %d: %s", + filename, linenum, string); + if (rfilename != NULL) + (void) fprintf(stderr, " (rule from \"%s\", line %d)", + rfilename, rlinenum); + (void) fprintf(stderr, "\n"); + ++errors; +} + +static void +usage() +{ + (void) fprintf(stderr, +"%s: usage is %s [ -s ] [ -v ] [ -l localtime ] [ -p posixrules ] [ -d directory ] \n\ +\t[ -L leapseconds ] [ -y yearistype ] [ filename ... ]\n", + progname, progname); + (void) exit(EXIT_FAILURE); +} + +static const char * psxrules; +static const char * lcltime; +static const char * directory; +static const char * leapsec; +static const char * yitcommand; +static int sflag = FALSE; + +int +main(argc, argv) +int argc; +char * argv[]; +{ + register int i, j; + register int c; + +#ifdef unix + (void) umask(umask(022) | 022); +#endif /* defined unix */ + progname = argv[0]; + while ((c = getopt(argc, argv, "d:l:p:L:vsy:")) != EOF) + switch (c) { + default: + usage(); + case 'd': + if (directory == NULL) + directory = optarg; + else { + (void) fprintf(stderr, +"%s: More than one -d option specified\n", + progname); + (void) exit(EXIT_FAILURE); + } + break; + case 'l': + if (lcltime == NULL) + lcltime = optarg; + else { + (void) fprintf(stderr, +"%s: More than one -l option specified\n", + progname); + (void) exit(EXIT_FAILURE); + } + break; + case 'p': + if (psxrules == NULL) + psxrules = optarg; + else { + (void) fprintf(stderr, +"%s: More than one -p option specified\n", + progname); + (void) exit(EXIT_FAILURE); + } + break; + case 'y': + if (yitcommand == NULL) + yitcommand = optarg; + else { + (void) fprintf(stderr, +"%s: More than one -y option specified\n", + progname); + (void) exit(EXIT_FAILURE); + } + break; + case 'L': + if (leapsec == NULL) + leapsec = optarg; + else { + (void) fprintf(stderr, +"%s: More than one -L option specified\n", + progname); + (void) exit(EXIT_FAILURE); + } + break; + case 'v': + noise = TRUE; + break; + case 's': + sflag = TRUE; + break; + } + if (optind == argc - 1 && strcmp(argv[optind], "=") == 0) + usage(); /* usage message by request */ + if (directory == NULL) + directory = TZDIR; + if (yitcommand == NULL) + yitcommand = "yearistype"; + + setboundaries(); + + if (optind < argc && leapsec != NULL) { + infile(leapsec); + adjleap(); + } + + zones = (struct zone *) emalloc(0); + rules = (struct rule *) emalloc(0); + links = (struct link *) emalloc(0); + for (i = optind; i < argc; ++i) + infile(argv[i]); + if (errors) + (void) exit(EXIT_FAILURE); + associate(); + for (i = 0; i < nzones; i = j) { + /* + ** Find the next non-continuation zone entry. + */ + for (j = i + 1; j < nzones && zones[j].z_name == NULL; ++j) + continue; + outzone(&zones[i], j - i); + } + /* + ** Make links. + */ + for (i = 0; i < nlinks; ++i) + dolink(links[i].l_from, links[i].l_to); + if (lcltime != NULL) + dolink(lcltime, TZDEFAULT); + if (psxrules != NULL) + dolink(psxrules, TZDEFRULES); + return (errors == 0) ? EXIT_SUCCESS : EXIT_FAILURE; +} + +static void +dolink(fromfile, tofile) +const char * const fromfile; +const char * const tofile; +{ + register char * fromname; + register char * toname; + + if (fromfile[0] == '/') + fromname = fromfile; + else { + fromname = ecpyalloc(directory); + fromname = ecatalloc(fromname, "/"); + fromname = ecatalloc(fromname, fromfile); + } + if (tofile[0] == '/') + toname = tofile; + else { + toname = ecpyalloc(directory); + toname = ecatalloc(toname, "/"); + toname = ecatalloc(toname, tofile); + } + /* + ** We get to be careful here since + ** there's a fair chance of root running us. + */ + if (!itsdir(toname)) + (void) remove(toname); + if (link(fromname, toname) != 0) { + if (mkdirs(toname) != 0) + (void) exit(EXIT_FAILURE); + if (link(fromname, toname) != 0) { + (void) fprintf(stderr, "%s: Can't link from %s to ", + progname, fromname); + (void) perror(toname); + (void) exit(EXIT_FAILURE); + } + } + if (fromname != fromfile) + ifree(fromname); + if (toname != tofile) + ifree(toname); +} + +static void +setboundaries() +{ + register time_t bit; + register int bii; + + for (bit = 1; bit > 0; bit <<= 1) + continue; + if (bit == 0) { /* time_t is an unsigned type */ + tt_signed = FALSE; + min_time = 0; + max_time = ~(time_t) 0; + if (sflag) + max_time >>= 1; + } else { + tt_signed = TRUE; + min_time = bit; + max_time = bit; + ++max_time; + max_time = -max_time; + if (sflag) + min_time = 0; + } + min_year = TM_YEAR_BASE + gmtime(&min_time)->tm_year; + max_year = TM_YEAR_BASE + gmtime(&max_time)->tm_year; + + for (bii = 1; bii > 0; bii <<= 1) + continue; + min_int = bii; + max_int = -1 - bii; +} + +static int +itsdir(name) +const char * const name; +{ + register char * myname; + register int accres; + + myname = ecpyalloc(name); + myname = ecatalloc(myname, "/."); + accres = access(myname, 0); + ifree(myname); + return accres == 0; +} + +/* +** Associate sets of rules with zones. +*/ + +/* +** Sort by rule name. +*/ + +static int +rcomp(cp1, cp2) +const genericptr_t cp1; +const genericptr_t cp2; +{ + return strcmp(((struct rule *) cp1)->r_name, + ((struct rule *) cp2)->r_name); +} + +static void +associate() +{ + register struct zone * zp; + register struct rule * rp; + register int base, out; + register int i; + + if (nrules != 0) + (void) qsort((genericptr_t) rules, + (qsort_size_t) nrules, + (qsort_size_t) sizeof *rules, rcomp); + for (i = 0; i < nzones; ++i) { + zp = &zones[i]; + zp->z_rules = NULL; + zp->z_nrules = 0; + } + for (base = 0; base < nrules; base = out) { + rp = &rules[base]; + for (out = base + 1; out < nrules; ++out) + if (strcmp(rp->r_name, rules[out].r_name) != 0) + break; + for (i = 0; i < nzones; ++i) { + zp = &zones[i]; + if (strcmp(zp->z_rule, rp->r_name) != 0) + continue; + zp->z_rules = rp; + zp->z_nrules = out - base; + } + } + for (i = 0; i < nzones; ++i) { + zp = &zones[i]; + if (zp->z_nrules == 0) { + /* + ** Maybe we have a local standard time offset. + */ + eat(zp->z_filename, zp->z_linenum); + zp->z_stdoff = gethms(zp->z_rule, "unruly zone", TRUE); + /* + ** Note, though, that if there's no rule, + ** a '%s' in the format is a bad thing. + */ + if (strchr(zp->z_format, '%') != 0) + error("%s in ruleless zone"); + } + } + if (errors) + (void) exit(EXIT_FAILURE); +} + +static void +infile(name) +const char * name; +{ + register FILE * fp; + register char ** fields; + register char * cp; + register const struct lookup * lp; + register int nfields; + register int wantcont; + register int num; + char buf[BUFSIZ]; + + if (strcmp(name, "-") == 0) { + name = "standard input"; + fp = stdin; + } else if ((fp = fopen(name, "r")) == NULL) { + (void) fprintf(stderr, "%s: Can't open ", progname); + (void) perror(name); + (void) exit(EXIT_FAILURE); + } + wantcont = FALSE; + for (num = 1; ; ++num) { + eat(name, num); + if (fgets(buf, (int) sizeof buf, fp) != buf) + break; + cp = strchr(buf, '\n'); + if (cp == NULL) { + error("line too long"); + (void) exit(EXIT_FAILURE); + } + *cp = '\0'; + fields = getfields(buf); + nfields = 0; + while (fields[nfields] != NULL) { + static char nada[1]; + + if (ciequal(fields[nfields], "-")) + fields[nfields] = nada; + ++nfields; + } + if (nfields == 0) { + /* nothing to do */ + } else if (wantcont) { + wantcont = inzcont(fields, nfields); + } else { + lp = byword(fields[0], line_codes); + if (lp == NULL) + error("input line of unknown type"); + else switch ((int) (lp->l_value)) { + case LC_RULE: + inrule(fields, nfields); + wantcont = FALSE; + break; + case LC_ZONE: + wantcont = inzone(fields, nfields); + break; + case LC_LINK: + inlink(fields, nfields); + wantcont = FALSE; + break; + case LC_LEAP: + if (name != leapsec) + (void) fprintf(stderr, +"%s: Leap line in non leap seconds file %s\n", + progname, name); + else inleap(fields, nfields); + wantcont = FALSE; + break; + default: /* "cannot happen" */ + (void) fprintf(stderr, +"%s: panic: Invalid l_value %d\n", + progname, lp->l_value); + (void) exit(EXIT_FAILURE); + } + } + ifree((char *) fields); + } + if (ferror(fp)) { + (void) fprintf(stderr, "%s: Error reading ", progname); + (void) perror(filename); + (void) exit(EXIT_FAILURE); + } + if (fp != stdin && fclose(fp)) { + (void) fprintf(stderr, "%s: Error closing ", progname); + (void) perror(filename); + (void) exit(EXIT_FAILURE); + } + if (wantcont) + error("expected continuation line not found"); +} + +/* +** Convert a string of one of the forms +** h -h hh:mm -hh:mm hh:mm:ss -hh:mm:ss +** into a number of seconds. +** A null string maps to zero. +** Call error with errstring and return zero on errors. +*/ + +static long +gethms(string, errstring, signable) +const char * string; +const char * const errstring; +const int signable; +{ + int hh, mm, ss, sign; + + if (string == NULL || *string == '\0') + return 0; + if (!signable) + sign = 1; + else if (*string == '-') { + sign = -1; + ++string; + } else sign = 1; + if (sscanf(string, scheck(string, "%d"), &hh) == 1) + mm = ss = 0; + else if (sscanf(string, scheck(string, "%d:%d"), &hh, &mm) == 2) + ss = 0; + else if (sscanf(string, scheck(string, "%d:%d:%d"), + &hh, &mm, &ss) != 3) { + error(errstring); + return 0; + } + if (hh < 0 || hh >= HOURSPERDAY || + mm < 0 || mm >= MINSPERHOUR || + ss < 0 || ss > SECSPERMIN) { + error(errstring); + return 0; + } + return eitol(sign) * + (eitol(hh * MINSPERHOUR + mm) * + eitol(SECSPERMIN) + eitol(ss)); +} + +static void +inrule(fields, nfields) +register char ** const fields; +const int nfields; +{ + static struct rule r; + + if (nfields != RULE_FIELDS) { + error("wrong number of fields on Rule line"); + return; + } + if (*fields[RF_NAME] == '\0') { + error("nameless rule"); + return; + } + r.r_filename = filename; + r.r_linenum = linenum; + r.r_stdoff = gethms(fields[RF_STDOFF], "invalid saved time", TRUE); + rulesub(&r, fields[RF_LOYEAR], fields[RF_HIYEAR], fields[RF_COMMAND], + fields[RF_MONTH], fields[RF_DAY], fields[RF_TOD]); + r.r_name = ecpyalloc(fields[RF_NAME]); + r.r_abbrvar = ecpyalloc(fields[RF_ABBRVAR]); + rules = (struct rule *) erealloc((char *) rules, + (int) ((nrules + 1) * sizeof *rules)); + rules[nrules++] = r; +} + +static int +inzone(fields, nfields) +register char ** const fields; +const int nfields; +{ + register int i; + static char * buf; + + if (nfields < ZONE_MINFIELDS || nfields > ZONE_MAXFIELDS) { + error("wrong number of fields on Zone line"); + return FALSE; + } + if (strcmp(fields[ZF_NAME], TZDEFAULT) == 0 && lcltime != NULL) { + buf = erealloc(buf, 132 + strlen(TZDEFAULT)); + (void) sprintf(buf, +"\"Zone %s\" line and -l option are mutually exclusive", + TZDEFAULT); + error(buf); + return FALSE; + } + if (strcmp(fields[ZF_NAME], TZDEFRULES) == 0 && psxrules != NULL) { + buf = erealloc(buf, 132 + strlen(TZDEFRULES)); + (void) sprintf(buf, +"\"Zone %s\" line and -p option are mutually exclusive", + TZDEFRULES); + error(buf); + return FALSE; + } + for (i = 0; i < nzones; ++i) + if (zones[i].z_name != NULL && + strcmp(zones[i].z_name, fields[ZF_NAME]) == 0) { + buf = erealloc(buf, 132 + + strlen(fields[ZF_NAME]) + + strlen(zones[i].z_filename)); + (void) sprintf(buf, +"duplicate zone name %s (file \"%s\", line %d)", + fields[ZF_NAME], + zones[i].z_filename, + zones[i].z_linenum); + error(buf); + return FALSE; + } + return inzsub(fields, nfields, FALSE); +} + +static int +inzcont(fields, nfields) +register char ** const fields; +const int nfields; +{ + if (nfields < ZONEC_MINFIELDS || nfields > ZONEC_MAXFIELDS) { + error("wrong number of fields on Zone continuation line"); + return FALSE; + } + return inzsub(fields, nfields, TRUE); +} + +static int +inzsub(fields, nfields, iscont) +register char ** const fields; +const int nfields; +const int iscont; +{ + register char * cp; + static struct zone z; + register int i_gmtoff, i_rule, i_format; + register int i_untilyear, i_untilmonth; + register int i_untilday, i_untiltime; + register int hasuntil; + + if (iscont) { + i_gmtoff = ZFC_GMTOFF; + i_rule = ZFC_RULE; + i_format = ZFC_FORMAT; + i_untilyear = ZFC_TILYEAR; + i_untilmonth = ZFC_TILMONTH; + i_untilday = ZFC_TILDAY; + i_untiltime = ZFC_TILTIME; + z.z_name = NULL; + } else { + i_gmtoff = ZF_GMTOFF; + i_rule = ZF_RULE; + i_format = ZF_FORMAT; + i_untilyear = ZF_TILYEAR; + i_untilmonth = ZF_TILMONTH; + i_untilday = ZF_TILDAY; + i_untiltime = ZF_TILTIME; + z.z_name = ecpyalloc(fields[ZF_NAME]); + } + z.z_filename = filename; + z.z_linenum = linenum; + z.z_gmtoff = gethms(fields[i_gmtoff], "invalid GMT offset", TRUE); + if ((cp = strchr(fields[i_format], '%')) != 0) { + if (*++cp != 's' || strchr(cp, '%') != 0) { + error("invalid abbreviation format"); + return FALSE; + } + } + z.z_rule = ecpyalloc(fields[i_rule]); + z.z_format = ecpyalloc(fields[i_format]); + hasuntil = nfields > i_untilyear; + if (hasuntil) { + z.z_untilrule.r_filename = filename; + z.z_untilrule.r_linenum = linenum; + rulesub(&z.z_untilrule, + fields[i_untilyear], + "only", + "", + (nfields > i_untilmonth) ? + fields[i_untilmonth] : "Jan", + (nfields > i_untilday) ? fields[i_untilday] : "1", + (nfields > i_untiltime) ? fields[i_untiltime] : "0"); + z.z_untiltime = rpytime(&z.z_untilrule, + z.z_untilrule.r_loyear); + if (iscont && nzones > 0 && + z.z_untiltime > min_time && + z.z_untiltime < max_time && + zones[nzones - 1].z_untiltime > min_time && + zones[nzones - 1].z_untiltime < max_time && + zones[nzones - 1].z_untiltime >= z.z_untiltime) { +error("Zone continuation line end time is not after end time of previous line"); + return FALSE; + } + } + zones = (struct zone *) erealloc((char *) zones, + (int) ((nzones + 1) * sizeof *zones)); + zones[nzones++] = z; + /* + ** If there was an UNTIL field on this line, + ** there's more information about the zone on the next line. + */ + return hasuntil; +} + +static void +inleap(fields, nfields) +register char ** const fields; +const int nfields; +{ + register const char * cp; + register const struct lookup * lp; + register int i, j; + int year, month, day; + long dayoff, tod; + time_t t; + + if (nfields != LEAP_FIELDS) { + error("wrong number of fields on Leap line"); + return; + } + dayoff = 0; + cp = fields[LP_YEAR]; + if (sscanf(cp, scheck(cp, "%d"), &year) != 1) { + /* + * Leapin' Lizards! + */ + error("invalid leaping year"); + return; + } + j = EPOCH_YEAR; + while (j != year) { + if (year > j) { + i = len_years[isleap(j)]; + ++j; + } else { + --j; + i = -len_years[isleap(j)]; + } + dayoff = oadd(dayoff, eitol(i)); + } + if ((lp = byword(fields[LP_MONTH], mon_names)) == NULL) { + error("invalid month name"); + return; + } + month = lp->l_value; + j = TM_JANUARY; + while (j != month) { + i = len_months[isleap(year)][j]; + dayoff = oadd(dayoff, eitol(i)); + ++j; + } + cp = fields[LP_DAY]; + if (sscanf(cp, scheck(cp, "%d"), &day) != 1 || + day <= 0 || day > len_months[isleap(year)][month]) { + error("invalid day of month"); + return; + } + dayoff = oadd(dayoff, eitol(day - 1)); + if (dayoff < 0 && !tt_signed) { + error("time before zero"); + return; + } + t = (time_t) dayoff * SECSPERDAY; + /* + ** Cheap overflow check. + */ + if (t / SECSPERDAY != dayoff) { + error("time overflow"); + return; + } + tod = gethms(fields[LP_TIME], "invalid time of day", FALSE); + cp = fields[LP_CORR]; + { + register int positive; + int count; + + if (strcmp(cp, "") == 0) { /* infile() turns "-" into "" */ + positive = FALSE; + count = 1; + } else if (strcmp(cp, "--") == 0) { + positive = FALSE; + count = 2; + } else if (strcmp(cp, "+") == 0) { + positive = TRUE; + count = 1; + } else if (strcmp(cp, "++") == 0) { + positive = TRUE; + count = 2; + } else { + error("illegal CORRECTION field on Leap line"); + return; + } + if ((lp = byword(fields[LP_ROLL], leap_types)) == NULL) { + error("illegal Rolling/Stationary field on Leap line"); + return; + } + leapadd(tadd(t, tod), positive, lp->l_value, count); + } +} + +static void +inlink(fields, nfields) +register char ** const fields; +const int nfields; +{ + struct link l; + + if (nfields != LINK_FIELDS) { + error("wrong number of fields on Link line"); + return; + } + if (*fields[LF_FROM] == '\0') { + error("blank FROM field on Link line"); + return; + } + if (*fields[LF_TO] == '\0') { + error("blank TO field on Link line"); + return; + } + l.l_filename = filename; + l.l_linenum = linenum; + l.l_from = ecpyalloc(fields[LF_FROM]); + l.l_to = ecpyalloc(fields[LF_TO]); + links = (struct link *) erealloc((char *) links, + (int) ((nlinks + 1) * sizeof *links)); + links[nlinks++] = l; +} + +static void +rulesub(rp, loyearp, hiyearp, typep, monthp, dayp, timep) +register struct rule * const rp; +char * const loyearp; +char * const hiyearp; +char * const typep; +char * const monthp; +char * const dayp; +char * const timep; +{ + register struct lookup const * lp; + register char * cp; + + if ((lp = byword(monthp, mon_names)) == NULL) { + error("invalid month name"); + return; + } + rp->r_month = lp->l_value; + rp->r_todisstd = FALSE; + cp = timep; + if (*cp != '\0') { + cp += strlen(cp) - 1; + switch (lowerit(*cp)) { + case 's': + rp->r_todisstd = TRUE; + *cp = '\0'; + break; + case 'w': + rp->r_todisstd = FALSE; + *cp = '\0'; + break; + } + } + rp->r_tod = gethms(timep, "invalid time of day", FALSE); + /* + ** Year work. + */ + cp = loyearp; + if ((lp = byword(cp, begin_years)) != NULL) switch ((int) lp->l_value) { + case YR_MINIMUM: + rp->r_loyear = min_int; + break; + case YR_MAXIMUM: + rp->r_loyear = max_int; + break; + default: /* "cannot happen" */ + (void) fprintf(stderr, + "%s: panic: Invalid l_value %d\n", + progname, lp->l_value); + (void) exit(EXIT_FAILURE); + } else if (sscanf(cp, scheck(cp, "%d"), &rp->r_loyear) != 1) { + error("invalid starting year"); + return; + } + cp = hiyearp; + if ((lp = byword(cp, end_years)) != NULL) switch ((int) lp->l_value) { + case YR_MINIMUM: + rp->r_hiyear = min_int; + break; + case YR_MAXIMUM: + rp->r_hiyear = max_int; + break; + case YR_ONLY: + rp->r_hiyear = rp->r_loyear; + break; + default: /* "cannot happen" */ + (void) fprintf(stderr, + "%s: panic: Invalid l_value %d\n", + progname, lp->l_value); + (void) exit(EXIT_FAILURE); + } else if (sscanf(cp, scheck(cp, "%d"), &rp->r_hiyear) != 1) { + error("invalid ending year"); + return; + } + if (rp->r_loyear > rp->r_hiyear) { + error("starting year greater than ending year"); + return; + } + if (*typep == '\0') + rp->r_yrtype = NULL; + else { + if (rp->r_loyear == rp->r_hiyear) { + error("typed single year"); + return; + } + rp->r_yrtype = ecpyalloc(typep); + } + /* + ** Day work. + ** Accept things such as: + ** 1 + ** last-Sunday + ** Sun<=20 + ** Sun>=7 + */ + if ((lp = byword(dayp, lasts)) != NULL) { + rp->r_dycode = DC_DOWLEQ; + rp->r_wday = lp->l_value; + rp->r_dayofmonth = len_months[1][rp->r_month]; + } else { + if ((cp = strchr(dayp, '<')) != 0) + rp->r_dycode = DC_DOWLEQ; + else if ((cp = strchr(dayp, '>')) != 0) + rp->r_dycode = DC_DOWGEQ; + else { + cp = dayp; + rp->r_dycode = DC_DOM; + } + if (rp->r_dycode != DC_DOM) { + *cp++ = 0; + if (*cp++ != '=') { + error("invalid day of month"); + return; + } + if ((lp = byword(dayp, wday_names)) == NULL) { + error("invalid weekday name"); + return; + } + rp->r_wday = lp->l_value; + } + if (sscanf(cp, scheck(cp, "%d"), &rp->r_dayofmonth) != 1 || + rp->r_dayofmonth <= 0 || + (rp->r_dayofmonth > len_months[1][rp->r_month])) { + error("invalid day of month"); + return; + } + } +} + +static void +convert(val, buf) +const long val; +char * const buf; +{ + register int i; + register long shift; + + for (i = 0, shift = 24; i < 4; ++i, shift -= 8) + buf[i] = val >> shift; +} + +static void +puttzcode(val, fp) +const long val; +FILE * const fp; +{ + char buf[4]; + + convert(val, buf); + (void) fwrite((genericptr_t) buf, + (fwrite_size_t) sizeof buf, + (fwrite_size_t) 1, fp); +} + +static void +writezone(name) +const char * const name; +{ + register FILE * fp; + register int i, j; + static char * fullname; + static struct tzhead tzh; + + fullname = erealloc(fullname, + strlen(directory) + 1 + strlen(name) + 1); + (void) sprintf(fullname, "%s/%s", directory, name); + if ((fp = fopen(fullname, "wb")) == NULL) { + if (mkdirs(fullname) != 0) + (void) exit(EXIT_FAILURE); + if ((fp = fopen(fullname, "wb")) == NULL) { + (void) fprintf(stderr, "%s: Can't create ", progname); + (void) perror(fullname); + (void) exit(EXIT_FAILURE); + } + } + convert(eitol(typecnt), tzh.tzh_ttisstdcnt); + convert(eitol(leapcnt), tzh.tzh_leapcnt); + convert(eitol(timecnt), tzh.tzh_timecnt); + convert(eitol(typecnt), tzh.tzh_typecnt); + convert(eitol(charcnt), tzh.tzh_charcnt); + (void) fwrite((genericptr_t) &tzh, + (fwrite_size_t) sizeof tzh, + (fwrite_size_t) 1, fp); + for (i = 0; i < timecnt; ++i) { + j = leapcnt; + while (--j >= 0) + if (ats[i] >= trans[j]) { + ats[i] = tadd(ats[i], corr[j]); + break; + } + puttzcode((long) ats[i], fp); + } + if (timecnt > 0) + (void) fwrite((genericptr_t) types, + (fwrite_size_t) sizeof types[0], + (fwrite_size_t) timecnt, fp); + for (i = 0; i < typecnt; ++i) { + puttzcode((long) gmtoffs[i], fp); + (void) putc(isdsts[i], fp); + (void) putc(abbrinds[i], fp); + } + if (charcnt != 0) + (void) fwrite((genericptr_t) chars, + (fwrite_size_t) sizeof chars[0], + (fwrite_size_t) charcnt, fp); + for (i = 0; i < leapcnt; ++i) { + if (roll[i]) { + if (timecnt == 0 || trans[i] < ats[0]) { + j = 0; + while (isdsts[j]) + if (++j >= typecnt) { + j = 0; + break; + } + } else { + j = 1; + while (j < timecnt && trans[i] >= ats[j]) + ++j; + j = types[j - 1]; + } + puttzcode((long) tadd(trans[i], -gmtoffs[j]), fp); + } else puttzcode((long) trans[i], fp); + puttzcode((long) corr[i], fp); + } + for (i = 0; i < typecnt; ++i) + (void) putc(ttisstds[i], fp); + if (ferror(fp) || fclose(fp)) { + (void) fprintf(stderr, "%s: Write error on ", progname); + (void) perror(fullname); + (void) exit(EXIT_FAILURE); + } +} + +static void +outzone(zpfirst, zonecount) +const struct zone * const zpfirst; +const int zonecount; +{ + register const struct zone * zp; + register struct rule * rp; + register int i, j; + register int usestart, useuntil; + register time_t starttime, untiltime; + register long gmtoff; + register long stdoff; + register int year; + register long startoff; + register int startisdst; + register int startttisstd; + register int type; + char startbuf[BUFSIZ]; + + /* + ** Now. . .finally. . .generate some useful data! + */ + timecnt = 0; + typecnt = 0; + charcnt = 0; + /* + ** A guess that may well be corrected later. + */ + stdoff = 0; + /* + ** Thanks to Earl Chew (earl@dnd.icp.nec.com.au) + ** for noting the need to unconditionally initialize startttisstd. + */ + startttisstd = FALSE; +#ifdef lint + starttime = 0; +#endif /* defined lint */ + for (i = 0; i < zonecount; ++i) { + zp = &zpfirst[i]; + usestart = i > 0 && (zp - 1)->z_untiltime > min_time; + useuntil = i < (zonecount - 1); + if (useuntil && zp->z_untiltime <= min_time) + continue; + gmtoff = zp->z_gmtoff; + eat(zp->z_filename, zp->z_linenum); + startisdst = -1; + if (zp->z_nrules == 0) { + stdoff = zp->z_stdoff; + (void) strcpy(startbuf, zp->z_format); + type = addtype(oadd(zp->z_gmtoff, stdoff), + startbuf, stdoff != 0, startttisstd); + if (usestart) + addtt(starttime, type); + else if (stdoff != 0) + addtt(min_time, type); + } else for (year = min_year; year <= max_year; ++year) { + if (useuntil && year > zp->z_untilrule.r_hiyear) + break; + /* + ** Mark which rules to do in the current year. + ** For those to do, calculate rpytime(rp, year); + */ + for (j = 0; j < zp->z_nrules; ++j) { + rp = &zp->z_rules[j]; + eats(zp->z_filename, zp->z_linenum, + rp->r_filename, rp->r_linenum); + rp->r_todo = year >= rp->r_loyear && + year <= rp->r_hiyear && + yearistype(year, rp->r_yrtype); + if (rp->r_todo) + rp->r_temp = rpytime(rp, year); + } + for ( ; ; ) { + register int k; + register time_t jtime, ktime; + register long offset; + char buf[BUFSIZ]; + + if (useuntil) { + /* + ** Turn untiltime into GMT + ** assuming the current gmtoff and + ** stdoff values. + */ + untiltime = tadd(zp->z_untiltime, + -gmtoff); + if (!zp->z_untilrule.r_todisstd) + untiltime = tadd(untiltime, + -stdoff); + } + /* + ** Find the rule (of those to do, if any) + ** that takes effect earliest in the year. + */ + k = -1; +#ifdef lint + ktime = 0; +#endif /* defined lint */ + for (j = 0; j < zp->z_nrules; ++j) { + rp = &zp->z_rules[j]; + if (!rp->r_todo) + continue; + eats(zp->z_filename, zp->z_linenum, + rp->r_filename, rp->r_linenum); + offset = gmtoff; + if (!rp->r_todisstd) + offset = oadd(offset, stdoff); + jtime = rp->r_temp; + if (jtime == min_time || + jtime == max_time) + continue; + jtime = tadd(jtime, -offset); + if (k < 0 || jtime < ktime) { + k = j; + ktime = jtime; + } + } + if (k < 0) + break; /* go on to next year */ + rp = &zp->z_rules[k]; + rp->r_todo = FALSE; + if (useuntil && ktime >= untiltime) + break; + if (usestart) { + if (ktime < starttime) { + stdoff = rp->r_stdoff; + startoff = oadd(zp->z_gmtoff, + rp->r_stdoff); + (void) sprintf(startbuf, zp->z_format, + rp->r_abbrvar); + startisdst = rp->r_stdoff != 0; + continue; + } + usestart = FALSE; + if (ktime != starttime) { + if (startisdst < 0 && + zp->z_gmtoff != + (zp - 1)->z_gmtoff) { + type = (timecnt == 0) ? 0 : + types[timecnt - 1]; + startoff = oadd(gmtoffs[type], + -(zp - 1)->z_gmtoff); + startisdst = startoff != 0; + startoff = oadd(startoff, + zp->z_gmtoff); + (void) strcpy(startbuf, + &chars[abbrinds[type]]); + } + if (startisdst >= 0) +addtt(starttime, addtype(startoff, startbuf, startisdst, startttisstd)); + } + } + eats(zp->z_filename, zp->z_linenum, + rp->r_filename, rp->r_linenum); + (void) sprintf(buf, zp->z_format, + rp->r_abbrvar); + offset = oadd(zp->z_gmtoff, rp->r_stdoff); + type = addtype(offset, buf, rp->r_stdoff != 0, + rp->r_todisstd); + addtt(ktime, type); + stdoff = rp->r_stdoff; + } + } + /* + ** Now we may get to set starttime for the next zone line. + */ + if (useuntil) { + starttime = tadd(zp->z_untiltime, -gmtoff); + startttisstd = zp->z_untilrule.r_todisstd; + if (!startttisstd) + starttime = tadd(starttime, -stdoff); + } + } + writezone(zpfirst->z_name); +} + +static void +addtt(starttime, type) +const time_t starttime; +const int type; +{ + if (timecnt != 0 && type == types[timecnt - 1]) + return; /* easy enough! */ + if (timecnt == 0 && type == 0 && isdsts[0] == 0) + return; /* handled by default rule */ + if (timecnt >= TZ_MAX_TIMES) { + error("too many transitions?!"); + (void) exit(EXIT_FAILURE); + } + ats[timecnt] = starttime; + types[timecnt] = type; + ++timecnt; +} + +static int +addtype(gmtoff, abbr, isdst, ttisstd) +const long gmtoff; +const char * const abbr; +const int isdst; +const int ttisstd; +{ + register int i, j; + + /* + ** See if there's already an entry for this zone type. + ** If so, just return its index. + */ + for (i = 0; i < typecnt; ++i) { + if (gmtoff == gmtoffs[i] && isdst == isdsts[i] && + strcmp(abbr, &chars[abbrinds[i]]) == 0 && + ttisstd == ttisstds[i]) + return i; + } + /* + ** There isn't one; add a new one, unless there are already too + ** many. + */ + if (typecnt >= TZ_MAX_TYPES) { + error("too many local time types"); + (void) exit(EXIT_FAILURE); + } + gmtoffs[i] = gmtoff; + isdsts[i] = isdst; + ttisstds[i] = ttisstd; + + for (j = 0; j < charcnt; ++j) + if (strcmp(&chars[j], abbr) == 0) + break; + if (j == charcnt) + newabbr(abbr); + abbrinds[i] = j; + ++typecnt; + return i; +} + +static void +leapadd(t, positive, rolling, count) +const time_t t; +const int positive; +const int rolling; +int count; +{ + register int i, j; + + if (leapcnt + (positive ? count : 1) > TZ_MAX_LEAPS) { + error("too many leap seconds"); + (void) exit(EXIT_FAILURE); + } + for (i = 0; i < leapcnt; ++i) + if (t <= trans[i]) { + if (t == trans[i]) { + error("repeated leap second moment"); + (void) exit(EXIT_FAILURE); + } + break; + } + do { + for (j = leapcnt; j > i; --j) { + trans[j] = trans[j - 1]; + corr[j] = corr[j - 1]; + roll[j] = roll[j - 1]; + } + trans[i] = t; + corr[i] = positive ? 1L : eitol(-count); + roll[i] = rolling; + ++leapcnt; + } while (positive && --count != 0); +} + +static void +adjleap() +{ + register int i; + register long last = 0; + + /* + ** propagate leap seconds forward + */ + for (i = 0; i < leapcnt; ++i) { + trans[i] = tadd(trans[i], last); + last = corr[i] += last; + } +} + +static int +yearistype(year, type) +const int year; +const char * const type; +{ + static char * buf; + int result; + + if (type == NULL || *type == '\0') + return TRUE; + if (strcmp(type, "uspres") == 0) + return (year % 4) == 0; + if (strcmp(type, "nonpres") == 0) + return (year % 4) != 0; + buf = erealloc(buf, 132 + strlen(yitcommand) + strlen(type)); + (void) sprintf(buf, "%s %d %s", yitcommand, year, type); + result = system(buf); + if (result == 0) + return TRUE; + if (result == (1 << 8)) + return FALSE; + error("Wild result from command execution"); + (void) fprintf(stderr, "%s: command was '%s', result was %d\n", + progname, buf, result); + for ( ; ; ) + (void) exit(EXIT_FAILURE); +} + +static int +lowerit(a) +const int a; +{ + return (isascii(a) && isupper(a)) ? tolower(a) : a; +} + +static int +ciequal(ap, bp) /* case-insensitive equality */ +register const char * ap; +register const char * bp; +{ + while (lowerit(*ap) == lowerit(*bp++)) + if (*ap++ == '\0') + return TRUE; + return FALSE; +} + +static int +itsabbr(abbr, word) +register const char * abbr; +register const char * word; +{ + if (lowerit(*abbr) != lowerit(*word)) + return FALSE; + ++word; + while (*++abbr != '\0') + do if (*word == '\0') + return FALSE; + while (lowerit(*word++) != lowerit(*abbr)); + return TRUE; +} + +static const struct lookup * +byword(word, table) +register const char * const word; +register const struct lookup * const table; +{ + register const struct lookup * foundlp; + register const struct lookup * lp; + + if (word == NULL || table == NULL) + return NULL; + /* + ** Look for exact match. + */ + for (lp = table; lp->l_word != NULL; ++lp) + if (ciequal(word, lp->l_word)) + return lp; + /* + ** Look for inexact match. + */ + foundlp = NULL; + for (lp = table; lp->l_word != NULL; ++lp) + if (itsabbr(word, lp->l_word)) + if (foundlp == NULL) + foundlp = lp; + else return NULL; /* multiple inexact matches */ + return foundlp; +} + +static char ** +getfields(cp) +register char * cp; +{ + register char * dp; + register char ** array; + register int nsubs; + + if (cp == NULL) + return NULL; + array = (char **) emalloc((int) ((strlen(cp) + 1) * sizeof *array)); + nsubs = 0; + for ( ; ; ) { + while (isascii(*cp) && isspace(*cp)) + ++cp; + if (*cp == '\0' || *cp == '#') + break; + array[nsubs++] = dp = cp; + do { + if ((*dp = *cp++) != '"') + ++dp; + else while ((*dp = *cp++) != '"') + if (*dp != '\0') + ++dp; + else error("Odd number of quotation marks"); + } while (*cp != '\0' && *cp != '#' && + (!isascii(*cp) || !isspace(*cp))); + if (isascii(*cp) && isspace(*cp)) + ++cp; + *dp = '\0'; + } + array[nsubs] = NULL; + return array; +} + +static long +oadd(t1, t2) +const long t1; +const long t2; +{ + register long t; + + t = t1 + t2; + if ((t2 > 0 && t <= t1) || (t2 < 0 && t >= t1)) { + error("time overflow"); + (void) exit(EXIT_FAILURE); + } + return t; +} + +static time_t +tadd(t1, t2) +const time_t t1; +const long t2; +{ + register time_t t; + + if (t1 == max_time && t2 > 0) + return max_time; + if (t1 == min_time && t2 < 0) + return min_time; + t = t1 + t2; + if ((t2 > 0 && t <= t1) || (t2 < 0 && t >= t1)) { + error("time overflow"); + (void) exit(EXIT_FAILURE); + } + return t; +} + +/* +** Given a rule, and a year, compute the date - in seconds since January 1, +** 1970, 00:00 LOCAL time - in that year that the rule refers to. +*/ + +static time_t +rpytime(rp, wantedy) +register const struct rule * const rp; +register const int wantedy; +{ + register int y, m, i; + register long dayoff; /* with a nod to Margaret O. */ + register time_t t; + + if (wantedy == min_int) + return min_time; + if (wantedy == max_int) + return max_time; + dayoff = 0; + m = TM_JANUARY; + y = EPOCH_YEAR; + while (wantedy != y) { + if (wantedy > y) { + i = len_years[isleap(y)]; + ++y; + } else { + --y; + i = -len_years[isleap(y)]; + } + dayoff = oadd(dayoff, eitol(i)); + } + while (m != rp->r_month) { + i = len_months[isleap(y)][m]; + dayoff = oadd(dayoff, eitol(i)); + ++m; + } + i = rp->r_dayofmonth; + if (m == TM_FEBRUARY && i == 29 && !isleap(y)) { + if (rp->r_dycode == DC_DOWLEQ) + --i; + else { + error("use of 2/29 in non leap-year"); + (void) exit(EXIT_FAILURE); + } + } + --i; + dayoff = oadd(dayoff, eitol(i)); + if (rp->r_dycode == DC_DOWGEQ || rp->r_dycode == DC_DOWLEQ) { + register long wday; + +#define LDAYSPERWEEK ((long) DAYSPERWEEK) + wday = eitol(EPOCH_WDAY); + /* + ** Don't trust mod of negative numbers. + */ + if (dayoff >= 0) + wday = (wday + dayoff) % LDAYSPERWEEK; + else { + wday -= ((-dayoff) % LDAYSPERWEEK); + if (wday < 0) + wday += LDAYSPERWEEK; + } + while (wday != eitol(rp->r_wday)) + if (rp->r_dycode == DC_DOWGEQ) { + dayoff = oadd(dayoff, (long) 1); + if (++wday >= LDAYSPERWEEK) + wday = 0; + ++i; + } else { + dayoff = oadd(dayoff, (long) -1); + if (--wday < 0) + wday = LDAYSPERWEEK - 1; + --i; + } + if (i < 0 || i >= len_months[isleap(y)][m]) { + error("no day in month matches rule"); + (void) exit(EXIT_FAILURE); + } + } + if (dayoff < 0 && !tt_signed) + return min_time; + t = (time_t) dayoff * SECSPERDAY; + /* + ** Cheap overflow check. + */ + if (t / SECSPERDAY != dayoff) + return (dayoff > 0) ? max_time : min_time; + return tadd(t, rp->r_tod); +} + +static void +newabbr(string) +const char * const string; +{ + register int i; + + i = strlen(string) + 1; + if (charcnt + i > TZ_MAX_CHARS) { + error("too many, or too long, time zone abbreviations"); + (void) exit(EXIT_FAILURE); + } + (void) strcpy(&chars[charcnt], string); + charcnt += eitol(i); +} + +static int +mkdirs(argname) +char * const argname; +{ + register char * name; + register char * cp; + + if (argname == NULL || *argname == '\0') + return 0; + cp = name = ecpyalloc(argname); + while ((cp = strchr(cp + 1, '/')) != 0) { + *cp = '\0'; +#ifndef unix + /* + ** MS-DOS drive specifier? + */ + if (strlen(name) == 2 && isascii(name[0]) && + isalpha(name[0]) && name[1] == ':') { + *cp = '/'; + continue; + } +#endif /* !defined unix */ + if (!itsdir(name)) { + /* + ** It doesn't seem to exist, so we try to create it. + */ + if (emkdir(name, 0755) != 0) { + (void) fprintf(stderr, + "%s: Can't create directory ", + progname); + (void) perror(name); + ifree(name); + return -1; + } + } + *cp = '/'; + } + ifree(name); + return 0; +} + +static long +eitol(i) +const int i; +{ + long l; + + l = i; + if ((i < 0 && l >= 0) || (i == 0 && l != 0) || (i > 0 && l <= 0)) { + (void) fprintf(stderr, + "%s: %d did not sign extend correctly\n", + progname, i); + (void) exit(EXIT_FAILURE); + } + return l; +} + +/* +** UNIX is a registered trademark of AT&T. +*/