[comp.bugs.4bsd.ucb-fixes] ARTICLE #16

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