bostic@OKEEFFE.BERKELEY.EDU.UUCP (03/27/87)
The next four articles posted to comp.bugs.4bsd.ucb-fixes, ARTICLES
#13 through #16, will concern the upcoming DST problem. They contain:
ARTICLE #13 A minimal fix, hopefully easy to install. It contains
fixes for both 4.2 and 4.3 BSD systems. To install
this fix, unshar ARTICLE #13 in an empty directory and
follow the instructions contained in the README file.
ARTICLES #14, #15, #16
What Berkeley has installed. To install this fix, create a
directory containing one other directory; the sub-directory
should be called "tzone". Unshar ARTICLES #15 and #16 in
this sub-directory. Unshar ARTICLE #14 in the top directory.
Follow the instructions contained in the README file.
You are reading ARTICLE #16.
If you have any problems getting either package to work,
please contact me.
Keith Bostic
bostic@ucbvax.berkeley.edu
ucbvax!bostic
seismo!keith
+1 (415) 642-4948
... cut here ...
echo x - zdump.8
sed 's/^X//' >zdump.8 << 'END-of-zdump.8'
X.TH ZDUMP 8
X.SH NAME
Xzdump \- time zone dumper
X.SH SYNOPSIS
X.B zdump
X[
X.B \-v
X] [
X.B \-c
Xcutoffyear ] [ zonename ... ]
X.SH DESCRIPTION
X.I Zdump
Xprints the current time in each
X.I zonename
Xnamed on the command line.
X.PP
XThese options are available:
X.TP
X.B \-v
XFor each
X.I zonename
Xon the command line,
Xprint the current time,
Xthe time at the lowest possible time value,
Xthe time one day after the lowest possible time value,
Xthe times both one second before and exactly at
Xeach time at which the rules for computing local time change,
Xthe time at the highest possible time value,
Xand the time at one day less than the highest possible time value.
XEach line ends with
X.B isdst=1
Xif the given time is Daylight Saving Time or
X.B isdst=0
Xotherwise.
X.TP
X.BI "\-c " cutoffyear
XCut off the verbose output near the start of the given year.
X.SH FILES
X/etc/zoneinfo standard zone information directory
X.SH "SEE ALSO"
Xnewctime(3), tzfile(5), zic(8)
X.. @(#)zdump.8 3.1
END-of-zdump.8
echo x - zdump.c
sed 's/^X//' >zdump.c << 'END-of-zdump.c'
X/*
X * @(#)zdump.c 1.1 zdump.c 3/4/87
X */
X
X#include "stdio.h"
X
X#include "sys/types.h"
X#include "tzfile.h"
X#include "time.h"
X
X#ifndef TRUE
X#define TRUE 1
X#define FALSE 0
X#endif
X
Xextern char * asctime();
Xextern char ** environ;
Xextern struct tm * gmtime();
Xextern char * imalloc();
Xextern char * optarg;
Xextern int optind;
Xextern char * sprintf();
Xextern long time();
Xextern char * tzname[2];
Xextern void tzset();
X
X/*
X** For the benefit of cyntax...
X*/
X
Xstatic long tzdecode();
Xstatic readerr();
Xstatic show();
X
Xstatic int longest;
X
Xstatic long
Xtzdecode(codep)
Xchar * codep;
X{
X register int i;
X register long result;
X
X result = 0;
X for (i = 0; i < 4; ++i)
X result = (result << 8) | (codep[i] & 0xff);
X return result;
X}
X
Xmain(argc, argv)
Xint argc;
Xchar * argv[];
X{
X register FILE * fp;
X register int i, j, c;
X register int vflag;
X register char * cutoff;
X register int cutyear;
X register long cuttime;
X time_t now;
X time_t t;
X long timecnt;
X char buf[BUFSIZ];
X
X vflag = 0;
X cutoff = NULL;
X while ((c = getopt(argc, argv, "c:v")) == 'c' || c == 'v')
X if (c == 'v')
X vflag = 1;
X else cutoff = optarg;
X if (c != EOF || optind == argc - 1 && strcmp(argv[optind], "=") == 0) {
X (void) fprintf(stderr, "%s: usage is %s [ -v ] zonename ...\n",
X argv[0], argv[0]);
X exit(1);
X }
X if (cutoff != NULL)
X cutyear = atoi(cutoff);
X /*
X ** VERY approximate.
X */
X cuttime = (long) (cutyear - EPOCH_YEAR) *
X SECS_PER_HOUR * HOURS_PER_DAY * DAYS_PER_NYEAR;
X (void) time(&now);
X longest = 0;
X for (i = optind; i < argc; ++i)
X if (strlen(argv[i]) > longest)
X longest = strlen(argv[i]);
X for (i = optind; i < argc; ++i) {
X register char ** saveenv;
X char * tzequals;
X char * fakeenv[2];
X
X tzequals = imalloc(strlen(argv[i]) + 4);
X if (tzequals == NULL) {
X (void) fprintf(stderr, "%s: can't allocate memory\n",
X argv[0]);
X exit(1);
X }
X (void) sprintf(tzequals, "TZ=%s", argv[i]);
X fakeenv[0] = tzequals;
X fakeenv[1] = NULL;
X saveenv = environ;
X environ = fakeenv;
X (void) tzset();
X environ = saveenv;
X show(argv[i], now, FALSE);
X if (!vflag)
X continue;
X if (argv[i][0] == '/')
X fp = fopen(argv[i], "r");
X else {
X j = strlen(TZDIR) + 1 + strlen(argv[i]) + 1;
X if (j > sizeof buf) {
X (void) fprintf(stderr,
X "%s: timezone name %s/%s is too long\n",
X argv[0], TZDIR, argv[i]);
X exit(1);
X }
X (void) sprintf(buf, "%s/%s", TZDIR, argv[i]);
X fp = fopen(buf, "r");
X }
X if (fp == NULL) {
X (void) fprintf(stderr, "%s: Can't open ", argv[0]);
X perror(argv[i]);
X exit(1);
X }
X {
X char code[4];
X
X(void) fseek(fp, (long) sizeof ((struct tzhead *) 0)->tzh_reserved, 0);
X if (fread((char *) code, sizeof code, 1, fp) != 1)
X readerr(fp, argv[0], argv[i]);
X timecnt = tzdecode(code);
X (void) fseek(fp, (long) (2 * sizeof code), 1);
X }
X t = 0x80000000;
X if (t > 0) /* time_t is unsigned */
X t = 0;
X show(argv[i], t, TRUE);
X t += SECS_PER_HOUR * HOURS_PER_DAY;
X show(argv[i], t, TRUE);
X while (timecnt-- > 0) {
X char code[4];
X
X if (fread((char *) code, sizeof code, 1, fp) != 1)
X readerr(fp, argv[0], argv[i]);
X t = tzdecode(code);
X if (cutoff != NULL && t > cuttime)
X break;
X show(argv[i], t - 1, TRUE);
X show(argv[i], t, TRUE);
X }
X if (fclose(fp)) {
X (void) fprintf(stderr, "%s: Error closing ", argv[0]);
X perror(argv[i]);
X exit(1);
X }
X t = 0xffffffff;
X if (t < 0) /* time_t is signed */
X t = 0x7fffffff ;
X t -= SECS_PER_HOUR * HOURS_PER_DAY;
X show(argv[i], t, TRUE);
X t += SECS_PER_HOUR * HOURS_PER_DAY;
X show(argv[i], t, TRUE);
X free(tzequals);
X }
X if (fflush(stdout) || ferror(stdout)) {
X (void) fprintf(stderr, "%s: Error writing standard output ",
X argv[0]);
X perror("standard output");
X exit(1);
X }
X return 0;
X}
X
Xstatic
Xshow(zone, t, v)
Xchar * zone;
Xtime_t t;
X{
X struct tm * tmp;
X extern struct tm * localtime();
X
X (void) printf("%-*s ", longest, zone);
X if (v)
X (void) printf("%.24s GMT = ", asctime(gmtime(&t)));
X tmp = localtime(&t);
X (void) printf("%.24s", asctime(tmp));
X if (*tzname[tmp->tm_isdst] != '\0')
X (void) printf(" %s", tzname[tmp->tm_isdst]);
X if (v) {
X (void) printf(" isdst=%d", tmp->tm_isdst);
X (void) printf(" gmtoff=%ld", tmp->tm_gmtoff);
X }
X (void) printf("\n");
X}
X
Xstatic
Xreaderr(fp, progname, filename)
XFILE * fp;
Xchar * progname;
Xchar * filename;
X{
X (void) fprintf(stderr, "%s: Error reading ", progname);
X if (ferror(fp))
X perror(filename);
X else (void) fprintf(stderr, "%s: Premature EOF\n", filename);
X exit(1);
X}
END-of-zdump.c
echo x - zic.8
sed 's/^X//' >zic.8 << 'END-of-zic.8'
X.TH ZIC 8
X.SH NAME
Xzic \- time zone compiler
X.SH SYNOPSIS
X.B zic
X[
X.B \-v
X] [
X.B \-d
X.I directory
X] [
X.B \-l
X.I localtime
X] [
X.I filename
X\&... ]
X.SH DESCRIPTION
X.I Zic
Xreads text from the file(s) named on the command line
Xand creates the time conversion information files specified in this input.
XIf a
X.I filename
Xis
X.BR \- ,
Xthe standard input is read.
X.PP
XThese options are available:
X.TP
X.BI "\-d " directory
XCreate time conversion information files in the named directory rather than
Xin the standard directory named below.
X.TP
X.BI "\-l " timezone
XUse the given time zone as local time.
X.I Zic
Xwill act as if the file contained a link line of the form
X.sp
X.ti +.5i
XLink \fItimezone\fP localtime
X.TP
X.B \-v
XComplain if a year that appears in a data file is outside the range
Xof years representable by
X.IR time (2)
Xvalues.
X.sp
XInput lines are made up of fields.
XFields are separated from one another by any number of white space characters.
XLeading and trailing white space on input lines is ignored.
XAn unquoted sharp character (#) in the input introduces a comment which extends
Xto the end of the line the sharp character appears on.
XWhite space characters and sharp characters may be enclosed in double quotes
X(") if they're to be used as part of a field.
XAny line that is blank (after comment stripping) is ignored.
XNon-blank lines are expected to be of one of three types:
Xrule lines, zone lines, and link lines.
X.PP
XA rule line has the form
X.nf
X.B
X.ti +.5i
X.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
X.sp
XRule NAME FROM TO TYPE IN ON AT SAVE LETTER/S
X.sp
XFor example:
X.ti +.5i
X.sp
XRule USA 1969 1973 \- Apr lastSun 2:00 1:00 D
X.sp
X.fi
XThe fields that make up a rule line are:
X.TP "\w'LETTER/S'u"
X.B NAME
XGives the (arbitrary) name of the set of rules this rule is part of.
X.TP
X.B FROM
XGives the first year in which the rule applies.
XThe word
X.B minimum
X(or an abbreviation) means the minimum year with a representable time value.
XThe word
X.B maximum
X(or an abbreviation) means the maximum year with a representable time value.
X.TP
X.B TO
XGives the final year in which the rule applies.
XIn addition to
X.B minimum
Xand
X.B maximum
X(as above),
Xthe word
X.B only
X(or an abbreviation)
Xmay be used to repeat the value of the
X.B FROM
Xfield.
X.TP
X.B TYPE
XGives the type of year in which the year applies.
XIf
X.B TYPE
Xis
X.B \-
Xthen the rule applies in all years between
X.B FROM
Xand
X.B TO
Xinclusive;
Xif
X.B TYPE
Xis
X.BR uspres ,
Xthe rule applies in U.S. Presidential election years;
Xif
X.B TYPE
Xis
X.BR nonpres ,
Xthe rule applies in years other than U.S. Presidential election years.
XIf
X.B TYPE
Xis something else, then
X.I zic
Xexecutes the command
X.ti +.5i
X\fByearistype\fP \fIyear\fP \fItype\fP
X.br
Xto check the type of a year:
Xan exit status of zero is taken to mean that the year is of the given type;
Xan exit status of one is taken to mean that the year is not of the given type.
X.TP
X.B IN
XNames the month in which the rule takes effect.
XMonth names may be abbreviated.
X.TP
X.B ON
XGives the day on which the rule takes effect.
XRecognized forms include:
X.nf
X.in +.5i
X.sp
X.ta \w'Sun<=25\0\0'u
X5 the fifth of the month
XlastSun the last Sunday in the month
XlastMon the last Monday in the month
XSun>=8 first Sunday on or after the eighth
XSun<=25 last Sunday on or before the 25th
X.fi
X.in -.5i
X.sp
XNames of days of the week may be abbreviated or spelled out in full.
XNote that there must be no spaces within the
X.B ON
Xfield.
X.TP
X.B AT
XGives the time of day at which the rule takes affect.
XRecognized forms include:
X.nf
X.in +.5i
X.sp
X.ta \w'1:28:13\0\0'u
X2 time in hours
X2:00 time in hours and minutes
X15:00 24-hour format time (for times after noon)
X1:28:14 time in hours, minutes, and seconds
X.fi
X.in -.5i
X.sp
XAny of these forms may be followed by the letter
X.B w
Xif the given time is local ``wall clock'' time or
X.B s
Xif the given time is local ``standard'' time; in the absence of
X.B w
Xor
X.BR s ,
Xwall clock time is assumed.
X.TP
X.B SAVE
XGives the amount of time to be added to local standard time when the rule is in
Xeffect.
XThis field has the same format as the
X.B AT
Xfield
X(although, of course, the
X.B w
Xand
X.B s
Xsuffixes are not used).
X.TP
X.B LETTER/S
XGives the ``variable part'' (for example, the ``S'' or ``D'' in ``EST''
Xor ``EDT'') of time zone abbreviations to be used when this rule is in effect.
XIf this field is
X.BR \- ,
Xthe variable part is null.
X.PP
XA zone line has the form
X.sp
X.nf
X.ti +.5i
X.ta \w'Zone\0\0'u +\w'Australia/South\-west\0\0'u +\w'GMTOFF\0\0'u +\w'RULES/SAVE\0\0'u +\w'FORMAT\0\0'u
XZone NAME GMTOFF RULES/SAVE FORMAT [UNTIL]
X.sp
XFor example:
X.sp
X.ti +.5i
XZone Australia/South\-west 9:30 Aus CST 1987 Mar 15 2:00
X.sp
X.fi
XThe fields that make up a zone line are:
X.TP "\w'GMTOFF'u"
X.B NAME
XThe name of the time zone.
XThis is the name used in creating the time conversion information file for the
Xzone.
X.TP
X.B GMTOFF
XThe amount of time to add to GMT to get standard time in this zone.
XThis field has the same format as the
X.B AT
Xand
X.B SAVE
Xfields of rule lines;
Xbegin the field with a minus sign if time must be subtracted from GMT.
X.TP
X.B RULES/SAVE
XThe name of the rule(s) that apply in the time zone or,
Xalternately, an amount of time to add to local standard time.
XIf this field is
X.B \-
Xthen standard time always applies in the time zone.
X.TP
X.B FORMAT
XThe format for time zone abbreviations in this time zone.
XThe pair of characters
X.B %s
Xis used to show where the ``variable part'' of the time zone abbreviation goes.
X.B UNTIL
XThe time at which the GMT offset or the rule(s) change for a location.
XIt is specified as a year, a month, a day, and a time of day.
XIf this is specified,
Xthe time zone information is generated from the given GMT offset
Xand rule change until the time specified.
X.IP
XThe next line must be a
X``continuation'' line; this has the same form as a zone line except that the
Xstring ``Zone'' and the name are omitted, as the continuation line will
Xplace information starting at the time specified as the
X.B UNTIL
Xfield in the previous line in the file used by the previous line.
XContinuation lines may contain an
X.B UNTIL
Xfield, just as zone lines do, indicating that the next line is a further
Xcontinuation.
X.PP
XA link line has the form
X.sp
X.nf
X.ti +.5i
X.if t .ta \w'Link\0\0'u +\w'LINK-FROM\0\0'u
X.if n .ta \w'Link\0\0'u +\w'US/Eastern\0\0'u
XLink LINK-FROM LINK-TO
X.sp
XFor example:
X.sp
X.ti +.5i
XLink US/Eastern EST5EDT
X.sp
X.fi
XThe
X.B LINK-FROM
Xfield should appear as the
X.B NAME
Xfield in some zone line;
Xthe
X.B LINK-TO
Xfield is used as an alternate name for that zone.
X.PP
XExcept for continuation lines,
Xlines may appear in any order in the input.
X.SH NOTE
XFor areas with more than two types of local time,
Xyou may need to use local standard time in the
X.B AT
Xfield of the earliest transition time's rule to ensure that
Xthe earliest transition time recorded in the compiled file is correct.
X.SH FILES
X/etc/zoneinfo standard directory used for created files
X.SH "SEE ALSO"
Xnewctime(3), tzfile(5), zdump(8)
X.. @(#)zic.8 3.1
END-of-zic.8
echo x - zic.c
sed 's/^X//' >zic.c << 'END-of-zic.c'
X/*
X * @(#)zic.c 1.1 zic.c 3/4/87
X */
X
X#include "stdio.h"
X#include "ctype.h"
X#include "sys/types.h"
X#include "sys/stat.h"
X#include "sys/file.h"
X#include "strings.h"
X#include "time.h"
X#include "tzfile.h"
X
X#ifndef BUFSIZ
X#define BUFSIZ 1024
X#endif
X
X#ifndef TRUE
X#define TRUE 1
X#define FALSE 0
X#endif
X
Xextern char * icpyalloc();
Xextern char * imalloc();
Xextern char * irealloc();
Xextern char * optarg;
Xextern int optind;
Xextern char * scheck();
Xextern char * sprintf();
X
Xstatic addtt();
Xstatic addtype();
Xstatic associate();
Xstatic int charcnt;
Xstatic ciequal();
Xstatic long eitol();
Xstatic int errors;
Xstatic char * filename;
Xstatic char ** getfields();
Xstatic long gethms();
Xstatic infile();
Xstatic inlink();
Xstatic inrule();
Xstatic inzcont();
Xstatic inzone();
Xstatic inzsub();
Xstatic int linenum;
Xstatic lowerit();
Xstatic time_t max_time;
Xstatic int max_year;
Xstatic time_t min_time;
Xstatic int min_year;
Xstatic mkdirs();
Xstatic newabbr();
Xstatic int noise;
Xstatic nondunlink();
Xstatic long oadd();
Xstatic outzone();
Xstatic char * progname;
Xstatic char * rfilename;
Xstatic int rlinenum;
Xstatic time_t rpytime();
Xstatic rulesub();
Xstatic setboundaries();
Xstatic time_t tadd();
Xstatic int timecnt;
Xstatic int tt_signed;
Xstatic int typecnt;
Xstatic yearistype();
X
X/*
X** Line codes.
X*/
X
X#define LC_RULE 0
X#define LC_ZONE 1
X#define LC_LINK 2
X
X/*
X** Which fields are which on a Zone line.
X*/
X
X#define ZF_NAME 1
X#define ZF_GMTOFF 2
X#define ZF_RULE 3
X#define ZF_FORMAT 4
X#define ZF_UNTILYEAR 5
X#define ZF_UNTILMONTH 6
X#define ZF_UNTILDAY 7
X#define ZF_UNTILTIME 8
X#define ZONE_MINFIELDS 5
X#define ZONE_MAXFIELDS 9
X
X/*
X** Which fields are which on a Zone continuation line.
X*/
X
X#define ZFC_GMTOFF 0
X#define ZFC_RULE 1
X#define ZFC_FORMAT 2
X#define ZFC_UNTILYEAR 3
X#define ZFC_UNTILMONTH 4
X#define ZFC_UNTILDAY 5
X#define ZFC_UNTILTIME 6
X#define ZONEC_MINFIELDS 3
X#define ZONEC_MAXFIELDS 7
X
X/*
X** Which files are which on a Rule line.
X*/
X
X#define RF_NAME 1
X#define RF_LOYEAR 2
X#define RF_HIYEAR 3
X#define RF_COMMAND 4
X#define RF_MONTH 5
X#define RF_DAY 6
X#define RF_TOD 7
X#define RF_STDOFF 8
X#define RF_ABBRVAR 9
X#define RULE_FIELDS 10
X
X/*
X** Which fields are which on a Link line.
X*/
X
X#define LF_FROM 1
X#define LF_TO 2
X#define LINK_FIELDS 3
X
Xstruct rule {
X char * r_filename;
X int r_linenum;
X char * r_name;
X
X int r_loyear; /* for example, 1986 */
X int r_hiyear; /* for example, 1986 */
X char * r_yrtype;
X
X int r_month; /* 0..11 */
X
X int r_dycode; /* see below */
X int r_dayofmonth;
X int r_wday;
X
X long r_tod; /* time from midnight */
X int r_todisstd; /* above is standard time if TRUE */
X /* above is wall clock time if FALSE */
X long r_stdoff; /* offset from standard time */
X char * r_abbrvar; /* variable part of time zone abbreviation */
X
X int r_todo; /* a rule to do (used in outzone) */
X time_t r_temp; /* used in outzone */
X};
X
X/*
X** r_dycode r_dayofmonth r_wday
X*/
X#define DC_DOM 0 /* 1..31 */ /* unused */
X#define DC_DOWGEQ 1 /* 1..31 */ /* 0..6 (Sun..Sat) */
X#define DC_DOWLEQ 2 /* 1..31 */ /* 0..6 (Sun..Sat) */
X
X/*
X** Year synonyms.
X*/
X
X#define YR_MINIMUM 0
X#define YR_MAXIMUM 1
X#define YR_ONLY 2
X
Xstatic struct rule * rules;
Xstatic int nrules; /* number of rules */
X
Xstruct zone {
X char * z_filename;
X int z_linenum;
X
X char * z_name;
X long z_gmtoff;
X char * z_rule;
X char * z_format;
X
X long z_stdoff;
X
X struct rule * z_rules;
X int z_nrules;
X
X struct rule z_untilrule;
X time_t z_untiltime;
X};
X
Xstatic struct zone * zones;
Xstatic int nzones; /* number of zones */
X
Xstruct link {
X char * l_filename;
X int l_linenum;
X char * l_from;
X char * l_to;
X};
X
Xstatic struct link * links;
Xstatic int nlinks;
X
Xstruct lookup {
X char * l_word;
X int l_value;
X};
X
Xstatic struct lookup * byword();
X
Xstatic struct lookup line_codes[] = {
X "Rule", LC_RULE,
X "Zone", LC_ZONE,
X "Link", LC_LINK,
X NULL, 0
X};
X
Xstatic struct lookup mon_names[] = {
X "January", TM_JANUARY,
X "February", TM_FEBRUARY,
X "March", TM_MARCH,
X "April", TM_APRIL,
X "May", TM_MAY,
X "June", TM_JUNE,
X "July", TM_JULY,
X "August", TM_AUGUST,
X "September", TM_SEPTEMBER,
X "October", TM_OCTOBER,
X "November", TM_NOVEMBER,
X "December", TM_DECEMBER,
X NULL, 0
X};
X
Xstatic struct lookup wday_names[] = {
X "Sunday", TM_SUNDAY,
X "Monday", TM_MONDAY,
X "Tuesday", TM_TUESDAY,
X "Wednesday", TM_WEDNESDAY,
X "Thursday", TM_THURSDAY,
X "Friday", TM_FRIDAY,
X "Saturday", TM_SATURDAY,
X NULL, 0
X};
X
Xstatic struct lookup lasts[] = {
X "last-Sunday", TM_SUNDAY,
X "last-Monday", TM_MONDAY,
X "last-Tuesday", TM_TUESDAY,
X "last-Wednesday", TM_WEDNESDAY,
X "last-Thursday", TM_THURSDAY,
X "last-Friday", TM_FRIDAY,
X "last-Saturday", TM_SATURDAY,
X NULL, 0
X};
X
Xstatic struct lookup begin_years[] = {
X "minimum", YR_MINIMUM,
X "maximum", YR_MAXIMUM,
X NULL, 0
X};
X
Xstatic struct lookup end_years[] = {
X "minimum", YR_MINIMUM,
X "maximum", YR_MAXIMUM,
X "only", YR_ONLY,
X NULL, 0
X};
X
Xstatic int len_months[2][MONS_PER_YEAR] = {
X 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31,
X 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
X};
X
Xstatic int len_years[2] = {
X DAYS_PER_NYEAR, DAYS_PER_LYEAR
X};
X
Xstatic time_t ats[TZ_MAX_TIMES];
Xstatic unsigned char types[TZ_MAX_TIMES];
Xstatic long gmtoffs[TZ_MAX_TYPES];
Xstatic char isdsts[TZ_MAX_TYPES];
Xstatic char abbrinds[TZ_MAX_TYPES];
Xstatic char chars[TZ_MAX_CHARS];
X
X/*
X** Memory allocation.
X*/
X
Xstatic char *
Xmemcheck(ptr)
Xchar * ptr;
X{
X if (ptr == NULL) {
X perror(progname);
X exit(1);
X }
X return ptr;
X}
X
X#define emalloc(size) memcheck(imalloc(size))
X#define erealloc(ptr, size) memcheck(irealloc(ptr, size))
X#define ecpyalloc(ptr) memcheck(icpyalloc(ptr))
X
X/*
X** Error handling.
X*/
X
Xstatic
Xeats(name, num, rname, rnum)
Xchar * name;
Xchar * rname;
X{
X filename = name;
X linenum = num;
X rfilename = rname;
X rlinenum = rnum;
X}
X
Xstatic
Xeat(name, num)
Xchar * name;
X{
X eats(name, num, (char *) NULL, -1);
X}
X
Xstatic
Xerror(string)
Xchar * string;
X{
X /*
X ** Match the format of "cc" to allow sh users to
X ** zic ... 2>&1 | error -t "*" -v
X ** on BSD systems.
X */
X (void) fprintf(stderr, "\"%s\", line %d: %s",
X filename, linenum, string);
X if (rfilename != NULL)
X (void) fprintf(stderr, " (rule from \"%s\", line %d)",
X rfilename, rlinenum);
X (void) fprintf(stderr, "\n");
X ++errors;
X}
X
Xstatic
Xusage()
X{
X (void) fprintf(stderr,
X"%s: usage is %s [ -v ] [ -l localtime ] [ -d directory ] [ filename ... ]\n",
X progname, progname);
X exit(1);
X}
X
Xstatic char * lcltime = NULL;
Xstatic char * directory = NULL;
X
Xmain(argc, argv)
Xint argc;
Xchar * argv[];
X{
X register int i, j;
X register int c;
X
X#ifdef unix
X umask(umask(022) | 022);
X#endif
X progname = argv[0];
X while ((c = getopt(argc, argv, "d:l:v")) != EOF)
X switch (c) {
X default:
X usage();
X case 'd':
X if (directory == NULL)
X directory = optarg;
X else {
X (void) fprintf(stderr,
X"%s: More than one -d option specified\n",
X progname);
X exit(1);
X }
X break;
X case 'l':
X if (lcltime == NULL)
X lcltime = optarg;
X else {
X (void) fprintf(stderr,
X"%s: More than one -l option specified\n",
X progname);
X exit(1);
X }
X break;
X case 'v':
X noise = TRUE;
X break;
X }
X if (optind == argc - 1 && strcmp(argv[optind], "=") == 0)
X usage(); /* usage message by request */
X if (directory == NULL)
X directory = TZDIR;
X
X setboundaries();
X
X zones = (struct zone *) emalloc(0);
X rules = (struct rule *) emalloc(0);
X links = (struct link *) emalloc(0);
X for (i = optind; i < argc; ++i)
X infile(argv[i]);
X if (errors)
X exit(1);
X associate();
X for (i = 0; i < nzones; i = j) {
X /*
X * Find the next non-continuation zone entry.
X */
X for (j = i + 1; j < nzones && zones[j].z_name == NULL; ++j)
X ;
X outzone(&zones[i], j - i);
X }
X /*
X ** We'll take the easy way out on this last part.
X */
X if (chdir(directory) != 0) {
X (void) fprintf(stderr, "%s: Can't chdir to ", progname);
X perror(directory);
X exit(1);
X }
X for (i = 0; i < nlinks; ++i) {
X nondunlink(links[i].l_to);
X if (link(links[i].l_from, links[i].l_to) != 0) {
X (void) fprintf(stderr, "%s: Can't link %s to ",
X progname, links[i].l_from);
X perror(links[i].l_to);
X exit(1);
X }
X }
X if (lcltime != NULL) {
X nondunlink(TZDEFAULT);
X if (link(lcltime, TZDEFAULT) != 0) {
X (void) fprintf(stderr, "%s: Can't link %s to ",
X progname, lcltime);
X perror(TZDEFAULT);
X exit(1);
X }
X }
X exit((errors == 0) ? 0 : 1);
X}
X
Xstatic
Xsetboundaries()
X{
X register time_t bit;
X
X for (bit = 1; bit > 0; bit <<= 1)
X ;
X if (bit == 0) { /* time_t is an unsigned type */
X tt_signed = FALSE;
X min_time = 0;
X max_time = ~(time_t) 0;
X } else {
X tt_signed = TRUE;
X min_time = bit;
X max_time = bit;
X ++max_time;
X max_time = -max_time;
X }
X min_year = TM_YEAR_BASE + gmtime(&min_time)->tm_year;
X max_year = TM_YEAR_BASE + gmtime(&max_time)->tm_year;
X}
X
X/*
X** We get to be careful here since there's a fair chance of root running us.
X*/
X
Xstatic
Xnondunlink(name)
Xchar * name;
X{
X struct stat s;
X
X if (stat(name, &s) != 0)
X return;
X if ((s.st_mode & S_IFMT) == S_IFDIR)
X return;
X (void) unlink(name);
X}
X
X/*
X** Associate sets of rules with zones.
X*/
X
X/*
X** Sort by rule name.
X*/
X
Xstatic
Xrcomp(cp1, cp2)
Xchar * cp1;
Xchar * cp2;
X{
X return strcmp(((struct rule *) cp1)->r_name,
X ((struct rule *) cp2)->r_name);
X}
X
Xstatic
Xassociate()
X{
X register struct zone * zp;
X register struct rule * rp;
X register int base, out;
X register int i;
X
X if (nrules != 0)
X (void) qsort((char *) rules, nrules, sizeof *rules, rcomp);
X for (i = 0; i < nzones; ++i) {
X zp = &zones[i];
X zp->z_rules = NULL;
X zp->z_nrules = 0;
X }
X for (base = 0; base < nrules; base = out) {
X rp = &rules[base];
X for (out = base + 1; out < nrules; ++out)
X if (strcmp(rp->r_name, rules[out].r_name) != 0)
X break;
X for (i = 0; i < nzones; ++i) {
X zp = &zones[i];
X if (strcmp(zp->z_rule, rp->r_name) != 0)
X continue;
X zp->z_rules = rp;
X zp->z_nrules = out - base;
X }
X }
X for (i = 0; i < nzones; ++i) {
X zp = &zones[i];
X if (zp->z_nrules == 0) {
X /*
X ** Maybe we have a local standard time offset.
X */
X eat(zp->z_filename, zp->z_linenum);
X zp->z_stdoff = gethms(zp->z_rule, "unruly zone", TRUE);
X /*
X ** Note, though, that if there's no rule,
X ** a '%s' in the format is a bad thing.
X */
X if (index(zp->z_format, '%') != 0)
X error("%s in ruleless zone");
X }
X }
X if (errors)
X exit(1);
X}
X
Xstatic
Xinfile(name)
Xchar * name;
X{
X register FILE * fp;
X register char ** fields;
X register char * cp;
X register struct lookup * lp;
X register int nfields;
X register int wantcont;
X register int num;
X char buf[BUFSIZ];
X
X if (strcmp(name, "-") == 0) {
X name = "standard input";
X fp = stdin;
X } else if ((fp = fopen(name, "r")) == NULL) {
X (void) fprintf(stderr, "%s: Can't open ", progname);
X perror(name);
X exit(1);
X }
X wantcont = FALSE;
X for (num = 1; ; ++num) {
X eat(name, num);
X if (fgets(buf, sizeof buf, fp) != buf)
X break;
X cp = index(buf, '\n');
X if (cp == NULL) {
X error("line too long");
X exit(1);
X }
X *cp = '\0';
X fields = getfields(buf);
X nfields = 0;
X while (fields[nfields] != NULL) {
X if (ciequal(fields[nfields], "-"))
X fields[nfields] = "";
X ++nfields;
X }
X if (nfields == 0) {
X /* nothing to do */
X } else if (wantcont) {
X wantcont = inzcont(fields, nfields);
X } else {
X lp = byword(fields[0], line_codes);
X if (lp == NULL)
X error("input line of unknown type");
X else switch ((int) (lp->l_value)) {
X case LC_RULE:
X inrule(fields, nfields);
X wantcont = FALSE;
X break;
X case LC_ZONE:
X wantcont = inzone(fields, nfields);
X break;
X case LC_LINK:
X inlink(fields, nfields);
X wantcont = FALSE;
X break;
X default: /* "cannot happen" */
X (void) fprintf(stderr,
X"%s: panic: Invalid l_value %d\n",
X progname, lp->l_value);
X exit(1);
X }
X }
X free((char *) fields);
X }
X if (ferror(fp)) {
X (void) fprintf(stderr, "%s: Error reading ", progname);
X perror(filename);
X exit(1);
X }
X if (fp != stdin && fclose(fp)) {
X (void) fprintf(stderr, "%s: Error closing ", progname);
X perror(filename);
X exit(1);
X }
X if (wantcont)
X error("expected continuation line not found");
X}
X
X/*
X** Convert a string of one of the forms
X** h -h hh:mm -hh:mm hh:mm:ss -hh:mm:ss
X** into a number of seconds.
X** A null string maps to zero.
X** Call error with errstring and return zero on errors.
X*/
X
Xstatic long
Xgethms(string, errstring, signable)
Xchar * string;
Xchar * errstring;
X{
X int hh, mm, ss, sign;
X
X if (string == NULL || *string == '\0')
X return 0;
X if (!signable)
X sign = 1;
X else if (*string == '-') {
X sign = -1;
X ++string;
X } else sign = 1;
X if (sscanf(string, scheck(string, "%d"), &hh) == 1)
X mm = ss = 0;
X else if (sscanf(string, scheck(string, "%d:%d"), &hh, &mm) == 2)
X ss = 0;
X else if (sscanf(string, scheck(string, "%d:%d:%d"),
X &hh, &mm, &ss) != 3) {
X error(errstring);
X return 0;
X }
X if (hh < 0 || hh >= HOURS_PER_DAY ||
X mm < 0 || mm >= MINS_PER_HOUR ||
X ss < 0 || ss >= SECS_PER_MIN) {
X error(errstring);
X return 0;
X }
X return eitol(sign) *
X (eitol(hh * MINS_PER_HOUR + mm) *
X eitol(SECS_PER_MIN) + eitol(ss));
X}
X
Xstatic
Xinrule(fields, nfields)
Xregister char ** fields;
X{
X struct rule r;
X
X if (nfields != RULE_FIELDS) {
X error("wrong number of fields on Rule line");
X return;
X }
X if (*fields[RF_NAME] == '\0') {
X error("nameless rule");
X return;
X }
X r.r_filename = filename;
X r.r_linenum = linenum;
X r.r_stdoff = gethms(fields[RF_STDOFF], "invalid saved time", TRUE);
X rulesub(&r, fields[RF_LOYEAR], fields[RF_HIYEAR], fields[RF_COMMAND],
X fields[RF_MONTH], fields[RF_DAY], fields[RF_TOD]);
X r.r_name = ecpyalloc(fields[RF_NAME]);
X r.r_abbrvar = ecpyalloc(fields[RF_ABBRVAR]);
X rules = (struct rule *) erealloc((char *) rules,
X (nrules + 1) * sizeof *rules);
X rules[nrules++] = r;
X}
X
Xstatic
Xinzone(fields, nfields)
Xregister char ** fields;
X{
X register int i;
X char buf[132];
X
X if (nfields < ZONE_MINFIELDS || nfields > ZONE_MAXFIELDS) {
X error("wrong number of fields on Zone line");
X return FALSE;
X }
X if (strcmp(fields[ZF_NAME], TZDEFAULT) == 0 && lcltime != NULL) {
X (void) sprintf(buf,
X "\"Zone %s\" line and -l option are mutually exclusive",
X TZDEFAULT);
X error(buf);
X return FALSE;
X }
X for (i = 0; i < nzones; ++i)
X if (zones[i].z_name != NULL &&
X strcmp(zones[i].z_name, fields[ZF_NAME]) == 0) {
X (void) sprintf(buf,
X"duplicate zone name %s (file \"%s\", line %d)",
X fields[ZF_NAME],
X zones[i].z_filename,
X zones[i].z_linenum);
X error(buf);
X return FALSE;
X }
X return inzsub(fields, nfields, FALSE);
X}
X
Xstatic
Xinzcont(fields, nfields)
Xregister char ** fields;
X{
X if (nfields < ZONEC_MINFIELDS || nfields > ZONEC_MAXFIELDS) {
X error("wrong number of fields on Zone continuation line");
X return FALSE;
X }
X return inzsub(fields, nfields, TRUE);
X}
X
Xstatic
Xinzsub(fields, nfields, iscont)
Xregister char ** fields;
X{
X register char * cp;
X struct zone z;
X register int i_gmtoff, i_rule, i_format;
X register int i_untilyear, i_untilmonth;
X register int i_untilday, i_untiltime;
X register int hasuntil;
X
X if (iscont) {
X i_gmtoff = ZFC_GMTOFF;
X i_rule = ZFC_RULE;
X i_format = ZFC_FORMAT;
X i_untilyear = ZFC_UNTILYEAR;
X i_untilmonth = ZFC_UNTILMONTH;
X i_untilday = ZFC_UNTILDAY;
X i_untiltime = ZFC_UNTILTIME;
X z.z_name = NULL;
X } else {
X i_gmtoff = ZF_GMTOFF;
X i_rule = ZF_RULE;
X i_format = ZF_FORMAT;
X i_untilyear = ZF_UNTILYEAR;
X i_untilmonth = ZF_UNTILMONTH;
X i_untilday = ZF_UNTILDAY;
X i_untiltime = ZF_UNTILTIME;
X z.z_name = ecpyalloc(fields[ZF_NAME]);
X }
X z.z_filename = filename;
X z.z_linenum = linenum;
X z.z_gmtoff = gethms(fields[i_gmtoff], "invalid GMT offset", TRUE);
X if ((cp = index(fields[i_format], '%')) != 0) {
X if (*++cp != 's' || index(cp, '%') != 0) {
X error("invalid abbreviation format");
X return FALSE;
X }
X }
X z.z_rule = ecpyalloc(fields[i_rule]);
X z.z_format = ecpyalloc(fields[i_format]);
X hasuntil = nfields > i_untilyear;
X if (hasuntil) {
X z.z_untilrule.r_filename = filename;
X z.z_untilrule.r_linenum = linenum;
X rulesub(&z.z_untilrule,
X fields[i_untilyear],
X "only",
X "",
X (nfields > i_untilmonth) ? fields[i_untilmonth] : "Jan",
X (nfields > i_untilday) ? fields[i_untilday] : "1",
X (nfields > i_untiltime) ? fields[i_untiltime] : "0");
X z.z_untiltime = rpytime(&z.z_untilrule, z.z_untilrule.r_loyear);
X if (iscont && nzones > 0 && z.z_untiltime < max_time &&
X z.z_untiltime > min_time &&
X zones[nzones - 1].z_untiltime >= z.z_untiltime) {
Xerror("Zone continuation line end time is not after end time of previous line");
X return FALSE;
X }
X }
X zones = (struct zone *) erealloc((char *) zones,
X (nzones + 1) * sizeof *zones);
X zones[nzones++] = z;
X /*
X ** If there was an UNTIL field on this line,
X ** there's more information about the zone on the next line.
X */
X return hasuntil;
X}
X
Xstatic
Xinlink(fields, nfields)
Xregister char ** fields;
X{
X struct link l;
X
X if (nfields != LINK_FIELDS) {
X error("wrong number of fields on Link line");
X return;
X }
X if (*fields[LF_FROM] == '\0') {
X error("blank FROM field on Link line");
X return;
X }
X if (*fields[LF_TO] == '\0') {
X error("blank TO field on Link line");
X return;
X }
X l.l_filename = filename;
X l.l_linenum = linenum;
X l.l_from = ecpyalloc(fields[LF_FROM]);
X l.l_to = ecpyalloc(fields[LF_TO]);
X links = (struct link *) erealloc((char *) links,
X (nlinks + 1) * sizeof *links);
X links[nlinks++] = l;
X}
X
Xstatic
Xrulesub(rp, loyearp, hiyearp, typep, monthp, dayp, timep)
Xregister struct rule * rp;
Xchar * loyearp;
Xchar * hiyearp;
Xchar * typep;
Xchar * monthp;
Xchar * dayp;
Xchar * timep;
X{
X register struct lookup * lp;
X register char * cp;
X
X if ((lp = byword(monthp, mon_names)) == NULL) {
X error("invalid month name");
X return;
X }
X rp->r_month = lp->l_value;
X rp->r_todisstd = FALSE;
X cp = timep;
X if (*cp != '\0') {
X cp += strlen(cp) - 1;
X switch (lowerit(*cp)) {
X case 's':
X rp->r_todisstd = TRUE;
X *cp = '\0';
X break;
X case 'w':
X rp->r_todisstd = FALSE;
X *cp = '\0';
X break;
X }
X }
X rp->r_tod = gethms(timep, "invalid time of day", FALSE);
X /*
X ** Year work.
X */
X cp = loyearp;
X if ((lp = byword(cp, begin_years)) != NULL) switch ((int) lp->l_value) {
X case YR_MINIMUM:
X rp->r_loyear = min_year;
X break;
X case YR_MAXIMUM:
X rp->r_loyear = max_year;
X break;
X default: /* "cannot happen" */
X (void) fprintf(stderr,
X "%s: panic: Invalid l_value %d\n",
X progname, lp->l_value);
X exit(1);
X } else if (sscanf(cp, scheck(cp, "%d"), &rp->r_loyear) != 1 ||
X rp->r_loyear < min_year || rp->r_loyear > max_year) {
X if (noise)
X error("invalid starting year");
X if (rp->r_loyear > max_year)
X return;
X }
X cp = hiyearp;
X if ((lp = byword(cp, end_years)) != NULL) switch ((int) lp->l_value) {
X case YR_MINIMUM:
X rp->r_hiyear = min_year;
X break;
X case YR_MAXIMUM:
X rp->r_hiyear = max_year;
X break;
X case YR_ONLY:
X rp->r_hiyear = rp->r_loyear;
X break;
X default: /* "cannot happen" */
X (void) fprintf(stderr,
X "%s: panic: Invalid l_value %d\n",
X progname, lp->l_value);
X exit(1);
X } else if (sscanf(cp, scheck(cp, "%d"), &rp->r_hiyear) != 1 ||
X rp->r_hiyear < min_year || rp->r_hiyear > max_year) {
X if (noise)
X error("invalid ending year");
X if (rp->r_hiyear < min_year)
X return;
X }
X if (rp->r_hiyear < min_year)
X return;
X if (rp->r_loyear < min_year)
X rp->r_loyear = min_year;
X if (rp->r_hiyear > max_year)
X rp->r_hiyear = max_year;
X if (rp->r_loyear > rp->r_hiyear) {
X error("starting year greater than ending year");
X return;
X }
X if (*typep == '\0')
X rp->r_yrtype = NULL;
X else {
X if (rp->r_loyear == rp->r_hiyear) {
X error("typed single year");
X return;
X }
X rp->r_yrtype = ecpyalloc(typep);
X }
X /*
X ** Day work.
X ** Accept things such as:
X ** 1
X ** last-Sunday
X ** Sun<=20
X ** Sun>=7
X */
X if ((lp = byword(dayp, lasts)) != NULL) {
X rp->r_dycode = DC_DOWLEQ;
X rp->r_wday = lp->l_value;
X rp->r_dayofmonth = len_months[1][rp->r_month];
X } else {
X if ((cp = index(dayp, '<')) != 0)
X rp->r_dycode = DC_DOWLEQ;
X else if ((cp = index(dayp, '>')) != 0)
X rp->r_dycode = DC_DOWGEQ;
X else {
X cp = dayp;
X rp->r_dycode = DC_DOM;
X }
X if (rp->r_dycode != DC_DOM) {
X *cp++ = 0;
X if (*cp++ != '=') {
X error("invalid day of month");
X return;
X }
X if ((lp = byword(dayp, wday_names)) == NULL) {
X error("invalid weekday name");
X return;
X }
X rp->r_wday = lp->l_value;
X }
X if (sscanf(cp, scheck(cp, "%d"), &rp->r_dayofmonth) != 1 ||
X rp->r_dayofmonth <= 0 ||
X (rp->r_dayofmonth > len_months[1][rp->r_month])) {
X error("invalid day of month");
X return;
X }
X }
X}
X
Xstatic
Xputtzcode(val, fp)
Xlong val;
XFILE * fp;
X{
X register int c;
X register int shift;
X
X for (shift = 24; shift >= 0; shift -= 8) {
X c = val >> shift;
X (void) putc(c, fp);
X }
X}
X
Xstatic
Xwritezone(name)
Xchar * name;
X{
X register FILE * fp;
X register int i;
X char fullname[BUFSIZ];
X
X if (strlen(directory) + 1 + strlen(name) >= sizeof fullname) {
X (void) fprintf(stderr,
X "%s: File name %s/%s too long\n", progname,
X directory, name);
X exit(1);
X }
X (void) sprintf(fullname, "%s/%s", directory, name);
X if ((fp = fopen(fullname, "w")) == NULL) {
X if (mkdirs(fullname) != 0)
X exit(1);
X if ((fp = fopen(fullname, "w")) == NULL) {
X (void) fprintf(stderr, "%s: Can't create ", progname);
X perror(fullname);
X exit(1);
X }
X }
X (void) fseek(fp, (long) sizeof ((struct tzhead *) 0)->tzh_reserved, 0);
X puttzcode(eitol(timecnt), fp);
X puttzcode(eitol(typecnt), fp);
X puttzcode(eitol(charcnt), fp);
X for (i = 0; i < timecnt; ++i)
X puttzcode((long) ats[i], fp);
X if (timecnt > 0)
X (void) fwrite((char *) types, sizeof types[0],
X (int) timecnt, fp);
X for (i = 0; i < typecnt; ++i) {
X puttzcode((long) gmtoffs[i], fp);
X (void) putc(isdsts[i], fp);
X (void) putc(abbrinds[i], fp);
X }
X if (charcnt != 0)
X (void) fwrite(chars, sizeof chars[0], (int) charcnt, fp);
X if (ferror(fp) || fclose(fp)) {
X (void) fprintf(stderr, "%s: Write error on ", progname);
X perror(fullname);
X exit(1);
X }
X}
X
Xstatic
Xoutzone(zpfirst, zonecount)
Xstruct zone * zpfirst;
X{
X register struct zone * zp;
X register struct rule * rp;
X register int i, j;
X register int usestart, useuntil;
X register time_t starttime, untiltime;
X register long gmtoff;
X register long stdoff;
X register int year;
X register long startoff;
X register int startisdst;
X register int type;
X char startbuf[BUFSIZ];
X
X /*
X ** Now. . .finally. . .generate some useful data!
X */
X timecnt = 0;
X typecnt = 0;
X charcnt = 0;
X /*
X ** Two guesses. . .the second may well be corrected later.
X */
X gmtoff = zpfirst->z_gmtoff;
X stdoff = 0;
X for (i = 0; i < zonecount; ++i) {
X usestart = i > 0;
X useuntil = i < (zonecount - 1);
X zp = &zpfirst[i];
X eat(zp->z_filename, zp->z_linenum);
X startisdst = -1;
X if (zp->z_nrules == 0) {
X type = addtype(oadd(zp->z_gmtoff, zp->z_stdoff),
X zp->z_format, zp->z_stdoff != 0);
X if (usestart)
X addtt(starttime, type);
X gmtoff = zp->z_gmtoff;
X stdoff = zp->z_stdoff;
X } else for (year = min_year; year <= max_year; ++year) {
X if (useuntil && year > zp->z_untilrule.r_hiyear)
X break;
X /*
X ** Mark which rules to do in the current year.
X ** For those to do, calculate rpytime(rp, year);
X */
X for (j = 0; j < zp->z_nrules; ++j) {
X rp = &zp->z_rules[j];
X eats(zp->z_filename, zp->z_linenum,
X rp->r_filename, rp->r_linenum);
X rp->r_todo = year >= rp->r_loyear &&
X year <= rp->r_hiyear &&
X yearistype(year, rp->r_yrtype);
X if (rp->r_todo)
X rp->r_temp = rpytime(rp, year);
X }
X for ( ; ; ) {
X register int k;
X register time_t jtime, ktime;
X register long offset;
X char buf[BUFSIZ];
X
X if (useuntil) {
X /*
X ** Turn untiltime into GMT
X ** assuming the current gmtoff and
X ** stdoff values.
X */
X offset = gmtoff;
X if (!zp->z_untilrule.r_todisstd)
X offset = oadd(offset, stdoff);
X untiltime = tadd(zp->z_untiltime,
X -offset);
X }
X /*
X ** Find the rule (of those to do, if any)
X ** that takes effect earliest in the year.
X */
X k = -1;
X for (j = 0; j < zp->z_nrules; ++j) {
X rp = &zp->z_rules[j];
X if (!rp->r_todo)
X continue;
X eats(zp->z_filename, zp->z_linenum,
X rp->r_filename, rp->r_linenum);
X offset = gmtoff;
X if (!rp->r_todisstd)
X offset = oadd(offset, stdoff);
X jtime = rp->r_temp;
X if (jtime == min_time ||
X jtime == max_time)
X continue;
X jtime = tadd(jtime, -offset);
X if (k < 0 || jtime < ktime) {
X k = j;
X ktime = jtime;
X }
X }
X if (k < 0)
X break; /* go on to next year */
X rp = &zp->z_rules[k];
X rp->r_todo = FALSE;
X if (useuntil && ktime >= untiltime)
X break;
X if (usestart) {
X if (ktime < starttime) {
X stdoff = rp->r_stdoff;
X startoff = oadd(zp->z_gmtoff,
X rp->r_stdoff);
X (void) sprintf(startbuf,
X zp->z_format,
X rp->r_abbrvar);
X startisdst =
X rp->r_stdoff != 0;
X continue;
X }
X if (ktime != starttime &&
X startisdst >= 0)
Xaddtt(starttime, addtype(startoff, startbuf, startisdst));
X usestart = FALSE;
X }
X eats(zp->z_filename, zp->z_linenum,
X rp->r_filename, rp->r_linenum);
X (void) sprintf(buf, zp->z_format,
X rp->r_abbrvar);
X offset = oadd(zp->z_gmtoff, rp->r_stdoff);
X type = addtype(offset, buf, rp->r_stdoff != 0);
X if (timecnt != 0 || rp->r_stdoff != 0)
X addtt(ktime, type);
X gmtoff = zp->z_gmtoff;
X stdoff = rp->r_stdoff;
X }
X }
X /*
X ** Now we may get to set starttime for the next zone line.
X */
X if (useuntil)
X starttime = tadd(zp->z_untiltime,
X -gmtoffs[types[timecnt - 1]]);
X }
X writezone(zpfirst->z_name);
X}
X
Xstatic
Xaddtt(starttime, type)
Xtime_t starttime;
X{
X if (timecnt != 0 && type == types[timecnt - 1])
X return; /* easy enough! */
X if (timecnt >= TZ_MAX_TIMES) {
X error("too many transitions?!");
X exit(1);
X }
X ats[timecnt] = starttime;
X types[timecnt] = type;
X ++timecnt;
X}
X
Xstatic
Xaddtype(gmtoff, abbr, isdst)
Xlong gmtoff;
Xchar * abbr;
X{
X register int i, j;
X
X /*
X ** See if there's already an entry for this zone type.
X ** If so, just return its index.
X */
X for (i = 0; i < typecnt; ++i) {
X if (gmtoff == gmtoffs[i] && isdst == isdsts[i] &&
X strcmp(abbr, &chars[abbrinds[i]]) == 0)
X return i;
X }
X /*
X ** There isn't one; add a new one, unless there are already too
X ** many.
X */
X if (typecnt >= TZ_MAX_TYPES) {
X error("too many local time types");
X exit(1);
X }
X gmtoffs[i] = gmtoff;
X isdsts[i] = isdst;
X
X for (j = 0; j < charcnt; ++j)
X if (strcmp(&chars[j], abbr) == 0)
X break;
X if (j == charcnt)
X newabbr(abbr);
X abbrinds[i] = j;
X ++typecnt;
X return i;
X}
X
Xstatic
Xyearistype(year, type)
Xchar * type;
X{
X char buf[BUFSIZ];
X int result;
X
X if (type == NULL || *type == '\0')
X return TRUE;
X if (strcmp(type, "uspres") == 0)
X return (year % 4) == 0;
X if (strcmp(type, "nonpres") == 0)
X return (year % 4) != 0;
X (void) sprintf(buf, "yearistype %d %s", year, type);
X result = system(buf);
X if (result == 0)
X return TRUE;
X if (result == 1 << 8)
X return FALSE;
X error("Wild result from command execution");
X (void) fprintf(stderr, "%s: command was '%s', result was %d\n",
X progname, buf, result);
X for ( ; ; )
X exit(1);
X}
X
Xstatic
Xlowerit(a)
X{
X return (isascii(a) && isupper(a)) ? tolower(a) : a;
X}
X
Xstatic
Xciequal(ap, bp) /* case-insensitive equality */
Xregister char * ap;
Xregister char * bp;
X{
X while (lowerit(*ap) == lowerit(*bp++))
X if (*ap++ == '\0')
X return TRUE;
X return FALSE;
X}
X
Xstatic
Xisabbr(abbr, word)
Xregister char * abbr;
Xregister char * word;
X{
X if (lowerit(*abbr) != lowerit(*word))
X return FALSE;
X ++word;
X while (*++abbr != '\0')
X do if (*word == '\0')
X return FALSE;
X while (lowerit(*word++) != lowerit(*abbr));
X return TRUE;
X}
X
Xstatic struct lookup *
Xbyword(word, table)
Xregister char * word;
Xregister struct lookup * table;
X{
X register struct lookup * foundlp;
X register struct lookup * lp;
X
X if (word == NULL || table == NULL)
X return NULL;
X /*
X ** Look for exact match.
X */
X for (lp = table; lp->l_word != NULL; ++lp)
X if (ciequal(word, lp->l_word))
X return lp;
X /*
X ** Look for inexact match.
X */
X foundlp = NULL;
X for (lp = table; lp->l_word != NULL; ++lp)
X if (isabbr(word, lp->l_word))
X if (foundlp == NULL)
X foundlp = lp;
X else return NULL; /* multiple inexact matches */
X return foundlp;
X}
X
Xstatic char **
Xgetfields(cp)
Xregister char * cp;
X{
X register char * dp;
X register char ** array;
X register int nsubs;
X
X if (cp == NULL)
X return NULL;
X array = (char **) emalloc((strlen(cp) + 1) * sizeof *array);
X nsubs = 0;
X for ( ; ; ) {
X while (isascii(*cp) && isspace(*cp))
X ++cp;
X if (*cp == '\0' || *cp == '#')
X break;
X array[nsubs++] = dp = cp;
X do {
X if ((*dp = *cp++) != '"')
X ++dp;
X else while ((*dp = *cp++) != '"')
X if (*dp != '\0')
X ++dp;
X else error("Odd number of quotation marks");
X } while (*cp != '\0' && *cp != '#' &&
X (!isascii(*cp) || !isspace(*cp)));
X if (isascii(*cp) && isspace(*cp))
X ++cp;
X *dp = '\0';
X }
X array[nsubs] = NULL;
X return array;
X}
X
Xstatic long
Xoadd(t1, t2)
Xlong t1;
Xlong t2;
X{
X register long t;
X
X t = t1 + t2;
X if (t2 > 0 && t <= t1 || t2 < 0 && t >= t1) {
X error("time overflow");
X exit(1);
X }
X return t;
X}
X
Xstatic time_t
Xtadd(t1, t2)
Xtime_t t1;
Xlong t2;
X{
X register time_t t;
X
X if (t1 == max_time && t2 > 0)
X return max_time;
X if (t1 == min_time && t2 < 0)
X return min_time;
X t = t1 + t2;
X if (t2 > 0 && t <= t1 || t2 < 0 && t >= t1) {
X error("time overflow");
X exit(1);
X }
X return t;
X}
X
X/*
X** Given a rule, and a year, compute the date - in seconds since January 1,
X** 1970, 00:00 LOCAL time - in that year that the rule refers to.
X*/
X
Xstatic time_t
Xrpytime(rp, wantedy)
Xregister struct rule * rp;
Xregister int wantedy;
X{
X register int y, m, i;
X register long dayoff; /* with a nod to Margaret O. */
X register time_t t;
X
X dayoff = 0;
X m = TM_JANUARY;
X y = EPOCH_YEAR;
X while (wantedy != y) {
X if (wantedy > y) {
X i = len_years[isleap(y)];
X ++y;
X } else {
X --y;
X i = -len_years[isleap(y)];
X }
X dayoff = oadd(dayoff, eitol(i));
X }
X while (m != rp->r_month) {
X i = len_months[isleap(y)][m];
X dayoff = oadd(dayoff, eitol(i));
X ++m;
X }
X i = rp->r_dayofmonth;
X if (m == TM_FEBRUARY && i == 29 && !isleap(y)) {
X if (rp->r_dycode == DC_DOWLEQ)
X --i;
X else {
X error("use of 2/29 in non leap-year");
X exit(1);
X }
X }
X --i;
X dayoff = oadd(dayoff, eitol(i));
X if (rp->r_dycode == DC_DOWGEQ || rp->r_dycode == DC_DOWLEQ) {
X register long wday;
X
X#define LDAYS_PER_WEEK ((long) DAYS_PER_WEEK)
X wday = eitol(EPOCH_WDAY);
X /*
X ** Don't trust mod of negative numbers.
X */
X if (dayoff >= 0)
X wday = (wday + dayoff) % LDAYS_PER_WEEK;
X else {
X wday -= ((-dayoff) % LDAYS_PER_WEEK);
X if (wday < 0)
X wday += LDAYS_PER_WEEK;
X }
X while (wday != eitol(rp->r_wday))
X if (rp->r_dycode == DC_DOWGEQ) {
X dayoff = oadd(dayoff, (long) 1);
X if (++wday >= LDAYS_PER_WEEK)
X wday = 0;
X ++i;
X } else {
X dayoff = oadd(dayoff, (long) -1);
X if (--wday < 0)
X wday = LDAYS_PER_WEEK;
X --i;
X }
X if (i < 0 || i >= len_months[isleap(y)][m]) {
X error("no day in month matches rule");
X exit(1);
X }
X }
X if (dayoff < 0 && !tt_signed) {
X if (wantedy == rp->r_loyear)
X return min_time;
X error("time before zero");
X exit(1);
X }
X t = (time_t) dayoff * SECS_PER_DAY;
X /*
X ** Cheap overflow check.
X */
X if (t / SECS_PER_DAY != dayoff) {
X if (wantedy == rp->r_hiyear)
X return max_time;
X if (wantedy == rp->r_loyear)
X return min_time;
X error("time overflow");
X exit(1);
X }
X return tadd(t, rp->r_tod);
X}
X
Xstatic
Xnewabbr(string)
Xchar * string;
X{
X register int i;
X
X i = strlen(string) + 1;
X if (charcnt + i >= TZ_MAX_CHARS) {
X error("too many, or too long, time zone abbreviations");
X exit(1);
X }
X (void) strcpy(&chars[charcnt], string);
X charcnt += eitol(i);
X}
X
Xstatic
Xmkdirs(name)
Xchar * name;
X{
X register char * cp;
X
X if ((cp = name) == NULL || *cp == '\0')
X return 0;
X while ((cp = index(cp + 1,'/')) != 0) {
X *cp = '\0';
X if (access(name,F_OK) && mkdir(name,0755)) {
X perror(name);
X return -1;
X }
X *cp = '/';
X }
X return 0;
X}
X
Xstatic long
Xeitol(i)
X{
X long l;
X
X l = i;
X if (i < 0 && l >= 0 || i == 0 && l != 0 || i > 0 && l <= 0) {
X (void) fprintf(stderr, "%s: %d did not sign extend correctly\n",
X progname, i);
X exit(1);
X }
X return l;
X}
X
X/*
X** UNIX is a registered trademark of AT&T.
X*/
END-of-zic.c
exit