Files
Arquivotheca.SunOS-4.1.4/usr.etc/zic/tzsetup.c
seta75D ff309bfe1c Init
2021-10-11 18:37:13 -03:00

630 lines
19 KiB
C

#ifndef lint
static char sccsid[] = "@(#)tzsetup.c 1.1 94/10/31 SMI";
#endif
/*
* Copyright (c) 1987 by Sun Microsystems, Inc.
*/
#include <stdio.h>
extern int fprintf();
extern char *sprintf();
#include <fcntl.h>
#include <errno.h>
#include <sys/param.h>
#include <sys/time.h>
extern time_t time();
extern int settimeofday();
#include <tzfile.h>
#include <values.h>
static long detzcode(/*char *codep*/);
static int tzload(/*char *name*/);
static void get_timezone_info(/*struct timezone *tz*/);
static void get_dsttime(/*struct timezone *tz*/);
static void test(/*time_t t, int minuteswest, struct dayrules *dr,
int weight*/);
static struct tm *oldlocaltime(/*time_t *tim, int minuteswest,
struct dayrules *dr*/);
static int sunday(/*struct tm *t, int d*/);
static char *strerror(/*int errnum*/);
static time_t now;
/*
* The following tables specify the days that daylight savings time
* started and ended for some year or, if the year in the table is
* 0, for all years not explicitly mentioned in the table.
* Both days are assumed to be Sundays. For entries for specific years,
* they are given as the day number of the Sunday of the change. For
* wildcard entries, it is assumed that the day is specified by a rule
* of the form "first Sunday of <some month>" or "last Sunday of <some
* month>." In the former case, the negative of the day number of the
* first day of that month is given; in the latter case, the day number
* of the last day of that month is given.
*
* In the northern hemisphere, Daylight Savings Time runs for a period in
* the middle of the year; thus, days between the start day and the end
* day have DST active. In the southern hemisphere, Daylight Savings Time
* runs from the beginning of the year to some time in the middle of the
* year, and from some time later in the year to the end of the year; thus,
* days after the start day or before the end day have DST active.
*/
struct dstab {
int dayyr;
int daylb;
int dayle;
};
/*
* The U.S. tables, including the latest hack.
*/
static struct dstab usdaytab[] = {
1970, 119, 303, /* 1970: last Sun. in Apr - last Sun. in Oct */
1971, 119, 303, /* 1971: last Sun. in Apr - last Sun. in Oct */
1972, 119, 303, /* 1972: last Sun. in Apr - last Sun. in Oct */
1973, 119, 303, /* 1973: last Sun. in Apr - last Sun. in Oct */
1974, 5, 303, /* 1974: Jan 6 - last Sun. in Oct */
1975, 58, 303, /* 1975: Last Sun. in Feb - last Sun. in Oct */
1976, 119, 303, /* 1976: last Sun. in Apr - last Sun. in Oct */
1977, 119, 303, /* 1977: last Sun. in Apr - last Sun. in Oct */
1978, 119, 303, /* 1978: last Sun. in Apr - last Sun. in Oct */
1979, 119, 303, /* 1979: last Sun. in Apr - last Sun. in Oct */
1980, 119, 303, /* 1980: last Sun. in Apr - last Sun. in Oct */
1981, 119, 303, /* 1981: last Sun. in Apr - last Sun. in Oct */
1982, 119, 303, /* 1982: last Sun. in Apr - last Sun. in Oct */
1983, 119, 303, /* 1983: last Sun. in Apr - last Sun. in Oct */
1984, 119, 303, /* 1984: last Sun. in Apr - last Sun. in Oct */
1985, 119, 303, /* 1985: last Sun. in Apr - last Sun. in Oct */
1986, 119, 303, /* 1986: last Sun. in Apr - last Sun. in Oct */
0, -90, 303, /* 1987 on: first Sun. in Apr - last Sun. in Oct */
};
/*
* Canada, same as the US, except no early 70's fluctuations and no late 80's
* hack.
*/
static struct dstab candaytab[] = {
1970, 119, 303, /* 1970: last Sun. in Apr - last Sun. in Oct */
1971, 119, 303, /* 1971: last Sun. in Apr - last Sun. in Oct */
1972, 119, 303, /* 1972: last Sun. in Apr - last Sun. in Oct */
1973, 119, 303, /* 1973: last Sun. in Apr - last Sun. in Oct */
1974, 119, 303, /* 1974: last Sun. in Apr - last Sun. in Oct */
1975, 119, 303, /* 1975: Last Sun. in Apr - last Sun. in Oct */
1976, 119, 303, /* 1976: last Sun. in Apr - last Sun. in Oct */
1977, 119, 303, /* 1977: last Sun. in Apr - last Sun. in Oct */
1978, 119, 303, /* 1978: last Sun. in Apr - last Sun. in Oct */
1979, 119, 303, /* 1979: last Sun. in Apr - last Sun. in Oct */
1980, 119, 303, /* 1980: last Sun. in Apr - last Sun. in Oct */
1981, 119, 303, /* 1981: last Sun. in Apr - last Sun. in Oct */
1982, 119, 303, /* 1982: last Sun. in Apr - last Sun. in Oct */
1983, 119, 303, /* 1983: last Sun. in Apr - last Sun. in Oct */
1984, 119, 303, /* 1984: last Sun. in Apr - last Sun. in Oct */
1985, 119, 303, /* 1985: last Sun. in Apr - last Sun. in Oct */
1986, 119, 303, /* 1986: last Sun. in Apr - last Sun. in Oct */
0, -90, 303, /* 1987 on: first Sun. in Apr - last Sun. in Oct */
};
/*
* The Australian tables, for states with DST that don't shift the ending time
* starting in 1986, but shift it starting in 1987.
*/
static struct dstab ausdaytab[] = {
1970, 400, 0, /* 1970: no daylight saving at all */
1971, 303, 0, /* 1971: daylight saving from last Sun. in Oct */
1972, 303, 57, /* 1972: Jan 1 -> Feb 27 & last Sun. in Oct -> Dec 31 */
1973, 303, -59, /* 1973: -> first Sun. in Mar, last Sun. in Oct -> */
1974, 303, -59, /* 1974: -> first Sun. in Mar, last Sun. in Oct -> */
1975, 303, -59, /* 1975: -> first Sun. in Mar, last Sun. in Oct -> */
1976, 303, -59, /* 1976: -> first Sun. in Mar, last Sun. in Oct -> */
1977, 303, -59, /* 1977: -> first Sun. in Mar, last Sun. in Oct -> */
1978, 303, -59, /* 1978: -> first Sun. in Mar, last Sun. in Oct -> */
1979, 303, -59, /* 1979: -> first Sun. in Mar, last Sun. in Oct -> */
1980, 303, -59, /* 1980: -> first Sun. in Mar, last Sun. in Oct -> */
1981, 303, -59, /* 1981: -> first Sun. in Mar, last Sun. in Oct -> */
1982, 303, -59, /* 1982: -> first Sun. in Mar, last Sun. in Oct -> */
1983, 303, -59, /* 1983: -> first Sun. in Mar, last Sun. in Oct -> */
1984, 303, -59, /* 1984: -> first Sun. in Mar, last Sun. in Oct -> */
1985, 303, -59, /* 1985: -> first Sun. in Mar, last Sun. in Oct -> */
1986, -290, -59, /* 1986: -> first Sun. in Mar, first Sun. after Oct 17 -> */
0, -290, 79, /* 1987 on: -> last Sun. before Mar 21, first Sun. after Oct 17 -> */
};
/*
* The Australian tables, for states with DST that do shift the ending time
* starting in 1986. NSW does so; there seems to be a difference of opinion
* about which other states do. There is also a variation in 1983, but
* Robert Elz didn't have it at hand when last he reported.
* Extending the 1986 shift on to infinity is Elz's best guess.
*/
static struct dstab ausaltdaytab[] = {
1970, 400, 0, /* 1970: no daylight saving at all */
1971, 303, 0, /* 1971: daylight saving from last Sun. in Oct */
1972, 303, 57, /* 1972: Jan 1 -> Feb 27 & last Sun. in Oct -> Dec 31 */
1973, 303, -59, /* 1973: -> first Sun. in Mar, last Sun. in Oct -> */
1974, 303, -59, /* 1974: -> first Sun. in Mar, last Sun. in Oct -> */
1975, 303, -59, /* 1975: -> first Sun. in Mar, last Sun. in Oct -> */
1976, 303, -59, /* 1976: -> first Sun. in Mar, last Sun. in Oct -> */
1977, 303, -59, /* 1977: -> first Sun. in Mar, last Sun. in Oct -> */
1978, 303, -59, /* 1978: -> first Sun. in Mar, last Sun. in Oct -> */
1979, 303, -59, /* 1979: -> first Sun. in Mar, last Sun. in Oct -> */
1980, 303, -59, /* 1980: -> first Sun. in Mar, last Sun. in Oct -> */
1981, 303, -59, /* 1981: -> first Sun. in Mar, last Sun. in Oct -> */
1982, 303, -59, /* 1982: -> first Sun. in Mar, last Sun. in Oct -> */
1983, 303, -59, /* 1983: -> first Sun. in Mar, last Sun. in Oct -> */
1984, 303, -59, /* 1984: -> first Sun. in Mar, last Sun. in Oct -> */
1985, 303, -59, /* 1985: -> first Sun. in Mar, last Sun. in Oct -> */
0, -290, 79, /* 1986 on: -> last Sun. before Mar 21, first Sun. after Oct 17 -> */
};
/*
* The European tables, based on investigations by PTB, Braunschweig, FRG.
* Believed correct for:
* GB: United Kingdom and Eire
* WE: Portugal, Poland (in fact MET, following WE dst rules)
* ME: Belgium, Luxembourg, Netherlands, Denmark, Norway,
* Austria, Czechoslovakia, Sweden, Switzerland,
* FRG, GDR, France, Spain, Hungary, Italy, Yugoslavia,
* Western USSR (in fact EET+1; following ME dst rules)
* EE: Finland, Greece, Israel?
*
* Problematic cases are:
* WE: Iceland (no dst)
* EE: Rumania, Turkey (in fact timezone EET+1)
* Terra incognita:
* Albania (MET), Bulgaria (EET), Cyprus (EET)
*
* Years before 1986 are suspect (complicated changes caused
* e.g. by enlargement of the European Community).
* Years before 1983 are VERY suspect (sigh!).
*/
static struct dstab gbdaytab[] = { /* GB and Eire */
0, 89, 303, /* all years: last Sun. in March - last Sun. in Oct */
};
static struct dstab cedaytab[] = { /* Continental European */
0, 89, 272, /* all years: last Sun. in March - last Sun. in Sep */
};
static struct dayrules {
int dst_type; /* number obtained from system */
int dst_hrs; /* hours to add when dst on */
struct dstab * dst_rules; /* one of the above */
enum {STH,NTH} dst_hemi; /* southern, northern hemisphere */
int dst_ontime; /* hour when DST turns on */
int dst_offtime; /* hour when DST turns off */
int dst_errs; /* number of incorrect conversions */
} dayrules [] = {
DST_USA, 1, usdaytab, NTH, 2, 1, 0,
DST_CAN, 1, candaytab, NTH, 2, 1, 0,
DST_AUST, 1, ausdaytab, STH, 2, 2, 0,
DST_AUSTALT, 1, ausaltdaytab, STH, 2, 2, 0,
DST_GB, 1, gbdaytab, NTH, 1, 1, 0,
DST_WET, 1, cedaytab, NTH, 1, 1, 0,
DST_MET, 1, cedaytab, NTH, 2, 2, 0,
DST_EET, 1, cedaytab, NTH, 3, 3, 0,
DST_RUM, 1, cedaytab, NTH, 0, 0, 0,
DST_TUR, 1, cedaytab, NTH, 1, 0, 0,
-1,
};
struct ttinfo { /* time type information */
long tt_gmtoff; /* GMT offset in seconds */
int tt_isdst; /* used to set tm_isdst */
int tt_abbrind; /* abbreviation list index */
int tt_ttisstd; /* TRUE if transition is std time */
};
struct state {
int timecnt;
int typecnt;
int charcnt;
time_t ats[TZ_MAX_TIMES];
unsigned char types[TZ_MAX_TIMES];
struct ttinfo ttis[TZ_MAX_TYPES];
};
struct state lclstate;
/*
* tzsetup - Given the current time zone rules, as defined by the
* "localtime" time zone information file, figure out what the offset from
* GMT is and which of the old-style time zone rules matches those rules.
*
* This is done by heuristics (the $10 word for "hacks"); it finds all the
* transition times in that information file, throws out all the ones
* before the epoch and after the day on which this is being written, and
* sees which, if any, of the old-style rules best reproduces the behavior
* of the new-style rules for all those transition times.
*/
/*ARGSUSED*/
int
main(argc, argv)
int argc;
char *argv[];
{
char buf[MAXPATHLEN];
register int i;
struct timezone tz;
now = time((time_t *)NULL);
tzsetwall(); /* make absolutely sure we get local time */
i = strlen(TZDIR) + 1 + strlen("localtime") + 1;
if (i > sizeof buf) {
(void) fprintf(stderr,
"tzsetup: timezone name %s/localtime is too long\n",
TZDIR);
return (1);
}
(void) sprintf(buf, "%s/localtime", TZDIR);
if (tzload(buf) < 0)
return (1);
get_timezone_info(&tz);
if (settimeofday((struct timeval *)NULL, &tz) < 0) {
perror("tzsetup: Can't set time zone");
return (1);
}
return (0);
}
static long
detzcode(codep)
char *codep;
{
register long result;
register int i;
result = 0;
for (i = 0; i < 4; ++i)
result = (result << 8) | (codep[i] & 0xff);
return (result);
}
/*
** Maximum size of a time zone file.
*/
#define MAX_TZFILESZ (sizeof (struct tzhead) + \
TZ_MAX_TIMES * (4 + sizeof (char)) + \
TZ_MAX_TYPES * (4 + 2 * sizeof (char)) + \
TZ_MAX_CHARS * sizeof (char) + \
TZ_MAX_LEAPS * 2 * 4 + \
TZ_MAX_TYPES * sizeof (char))
static int
tzload(name)
register char *name;
{
register char *p;
register int i;
register int fid;
register struct tzhead *tzhp;
char buf[MAX_TZFILESZ];
int leapcnt;
int ttisstdcnt;
if ((fid = open(name, O_RDONLY, 0)) == -1) {
(void) fprintf(stderr,
"tzsetup: Can't open %s/localtime: %s\n", TZDIR,
strerror(errno));
return -1;
}
i = read(fid, buf, sizeof buf);
if (i < 0) {
(void) fprintf(stderr,
"tzsetup: Error reading %s/localtime: %s", TZDIR,
strerror(errno));
return -1;
}
(void) close(fid);
if (i < sizeof *tzhp) {
(void) fprintf(stderr,
"tzsetup: File %s/localtime is damaged\n", TZDIR);
return -1;
}
tzhp = (struct tzhead *) buf;
ttisstdcnt = (int) detzcode(tzhp->tzh_ttisstdcnt);
leapcnt = (int) detzcode(tzhp->tzh_leapcnt);
lclstate.timecnt = (int) detzcode(tzhp->tzh_timecnt);
lclstate.typecnt = (int) detzcode(tzhp->tzh_typecnt);
lclstate.charcnt = (int) detzcode(tzhp->tzh_charcnt);
if (leapcnt < 0 || leapcnt > TZ_MAX_LEAPS ||
lclstate.typecnt <= 0 || lclstate.typecnt > TZ_MAX_TYPES ||
lclstate.timecnt < 0 || lclstate.timecnt > TZ_MAX_TIMES ||
lclstate.charcnt < 0 || lclstate.charcnt > TZ_MAX_CHARS ||
(ttisstdcnt != lclstate.typecnt && ttisstdcnt != 0)) {
(void) fprintf(stderr,
"tzsetup: File %s/localtime is damaged\n", TZDIR);
return -1;
}
if (i < sizeof *tzhp +
lclstate.timecnt * (4 + sizeof (char)) +
lclstate.typecnt * (4 + 2 * sizeof (char)) +
lclstate.charcnt * sizeof (char) +
leapcnt * 2 * 4 +
ttisstdcnt * sizeof (char)) {
(void) fprintf(stderr,
"tzsetup: File %s/localtime is damaged\n", TZDIR);
return -1;
}
p = buf + sizeof *tzhp;
for (i = 0; i < lclstate.timecnt; ++i) {
lclstate.ats[i] = detzcode(p);
p += 4;
}
for (i = 0; i < lclstate.timecnt; ++i) {
lclstate.types[i] = (unsigned char) *p++;
if (lclstate.types[i] >= lclstate.typecnt) {
(void) fprintf(stderr,
"tzsetup: File %s/localtime is damaged\n", TZDIR);
return -1;
}
}
for (i = 0; i < lclstate.typecnt; ++i) {
register struct ttinfo * ttisp;
ttisp = &lclstate.ttis[i];
ttisp->tt_gmtoff = detzcode(p);
p += 4;
ttisp->tt_isdst = (unsigned char) *p++;
if (ttisp->tt_isdst != 0 && ttisp->tt_isdst != 1) {
(void) fprintf(stderr,
"tzsetup: File %s/localtime is damaged\n", TZDIR);
return -1;
}
ttisp->tt_abbrind = (unsigned char) *p++;
if (ttisp->tt_abbrind < 0 ||
ttisp->tt_abbrind > lclstate.charcnt) {
(void) fprintf(stderr,
"tzsetup: File %s/localtime is damaged\n", TZDIR);
return -1;
}
}
return 0;
}
/*
* Get the appropriate values for "tz_minuteswest" and "tz_dsttime".
*/
static void
get_timezone_info(tz)
register struct timezone *tz;
{
register int i;
register int daylight;
tz->tz_minuteswest = 0;
daylight = 0;
for (i = 0; i < lclstate.typecnt; ++i) {
register struct ttinfo *ttisp = &lclstate.ttis[i];
if (ttisp->tt_isdst)
daylight = 1;
if (i == 0 || !ttisp->tt_isdst)
tz->tz_minuteswest = -(ttisp->tt_gmtoff/60);
}
if (daylight)
get_dsttime(tz);
else
tz->tz_dsttime = DST_NONE; /* no DST */
}
/*
* Try to find the appropriate value for "tz_dsttime".
*/
static void
get_dsttime(tz)
register struct timezone *tz;
{
register int i, j;
register int maxerrs;
register int ntried;
register int cand;
time_t t;
ntried = 0;
for (j = 0; j < lclstate.timecnt; ++j) {
t = lclstate.ats[j];
/*
* If the time is before the epoch, there's no guarantee
* that it will be converted correctly by the old-style
* mechanism; skip it.
*/
if (t < 0)
continue;
/*
* If the time is in the future, skip it.
*/
if (t > now)
continue;
ntried += 2; /* we try two times a crack */
for (i = 0; dayrules[i].dst_type != -1; i++) {
test(t - 1, tz->tz_minuteswest, &dayrules[i], j);
test(t, tz->tz_minuteswest, &dayrules[i], j);
}
}
maxerrs = MAXINT; /* haven't picked one yet */
for (i = 0; dayrules[i].dst_type != -1; i++) {
if (dayrules[i].dst_errs < maxerrs) {
/*
* This rule does better than the rules we've
* checked so far.
*/
maxerrs = dayrules[i].dst_errs;
cand = i;
}
}
for (i = 0; dayrules[i].dst_type != -1; i++) {
if (i != cand && dayrules[i].dst_errs == maxerrs) {
/*
* This rule isn't the one we picked, but it's just
* as good. We will set the offset from GMT, so
* they don't lose totally, but set the DST rule
* type to "no DST".
*/
(void) fprintf(stderr,
"tzsetup: Two or more time zone types are equally valid - no DST selected\n");
tz->tz_dsttime = DST_NONE;
return;
}
}
if (maxerrs == ntried) {
/*
* NONE of the old-style time zone types matches what we
* have here! We will set the offset from GMT, so they
* don't lose totally, but set the DST rule type to "no
* DST".
*/
(void) fprintf(stderr,
"tzsetup: No old-style time zone type is valid - no DST selected\n");
tz->tz_dsttime = DST_NONE;
} else {
if (maxerrs > 0)
/*
* The best candidate isn't perfect.
*/
(void) fprintf(stderr,
"tzsetup: Warning: No old-style time zone type is completely valid\n");
tz->tz_dsttime = dayrules[cand].dst_type;
}
}
static void
test(t, minuteswest, dr, weight)
time_t t;
int minuteswest;
register struct dayrules *dr;
int weight;
{
struct tm tm;
register struct tm *oldtmp;
/*
* Note: we must make a copy of the result of "localtime". Since
* "oldlocaltime" calls "gmtime", it will trash the results of any
* previous call to "localtime".
*/
tm = *localtime(&t);
oldtmp = oldlocaltime(&t, minuteswest, dr);
if (tm.tm_sec != oldtmp->tm_sec
|| tm.tm_min != oldtmp->tm_min
|| tm.tm_hour != oldtmp->tm_hour
|| tm.tm_mday != oldtmp->tm_mday
|| tm.tm_mon != oldtmp->tm_mon
|| tm.tm_year != oldtmp->tm_year
|| tm.tm_wday != oldtmp->tm_wday
|| tm.tm_yday != oldtmp->tm_yday
|| tm.tm_isdst != oldtmp->tm_isdst)
dr->dst_errs += weight; /* not correct */
}
/*
* Old "localtime" code from SunOS 3.x.
*/
static struct tm *
oldlocaltime(tim, minuteswest, dr)
time_t *tim;
int minuteswest;
register struct dayrules *dr;
{
register int dayno;
register struct tm *ct;
register daylbegin, daylend;
register struct dstab *ds;
int year;
time_t copyt;
copyt = *tim - (time_t)minuteswest*60;
ct = gmtime(&copyt);
dayno = ct->tm_yday;
if (dr->dst_type >= 0) {
year = ct->tm_year + 1900;
for (ds = dr->dst_rules; ds->dayyr; ds++)
if (ds->dayyr == year)
break;
daylbegin = sunday(ct, ds->daylb); /* Sun on which dst starts */
daylend = sunday(ct, ds->dayle); /* Sun on which dst ends */
switch (dr->dst_hemi) {
case NTH:
if (!(
(dayno>daylbegin
|| (dayno==daylbegin && ct->tm_hour>=dr->dst_ontime)) &&
(dayno<daylend
|| (dayno==daylend && ct->tm_hour<dr->dst_offtime))
))
return(ct);
break;
case STH:
if (!(
(dayno>daylbegin
|| (dayno==daylbegin && ct->tm_hour>=dr->dst_ontime)) ||
(dayno<daylend
|| (dayno==daylend && ct->tm_hour<dr->dst_offtime))
))
return(ct);
break;
default:
return(ct);
}
copyt += dr->dst_hrs*60*60;
ct = gmtime(&copyt);
ct->tm_isdst++;
}
return(ct);
}
/*
* The argument is a 0-origin day number.
* The value is the day number of the last
* Sunday on or before the day (if "d" is positive)
* or of the first Sunday on or after the day (if "d" is
* negative).
*/
static int
sunday(t, d)
register struct tm *t;
register int d;
{
register int offset; /* 700 if before, -700 if after */
offset = 700;
if (d < 0) {
offset = -700;
d = -d;
}
if (d >= 58 && (t->tm_year%4) == 0)
d++; /* leap year */
return(d - (d - t->tm_yday + t->tm_wday + offset) % 7);
}
static char *
strerror(errnum)
int errnum;
{
extern int sys_nerr;
extern char *sys_errlist[];
static char unknown_error[6+10+1]; /* "Error %d\0" */
if (errnum < 0 || errnum > sys_nerr) {
(void) sprintf(unknown_error, "Error %d", errnum);
return unknown_error;
} else
return sys_errlist[errnum];
}