diff --git a/src/timezone/Makefile b/src/timezone/Makefile index a289050d82..bed5727f53 100644 --- a/src/timezone/Makefile +++ b/src/timezone/Makefile @@ -19,7 +19,7 @@ include $(top_builddir)/src/Makefile.global OBJS= localtime.o strftime.o pgtz.o # files needed to build zic utility program -ZICOBJS= zic.o ialloc.o scheck.o localtime.o $(WIN32RES) +ZICOBJS= zic.o $(WIN32RES) # timezone data files TZDATA = africa antarctica asia australasia europe northamerica southamerica \ diff --git a/src/timezone/README b/src/timezone/README index 98d98f235f..1df4ce1f69 100644 --- a/src/timezone/README +++ b/src/timezone/README @@ -1,26 +1,24 @@ src/timezone/README -Timezone -======== +This is a PostgreSQL adapted version of the IANA timezone library from -This is a PostgreSQL adapted version of the timezone library from -http://www.iana.org/time-zones + http://www.iana.org/time-zones -The source code can be found at: - - ftp://ftp.iana.org/tz/releases/tzcode*.tar.gz - -The code is currently synced with release 2010c. There are many cosmetic -(and not so cosmetic) differences from the original tzcode library, but -diffs in the upstream version should usually be propagated to our version. - -The data files under data/ are an exact copy of the latest data set from: - - ftp://ftp.iana.org/tz/releases/tzdata*.tar.gz +The latest versions of both the tzdata and tzcode tarballs are normally +available right from that page. Historical versions can be found +elsewhere on the site. Since time zone rules change frequently in some parts of the world, we should endeavor to update the data files before each PostgreSQL -release. +release. The code need not be updated as often, but we must track +changes that might affect interpretation of the data files. + + +Time Zone data +============== + +The data files under data/ are an exact copy of the latest tzdata set, +except that we omit some files that are not of interest for our purposes. While the files under data/ can just be duplicated when updating, manual effort is needed to update the time zone abbreviation lists under tznames/. @@ -32,7 +30,9 @@ which will produce a file showing all abbreviations that are in current use according to the data/ files. Compare this to known_abbrevs.txt, which is the list that existed last time the tznames/ files were updated. Update tznames/ as seems appropriate, then replace known_abbrevs.txt -in the same commit. +in the same commit. Usually, if a known abbreviation has changed meaning, +the appropriate fix is to make it refer to a long-form zone name instead +of a fixed GMT offset. When there has been a new release of Windows (probably including Service Packs), the list of matching timezones need to be updated. Run the @@ -40,3 +40,64 @@ script in src/tools/win32tzlist.pl on a Windows machine running this new release and apply any new timezones that it detects. Never remove any mappings in case they are removed in Windows, since we still need to match properly on the old version. + + +Time Zone code +============== + +The code in this directory is currently synced with tzcode release 2016c. +There are many cosmetic (and not so cosmetic) differences from the +original tzcode library, but diffs in the upstream version should usually +be propagated to our version. Here are some notes about that. + +For the most part we want to use the upstream code as-is, but there are +several considerations preventing an exact match: + +* For readability/maintainability we reformat the code to match our own +conventions; this includes pgindent'ing it and getting rid of upstream's +overuse of "register" declarations. (It used to include conversion of +old-style function declarations to C89 style, but thank goodness they +fixed that.) + +* We need the code to follow Postgres' portability conventions; this +includes relying on configure's results rather than hand-hacked #defines, +and not relying on features that may not exist on old systems. +(In particular this means using Postgres' definitions of the int32 and +int64 typedefs, not int_fast32_t/int_fast64_t.) + +* Since Postgres is typically built on a system that has its own copy +of the functions, we must avoid conflicting with those. This +mandates renaming typedef time_t to pg_time_t, and similarly for most +other exposed names. + +* We have exposed the tzload() and tzparse() internal functions, and +slightly modified the API of the former, in part because it now relies +on our own pg_open_tzfile() rather than opening files for itself. + +* There's a fair amount of code we don't need and have removed, +including all the nonstandard optional APIs. We have also added +a few functions of our own at the bottom of localtime.c. + +* In zic.c, we have added support for a -P (print_abbrevs) switch, which +is used to create the "abbrevs.txt" summary of currently-in-use zone +abbreviations that was described above. + + +The most convenient way to compare a new tzcode release to our code is +to first run the tzcode source files through a sed filter like this: + + sed -r \ + -e 's/^([ \t]*)\*\*([ \t])/\1 *\2/' \ + -e 's/^([ \t]*)\*\*$/\1 */' \ + -e 's|^\*/| */|' \ + -e 's/\bregister[ \t]//g' \ + -e 's/int_fast32_t/int32/g' \ + -e 's/int_fast64_t/int64/g' \ + -e 's/struct[ \t]+tm\b/struct pg_tm/g' \ + -e 's/\btime_t\b/pg_time_t/g' \ + +and then run them through pgindent. (The first three sed patterns deal +with conversion of their block comment style to something pgindent +won't make a hash of; the remainder address other points noted above.) +After that, the files can be diff'd directly against our corresponding +files. diff --git a/src/timezone/ialloc.c b/src/timezone/ialloc.c deleted file mode 100644 index 05faafe262..0000000000 --- a/src/timezone/ialloc.c +++ /dev/null @@ -1,76 +0,0 @@ -/* - * This file is in the public domain, so clarified as of - * 2006-07-17 by Arthur David Olson. - * - * IDENTIFICATION - * src/timezone/ialloc.c - */ - -#include "postgres_fe.h" - -#include "private.h" - - -#define nonzero(n) (((n) == 0) ? 1 : (n)) - -char * -imalloc(int n) -{ - return malloc((size_t) nonzero(n)); -} - -char * -icalloc(int nelem, int elsize) -{ - if (nelem == 0 || elsize == 0) - nelem = elsize = 1; - return calloc((size_t) nelem, (size_t) elsize); -} - -void * -irealloc(void *pointer, int size) -{ - if (pointer == NULL) - return imalloc(size); - return realloc((void *) pointer, (size_t) nonzero(size)); -} - -char * -icatalloc(char *old, const char *new) -{ - char *result; - int oldsize, - newsize; - - newsize = (new == NULL) ? 0 : strlen(new); - if (old == NULL) - oldsize = 0; - else if (newsize == 0) - return old; - else - oldsize = strlen(old); - if ((result = irealloc(old, oldsize + newsize + 1)) != NULL) - if (new != NULL) - (void) strcpy(result + oldsize, new); - return result; -} - -char * -icpyalloc(const char *string) -{ - return icatalloc((char *) NULL, string); -} - -void -ifree(char *p) -{ - if (p != NULL) - (void) free(p); -} - -void -icfree(char *p) -{ - if (p != NULL) - (void) free(p); -} diff --git a/src/timezone/localtime.c b/src/timezone/localtime.c index 19a24e1d96..23e022695f 100644 --- a/src/timezone/localtime.c +++ b/src/timezone/localtime.c @@ -23,7 +23,7 @@ #ifndef WILDABBR -/*---------- +/* * Someone might make incorrect use of a time zone abbreviation: * 1. They might reference tzname[0] before calling tzset (explicitly * or implicitly). @@ -36,20 +36,23 @@ * 5. They might reference tm.TM_ZONE after calling offtime. * What's best to do in the above cases is open to debate; * for now, we just set things up so that in any of the five cases - * WILDABBR is used. Another possibility: initialize tzname[0] to the + * WILDABBR is used. Another possibility: initialize tzname[0] to the * string "tzname[0] used before set", and similarly for the other cases. * And another: initialize tzname[0] to "ERA", with an explanation in the * manual page of what this "time zone abbreviation" means (doing this so * that tzname[0] has the "normal" length of three characters). - *---------- */ #define WILDABBR " " #endif /* !defined WILDABBR */ -static char wildabbr[] = WILDABBR; +static const char wildabbr[] = WILDABBR; static const char gmt[] = "GMT"; +/* The minimum and maximum finite time values. This assumes no padding. */ +static const pg_time_t time_t_min = MINVAL(pg_time_t, TYPE_BIT(pg_time_t)); +static const pg_time_t time_t_max = MAXVAL(pg_time_t, TYPE_BIT(pg_time_t)); + /* * The DST rules to use if TZ has no rules and we can't load TZDEFRULES. * We default to US rules as of 1999-08-17. @@ -59,51 +62,35 @@ static const char gmt[] = "GMT"; */ #define TZDEFRULESTRING ",M4.1.0,M10.5.0" +/* structs ttinfo, lsinfo, state have been moved to pgtz.h */ + +enum r_type +{ + JULIAN_DAY, /* Jn = Julian day */ + DAY_OF_YEAR, /* n = day of year */ + MONTH_NTH_DAY_OF_WEEK /* Mm.n.d = month, week, day of week */ +}; + struct rule { - int r_type; /* type of rule--see below */ + enum r_type r_type; /* type of rule */ int r_day; /* day number of rule */ int r_week; /* week number of rule */ int r_mon; /* month number of rule */ - long r_time; /* transition time of rule */ + int32 r_time; /* transition time of rule */ }; -#define JULIAN_DAY 0 /* Jn - Julian day */ -#define DAY_OF_YEAR 1 /* n - day of year */ -#define MONTH_NTH_DAY_OF_WEEK 2 /* Mm.n.d - month, week, day of week */ - /* * Prototypes for static functions. */ -static long detzcode(const char *codep); -static pg_time_t detzcode64(const char *codep); -static int differ_by_repeat(pg_time_t t1, pg_time_t t0); -static const char *getzname(const char *strp); -static const char *getqzname(const char *strp, int delim); -static const char *getnum(const char *strp, int *nump, int min, int max); -static const char *getsecs(const char *strp, long *secsp); -static const char *getoffset(const char *strp, long *offsetp); -static const char *getrule(const char *strp, struct rule * rulep); -static void gmtload(struct state * sp); -static struct pg_tm *gmtsub(const pg_time_t *timep, long offset, - struct pg_tm * tmp); -static struct pg_tm *localsub(const pg_time_t *timep, long offset, - struct pg_tm * tmp, const pg_tz *tz); -static int increment_overflow(int *number, int delta); -static pg_time_t transtime(pg_time_t janfirst, int year, - const struct rule * rulep, long offset); -static int typesequiv(const struct state * sp, int a, int b); -static struct pg_tm *timesub(const pg_time_t *timep, long offset, - const struct state * sp, struct pg_tm * tmp); +static struct pg_tm *gmtsub(pg_time_t const *, int32, struct pg_tm *); +static bool increment_overflow(int *, int); +static bool increment_overflow_time(pg_time_t *, int32); +static struct pg_tm *timesub(pg_time_t const *, int32, struct state const *, + struct pg_tm *); +static bool typesequiv(struct state const *, int, int); -/* GMT timezone */ -static struct state gmtmem; - -#define gmtptr (&gmtmem) - - -static int gmt_is_set = 0; /* * Section 4.12.3 of X3.159-1989 requires that @@ -115,147 +102,281 @@ static int gmt_is_set = 0; static struct pg_tm tm; +/* Initialize *S to a value based on GMTOFF, ISDST, and ABBRIND. */ +static void +init_ttinfo(struct ttinfo * s, int32 gmtoff, bool isdst, int abbrind) +{ + s->tt_gmtoff = gmtoff; + s->tt_isdst = isdst; + s->tt_abbrind = abbrind; + s->tt_ttisstd = false; + s->tt_ttisgmt = false; +} -static long +static int32 detzcode(const char *codep) { - long result; + int32 result; int i; + int32 one = 1; + int32 halfmaxval = one << (32 - 2); + int32 maxval = halfmaxval - 1 + halfmaxval; + int32 minval = -1 - maxval; - result = (codep[0] & 0x80) ? ~0L : 0; - for (i = 0; i < 4; ++i) + result = codep[0] & 0x7f; + for (i = 1; i < 4; ++i) result = (result << 8) | (codep[i] & 0xff); + + if (codep[0] & 0x80) + { + /* + * Do two's-complement negation even on non-two's-complement machines. + * If the result would be minval - 1, return minval. + */ + result -= !TWOS_COMPLEMENT(int32) &&result != 0; + result += minval; + } return result; } -static pg_time_t +static int64 detzcode64(const char *codep) { - pg_time_t result; + uint64 result; int i; + int64 one = 1; + int64 halfmaxval = one << (64 - 2); + int64 maxval = halfmaxval - 1 + halfmaxval; + int64 minval = -TWOS_COMPLEMENT(int64) -maxval; - result = (codep[0] & 0x80) ? (~(int64) 0) : 0; - for (i = 0; i < 8; ++i) - result = result * 256 + (codep[i] & 0xff); + result = codep[0] & 0x7f; + for (i = 1; i < 8; ++i) + result = (result << 8) | (codep[i] & 0xff); + + if (codep[0] & 0x80) + { + /* + * Do two's-complement negation even on non-two's-complement machines. + * If the result would be minval - 1, return minval. + */ + result -= !TWOS_COMPLEMENT(int64) &&result != 0; + result += minval; + } return result; } -static int -differ_by_repeat(pg_time_t t1, pg_time_t t0) +static bool +differ_by_repeat(const pg_time_t t1, const pg_time_t t0) { - if (TYPE_INTEGRAL(pg_time_t) && - TYPE_BIT(pg_time_t) -TYPE_SIGNED(pg_time_t) TZ_STRLEN_MAX bytes!). + */ +static int +tzloadbody(char const * name, char *canonname, struct state * sp, bool doextend, + union local_storage * lsp) { - const char *p; int i; int fid; int stored; - int nread; - union - { - struct tzhead tzhead; - char buf[2 * sizeof(struct tzhead) + - 2 * sizeof *sp + - 4 * TZ_MAX_TIMES]; - } u; + ssize_t nread; + union input_buffer *up = &lsp->u.u; + int tzheadsize = sizeof(struct tzhead); + + sp->goback = sp->goahead = false; + + if (!name) + { + name = TZDEFAULT; + if (!name) + return EINVAL; + } - sp->goback = sp->goahead = FALSE; - if (name == NULL && (name = TZDEFAULT) == NULL) - return -1; if (name[0] == ':') ++name; + fid = pg_open_tzfile(name, canonname); if (fid < 0) - return -1; - nread = read(fid, u.buf, sizeof u.buf); - if (close(fid) != 0 || nread <= 0) - return -1; + return ENOENT; /* pg_open_tzfile may not set errno */ + + nread = read(fid, up->buf, sizeof up->buf); + if (nread < tzheadsize) + { + int err = nread < 0 ? errno : EINVAL; + + close(fid); + return err; + } + if (close(fid) < 0) + return errno; for (stored = 4; stored <= 8; stored *= 2) { - int ttisstdcnt; - int ttisgmtcnt; + int32 ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt); + int32 ttisgmtcnt = detzcode(up->tzhead.tzh_ttisgmtcnt); + int32 leapcnt = detzcode(up->tzhead.tzh_leapcnt); + int32 timecnt = detzcode(up->tzhead.tzh_timecnt); + int32 typecnt = detzcode(up->tzhead.tzh_typecnt); + int32 charcnt = detzcode(up->tzhead.tzh_charcnt); + char const *p = up->buf + tzheadsize; - ttisstdcnt = (int) detzcode(u.tzhead.tzh_ttisstdcnt); - ttisgmtcnt = (int) detzcode(u.tzhead.tzh_ttisgmtcnt); - sp->leapcnt = (int) detzcode(u.tzhead.tzh_leapcnt); - sp->timecnt = (int) detzcode(u.tzhead.tzh_timecnt); - sp->typecnt = (int) detzcode(u.tzhead.tzh_typecnt); - sp->charcnt = (int) detzcode(u.tzhead.tzh_charcnt); - p = u.tzhead.tzh_charcnt + sizeof u.tzhead.tzh_charcnt; - if (sp->leapcnt < 0 || sp->leapcnt > TZ_MAX_LEAPS || - sp->typecnt <= 0 || sp->typecnt > TZ_MAX_TYPES || - sp->timecnt < 0 || sp->timecnt > TZ_MAX_TIMES || - sp->charcnt < 0 || sp->charcnt > TZ_MAX_CHARS || - (ttisstdcnt != sp->typecnt && ttisstdcnt != 0) || - (ttisgmtcnt != sp->typecnt && ttisgmtcnt != 0)) - return -1; - if (nread - (p - u.buf) < - sp->timecnt * stored + /* ats */ - sp->timecnt + /* types */ - sp->typecnt * 6 + /* ttinfos */ - sp->charcnt + /* chars */ - sp->leapcnt * (stored + 4) + /* lsinfos */ - ttisstdcnt + /* ttisstds */ - ttisgmtcnt) /* ttisgmts */ - return -1; + if (!(0 <= leapcnt && leapcnt < TZ_MAX_LEAPS + && 0 < typecnt && typecnt < TZ_MAX_TYPES + && 0 <= timecnt && timecnt < TZ_MAX_TIMES + && 0 <= charcnt && charcnt < TZ_MAX_CHARS + && (ttisstdcnt == typecnt || ttisstdcnt == 0) + && (ttisgmtcnt == typecnt || ttisgmtcnt == 0))) + return EINVAL; + if (nread + < (tzheadsize /* struct tzhead */ + + timecnt * stored /* ats */ + + timecnt /* types */ + + typecnt * 6 /* ttinfos */ + + charcnt /* chars */ + + leapcnt * (stored + 4) /* lsinfos */ + + ttisstdcnt /* ttisstds */ + + ttisgmtcnt)) /* ttisgmts */ + return EINVAL; + sp->leapcnt = leapcnt; + sp->timecnt = timecnt; + sp->typecnt = typecnt; + sp->charcnt = charcnt; + + /* + * Read transitions, discarding those out of pg_time_t range. But + * pretend the last transition before time_t_min occurred at + * time_t_min. + */ + timecnt = 0; for (i = 0; i < sp->timecnt; ++i) { - sp->ats[i] = (stored == 4) ? detzcode(p) : detzcode64(p); + int64 at + = stored == 4 ? detzcode(p) : detzcode64(p); + + sp->types[i] = at <= time_t_max; + if (sp->types[i]) + { + pg_time_t attime + = ((TYPE_SIGNED(pg_time_t) ? at < time_t_min : at < 0) + ? time_t_min : at); + + if (timecnt && attime <= sp->ats[timecnt - 1]) + { + if (attime < sp->ats[timecnt - 1]) + return EINVAL; + sp->types[i - 1] = 0; + timecnt--; + } + sp->ats[timecnt++] = attime; + } p += stored; } + + timecnt = 0; for (i = 0; i < sp->timecnt; ++i) { - sp->types[i] = (unsigned char) *p++; - if (sp->types[i] >= sp->typecnt) - return -1; + unsigned char typ = *p++; + + if (sp->typecnt <= typ) + return EINVAL; + if (sp->types[i]) + sp->types[timecnt++] = typ; } + sp->timecnt = timecnt; for (i = 0; i < sp->typecnt; ++i) { struct ttinfo *ttisp; + unsigned char isdst, + abbrind; ttisp = &sp->ttis[i]; ttisp->tt_gmtoff = detzcode(p); p += 4; - ttisp->tt_isdst = (unsigned char) *p++; - if (ttisp->tt_isdst != 0 && ttisp->tt_isdst != 1) - return -1; - ttisp->tt_abbrind = (unsigned char) *p++; - if (ttisp->tt_abbrind < 0 || - ttisp->tt_abbrind > sp->charcnt) - return -1; + isdst = *p++; + if (!(isdst < 2)) + return EINVAL; + ttisp->tt_isdst = isdst; + abbrind = *p++; + if (!(abbrind < sp->charcnt)) + return EINVAL; + ttisp->tt_abbrind = abbrind; } for (i = 0; i < sp->charcnt; ++i) sp->chars[i] = *p++; sp->chars[i] = '\0'; /* ensure '\0' at end */ + + /* Read leap seconds, discarding those out of pg_time_t range. */ + leapcnt = 0; for (i = 0; i < sp->leapcnt; ++i) { - struct lsinfo *lsisp; + int64 tr = stored == 4 ? detzcode(p) : detzcode64(p); + int32 corr = detzcode(p + stored); - lsisp = &sp->lsis[i]; - lsisp->ls_trans = (stored == 4) ? detzcode(p) : detzcode64(p); - p += stored; - lsisp->ls_corr = detzcode(p); - p += 4; + p += stored + 4; + if (tr <= time_t_max) + { + pg_time_t trans + = ((TYPE_SIGNED(pg_time_t) ? tr < time_t_min : tr < 0) + ? time_t_min : tr); + + if (leapcnt && trans <= sp->lsis[leapcnt - 1].ls_trans) + { + if (trans < sp->lsis[leapcnt - 1].ls_trans) + return EINVAL; + leapcnt--; + } + sp->lsis[leapcnt].ls_trans = trans; + sp->lsis[leapcnt].ls_corr = corr; + leapcnt++; + } } + sp->leapcnt = leapcnt; + for (i = 0; i < sp->typecnt; ++i) { struct ttinfo *ttisp; ttisp = &sp->ttis[i]; if (ttisstdcnt == 0) - ttisp->tt_ttisstd = FALSE; + ttisp->tt_ttisstd = false; else { + if (*p != true && *p != false) + return EINVAL; ttisp->tt_ttisstd = *p++; - if (ttisp->tt_ttisstd != TRUE && - ttisp->tt_ttisstd != FALSE) - return -1; } } for (i = 0; i < sp->typecnt; ++i) @@ -264,99 +385,87 @@ tzload(const char *name, char *canonname, struct state * sp, int doextend) ttisp = &sp->ttis[i]; if (ttisgmtcnt == 0) - ttisp->tt_ttisgmt = FALSE; + ttisp->tt_ttisgmt = false; else { + if (*p != true && *p != false) + return EINVAL; ttisp->tt_ttisgmt = *p++; - if (ttisp->tt_ttisgmt != TRUE && - ttisp->tt_ttisgmt != FALSE) - return -1; } } - /* - * Out-of-sort ats should mean we're running on a signed time_t system - * but using a data file with unsigned values (or vice versa). - */ - for (i = 0; i < sp->timecnt - 2; ++i) - if (sp->ats[i] > sp->ats[i + 1]) - { - ++i; - if (TYPE_SIGNED(pg_time_t)) - { - /* - * Ignore the end (easy). - */ - sp->timecnt = i; - } - else - { - /* - * Ignore the beginning (harder). - */ - int j; - - for (j = 0; j + i < sp->timecnt; ++j) - { - sp->ats[j] = sp->ats[j + i]; - sp->types[j] = sp->types[j + i]; - } - sp->timecnt = j; - } - break; - } - /* * If this is an old file, we're done. */ - if (u.tzhead.tzh_version[0] == '\0') - break; - nread -= p - u.buf; - for (i = 0; i < nread; ++i) - u.buf[i] = p[i]; - - /* - * If this is a narrow integer time_t system, we're done. - */ - if (stored >= (int) sizeof(pg_time_t) && TYPE_INTEGRAL(pg_time_t)) + if (up->tzhead.tzh_version[0] == '\0') break; + nread -= p - up->buf; + memmove(up->buf, p, nread); } if (doextend && nread > 2 && - u.buf[0] == '\n' && u.buf[nread - 1] == '\n' && + up->buf[0] == '\n' && up->buf[nread - 1] == '\n' && sp->typecnt + 2 <= TZ_MAX_TYPES) { - struct state ts; - int result; + struct state *ts = &lsp->u.st; - u.buf[nread - 1] = '\0'; - result = tzparse(&u.buf[1], &ts, FALSE); - if (result == 0 && ts.typecnt == 2 && - sp->charcnt + ts.charcnt <= TZ_MAX_CHARS) + up->buf[nread - 1] = '\0'; + if (tzparse(&up->buf[1], ts, false) == 0 + && ts->typecnt == 2) { - for (i = 0; i < 2; ++i) - ts.ttis[i].tt_abbrind += - sp->charcnt; - for (i = 0; i < ts.charcnt; ++i) - sp->chars[sp->charcnt++] = - ts.chars[i]; - i = 0; - while (i < ts.timecnt && - ts.ats[i] <= - sp->ats[sp->timecnt - 1]) - ++i; - while (i < ts.timecnt && - sp->timecnt < TZ_MAX_TIMES) + /* + * Attempt to reuse existing abbreviations. Without this, + * America/Anchorage would stop working after 2037 when + * TZ_MAX_CHARS is 50, as sp->charcnt equals 42 (for LMT CAT CAWT + * CAPT AHST AHDT YST AKDT AKST) and ts->charcnt equals 10 (for + * AKST AKDT). Reusing means sp->charcnt can stay 42 in this + * example. + */ + int gotabbr = 0; + int charcnt = sp->charcnt; + + for (i = 0; i < 2; i++) { - sp->ats[sp->timecnt] = - ts.ats[i]; - sp->types[sp->timecnt] = - sp->typecnt + - ts.types[i]; - ++sp->timecnt; - ++i; + char *tsabbr = ts->chars + ts->ttis[i].tt_abbrind; + int j; + + for (j = 0; j < charcnt; j++) + if (strcmp(sp->chars + j, tsabbr) == 0) + { + ts->ttis[i].tt_abbrind = j; + gotabbr++; + break; + } + if (!(j < charcnt)) + { + int tsabbrlen = strlen(tsabbr); + + if (j + tsabbrlen < TZ_MAX_CHARS) + { + strcpy(sp->chars + j, tsabbr); + charcnt = j + tsabbrlen + 1; + ts->ttis[i].tt_abbrind = j; + gotabbr++; + } + } + } + if (gotabbr == 2) + { + sp->charcnt = charcnt; + for (i = 0; i < ts->timecnt; i++) + if (sp->ats[sp->timecnt - 1] < ts->ats[i]) + break; + while (i < ts->timecnt + && sp->timecnt < TZ_MAX_TIMES) + { + sp->ats[sp->timecnt] = ts->ats[i]; + sp->types[sp->timecnt] = (sp->typecnt + + ts->types[i]); + sp->timecnt++; + i++; + } + sp->ttis[sp->typecnt++] = ts->ttis[0]; + sp->ttis[sp->typecnt++] = ts->ttis[1]; } - sp->ttis[sp->typecnt++] = ts.ttis[0]; - sp->ttis[sp->typecnt++] = ts.ttis[1]; } } if (sp->timecnt > 1) @@ -365,7 +474,7 @@ tzload(const char *name, char *canonname, struct state * sp, int doextend) if (typesequiv(sp, sp->types[i], sp->types[0]) && differ_by_repeat(sp->ats[i], sp->ats[0])) { - sp->goback = TRUE; + sp->goback = true; break; } for (i = sp->timecnt - 2; i >= 0; --i) @@ -374,22 +483,73 @@ tzload(const char *name, char *canonname, struct state * sp, int doextend) differ_by_repeat(sp->ats[sp->timecnt - 1], sp->ats[i])) { - sp->goahead = TRUE; + sp->goahead = true; break; } } + + /* + * If type 0 is is unused in transitions, it's the type to use for early + * times. + */ + for (i = 0; i < sp->timecnt; ++i) + if (sp->types[i] == 0) + break; + i = i < sp->timecnt ? -1 : 0; + + /* + * Absent the above, if there are transition times and the first + * transition is to a daylight time find the standard type less than and + * closest to the type of the first transition. + */ + if (i < 0 && sp->timecnt > 0 && sp->ttis[sp->types[0]].tt_isdst) + { + i = sp->types[0]; + while (--i >= 0) + if (!sp->ttis[i].tt_isdst) + break; + } + + /* + * If no result yet, find the first standard type. If there is none, punt + * to type zero. + */ + if (i < 0) + { + i = 0; + while (sp->ttis[i].tt_isdst) + if (++i >= sp->typecnt) + { + i = 0; + break; + } + } + sp->defaulttype = i; return 0; } -static int +/* Load tz data from the file named NAME into *SP. Read extended + * format if DOEXTEND. Return 0 on success, an errno value on failure. + * PG: If "canonname" is not NULL, then on success the canonical spelling of + * given name is stored there (the buffer must be > TZ_STRLEN_MAX bytes!). + */ +int +tzload(const char *name, char *canonname, struct state * sp, int doextend) +{ + union local_storage ls; + + return tzloadbody(name, canonname, sp, doextend, &ls); +} + +static bool typesequiv(const struct state * sp, int a, int b) { - int result; + bool result; if (sp == NULL || a < 0 || a >= sp->typecnt || b < 0 || b >= sp->typecnt) - result = FALSE; + result = false; else { const struct ttinfo *ap = &sp->ttis[a]; @@ -484,19 +644,19 @@ getnum(const char *strp, int *nump, int min, int max) * of seconds. */ static const char * -getsecs(const char *strp, long *secsp) +getsecs(const char *strp, int32 *secsp) { int num; /* - * `HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like + * 'HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like * "M10.4.6/26", which does not conform to Posix, but which specifies the - * equivalent of ``02:00 on the first Sunday on or after 23 Oct''. + * equivalent of "02:00 on the first Sunday on or after 23 Oct". */ strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1); if (strp == NULL) return NULL; - *secsp = num * (long) SECSPERHOUR; + *secsp = num * (int32) SECSPERHOUR; if (*strp == ':') { ++strp; @@ -507,7 +667,7 @@ getsecs(const char *strp, long *secsp) if (*strp == ':') { ++strp; - /* `SECSPERMIN' allows for leap seconds. */ + /* 'SECSPERMIN' allows for leap seconds. */ strp = getnum(strp, &num, 0, SECSPERMIN); if (strp == NULL) return NULL; @@ -524,13 +684,13 @@ getsecs(const char *strp, long *secsp) * Otherwise, return a pointer to the first character not part of the time. */ static const char * -getoffset(const char *strp, long *offsetp) +getoffset(const char *strp, int32 *offsetp) { - int neg = 0; + bool neg = false; if (*strp == '-') { - neg = 1; + neg = true; ++strp; } else if (*strp == '+') @@ -598,7 +758,7 @@ getrule(const char *strp, struct rule * rulep) * Time specified. */ ++strp; - strp = getsecs(strp, &rulep->r_time); + strp = getoffset(strp, &rulep->r_time); } else rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */ @@ -606,16 +766,15 @@ getrule(const char *strp, struct rule * rulep) } /* - * Given the Epoch-relative time of January 1, 00:00:00 UTC, in a year, the - * year, a rule, and the offset from UTC at the time that rule takes effect, - * calculate the Epoch-relative time that rule takes effect. + * Given a year, a rule, and the offset from UT at the time that rule takes + * effect, calculate the year-relative time that rule takes effect. */ -static pg_time_t -transtime(pg_time_t janfirst, int year, - const struct rule * rulep, long offset) +static int32 +transtime(int year, const struct rule * rulep, + int32 offset) { - int leapyear; - pg_time_t value = 0; + bool leapyear; + int32 value; int i, d, m1, @@ -624,6 +783,7 @@ transtime(pg_time_t janfirst, int year, yy2, dow; + INITIALIZE(value); leapyear = isleap(year); switch (rulep->r_type) { @@ -636,7 +796,7 @@ transtime(pg_time_t janfirst, int year, * just add SECSPERDAY times the day number-1 to the time of * January 1, midnight, to get the day. */ - value = janfirst + (rulep->r_day - 1) * SECSPERDAY; + value = (rulep->r_day - 1) * SECSPERDAY; if (leapyear && rulep->r_day >= 60) value += SECSPERDAY; break; @@ -647,7 +807,7 @@ transtime(pg_time_t janfirst, int year, * n - day of year. Just add SECSPERDAY times the day number to * the time of January 1, midnight, to get the day. */ - value = janfirst + rulep->r_day * SECSPERDAY; + value = rulep->r_day * SECSPERDAY; break; case MONTH_NTH_DAY_OF_WEEK: @@ -655,9 +815,6 @@ transtime(pg_time_t janfirst, int year, /* * Mm.n.d - nth "dth day" of month m. */ - value = janfirst; - for (i = 0; i < rulep->r_mon - 1; ++i) - value += mon_lengths[leapyear][i] * SECSPERDAY; /* * Use Zeller's Congruence to get day-of-week of first day of @@ -682,7 +839,7 @@ transtime(pg_time_t janfirst, int year, for (i = 1; i < rulep->r_week; ++i) { if (d + DAYSPERWEEK >= - mon_lengths[leapyear][rulep->r_mon - 1]) + mon_lengths[(int) leapyear][rulep->r_mon - 1]) break; d += DAYSPERWEEK; } @@ -690,14 +847,16 @@ transtime(pg_time_t janfirst, int year, /* * "d" is the day-of-month (zero-origin) of the day we want. */ - value += d * SECSPERDAY; + value = d * SECSPERDAY; + for (i = 0; i < rulep->r_mon - 1; ++i) + value += mon_lengths[(int) leapyear][i] * SECSPERDAY; break; } /* - * "value" is the Epoch-relative time of 00:00:00 UTC on the day in - * question. To get the Epoch-relative time of the specified local time - * on that day, add the transition time and the current offset from UTC. + * "value" is the year-relative time of 00:00:00 UT on the day in + * question. To get the year-relative time of the specified local time on + * that day, add the transition time and the current offset from UT. */ return value + rulep->r_time + offset; } @@ -705,8 +864,11 @@ transtime(pg_time_t janfirst, int year, /* * Given a POSIX section 8-style TZ string, fill in the rule tables as * appropriate. + * + * Returns 0 on success, -1 on failure. (Note: tzcode has converted this + * to a bool true-on-success convention, but we're holding the line in PG + * for the moment, to avoid external API changes.) */ - int tzparse(const char *name, struct state * sp, int lastditch) { @@ -714,29 +876,29 @@ tzparse(const char *name, struct state * sp, int lastditch) const char *dstname = NULL; size_t stdlen; size_t dstlen; - long stdoffset; - long dstoffset; - pg_time_t *atp; - unsigned char *typep; + size_t charcnt; + int32 stdoffset; + int32 dstoffset; char *cp; - int load_result; + bool load_ok; stdname = name; if (lastditch) { + /* + * This is intentionally somewhat different from the IANA code. We do + * not want to invoke tzload() in the lastditch case: we can't assume + * pg_open_tzfile() is sane yet, and we don't care about leap seconds + * anyway. + */ stdlen = strlen(name); /* length of standard zone name */ name += stdlen; if (stdlen >= sizeof sp->chars) stdlen = (sizeof sp->chars) - 1; + charcnt = stdlen + 1; stdoffset = 0; - - /* - * Unlike the original zic library, do NOT invoke tzload() here; we - * can't assume pg_open_tzfile() is sane yet, and we don't care about - * leap seconds anyway. - */ - sp->goback = sp->goahead = FALSE; - load_result = -1; + sp->goback = sp->goahead = false; /* simulate failed tzload() */ + load_ok = false; } else { @@ -746,7 +908,7 @@ tzparse(const char *name, struct state * sp, int lastditch) stdname = name; name = getqzname(name, '>'); if (*name != '>') - return (-1); + return -1; stdlen = name - stdname; name++; } @@ -755,14 +917,17 @@ tzparse(const char *name, struct state * sp, int lastditch) name = getzname(name); stdlen = name - stdname; } - if (*name == '\0') + if (*name == '\0') /* we allow empty STD abbrev, unlike IANA */ return -1; name = getoffset(name, &stdoffset); if (name == NULL) return -1; - load_result = tzload(TZDEFRULES, NULL, sp, FALSE); + charcnt = stdlen + 1; + if (sizeof sp->chars < charcnt) + return -1; + load_ok = tzload(TZDEFRULES, NULL, sp, false) == 0; } - if (load_result != 0) + if (!load_ok) sp->leapcnt = 0; /* so, we're off a little */ if (*name != '\0') { @@ -781,6 +946,11 @@ tzparse(const char *name, struct state * sp, int lastditch) name = getzname(name); dstlen = name - dstname; /* length of DST zone name */ } + if (!dstlen) + return -1; + charcnt += dstlen + 1; + if (sizeof sp->chars < charcnt) + return -1; if (*name != '\0' && *name != ',' && *name != ';') { name = getoffset(name, &dstoffset); @@ -789,16 +959,16 @@ tzparse(const char *name, struct state * sp, int lastditch) } else dstoffset = stdoffset - SECSPERHOUR; - if (*name == '\0' && load_result != 0) + if (*name == '\0' && !load_ok) name = TZDEFRULESTRING; if (*name == ',' || *name == ';') { struct rule start; struct rule end; int year; + int yearlim; + int timecnt; pg_time_t janfirst; - pg_time_t starttime; - pg_time_t endtime; ++name; if ((name = getrule(name, &start)) == NULL) @@ -814,55 +984,62 @@ tzparse(const char *name, struct state * sp, int lastditch) /* * Two transitions per year, from EPOCH_YEAR forward. */ - sp->ttis[0].tt_gmtoff = -dstoffset; - sp->ttis[0].tt_isdst = 1; - sp->ttis[0].tt_abbrind = stdlen + 1; - sp->ttis[1].tt_gmtoff = -stdoffset; - sp->ttis[1].tt_isdst = 0; - sp->ttis[1].tt_abbrind = 0; - atp = sp->ats; - typep = sp->types; + init_ttinfo(&sp->ttis[0], -dstoffset, true, stdlen + 1); + init_ttinfo(&sp->ttis[1], -stdoffset, false, 0); + sp->defaulttype = 0; + timecnt = 0; janfirst = 0; - sp->timecnt = 0; - for (year = EPOCH_YEAR; - sp->timecnt + 2 <= TZ_MAX_TIMES; - ++year) + yearlim = EPOCH_YEAR + YEARSPERREPEAT; + for (year = EPOCH_YEAR; year < yearlim; year++) { - pg_time_t newfirst; + int32 + starttime = transtime(year, &start, stdoffset), + endtime = transtime(year, &end, dstoffset); + int32 + yearsecs = (year_lengths[isleap(year)] + * SECSPERDAY); + bool reversed = endtime < starttime; - starttime = transtime(janfirst, year, &start, - stdoffset); - endtime = transtime(janfirst, year, &end, - dstoffset); - if (starttime > endtime) + if (reversed) { - *atp++ = endtime; - *typep++ = 1; /* DST ends */ - *atp++ = starttime; - *typep++ = 0; /* DST begins */ + int32 swap = starttime; + + starttime = endtime; + endtime = swap; } - else + if (reversed + || (starttime < endtime + && (endtime - starttime + < (yearsecs + + (stdoffset - dstoffset))))) { - *atp++ = starttime; - *typep++ = 0; /* DST begins */ - *atp++ = endtime; - *typep++ = 1; /* DST ends */ + if (TZ_MAX_TIMES - 2 < timecnt) + break; + yearlim = year + YEARSPERREPEAT + 1; + sp->ats[timecnt] = janfirst; + if (increment_overflow_time + (&sp->ats[timecnt], starttime)) + break; + sp->types[timecnt++] = reversed; + sp->ats[timecnt] = janfirst; + if (increment_overflow_time + (&sp->ats[timecnt], endtime)) + break; + sp->types[timecnt++] = !reversed; } - sp->timecnt += 2; - newfirst = janfirst; - newfirst += year_lengths[isleap(year)] * - SECSPERDAY; - if (newfirst <= janfirst) + if (increment_overflow_time(&janfirst, yearsecs)) break; - janfirst = newfirst; } + sp->timecnt = timecnt; + if (!timecnt) + sp->typecnt = 1; /* Perpetual DST. */ } else { - long theirstdoffset; - long theirdstoffset; - long theiroffset; - int isdst; + int32 theirstdoffset; + int32 theirdstoffset; + int32 theiroffset; + bool isdst; int i; int j; @@ -898,7 +1075,7 @@ tzparse(const char *name, struct state * sp, int lastditch) /* * Initially we're assumed to be in standard time. */ - isdst = FALSE; + isdst = false; theiroffset = theirstdoffset; /* @@ -945,15 +1122,12 @@ tzparse(const char *name, struct state * sp, int lastditch) } /* - * Finally, fill in ttis. ttisstd and ttisgmt need not be handled. + * Finally, fill in ttis. */ - sp->ttis[0].tt_gmtoff = -stdoffset; - sp->ttis[0].tt_isdst = FALSE; - sp->ttis[0].tt_abbrind = 0; - sp->ttis[1].tt_gmtoff = -dstoffset; - sp->ttis[1].tt_isdst = TRUE; - sp->ttis[1].tt_abbrind = stdlen + 1; + init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); + init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); sp->typecnt = 2; + sp->defaulttype = 0; } } else @@ -961,22 +1135,17 @@ tzparse(const char *name, struct state * sp, int lastditch) dstlen = 0; sp->typecnt = 1; /* only standard time */ sp->timecnt = 0; - sp->ttis[0].tt_gmtoff = -stdoffset; - sp->ttis[0].tt_isdst = 0; - sp->ttis[0].tt_abbrind = 0; + init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); + sp->defaulttype = 0; } - sp->charcnt = stdlen + 1; - if (dstlen != 0) - sp->charcnt += dstlen + 1; - if ((size_t) sp->charcnt > sizeof sp->chars) - return -1; + sp->charcnt = charcnt; cp = sp->chars; - (void) strncpy(cp, stdname, stdlen); + memcpy(cp, stdname, stdlen); cp += stdlen; *cp++ = '\0'; if (dstlen != 0) { - (void) strncpy(cp, dstname, dstlen); + memcpy(cp, dstname, dstlen); *(cp + dstlen) = '\0'; } return 0; @@ -985,51 +1154,42 @@ tzparse(const char *name, struct state * sp, int lastditch) static void gmtload(struct state * sp) { - if (tzload(gmt, NULL, sp, TRUE) != 0) - (void) tzparse(gmt, sp, TRUE); + if (tzload(gmt, NULL, sp, true) != 0) + tzparse(gmt, sp, true); } /* * The easy way to behave "as if no library function calls" localtime - * is to not call it--so we drop its guts into "localsub", which can be - * freely called. (And no, the PANS doesn't require the above behavior-- + * is to not call it, so we drop its guts into "localsub", which can be + * freely called. (And no, the PANS doesn't require the above behavior, * but it *is* desirable.) - * - * The unused offset argument is for the benefit of mktime variants. */ static struct pg_tm * -localsub(const pg_time_t *timep, long offset, - struct pg_tm * tmp, const pg_tz *tz) +localsub(struct state const * sp, pg_time_t const * timep, + struct pg_tm * tmp) { - const struct state *sp; const struct ttinfo *ttisp; int i; struct pg_tm *result; const pg_time_t t = *timep; - sp = &tz->state; + if (sp == NULL) + return gmtsub(timep, 0, tmp); if ((sp->goback && t < sp->ats[0]) || (sp->goahead && t > sp->ats[sp->timecnt - 1])) { pg_time_t newt = t; pg_time_t seconds; - pg_time_t tcycles; - int64 icycles; + pg_time_t years; if (t < sp->ats[0]) seconds = sp->ats[0] - t; else seconds = t - sp->ats[sp->timecnt - 1]; --seconds; - tcycles = seconds / YEARSPERREPEAT / AVGSECSPERYEAR; - ++tcycles; - icycles = tcycles; - if (tcycles - icycles >= 1 || icycles - tcycles >= 1) - return NULL; - seconds = icycles; - seconds *= YEARSPERREPEAT; - seconds *= AVGSECSPERYEAR; + years = (seconds / SECSPERREPEAT + 1) * YEARSPERREPEAT; + seconds = years * AVGSECSPERYEAR; if (t < sp->ats[0]) newt += seconds; else @@ -1037,31 +1197,25 @@ localsub(const pg_time_t *timep, long offset, if (newt < sp->ats[0] || newt > sp->ats[sp->timecnt - 1]) return NULL; /* "cannot happen" */ - result = localsub(&newt, offset, tmp, tz); - if (result == tmp) + result = localsub(sp, &newt, tmp); + if (result) { - pg_time_t newy; + int64 newy; - newy = tmp->tm_year; + newy = result->tm_year; if (t < sp->ats[0]) - newy -= icycles * YEARSPERREPEAT; + newy -= years; else - newy += icycles * YEARSPERREPEAT; - tmp->tm_year = newy; - if (tmp->tm_year != newy) + newy += years; + if (!(INT_MIN <= newy && newy <= INT_MAX)) return NULL; + result->tm_year = newy; } return result; } if (sp->timecnt == 0 || t < sp->ats[0]) { - i = 0; - while (sp->ttis[i].tt_isdst) - if (++i >= sp->typecnt) - { - i = 0; - break; - } + i = sp->defaulttype; } else { @@ -1082,8 +1236,11 @@ localsub(const pg_time_t *timep, long offset, ttisp = &sp->ttis[i]; result = timesub(&t, ttisp->tt_gmtoff, sp, tmp); - tmp->tm_isdst = ttisp->tt_isdst; - tmp->tm_zone = &sp->chars[ttisp->tt_abbrind]; + if (result) + { + result->tm_isdst = ttisp->tt_isdst; + result->tm_zone = (char *) &sp->chars[ttisp->tt_abbrind]; + } return result; } @@ -1091,28 +1248,35 @@ localsub(const pg_time_t *timep, long offset, struct pg_tm * pg_localtime(const pg_time_t *timep, const pg_tz *tz) { - return localsub(timep, 0L, &tm, tz); + return localsub(&tz->state, timep, &tm); } /* * gmtsub is to gmtime as localsub is to localtime. + * + * Except we have a private "struct state" for GMT, so no sp is passed in. */ static struct pg_tm * -gmtsub(const pg_time_t *timep, long offset, struct pg_tm * tmp) +gmtsub(pg_time_t const * timep, int32 offset, struct pg_tm * tmp) { struct pg_tm *result; + /* GMT timezone state data is kept here */ + static struct state gmtmem; + static bool gmt_is_set = false; +#define gmtptr (&gmtmem) + if (!gmt_is_set) { - gmt_is_set = TRUE; + gmt_is_set = true; gmtload(gmtptr); } result = timesub(timep, offset, gmtptr, tmp); /* - * Could get fancy here and deliver something such as "UTC+xxxx" or - * "UTC-xxxx" if offset is non-zero, but this is no time for a treasure + * Could get fancy here and deliver something such as "UT+xxxx" or + * "UT-xxxx" if offset is non-zero, but this is no time for a treasure * hunt. */ if (offset != 0) @@ -1126,7 +1290,7 @@ gmtsub(const pg_time_t *timep, long offset, struct pg_tm * tmp) struct pg_tm * pg_gmtime(const pg_time_t *timep) { - return gmtsub(timep, 0L, &tm); + return gmtsub(timep, 0, &tm); } /* @@ -1140,24 +1304,23 @@ leaps_thru_end_of(const int y) -(leaps_thru_end_of(-(y + 1)) + 1); } - static struct pg_tm * -timesub(const pg_time_t *timep, long offset, +timesub(const pg_time_t *timep, int32 offset, const struct state * sp, struct pg_tm * tmp) { const struct lsinfo *lp; pg_time_t tdays; int idays; /* unsigned would be so 2003 */ - long rem; + int64 rem; int y; const int *ip; - long corr; - int hit; + int64 corr; + bool hit; int i; corr = 0; - hit = 0; - i = sp->leapcnt; + hit = false; + i = (sp == NULL) ? 0 : sp->leapcnt; while (--i >= 0) { lp = &sp->lsis[i]; @@ -1184,7 +1347,7 @@ timesub(const pg_time_t *timep, long offset, } y = EPOCH_YEAR; tdays = *timep / SECSPERDAY; - rem = *timep - tdays * SECSPERDAY; + rem = *timep % SECSPERDAY; while (tdays < 0 || tdays >= year_lengths[isleap(y)]) { int newy; @@ -1193,27 +1356,21 @@ timesub(const pg_time_t *timep, long offset, int leapdays; tdelta = tdays / DAYSPERLYEAR; + if (!((!TYPE_SIGNED(pg_time_t) ||INT_MIN <= tdelta) + && tdelta <= INT_MAX)) + goto out_of_range; idelta = tdelta; - if (tdelta - idelta >= 1 || idelta - tdelta >= 1) - return NULL; if (idelta == 0) idelta = (tdays < 0) ? -1 : 1; newy = y; if (increment_overflow(&newy, idelta)) - return NULL; + goto out_of_range; leapdays = leaps_thru_end_of(newy - 1) - leaps_thru_end_of(y - 1); tdays -= ((pg_time_t) newy - y) * DAYSPERNYEAR; tdays -= leapdays; y = newy; } - { - long seconds; - - seconds = tdays * SECSPERDAY + 0.5; - tdays = seconds / SECSPERDAY; - rem += seconds - tdays * SECSPERDAY; - } /* * Given the range, we can now fearlessly cast... @@ -1233,18 +1390,18 @@ timesub(const pg_time_t *timep, long offset, while (idays < 0) { if (increment_overflow(&y, -1)) - return NULL; + goto out_of_range; idays += year_lengths[isleap(y)]; } while (idays >= year_lengths[isleap(y)]) { idays -= year_lengths[isleap(y)]; if (increment_overflow(&y, 1)) - return NULL; + goto out_of_range; } tmp->tm_year = y; if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE)) - return NULL; + goto out_of_range; tmp->tm_yday = idays; /* @@ -1275,20 +1432,49 @@ timesub(const pg_time_t *timep, long offset, tmp->tm_isdst = 0; tmp->tm_gmtoff = offset; return tmp; + +out_of_range: + errno = EOVERFLOW; + return NULL; } /* - * Simplified normalize logic courtesy Paul Eggert. + * Normalize logic courtesy Paul Eggert. */ -static int -increment_overflow(int *number, int delta) +static bool +increment_overflow(int *ip, int j) { - int number0; + int const i = *ip; - number0 = *number; - *number += delta; - return (*number < number0) != (delta < 0); + /*---------- + * If i >= 0 there can only be overflow if i + j > INT_MAX + * or if j > INT_MAX - i; given i >= 0, INT_MAX - i cannot overflow. + * If i < 0 there can only be overflow if i + j < INT_MIN + * or if j < INT_MIN - i; given i < 0, INT_MIN - i cannot overflow. + *---------- + */ + if ((i >= 0) ? (j > INT_MAX - i) : (j < INT_MIN - i)) + return true; + *ip += j; + return false; +} + +static bool +increment_overflow_time(pg_time_t *tp, int32 j) +{ + /*---------- + * This is like + * 'if (! (time_t_min <= *tp + j && *tp + j <= time_t_max)) ...', + * except that it does the right thing even if *tp + j would overflow. + *---------- + */ + if (!(j < 0 + ? (TYPE_SIGNED(pg_time_t) ? time_t_min - j <= *tp : -1 - j < *tp) + : *tp <= time_t_max - j)) + return true; + *tp += j; + return false; } /* @@ -1296,7 +1482,7 @@ increment_overflow(int *number, int delta) * * *timep and *tz are input arguments, the other parameters are output values. * - * When the function result is 1, *boundary is set to the time_t + * When the function result is 1, *boundary is set to the pg_time_t * representation of the next DST transition time after *timep, * *before_gmtoff and *before_isdst are set to the GMT offset and isdst * state prevailing just before that boundary (in particular, the state @@ -1453,8 +1639,8 @@ pg_next_dst_boundary(const pg_time_t *timep, * the meaning in use at or most recently before that time, or the meaning * in first use after that time if the abbrev was never used before that. * - * On success, returns TRUE and sets *gmtoff and *isdst. If the abbreviation - * was never used at all in this zone, returns FALSE. + * On success, returns true and sets *gmtoff and *isdst. If the abbreviation + * was never used at all in this zone, returns false. * * Note: abbrev is matched case-sensitively; it should be all-upper-case. */ @@ -1490,7 +1676,7 @@ pg_interpret_timezone_abbrev(const char *abbrev, abbrind++; } if (abbrind >= sp->charcnt) - return FALSE; /* not there! */ + return false; /* not there! */ /* * Unlike pg_next_dst_boundary, we needn't sweat about extrapolation @@ -1527,7 +1713,7 @@ pg_interpret_timezone_abbrev(const char *abbrev, { *gmtoff = ttisp->tt_gmtoff; *isdst = ttisp->tt_isdst; - return TRUE; + return true; } } @@ -1541,23 +1727,23 @@ pg_interpret_timezone_abbrev(const char *abbrev, { *gmtoff = ttisp->tt_gmtoff; *isdst = ttisp->tt_isdst; - return TRUE; + return true; } } - return FALSE; /* hm, not actually used in any interval? */ + return false; /* hm, not actually used in any interval? */ } /* * If the given timezone uses only one GMT offset, store that offset - * into *gmtoff and return TRUE, else return FALSE. + * into *gmtoff and return true, else return false. */ bool pg_get_timezone_offset(const pg_tz *tz, long int *gmtoff) { /* * The zone could have more than one ttinfo, if it's historically used - * more than one abbreviation. We return TRUE as long as they all have + * more than one abbreviation. We return true as long as they all have * the same gmtoff. */ const struct state *sp; diff --git a/src/timezone/pgtz.c b/src/timezone/pgtz.c index baf9733395..c28e6dbfad 100644 --- a/src/timezone/pgtz.c +++ b/src/timezone/pgtz.c @@ -256,7 +256,7 @@ pg_tzset(const char *name) */ if (strcmp(uppername, "GMT") == 0) { - if (tzparse(uppername, &tzstate, TRUE) != 0) + if (tzparse(uppername, &tzstate, true) != 0) { /* This really, really should not happen ... */ elog(ERROR, "could not initialize GMT time zone"); @@ -264,9 +264,9 @@ pg_tzset(const char *name) /* Use uppercase name as canonical */ strcpy(canonname, uppername); } - else if (tzload(uppername, canonname, &tzstate, TRUE) != 0) + else if (tzload(uppername, canonname, &tzstate, true) != 0) { - if (uppername[0] == ':' || tzparse(uppername, &tzstate, FALSE) != 0) + if (uppername[0] == ':' || tzparse(uppername, &tzstate, false) != 0) { /* Unknown timezone. Fail our call instead of loading GMT! */ return NULL; @@ -460,7 +460,7 @@ pg_tzenumerate_next(pg_tzenum *dir) * the cache */ if (tzload(fullname + dir->baselen, dir->tz.TZname, &dir->tz.state, - TRUE) != 0) + true) != 0) { /* Zone could not be loaded, ignore it */ continue; diff --git a/src/timezone/pgtz.h b/src/timezone/pgtz.h index ad93291d0a..deee7464ed 100644 --- a/src/timezone/pgtz.h +++ b/src/timezone/pgtz.h @@ -16,25 +16,26 @@ #ifndef _PGTZ_H #define _PGTZ_H -#include "tzfile.h" #include "pgtime.h" +#include "tzfile.h" +#define SMALLEST(a, b) (((a) < (b)) ? (a) : (b)) #define BIGGEST(a, b) (((a) > (b)) ? (a) : (b)) struct ttinfo { /* time type information */ - long tt_gmtoff; /* UTC offset in seconds */ - int tt_isdst; /* used to set tm_isdst */ + int32 tt_gmtoff; /* UT offset in seconds */ + bool tt_isdst; /* used to set tm_isdst */ int tt_abbrind; /* abbreviation list index */ - int tt_ttisstd; /* TRUE if transition is std time */ - int tt_ttisgmt; /* TRUE if transition is UTC */ + bool tt_ttisstd; /* transition is std time */ + bool tt_ttisgmt; /* transition is UT */ }; struct lsinfo { /* leap second information */ pg_time_t ls_trans; /* transition time */ - long ls_corr; /* correction to apply */ + int64 ls_corr; /* correction to apply */ }; struct state @@ -43,14 +44,15 @@ struct state int timecnt; int typecnt; int charcnt; - int goback; - int goahead; + bool goback; + bool goahead; pg_time_t ats[TZ_MAX_TIMES]; unsigned char types[TZ_MAX_TIMES]; struct ttinfo ttis[TZ_MAX_TYPES]; char chars[BIGGEST(BIGGEST(TZ_MAX_CHARS + 1, 3 /* sizeof gmt */ ), (2 * (TZ_STRLEN_MAX + 1)))]; struct lsinfo lsis[TZ_MAX_LEAPS]; + int defaulttype; /* for early times or if no transitions */ }; diff --git a/src/timezone/private.h b/src/timezone/private.h index b1ac9edfcf..8480d38961 100644 --- a/src/timezone/private.h +++ b/src/timezone/private.h @@ -25,6 +25,18 @@ #define GRANDPARENTED "Local time zone must be set--see zic manual page" +/* + * IANA has a bunch of HAVE_FOO #defines here, but in PG we want pretty + * much all of that to be done by PG's configure script. + */ + +#ifndef ENOTSUP +#define ENOTSUP EINVAL +#endif +#ifndef EOVERFLOW +#define EOVERFLOW EINVAL +#endif + #ifndef WIFEXITED #define WIFEXITED(status) (((status) & 0xff) == 0) #endif /* !defined WIFEXITED */ @@ -35,6 +47,10 @@ /* Unlike 's isdigit, this also works if c < 0 | c > UCHAR_MAX. */ #define is_digit(c) ((unsigned)(c) - '0' <= 9) +#ifndef SIZE_MAX +#define SIZE_MAX ((size_t) -1) +#endif + /* * SunOS 4.1.1 libraries lack remove. */ @@ -45,31 +61,11 @@ extern int unlink(const char *filename); #define remove unlink #endif /* !defined remove */ -/* - * Private function declarations. - */ -extern char *icalloc(int nelem, int elsize); -extern char *icatalloc(char *old, const char *new); -extern char *icpyalloc(const char *string); -extern char *imalloc(int n); -extern void *irealloc(void *pointer, int size); -extern void icfree(char *pointer); -extern void ifree(char *pointer); -extern const char *scheck(const char *string, const char *format); - /* * Finally, some convenience items. */ -#ifndef TRUE -#define TRUE 1 -#endif /* !defined TRUE */ - -#ifndef FALSE -#define FALSE 0 -#endif /* !defined FALSE */ - #ifndef TYPE_BIT #define TYPE_BIT(type) (sizeof (type) * CHAR_BIT) #endif /* !defined TYPE_BIT */ @@ -78,14 +74,18 @@ extern const char *scheck(const char *string, const char *format); #define TYPE_SIGNED(type) (((type) -1) < 0) #endif /* !defined TYPE_SIGNED */ -/* - * Since the definition of TYPE_INTEGRAL contains floating point numbers, - * it cannot be used in preprocessor directives. - */ +#define TWOS_COMPLEMENT(t) ((t) ~ (t) 0 < 0) -#ifndef TYPE_INTEGRAL -#define TYPE_INTEGRAL(type) (((type) 0.5) != 0.5) -#endif /* !defined TYPE_INTEGRAL */ +/* + * Max and min values of the integer type T, of which only the bottom + * B bits are used, and where the highest-order used bit is considered + * to be a sign bit if T is signed. + */ +#define MAXVAL(t, b) \ + ((t) (((t) 1 << ((b) - 1 - TYPE_SIGNED(t))) \ + - 1 + ((t) 1 << ((b) - 1 - TYPE_SIGNED(t))))) +#define MINVAL(t, b) \ + ((t) (TYPE_SIGNED(t) ? - TWOS_COMPLEMENT(t) - MAXVAL(t, b) : 0)) #ifndef INT_STRLEN_MAXIMUM /* @@ -95,34 +95,36 @@ extern const char *scheck(const char *string, const char *format); * add one more for a minus sign if the type is signed. */ #define INT_STRLEN_MAXIMUM(type) \ - ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + 1 + TYPE_SIGNED(type)) + ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \ + 1 + TYPE_SIGNED(type)) #endif /* !defined INT_STRLEN_MAXIMUM */ +/* + * INITIALIZE(x) + */ +#define INITIALIZE(x) ((x) = 0) + #undef _ #define _(msgid) (msgid) #ifndef YEARSPERREPEAT -#define YEARSPERREPEAT 400 /* years before a Gregorian repeat */ +#define YEARSPERREPEAT 400 /* years before a Gregorian repeat */ #endif /* !defined YEARSPERREPEAT */ /* -** The Gregorian year averages 365.2425 days, which is 31556952 seconds. -*/ + * The Gregorian year averages 365.2425 days, which is 31556952 seconds. + */ #ifndef AVGSECSPERYEAR -#define AVGSECSPERYEAR 31556952L +#define AVGSECSPERYEAR 31556952L #endif /* !defined AVGSECSPERYEAR */ #ifndef SECSPERREPEAT -#define SECSPERREPEAT ((int64) YEARSPERREPEAT * (int64) AVGSECSPERYEAR) +#define SECSPERREPEAT ((int64) YEARSPERREPEAT * (int64) AVGSECSPERYEAR) #endif /* !defined SECSPERREPEAT */ #ifndef SECSPERREPEAT_BITS -#define SECSPERREPEAT_BITS 34 /* ceil(log2(SECSPERREPEAT)) */ +#define SECSPERREPEAT_BITS 34 /* ceil(log2(SECSPERREPEAT)) */ #endif /* !defined SECSPERREPEAT_BITS */ -/* - * UNIX was a registered trademark of The Open Group in 2003. - */ - #endif /* !defined PRIVATE_H */ diff --git a/src/timezone/scheck.c b/src/timezone/scheck.c deleted file mode 100644 index 67bf2bcd4f..0000000000 --- a/src/timezone/scheck.c +++ /dev/null @@ -1,62 +0,0 @@ -/* - * This file is in the public domain, so clarified as of - * 2006-07-17 by Arthur David Olson. - * - * IDENTIFICATION - * src/timezone/scheck.c - */ - -#include "postgres_fe.h" - -#include "private.h" - - -const char * -scheck(const char *string, const char *format) -{ - char *fbuf; - const char *fp; - char *tp; - int c; - const char *result; - char dummy; - - result = ""; - if (string == NULL || format == NULL) - return result; - fbuf = imalloc((int) (2 * strlen(format) + 4)); - if (fbuf == NULL) - return result; - fp = format; - tp = fbuf; - while ((*tp++ = c = *fp++) != '\0') - { - if (c != '%') - continue; - if (*fp == '%') - { - *tp++ = *fp++; - continue; - } - *tp++ = '*'; - if (*fp == '*') - ++fp; - while (is_digit(*fp)) - *tp++ = *fp++; - if (*fp == 'l' || *fp == 'h') - *tp++ = *fp++; - else if (*fp == '[') - do - *tp++ = *fp++; - while (*fp != '\0' && *fp != ']'); - if ((*tp++ = *fp++) == '\0') - break; - } - *(tp - 1) = '%'; - *tp++ = 'c'; - *tp = '\0'; - if (sscanf(string, fbuf, &dummy) != 1) - result = (char *) format; - ifree(fbuf); - return result; -} diff --git a/src/timezone/strftime.c b/src/timezone/strftime.c index bdc8dd3240..5630619321 100644 --- a/src/timezone/strftime.c +++ b/src/timezone/strftime.c @@ -1,18 +1,38 @@ +/* Convert a broken-down time stamp to a string. */ + /* - * Copyright (c) 1989 The Regents of the University of California. + * Copyright 1989 The Regents of the University of California. * All rights reserved. * - * Redistribution and use in source and binary forms are permitted - * provided that the above copyright notice and this paragraph are - * duplicated in all such forms and that any documentation, - * advertising materials, and other materials related to such - * distribution and use acknowledge that the software was developed - * by the University of California, Berkeley. The name of the - * University may not be used to endorse or promote products derived - * from this software without specific prior written permission. - * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR - * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED - * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Based on the UCB version with the copyright notice appearing above. + * + * This is ANSIish only when "multibyte character == plain character". * * IDENTIFICATION * src/timezone/strftime.c @@ -92,8 +112,7 @@ static char *_add(const char *, char *, const char *); static char *_conv(int, const char *, char *, const char *); static char *_fmt(const char *, const struct pg_tm *, char *, const char *, int *); -static char *_yconv(const int, const int, const int, const int, - char *, const char *const); +static char *_yconv(int, int, bool, bool, char *, const char *); #define IN_NONE 0 #define IN_SOME 1 @@ -162,8 +181,8 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim, * ...whereas now POSIX 1003.2 calls for something * completely different. (ado, 1993-05-24) */ - pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 0, - pt, ptlim); + pt = _yconv(t->tm_year, TM_YEAR_BASE, + true, false, pt, ptlim); continue; case 'c': { @@ -224,7 +243,7 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim, case 'K': /* - * * After all this time, still unclaimed! + * After all this time, still unclaimed! */ pt = _add("kitchen sink", pt, ptlim); continue; @@ -296,7 +315,7 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim, * (01-53)." * (ado, 1993-05-24) * - * From "http://www.ft.uni-erlangen.de/~mskuhn/iso-time.html" by Markus Kuhn: + * From by Markus Kuhn: * "Week 01 of a year is per definition the first week which has the * Thursday in this year, which is equivalent to the week which contains * the fourth day of January. In other words, the first week of a new year @@ -367,11 +386,13 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim, else if (*format == 'g') { *warnp = IN_ALL; - pt = _yconv(year, base, 0, 1, + pt = _yconv(year, base, + false, true, pt, ptlim); } else - pt = _yconv(year, base, 1, 1, + pt = _yconv(year, base, + true, true, pt, ptlim); } continue; @@ -409,11 +430,13 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim, continue; case 'y': *warnp = IN_ALL; - pt = _yconv(t->tm_year, TM_YEAR_BASE, 0, 1, + pt = _yconv(t->tm_year, TM_YEAR_BASE, + false, true, pt, ptlim); continue; case 'Y': - pt = _yconv(t->tm_year, TM_YEAR_BASE, 1, 1, + pt = _yconv(t->tm_year, TM_YEAR_BASE, + true, true, pt, ptlim); continue; case 'Z': @@ -427,7 +450,7 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim, continue; case 'z': { - int diff; + long diff; char const *sign; if (t->tm_isdst < 0) @@ -441,9 +464,10 @@ _fmt(const char *format, const struct pg_tm * t, char *pt, const char *ptlim, else sign = "+"; pt = _add(sign, pt, ptlim); - diff /= 60; - pt = _conv((diff / 60) * 100 + diff % 60, - "%04d", pt, ptlim); + diff /= SECSPERMIN; + diff = (diff / MINSPERHOUR) * 100 + + (diff % MINSPERHOUR); + pt = _conv(diff, "%04d", pt, ptlim); } continue; case '+': @@ -473,7 +497,7 @@ _conv(int n, const char *format, char *pt, const char *ptlim) { char buf[INT_STRLEN_MAXIMUM(int) +1]; - (void) sprintf(buf, format, n); + sprintf(buf, format, n); return _add(buf, pt, ptlim); } @@ -493,13 +517,13 @@ _add(const char *str, char *pt, const char *ptlim) * with more only if necessary. */ static char * -_yconv(const int a, const int b, const int convert_top, - const int convert_yy, char *pt, const char *const ptlim) +_yconv(int a, int b, bool convert_top, bool convert_yy, + char *pt, const char *ptlim) { int lead; int trail; -#define DIVISOR 100 +#define DIVISOR 100 trail = a % DIVISOR + b % DIVISOR; lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; trail %= DIVISOR; diff --git a/src/timezone/tzfile.h b/src/timezone/tzfile.h index 065db9032e..32d237b827 100644 --- a/src/timezone/tzfile.h +++ b/src/timezone/tzfile.h @@ -33,8 +33,8 @@ struct tzhead { char tzh_magic[4]; /* TZ_MAGIC */ - char tzh_version[1]; /* '\0' or '2' as of 2005 */ - char tzh_reserved[15]; /* reserved--must be zero */ + char tzh_version[1]; /* '\0' or '2' or '3' as of 2013 */ + char tzh_reserved[15]; /* reserved; must be zero */ char tzh_ttisgmtcnt[4]; /* coded number of trans. time flags */ char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ char tzh_leapcnt[4]; /* coded number of leap seconds */ @@ -43,30 +43,29 @@ struct tzhead char tzh_charcnt[4]; /* coded number of abbr. chars */ }; -/*---------- +/* * . . .followed by. . . * * tzh_timecnt (char [4])s coded transition times a la time(2) * tzh_timecnt (unsigned char)s types of local time starting at above * tzh_typecnt repetitions of - * one (char [4]) coded UTC offset in seconds + * one (char [4]) coded UT offset in seconds * one (unsigned char) used to set tm_isdst * one (unsigned char) that's an abbreviation list index * tzh_charcnt (char)s '\0'-terminated zone abbreviations * tzh_leapcnt repetitions of * one (char [4]) coded leap second transition times * one (char [4]) total correction after above - * tzh_ttisstdcnt (char)s indexed by type; if TRUE, transition - * time is standard time, if FALSE, + * tzh_ttisstdcnt (char)s indexed by type; if 1, transition + * time is standard time, if 0, * transition time is wall clock time * if absent, transition times are * assumed to be wall clock time - * tzh_ttisgmtcnt (char)s indexed by type; if TRUE, transition - * time is UTC, if FALSE, + * tzh_ttisgmtcnt (char)s indexed by type; if 1, transition + * time is UT, if 0, * transition time is local time * if absent, transition times are * assumed to be local time - *---------- */ /* @@ -77,6 +76,13 @@ struct tzhead * instants after the last transition time stored in the file * (with nothing between the newlines if there is no POSIX representation for * such instants). + * + * If tz_version is '3' or greater, the above is extended as follows. + * First, the POSIX TZ string's hour offset may range from -167 + * through 167 as compared to the POSIX-required 0 through 24. + * Second, its DST start time may be January 1 at 00:00 and its stop + * time December 31 at 24:00 plus the difference between DST and + * standard time, indicating DST all year. */ /* @@ -84,8 +90,9 @@ struct tzhead * exceed any of the limits below. */ -#define TZ_MAX_TIMES 1200 +#define TZ_MAX_TIMES 2000 +/* This must be at least 17 for Europe/Samara and Europe/Vilnius. */ #define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ #define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ @@ -100,7 +107,7 @@ struct tzhead #define DAYSPERNYEAR 365 #define DAYSPERLYEAR 366 #define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) -#define SECSPERDAY ((long) SECSPERHOUR * HOURSPERDAY) +#define SECSPERDAY ((int32) SECSPERHOUR * HOURSPERDAY) #define MONSPERYEAR 12 #define TM_SUNDAY 0 @@ -143,6 +150,6 @@ struct tzhead * We use this to avoid addition overflow problems. */ -#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) +#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) #endif /* !defined TZFILE_H */ diff --git a/src/timezone/zic.c b/src/timezone/zic.c index 9fc20c6bac..8d4347a47a 100644 --- a/src/timezone/zic.c +++ b/src/timezone/zic.c @@ -8,28 +8,27 @@ #include "postgres_fe.h" -#include #include +#include #include #include "pg_getopt.h" #include "private.h" -#include "pgtz.h" #include "tzfile.h" -#define ZIC_VERSION '2' +#define ZIC_VERSION_PRE_2013 '2' +#define ZIC_VERSION '3' typedef int64 zic_t; +#define ZIC_MIN PG_INT64_MIN +#define ZIC_MAX PG_INT64_MAX +#define SCNdZIC INT64_MODIFIER "d" #ifndef ZIC_MAX_ABBR_LEN_WO_WARN #define ZIC_MAX_ABBR_LEN_WO_WARN 6 #endif /* !defined ZIC_MAX_ABBR_LEN_WO_WARN */ -#ifdef HAVE_SYS_STAT_H -#include -#endif - #ifndef WIN32 #ifdef S_IRUSR #define MKDIR_UMASK (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH) @@ -38,37 +37,17 @@ typedef int64 zic_t; #endif #endif -static char elsieid[] = "@(#)zic.c 8.20"; - -/* - * On some ancient hosts, predicates like `isspace(C)' are defined - * only if isascii(C) || C == EOF. Modern hosts obey the C Standard, - * which says they are defined only if C == ((unsigned char) C) || C == EOF. - * Neither the C Standard nor Posix require that `isascii' exist. - * For portability, we check both ancient and modern requirements. - * If isascii is not defined, the isascii check succeeds trivially. - */ -#include -#ifndef isascii -#define isascii(x) 1 -#endif - -#define OFFSET_STRLEN_MAXIMUM (7 + INT_STRLEN_MAXIMUM(long)) -#define RULE_STRLEN_MAXIMUM 8 /* "Mdd.dd.d" */ - -#define end(cp) (strchr((cp), '\0')) - struct rule { const char *r_filename; int r_linenum; const char *r_name; - int r_loyear; /* for example, 1986 */ - int r_hiyear; /* for example, 1986 */ + zic_t r_loyear; /* for example, 1986 */ + zic_t r_hiyear; /* for example, 1986 */ const char *r_yrtype; - int r_lowasnum; - int r_hiwasnum; + bool r_lowasnum; + bool r_hiwasnum; int r_month; /* 0..11 */ @@ -76,12 +55,11 @@ struct rule int r_dayofmonth; int r_wday; - long r_tod; /* time from midnight */ - int r_todisstd; /* above is standard time if TRUE */ - /* or wall clock time if FALSE */ - int r_todisgmt; /* above is GMT if TRUE */ - /* or local time if FALSE */ - long r_stdoff; /* offset from standard time */ + zic_t r_tod; /* time from midnight */ + bool r_todisstd; /* above is standard time if 1 or wall clock + * time if 0 */ + bool r_todisgmt; /* above is GMT if 1 or local time if 0 */ + zic_t r_stdoff; /* offset from standard time */ const char *r_abbrvar; /* variable part of abbreviation */ int r_todo; /* a rule to do (used in outzone) */ @@ -102,11 +80,12 @@ struct zone int z_linenum; const char *z_name; - long z_gmtoff; + zic_t z_gmtoff; const char *z_rule; const char *z_format; + char z_format_specifier; - long z_stdoff; + zic_t z_stdoff; struct rule *z_rules; int z_nrules; @@ -116,74 +95,69 @@ struct zone }; extern int link(const char *fromname, const char *toname); -static void addtt(const pg_time_t starttime, int type); -static int addtype(long gmtoff, const char *abbr, int isdst, - int ttisstd, int ttisgmt); -static void leapadd(const pg_time_t t, int positive, int rolling, int count); + +static void memory_exhausted(const char *msg) pg_attribute_noreturn(); +static void verror(const char *string, va_list args) pg_attribute_printf(1, 0); +static void error(const char *string,...) pg_attribute_printf(1, 2); +static void warning(const char *string,...) pg_attribute_printf(1, 2); +static void usage(FILE *stream, int status) pg_attribute_noreturn(); +static void addtt(zic_t starttime, int type); +static int addtype(zic_t, char const *, bool, bool, bool); +static void leapadd(zic_t, bool, int, int); static void adjleap(void); static void associate(void); -static int ciequal(const char *ap, const char *bp); -static void convert(long val, char *buf); -static void dolink(const char *fromfile, const char *tofile); -static void doabbr(char *abbr, const char *format, - const char *letters, int isdst, int doquotes); -static void eat(const char *name, int num); -static void eats(const char *name, int num, - const char *rname, int rnum); -static long eitol(int i); -static void error(const char *message); +static void dolink(const char *fromfield, const char *tofield); static char **getfields(char *buf); -static long gethms(const char *string, const char *errstrng, - int signable); +static zic_t gethms(const char *string, const char *errstring, + bool); static void infile(const char *filename); static void inleap(char **fields, int nfields); static void inlink(char **fields, int nfields); static void inrule(char **fields, int nfields); -static int inzcont(char **fields, int nfields); -static int inzone(char **fields, int nfields); -static int inzsub(char **fields, int nfields, int iscont); -static int itsabbr(const char *abbr, const char *word); +static bool inzcont(char **fields, int nfields); +static bool inzone(char **fields, int nfields); +static bool inzsub(char **, int, bool); static int itsdir(const char *name); -static int lowerit(int c); -static char *memcheck(char *tocheck); -static int mkdirs(char *filename); +static bool is_alpha(char a); +static char lowerit(char); +static bool mkdirs(char *); static void newabbr(const char *abbr); -static long oadd(long t1, long t2); +static zic_t oadd(zic_t t1, zic_t t2); static void outzone(const struct zone * zp, int ntzones); -static void puttzcode(long code, FILE *fp); -static int rcomp(const void *leftp, const void *rightp); -static pg_time_t rpytime(const struct rule * rp, int wantedy); +static zic_t rpytime(const struct rule * rp, zic_t wantedy); static void rulesub(struct rule * rp, const char *loyearp, const char *hiyearp, const char *typep, const char *monthp, const char *dayp, const char *timep); -static void setboundaries(void); -static pg_time_t tadd(const pg_time_t t1, long t2); -static void usage(FILE *stream, int status); -static void writezone(const char *name, const char *string); -static int yearistype(int year, const char *type); +static zic_t tadd(zic_t t1, zic_t t2); +static bool yearistype(int year, const char *type); + +/* Bound on length of what %z can expand to. */ +enum +{ +PERCENT_Z_LEN_BOUND = sizeof "+995959" - 1}; static int charcnt; -static int errors; +static bool errors; +static bool warnings; static const char *filename; static int leapcnt; -static int leapseen; -static int leapminyear; -static int leapmaxyear; +static bool leapseen; +static zic_t leapminyear; +static zic_t leapmaxyear; static int linenum; -static int max_abbrvar_len; +static int max_abbrvar_len = PERCENT_Z_LEN_BOUND; static int max_format_len; -static zic_t max_time; -static int max_year; -static zic_t min_time; -static int min_year; -static int noise; -static int print_abbrevs; +static zic_t max_year; +static zic_t min_year; +static bool noise; +static bool print_abbrevs; static zic_t print_cutoff; static const char *rfilename; static int rlinenum; static const char *progname; static int timecnt; +static int timecnt_alloc; static int typecnt; /* @@ -269,9 +243,11 @@ static int typecnt; static struct rule *rules; static int nrules; /* number of rules */ +static int nrules_alloc; static struct zone *zones; static int nzones; /* number of zones */ +static int nzones_alloc; struct link { @@ -283,6 +259,7 @@ struct link static struct link *links; static int nlinks; +static int nlinks_alloc; struct lookup { @@ -353,8 +330,8 @@ static struct lookup const end_years[] = { }; static struct lookup const leap_types[] = { - {"Rolling", TRUE}, - {"Stationary", FALSE}, + {"Rolling", true}, + {"Stationary", false}, {NULL, 0} }; @@ -371,39 +348,77 @@ static struct attype { zic_t at; unsigned char type; -} attypes[TZ_MAX_TIMES]; -static long gmtoffs[TZ_MAX_TYPES]; +} *attypes; +static zic_t gmtoffs[TZ_MAX_TYPES]; static char isdsts[TZ_MAX_TYPES]; static unsigned char abbrinds[TZ_MAX_TYPES]; -static char ttisstds[TZ_MAX_TYPES]; -static char ttisgmts[TZ_MAX_TYPES]; +static bool ttisstds[TZ_MAX_TYPES]; +static bool ttisgmts[TZ_MAX_TYPES]; static char chars[TZ_MAX_CHARS]; static zic_t trans[TZ_MAX_LEAPS]; -static long corr[TZ_MAX_LEAPS]; +static zic_t corr[TZ_MAX_LEAPS]; static char roll[TZ_MAX_LEAPS]; /* * Memory allocation. */ -static char * -memcheck(char *ptr) +static void +memory_exhausted(const char *msg) +{ + fprintf(stderr, _("%s: Memory exhausted: %s\n"), progname, msg); + exit(EXIT_FAILURE); +} + +static size_t +size_product(size_t nitems, size_t itemsize) +{ + if (SIZE_MAX / itemsize < nitems) + memory_exhausted(_("size overflow")); + return nitems * itemsize; +} + +static void * +memcheck(void *ptr) { if (ptr == NULL) - { - const char *e = strerror(errno); - - (void) fprintf(stderr, _("%s: Memory exhausted: %s\n"), - progname, e); - exit(EXIT_FAILURE); - } + memory_exhausted(strerror(errno)); return ptr; } -#define emalloc(size) memcheck(imalloc(size)) -#define erealloc(ptr, size) memcheck(irealloc((ptr), (size))) -#define ecpyalloc(ptr) memcheck(icpyalloc(ptr)) -#define ecatalloc(oldp, newp) memcheck(icatalloc((oldp), (newp))) +static void * +emalloc(size_t size) +{ + return memcheck(malloc(size)); +} + +static void * +erealloc(void *ptr, size_t size) +{ + return memcheck(realloc(ptr, size)); +} + +static char * +ecpyalloc(char const * str) +{ + return memcheck(strdup(str)); +} + +static void * +growalloc(void *ptr, size_t itemsize, int nitems, int *nitems_alloc) +{ + if (nitems < *nitems_alloc) + return ptr; + else + { + int amax = INT_MAX < SIZE_MAX ? INT_MAX : SIZE_MAX; + + if ((amax - 1) / 3 * 2 < *nitems_alloc) + memory_exhausted(_("int overflow")); + *nitems_alloc = *nitems_alloc + (*nitems_alloc >> 1) + 1; + return erealloc(ptr, size_product(*nitems_alloc, itemsize)); + } +} /* * Error handling. @@ -421,46 +436,75 @@ eats(const char *name, int num, const char *rname, int rnum) static void eat(const char *name, int num) { - eats(name, num, (char *) NULL, -1); + eats(name, num, NULL, -1); } static void -error(const char *string) +verror(const char *string, va_list args) { /* * Match the format of "cc" to allow sh users to zic ... 2>&1 | error -t * "*" -v on BSD systems. */ - (void) fprintf(stderr, _("\"%s\", line %d: %s"), - filename, linenum, string); + if (filename) + fprintf(stderr, _("\"%s\", line %d: "), filename, linenum); + vfprintf(stderr, string, args); if (rfilename != NULL) - (void) fprintf(stderr, _(" (rule from \"%s\", line %d)"), - rfilename, rlinenum); - (void) fprintf(stderr, "\n"); - ++errors; + fprintf(stderr, _(" (rule from \"%s\", line %d)"), + rfilename, rlinenum); + fprintf(stderr, "\n"); } static void -warning(const char *string) +error(const char *string,...) { - char *cp; + va_list args; - cp = ecpyalloc(_("warning: ")); - cp = ecatalloc(cp, string); - error(cp); - ifree(cp); - --errors; + va_start(args, string); + verror(string, args); + va_end(args); + errors = true; +} + +static void +warning(const char *string,...) +{ + va_list args; + + fprintf(stderr, _("warning: ")); + va_start(args, string); + verror(string, args); + va_end(args); + warnings = true; +} + +static void +close_file(FILE *stream, char const * name) +{ + char const *e = (ferror(stream) ? _("I/O error") + : fclose(stream) != 0 ? strerror(errno) : NULL); + + if (e) + { + fprintf(stderr, "%s: ", progname); + if (name) + fprintf(stderr, "%s: ", name); + fprintf(stderr, "%s\n", e); + exit(EXIT_FAILURE); + } } static void usage(FILE *stream, int status) { - (void) fprintf(stream, _("%s: usage is %s \ -[ --version ] [ --help ] [ -v ] [ -P ] [ -l localtime ] [ -p posixrules ] \\\n\ -\t[ -d directory ] [ -L leapseconds ] [ -y yearistype ] [ filename ... ]\n\ -\n\ -Report bugs to tz@elsie.nci.nih.gov.\n"), - progname, progname); + fprintf(stream, + _("%s: usage is %s [ --version ] [ --help ] [ -v ] [ -P ] \\\n" + "\t[ -l localtime ] [ -p posixrules ] [ -d directory ] \\\n" + "\t[ -L leapseconds ] [ filename ... ]\n\n" + "Report bugs to %s.\n"), + progname, progname, PACKAGE_BUGREPORT); + if (status == EXIT_SUCCESS) + close_file(stream, NULL); exit(status); } @@ -478,20 +522,21 @@ main(int argc, char *argv[]) int c; #ifndef WIN32 - (void) umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH)); + umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH)); #endif /* !WIN32 */ progname = argv[0]; if (TYPE_BIT(zic_t) <64) { - (void) fprintf(stderr, "%s: %s\n", progname, - _("wild compilation-time specification of zic_t")); - exit(EXIT_FAILURE); + fprintf(stderr, "%s: %s\n", progname, + _("wild compilation-time specification of zic_t")); + return EXIT_FAILURE; } for (i = 1; i < argc; ++i) if (strcmp(argv[i], "--version") == 0) { - (void) printf("%s\n", elsieid); - exit(EXIT_SUCCESS); + printf("zic %s\n", PG_VERSION); + close_file(stdout, NULL); + return EXIT_SUCCESS; } else if (strcmp(argv[i], "--help") == 0) { @@ -507,10 +552,10 @@ main(int argc, char *argv[]) directory = strdup(optarg); else { - (void) fprintf(stderr, - _("%s: More than one -d option specified\n"), - progname); - exit(EXIT_FAILURE); + fprintf(stderr, + _("%s: More than one -d option specified\n"), + progname); + return EXIT_FAILURE; } break; case 'l': @@ -518,10 +563,10 @@ main(int argc, char *argv[]) lcltime = strdup(optarg); else { - (void) fprintf(stderr, - _("%s: More than one -l option specified\n"), - progname); - exit(EXIT_FAILURE); + fprintf(stderr, + _("%s: More than one -l option specified\n"), + progname); + return EXIT_FAILURE; } break; case 'p': @@ -529,10 +574,10 @@ main(int argc, char *argv[]) psxrules = strdup(optarg); else { - (void) fprintf(stderr, - _("%s: More than one -p option specified\n"), - progname); - exit(EXIT_FAILURE); + fprintf(stderr, + _("%s: More than one -p option specified\n"), + progname); + return EXIT_FAILURE; } break; case 'y': @@ -540,10 +585,10 @@ main(int argc, char *argv[]) yitcommand = strdup(optarg); else { - (void) fprintf(stderr, - _("%s: More than one -y option specified\n"), - progname); - exit(EXIT_FAILURE); + fprintf(stderr, + _("%s: More than one -y option specified\n"), + progname); + return EXIT_FAILURE; } break; case 'L': @@ -551,21 +596,21 @@ main(int argc, char *argv[]) leapsec = strdup(optarg); else { - (void) fprintf(stderr, - _("%s: More than one -L option specified\n"), - progname); - exit(EXIT_FAILURE); + fprintf(stderr, + _("%s: More than one -L option specified\n"), + progname); + return EXIT_FAILURE; } break; case 'v': - noise = TRUE; + noise = true; break; case 'P': - print_abbrevs = TRUE; + print_abbrevs = true; print_cutoff = time(NULL); break; case 's': - (void) printf("%s: -s ignored\n", progname); + warning(_("-s ignored")); break; } if (optind == argc - 1 && strcmp(argv[optind], "=") == 0) @@ -575,8 +620,6 @@ main(int argc, char *argv[]) if (yitcommand == NULL) yitcommand = "yearistype"; - setboundaries(); - if (optind < argc && leapsec != NULL) { infile(leapsec); @@ -586,7 +629,7 @@ main(int argc, char *argv[]) for (i = optind; i < argc; ++i) infile(argv[i]); if (errors) - exit(EXIT_FAILURE); + return EXIT_FAILURE; associate(); for (i = 0; i < nzones; i = j) { @@ -613,110 +656,284 @@ main(int argc, char *argv[]) } if (lcltime != NULL) { - eat("command line", 1); + eat(_("command line"), 1); dolink(lcltime, TZDEFAULT); } if (psxrules != NULL) { - eat("command line", 1); + eat(_("command line"), 1); dolink(psxrules, TZDEFRULES); } - return (errors == 0) ? EXIT_SUCCESS : EXIT_FAILURE; + if (warnings && (ferror(stderr) || fclose(stderr) != 0)) + return EXIT_FAILURE; + return errors ? EXIT_FAILURE : EXIT_SUCCESS; +} + +static bool +componentcheck(char const * name, char const * component, + char const * component_end) +{ + enum + { + component_len_max = 14}; + size_t component_len = component_end - component; + + if (component_len == 0) + { + if (!*name) + error(_("empty file name")); + else + error(_(component == name + ? "file name '%s' begins with '/'" + : *component_end + ? "file name '%s' contains '//'" + : "file name '%s' ends with '/'"), + name); + return false; + } + if (0 < component_len && component_len <= 2 + && component[0] == '.' && component_end[-1] == '.') + { + error(_("file name '%s' contains '%.*s' component"), + name, (int) component_len, component); + return false; + } + if (noise) + { + if (0 < component_len && component[0] == '-') + warning(_("file name '%s' component contains leading '-'"), + name); + if (component_len_max < component_len) + warning(_("file name '%s' contains overlength component" + " '%.*s...'"), + name, component_len_max, component); + } + return true; +} + +static bool +namecheck(const char *name) +{ + char const *cp; + + /* Benign characters in a portable file name. */ + static char const benign[] = + "-/_" + "abcdefghijklmnopqrstuvwxyz" + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; + + /* + * Non-control chars in the POSIX portable character set, excluding the + * benign characters. + */ + static char const printable_and_not_benign[] = + " !\"#$%&'()*+,.0123456789:;<=>?@[\\]^`{|}~"; + + char const *component = name; + + for (cp = name; *cp; cp++) + { + unsigned char c = *cp; + + if (noise && !strchr(benign, c)) + { + warning((strchr(printable_and_not_benign, c) + ? _("file name '%s' contains byte '%c'") + : _("file name '%s' contains byte '\\%o'")), + name, c); + } + if (c == '/') + { + if (!componentcheck(name, component, cp)) + return false; + component = cp + 1; + } + } + return componentcheck(name, component, cp); +} + +static char * +relname(char const * dir, char const * base) +{ + if (*base == '/') + return ecpyalloc(base); + else + { + size_t dir_len = strlen(dir); + bool needs_slash = dir_len && dir[dir_len - 1] != '/'; + char *result = emalloc(dir_len + needs_slash + strlen(base) + 1); + + result[dir_len] = '/'; + strcpy(result + dir_len + needs_slash, base); + return memcpy(result, dir, dir_len); + } } static void -dolink(const char *fromfield, const char *tofield) +dolink(char const * fromfield, char const * tofield) { char *fromname; char *toname; + int fromisdir; - if (fromfield[0] == '/') - fromname = ecpyalloc(fromfield); - else - { - fromname = ecpyalloc(directory); - fromname = ecatalloc(fromname, "/"); - fromname = ecatalloc(fromname, fromfield); - } - if (tofield[0] == '/') - toname = ecpyalloc(tofield); - else - { - toname = ecpyalloc(directory); - toname = ecatalloc(toname, "/"); - toname = ecatalloc(toname, tofield); - } + fromname = relname(directory, fromfield); + toname = relname(directory, tofield); /* * We get to be careful here since there's a fair chance of root running * us. */ - if (!itsdir(toname)) - (void) remove(toname); + fromisdir = itsdir(fromname); + if (fromisdir) + { + char const *e = strerror(fromisdir < 0 ? errno : EPERM); + + fprintf(stderr, _("%s: link from %s failed: %s"), + progname, fromname, e); + exit(EXIT_FAILURE); + } if (link(fromname, toname) != 0) { - int result; + int link_errno = errno; + bool retry_if_link_supported = false; - if (mkdirs(toname) != 0) - exit(EXIT_FAILURE); - - result = link(fromname, toname); -#ifdef HAVE_SYMLINK - if (result != 0 && - access(fromname, F_OK) == 0 && - !itsdir(fromname)) + if (link_errno == ENOENT || link_errno == ENOTSUP) { - const char *s = tofield; - char *symlinkcontents = NULL; - - while ((s = strchr(s + 1, '/')) != NULL) - symlinkcontents = ecatalloc(symlinkcontents, "../"); - symlinkcontents = ecatalloc(symlinkcontents, fromfield); - - result = symlink(symlinkcontents, toname); - if (result == 0) - warning(_("hard link failed, symbolic link used")); - ifree(symlinkcontents); + if (!mkdirs(toname)) + exit(EXIT_FAILURE); + retry_if_link_supported = true; } -#endif - if (result != 0) + if ((link_errno == EEXIST || link_errno == ENOTSUP) + && itsdir(toname) == 0 + && (remove(toname) == 0 || errno == ENOENT)) + retry_if_link_supported = true; + if (retry_if_link_supported && link_errno != ENOTSUP) + link_errno = link(fromname, toname) == 0 ? 0 : errno; + if (link_errno != 0) { - const char *e = strerror(errno); + const char *s = fromfield; + const char *t; + char *p; + size_t dotdots = 0; + char *symlinkcontents; + int symlink_result; - (void) fprintf(stderr, - _("%s: Cannot link from %s to %s: %s\n"), - progname, fromname, toname, e); - exit(EXIT_FAILURE); + do + t = s; + while ((s = strchr(s, '/')) + && strncmp(fromfield, tofield, ++s - fromfield) == 0); + + for (s = tofield + (t - fromfield); *s; s++) + dotdots += *s == '/'; + symlinkcontents = emalloc(3 * dotdots + strlen(t) + 1); + for (p = symlinkcontents; dotdots-- != 0; p += 3) + memcpy(p, "../", 3); + strcpy(p, t); + symlink_result = symlink(symlinkcontents, toname); + free(symlinkcontents); + if (symlink_result == 0) + { + if (link_errno != ENOTSUP) + warning(_("symbolic link used because hard link failed: %s"), + strerror(link_errno)); + } + else + { + FILE *fp, + *tp; + int c; + + fp = fopen(fromname, "rb"); + if (!fp) + { + const char *e = strerror(errno); + + fprintf(stderr, + _("%s: Can't read %s: %s\n"), + progname, fromname, e); + exit(EXIT_FAILURE); + } + tp = fopen(toname, "wb"); + if (!tp) + { + const char *e = strerror(errno); + + fprintf(stderr, + _("%s: Can't create %s: %s\n"), + progname, toname, e); + exit(EXIT_FAILURE); + } + while ((c = getc(fp)) != EOF) + putc(c, tp); + close_file(fp, fromname); + close_file(tp, toname); + if (link_errno != ENOTSUP) + warning(_("copy used because hard link failed: %s"), + strerror(link_errno)); + } } } - ifree(fromname); - ifree(toname); + free(fromname); + free(toname); } -#define TIME_T_BITS_IN_FILE 64 +#define TIME_T_BITS_IN_FILE 64 -static void -setboundaries(void) -{ - int i; +static zic_t const min_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE); +static zic_t const max_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE); - min_time = -1; - for (i = 0; i < TIME_T_BITS_IN_FILE - 1; ++i) - min_time *= 2; - max_time = -(min_time + 1); -} +/* + * Estimated time of the Big Bang, in seconds since the POSIX epoch. + * rounded downward to the negation of a power of two that is + * comfortably outside the error bounds. + * + * zic does not output time stamps before this, partly because they + * are physically suspect, and partly because GNOME mishandles them; see + * GNOME bug 730332 . + * + * For the time of the Big Bang, see: + * + * Ade PAR, Aghanim N, Armitage-Caplan C et al. Planck 2013 results. + * I. Overview of products and scientific results. + * arXiv:1303.5062 2013-03-20 20:10:01 UTC + * [PDF] + * + * Page 36, Table 9, row Age/Gyr, column Planck+WP+highL+BAO 68% limits + * gives the value 13.798 plus-or-minus 0.037 billion years. + * Multiplying this by 1000000000 and then by 31557600 (the number of + * seconds in an astronomical year) gives a value that is comfortably + * less than 2**59, so BIG_BANG is - 2**59. + * + * BIG_BANG is approximate, and may change in future versions. + * Please do not rely on its exact value. + */ +#ifndef BIG_BANG +#define BIG_BANG (- (((zic_t) 1) << 59)) +#endif + +static const zic_t big_bang_time = BIG_BANG; + +/* Return 1 if NAME is a directory, 0 if it's something else, -1 if trouble. */ static int -itsdir(const char *name) +itsdir(char const * name) { - char *myname; - int accres; + struct stat st; + int res = stat(name, &st); - myname = ecpyalloc(name); - myname = ecatalloc(myname, "/."); - accres = access(myname, F_OK); - ifree(myname); - return accres == 0; +#ifdef S_ISDIR + if (res == 0) + return S_ISDIR(st.st_mode) != 0; +#endif + if (res == 0 || errno == EOVERFLOW) + { + char *nameslashdot = relname(name, "."); + bool dir = stat(nameslashdot, &st) == 0 || errno == EOVERFLOW; + + free(nameslashdot); + return dir; + } + return -1; } /* @@ -746,8 +963,7 @@ associate(void) if (nrules != 0) { - (void) qsort((void *) rules, (size_t) nrules, - (size_t) sizeof *rules, rcomp); + qsort(rules, nrules, sizeof *rules, rcomp); for (i = 0; i < nrules - 1; ++i) { if (strcmp(rules[i].r_name, @@ -807,14 +1023,14 @@ associate(void) */ eat(zp->z_filename, zp->z_linenum); zp->z_stdoff = gethms(zp->z_rule, _("unruly zone"), - TRUE); + true); /* * Note, though, that if there's no rule, a '%s' in the format is * a bad thing. */ - if (strchr(zp->z_format, '%') != NULL) - error(_("%s in ruleless zone")); + if (zp->z_format_specifier == 's') + error("%s", _("%s in ruleless zone")); } } if (errors) @@ -829,7 +1045,7 @@ infile(const char *name) char *cp; const struct lookup *lp; int nfields; - int wantcont; + bool wantcont; int num; char buf[BUFSIZ]; @@ -842,15 +1058,15 @@ infile(const char *name) { const char *e = strerror(errno); - (void) fprintf(stderr, _("%s: Cannot open %s: %s\n"), - progname, name, e); + fprintf(stderr, _("%s: Cannot open %s: %s\n"), + progname, name, e); exit(EXIT_FAILURE); } - wantcont = FALSE; + wantcont = false; for (num = 1;; ++num) { eat(name, num); - if (fgets(buf, (int) sizeof buf, fp) != buf) + if (fgets(buf, sizeof buf, fp) != buf) break; cp = strchr(buf, '\n'); if (cp == NULL) @@ -885,66 +1101,53 @@ infile(const char *name) { case LC_RULE: inrule(fields, nfields); - wantcont = FALSE; + wantcont = false; break; case LC_ZONE: wantcont = inzone(fields, nfields); break; case LC_LINK: inlink(fields, nfields); - wantcont = FALSE; + wantcont = false; break; case LC_LEAP: if (name != leapsec) - (void) fprintf(stderr, - _("%s: Leap line in non leap seconds file %s\n"), - progname, name); + warning(_("%s: Leap line in non leap" + " seconds file %s"), + progname, name); else inleap(fields, nfields); - wantcont = FALSE; + wantcont = false; break; default: /* "cannot happen" */ - (void) fprintf(stderr, - _("%s: panic: Invalid l_value %d\n"), - progname, lp->l_value); + fprintf(stderr, + _("%s: panic: Invalid l_value %d\n"), + progname, lp->l_value); exit(EXIT_FAILURE); } } - ifree((char *) fields); - } - if (ferror(fp)) - { - (void) fprintf(stderr, _("%s: Error reading %s\n"), - progname, filename); - exit(EXIT_FAILURE); - } - if (fp != stdin && fclose(fp)) - { - const char *e = strerror(errno); - - (void) fprintf(stderr, _("%s: Error closing %s: %s\n"), - progname, filename, e); - exit(EXIT_FAILURE); + free(fields); } + close_file(fp, filename); if (wantcont) error(_("expected continuation line not found")); } -/*---------- +/* * Convert a string of one of the forms * h -h hh:mm -hh:mm hh:mm:ss -hh:mm:ss * into a number of seconds. * A null string maps to zero. * Call error with errstring and return zero on errors. - *---------- */ -static long -gethms(const char *string, const char *errstring, int signable) +static zic_t +gethms(char const * string, char const * errstring, bool signable) { - long hh; + zic_t hh; int mm, ss, sign; + char xs; if (string == NULL || *string == '\0') return 0; @@ -957,35 +1160,33 @@ gethms(const char *string, const char *errstring, int signable) } else sign = 1; - if (sscanf(string, scheck(string, "%ld"), &hh) == 1) + if (sscanf(string, "%" SCNdZIC "%c", &hh, &xs) == 1) mm = ss = 0; - else if (sscanf(string, scheck(string, "%ld:%d"), &hh, &mm) == 2) + else if (sscanf(string, "%" SCNdZIC ":%d%c", &hh, &mm, &xs) == 2) ss = 0; - else if (sscanf(string, scheck(string, "%ld:%d:%d"), - &hh, &mm, &ss) != 3) + else if (sscanf(string, "%" SCNdZIC ":%d:%d%c", &hh, &mm, &ss, &xs) + != 3) { - error(errstring); + error("%s", errstring); return 0; } if (hh < 0 || mm < 0 || mm >= MINSPERHOUR || ss < 0 || ss > SECSPERMIN) { - error(errstring); + error("%s", errstring); return 0; } - if (LONG_MAX / SECSPERHOUR < hh) + if (ZIC_MAX / SECSPERHOUR < hh) { error(_("time overflow")); return 0; } - if (noise && hh == HOURSPERDAY && mm == 0 && ss == 0) - warning(_("24:00 not handled by pre-1998 versions of zic")); if (noise && (hh > HOURSPERDAY || (hh == HOURSPERDAY && (mm != 0 || ss != 0)))) warning(_("values over 24 hours not handled by pre-2007 versions of zic")); - return oadd(eitol(sign) * hh * eitol(SECSPERHOUR), - eitol(sign) * (eitol(mm) * eitol(SECSPERMIN) + eitol(ss))); + return oadd(sign * hh * SECSPERHOUR, + sign * (mm * SECSPERMIN + ss)); } static void @@ -1005,80 +1206,71 @@ inrule(char **fields, int nfields) } r.r_filename = filename; r.r_linenum = linenum; - r.r_stdoff = gethms(fields[RF_STDOFF], _("invalid saved time"), TRUE); + r.r_stdoff = gethms(fields[RF_STDOFF], _("invalid saved time"), true); rulesub(&r, fields[RF_LOYEAR], fields[RF_HIYEAR], fields[RF_COMMAND], fields[RF_MONTH], fields[RF_DAY], fields[RF_TOD]); r.r_name = ecpyalloc(fields[RF_NAME]); r.r_abbrvar = ecpyalloc(fields[RF_ABBRVAR]); if (max_abbrvar_len < strlen(r.r_abbrvar)) max_abbrvar_len = strlen(r.r_abbrvar); - rules = (struct rule *) (void *) erealloc((char *) rules, - (int) ((nrules + 1) * sizeof *rules)); + rules = growalloc(rules, sizeof *rules, nrules, &nrules_alloc); rules[nrules++] = r; } -static int +static bool inzone(char **fields, int nfields) { int i; - static char *buf; if (nfields < ZONE_MINFIELDS || nfields > ZONE_MAXFIELDS) { error(_("wrong number of fields on Zone line")); - return FALSE; + return false; } if (strcmp(fields[ZF_NAME], TZDEFAULT) == 0 && lcltime != NULL) { - buf = erealloc(buf, (int) (132 + strlen(TZDEFAULT))); - (void) sprintf(buf, - _("\"Zone %s\" line and -l option are mutually exclusive"), - TZDEFAULT); - error(buf); - return FALSE; + error( + _("\"Zone %s\" line and -l option are mutually exclusive"), + TZDEFAULT); + return false; } if (strcmp(fields[ZF_NAME], TZDEFRULES) == 0 && psxrules != NULL) { - buf = erealloc(buf, (int) (132 + strlen(TZDEFRULES))); - (void) sprintf(buf, - _("\"Zone %s\" line and -p option are mutually exclusive"), - TZDEFRULES); - error(buf); - return FALSE; + error( + _("\"Zone %s\" line and -p option are mutually exclusive"), + TZDEFRULES); + return false; } for (i = 0; i < nzones; ++i) if (zones[i].z_name != NULL && strcmp(zones[i].z_name, fields[ZF_NAME]) == 0) { - buf = erealloc(buf, (int) (132 + - strlen(fields[ZF_NAME]) + - strlen(zones[i].z_filename))); - (void) sprintf(buf, - _("duplicate zone name %s (file \"%s\", line %d)"), - fields[ZF_NAME], - zones[i].z_filename, - zones[i].z_linenum); - error(buf); - return FALSE; + error( + _("duplicate zone name %s (file \"%s\", line %d)"), + fields[ZF_NAME], + zones[i].z_filename, + zones[i].z_linenum); + return false; } - return inzsub(fields, nfields, FALSE); + return inzsub(fields, nfields, false); } -static int +static bool inzcont(char **fields, int nfields) { if (nfields < ZONEC_MINFIELDS || nfields > ZONEC_MAXFIELDS) { error(_("wrong number of fields on Zone continuation line")); - return FALSE; + return false; } - return inzsub(fields, nfields, TRUE); + return inzsub(fields, nfields, true); } -static int -inzsub(char **fields, int nfields, int iscont) +static bool +inzsub(char **fields, int nfields, bool iscont) { char *cp; + char *cp1; static struct zone z; int i_gmtoff, i_rule, @@ -1087,7 +1279,7 @@ inzsub(char **fields, int nfields, int iscont) i_untilmonth; int i_untilday, i_untiltime; - int hasuntil; + bool hasuntil; if (iscont) { @@ -1100,6 +1292,8 @@ inzsub(char **fields, int nfields, int iscont) i_untiltime = ZFC_TILTIME; z.z_name = NULL; } + else if (!namecheck(fields[ZF_NAME])) + return false; else { i_gmtoff = ZF_GMTOFF; @@ -1113,17 +1307,26 @@ inzsub(char **fields, int nfields, int iscont) } z.z_filename = filename; z.z_linenum = linenum; - z.z_gmtoff = gethms(fields[i_gmtoff], _("invalid UTC offset"), TRUE); - if ((cp = strchr(fields[i_format], '%')) != NULL) + z.z_gmtoff = gethms(fields[i_gmtoff], _("invalid UT offset"), true); + if ((cp = strchr(fields[i_format], '%')) != 0) { - if (*++cp != 's' || strchr(cp, '%') != NULL) + if ((*++cp != 's' && *cp != 'z') || strchr(cp, '%') + || strchr(fields[i_format], '/')) { error(_("invalid abbreviation format")); - return FALSE; + return false; } } z.z_rule = ecpyalloc(fields[i_rule]); - z.z_format = ecpyalloc(fields[i_format]); + z.z_format = cp1 = ecpyalloc(fields[i_format]); + z.z_format_specifier = cp ? *cp : '\0'; + if (z.z_format_specifier == 'z') + { + if (noise) + warning(_("format '%s' not handled by pre-2015 versions of zic"), + z.z_format); + cp1[cp - fields[i_format]] = 's'; + } if (max_format_len < strlen(z.z_format)) max_format_len = strlen(z.z_format); hasuntil = nfields > i_untilyear; @@ -1149,11 +1352,10 @@ inzsub(char **fields, int nfields, int iscont) zones[nzones - 1].z_untiltime >= z.z_untiltime) { error(_("Zone continuation line end time is not after end time of previous line")); - return FALSE; + return false; } } - zones = (struct zone *) (void *) erealloc((char *) zones, - (int) ((nzones + 1) * sizeof *zones)); + zones = growalloc(zones, sizeof *zones, nzones, &nzones_alloc); zones[nzones++] = z; /* @@ -1170,12 +1372,13 @@ inleap(char **fields, int nfields) const struct lookup *lp; int i, j; - int year, - month, + zic_t year; + int month, day; - long dayoff, + zic_t dayoff, tod; zic_t t; + char xs; if (nfields != LEAP_FIELDS) { @@ -1184,7 +1387,7 @@ inleap(char **fields, int nfields) } dayoff = 0; cp = fields[LP_YEAR]; - if (sscanf(cp, scheck(cp, "%d"), &year) != 1) + if (sscanf(cp, "%" SCNdZIC "%c", &year, &xs) != 1) { /* * Leapin' Lizards! @@ -1196,7 +1399,7 @@ inleap(char **fields, int nfields) leapmaxyear = year; if (!leapseen || leapminyear > year) leapminyear = year; - leapseen = TRUE; + leapseen = true; j = EPOCH_YEAR; while (j != year) { @@ -1210,7 +1413,7 @@ inleap(char **fields, int nfields) --j; i = -len_years[isleap(j)]; } - dayoff = oadd(dayoff, eitol(i)); + dayoff = oadd(dayoff, i); } if ((lp = byword(fields[LP_MONTH], mon_names)) == NULL) { @@ -1222,17 +1425,17 @@ inleap(char **fields, int nfields) while (j != month) { i = len_months[isleap(year)][j]; - dayoff = oadd(dayoff, eitol(i)); + dayoff = oadd(dayoff, i); ++j; } cp = fields[LP_DAY]; - if (sscanf(cp, scheck(cp, "%d"), &day) != 1 || + if (sscanf(cp, "%d%c", &day, &xs) != 1 || day <= 0 || day > len_months[isleap(year)][month]) { error(_("invalid day of month")); return; } - dayoff = oadd(dayoff, eitol(day - 1)); + dayoff = oadd(dayoff, day - 1); if (dayoff < min_time / SECSPERDAY) { error(_("time too small")); @@ -1243,32 +1446,31 @@ inleap(char **fields, int nfields) error(_("time too large")); return; } - t = (zic_t) dayoff *SECSPERDAY; - - tod = gethms(fields[LP_TIME], _("invalid time of day"), FALSE); + t = dayoff * SECSPERDAY; + tod = gethms(fields[LP_TIME], _("invalid time of day"), false); cp = fields[LP_CORR]; { - int positive; + bool positive; int count; if (strcmp(cp, "") == 0) { /* infile() turns "-" into "" */ - positive = FALSE; + positive = false; count = 1; } else if (strcmp(cp, "--") == 0) { - positive = FALSE; + positive = false; count = 2; } else if (strcmp(cp, "+") == 0) { - positive = TRUE; + positive = true; count = 1; } else if (strcmp(cp, "++") == 0) { - positive = TRUE; + positive = true; count = 2; } else @@ -1281,7 +1483,13 @@ inleap(char **fields, int nfields) error(_("illegal Rolling/Stationary field on Leap line")); return; } - leapadd(tadd(t, tod), positive, lp->l_value, count); + t = tadd(t, tod); + if (t < big_bang_time) + { + error(_("leap second precedes Big Bang")); + return; + } + leapadd(t, positive, lp->l_value, count); } } @@ -1300,17 +1508,13 @@ inlink(char **fields, int nfields) error(_("blank FROM field on Link line")); return; } - if (*fields[LF_TO] == '\0') - { - error(_("blank TO field on Link line")); + if (!namecheck(fields[LF_TO])) return; - } l.l_filename = filename; l.l_linenum = linenum; l.l_from = ecpyalloc(fields[LF_FROM]); l.l_to = ecpyalloc(fields[LF_TO]); - links = (struct link *) (void *) erealloc((char *) links, - (int) ((nlinks + 1) * sizeof *links)); + links = growalloc(links, sizeof *links, nlinks, &nlinks_alloc); links[nlinks++] = l; } @@ -1323,6 +1527,7 @@ rulesub(struct rule * rp, const char *loyearp, const char *hiyearp, const char *cp; char *dp; char *ep; + char xs; if ((lp = byword(monthp, mon_names)) == NULL) { @@ -1330,8 +1535,8 @@ rulesub(struct rule * rp, const char *loyearp, const char *hiyearp, return; } rp->r_month = lp->l_value; - rp->r_todisstd = FALSE; - rp->r_todisgmt = FALSE; + rp->r_todisstd = false; + rp->r_todisgmt = false; dp = ecpyalloc(timep); if (*dp != '\0') { @@ -1339,26 +1544,26 @@ rulesub(struct rule * rp, const char *loyearp, const char *hiyearp, switch (lowerit(*ep)) { case 's': /* Standard */ - rp->r_todisstd = TRUE; - rp->r_todisgmt = FALSE; + rp->r_todisstd = true; + rp->r_todisgmt = false; *ep = '\0'; break; case 'w': /* Wall */ - rp->r_todisstd = FALSE; - rp->r_todisgmt = FALSE; + rp->r_todisstd = false; + rp->r_todisgmt = false; *ep = '\0'; break; case 'g': /* Greenwich */ case 'u': /* Universal */ case 'z': /* Zulu */ - rp->r_todisstd = TRUE; - rp->r_todisgmt = TRUE; + rp->r_todisstd = true; + rp->r_todisgmt = true; *ep = '\0'; break; } } - rp->r_tod = gethms(dp, _("invalid time of day"), FALSE); - ifree(dp); + rp->r_tod = gethms(dp, _("invalid time of day"), false); + free(dp); /* * Year work. @@ -1370,18 +1575,18 @@ rulesub(struct rule * rp, const char *loyearp, const char *hiyearp, switch ((int) lp->l_value) { case YR_MINIMUM: - rp->r_loyear = INT_MIN; + rp->r_loyear = ZIC_MIN; break; case YR_MAXIMUM: - rp->r_loyear = INT_MAX; + rp->r_loyear = ZIC_MAX; break; default: /* "cannot happen" */ - (void) fprintf(stderr, - _("%s: panic: Invalid l_value %d\n"), - progname, lp->l_value); + fprintf(stderr, + _("%s: panic: Invalid l_value %d\n"), + progname, lp->l_value); exit(EXIT_FAILURE); } - else if (sscanf(cp, scheck(cp, "%d"), &rp->r_loyear) != 1) + else if (sscanf(cp, "%" SCNdZIC "%c", &rp->r_loyear, &xs) != 1) { error(_("invalid starting year")); return; @@ -1393,21 +1598,21 @@ rulesub(struct rule * rp, const char *loyearp, const char *hiyearp, switch ((int) lp->l_value) { case YR_MINIMUM: - rp->r_hiyear = INT_MIN; + rp->r_hiyear = ZIC_MIN; break; case YR_MAXIMUM: - rp->r_hiyear = INT_MAX; + rp->r_hiyear = ZIC_MAX; break; case YR_ONLY: rp->r_hiyear = rp->r_loyear; break; default: /* "cannot happen" */ - (void) fprintf(stderr, - _("%s: panic: Invalid l_value %d\n"), - progname, lp->l_value); + fprintf(stderr, + _("%s: panic: Invalid l_value %d\n"), + progname, lp->l_value); exit(EXIT_FAILURE); } - else if (sscanf(cp, scheck(cp, "%d"), &rp->r_hiyear) != 1) + else if (sscanf(cp, "%" SCNdZIC "%c", &rp->r_hiyear, &xs) != 1) { error(_("invalid ending year")); return; @@ -1456,65 +1661,67 @@ rulesub(struct rule * rp, const char *loyearp, const char *hiyearp, if (*ep++ != '=') { error(_("invalid day of month")); - ifree(dp); + free(dp); return; } if ((lp = byword(dp, wday_names)) == NULL) { error(_("invalid weekday name")); - ifree(dp); + free(dp); return; } rp->r_wday = lp->l_value; } - if (sscanf(ep, scheck(ep, "%d"), &rp->r_dayofmonth) != 1 || + if (sscanf(ep, "%d%c", &rp->r_dayofmonth, &xs) != 1 || rp->r_dayofmonth <= 0 || (rp->r_dayofmonth > len_months[1][rp->r_month])) { error(_("invalid day of month")); - ifree(dp); + free(dp); return; } } - ifree(dp); + free(dp); } static void -convert(long val, char *buf) +convert(const int32 val, char *const buf) { int i; int shift; + unsigned char *const b = (unsigned char *) buf; for (i = 0, shift = 24; i < 4; ++i, shift -= 8) - buf[i] = val >> shift; + b[i] = val >> shift; } static void -convert64(zic_t val, char *buf) +convert64(const zic_t val, char *const buf) { int i; int shift; + unsigned char *const b = (unsigned char *) buf; for (i = 0, shift = 56; i < 8; ++i, shift -= 8) - buf[i] = val >> shift; + b[i] = val >> shift; } static void -puttzcode(long val, FILE *fp) +puttzcode(const int32 val, FILE *const fp) { char buf[4]; convert(val, buf); - (void) fwrite((void *) buf, (size_t) sizeof buf, (size_t) 1, fp); + fwrite(buf, sizeof buf, 1, fp); } static void -puttzcode64(zic_t val, FILE *fp) +puttzcode64(const zic_t val, FILE *const fp) { char buf[8]; convert64(val, buf); - (void) fwrite((void *) buf, (size_t) sizeof buf, (size_t) 1, fp); + fwrite(buf, sizeof buf, 1, fp); } static int @@ -1526,14 +1733,14 @@ atcomp(const void *avp, const void *bvp) return (a < b) ? -1 : (a > b); } -static int -is32(zic_t x) +static bool +is32(const zic_t x) { return x == ((zic_t) ((int32) x)); } static void -writezone(const char *name, const char *string) +writezone(const char *const name, const char *const string, char version) { FILE *fp; int i, @@ -1543,18 +1750,18 @@ writezone(const char *name, const char *string) int timecnt32, timei32; int pass; - static char *fullname; + char *fullname; static const struct tzhead tzh0; static struct tzhead tzh; - zic_t ats[TZ_MAX_TIMES]; - unsigned char types[TZ_MAX_TIMES]; + zic_t *ats = emalloc(size_product(timecnt, sizeof *ats + 1)); + void *typesptr = ats + timecnt; + unsigned char *types = typesptr; /* * Sort. */ if (timecnt > 1) - (void) qsort((void *) attypes, (size_t) timecnt, - (size_t) sizeof *attypes, atcomp); + qsort(attypes, timecnt, sizeof *attypes, atcomp); /* * Optimize. @@ -1565,21 +1772,17 @@ writezone(const char *name, const char *string) toi = 0; fromi = 0; - while (fromi < timecnt && attypes[fromi].at < min_time) + while (fromi < timecnt && attypes[fromi].at < big_bang_time) ++fromi; - if (isdsts[0] == 0) - while (fromi < timecnt && attypes[fromi].type == 0) - ++fromi; /* handled by default rule */ for (; fromi < timecnt; ++fromi) { - if (toi != 0 - && ((attypes[fromi].at - + gmtoffs[attypes[toi - 1].type]) - <= (attypes[toi - 1].at - + gmtoffs[toi == 1 ? 0 - : attypes[toi - 2].type]))) + if (toi > 1 && ((attypes[fromi].at + + gmtoffs[attypes[toi - 1].type]) <= + (attypes[toi - 1].at + + gmtoffs[attypes[toi - 2].type]))) { - attypes[toi - 1].type = attypes[fromi].type; + attypes[toi - 1].type = + attypes[fromi].type; continue; } if (toi == 0 || @@ -1588,6 +1791,9 @@ writezone(const char *name, const char *string) } timecnt = toi; } + if (noise && timecnt > 1200) + warning(_("pre-2014 clients may mishandle" + " more than 1200 transition times")); /* * Transfer. @@ -1626,6 +1832,15 @@ writezone(const char *name, const char *string) --timecnt32; ++timei32; } + + /* + * Output an INT32_MIN "transition" if appropriate; see below. + */ + if (timei32 > 0 && ats[timei32] > PG_INT32_MIN) + { + --timei32; + ++timecnt32; + } while (leapcnt32 > 0 && !is32(trans[leapcnt32 - 1])) --leapcnt32; while (leapcnt32 > 0 && !is32(trans[leapi32])) @@ -1633,45 +1848,43 @@ writezone(const char *name, const char *string) --leapcnt32; ++leapi32; } - fullname = erealloc(fullname, - (int) (strlen(directory) + 1 + strlen(name) + 1)); - (void) sprintf(fullname, "%s/%s", directory, name); + fullname = relname(directory, name); /* * Remove old file, if any, to snap links. */ - if (!itsdir(fullname) && remove(fullname) != 0 && errno != ENOENT) + if (itsdir(fullname) == 0 && remove(fullname) != 0 && errno != ENOENT) { const char *e = strerror(errno); - (void) fprintf(stderr, _("%s: Cannot remove %s: %s\n"), - progname, fullname, e); + fprintf(stderr, _("%s: Cannot remove %s: %s\n"), + progname, fullname, e); exit(EXIT_FAILURE); } if ((fp = fopen(fullname, "wb")) == NULL) { - if (mkdirs(fullname) != 0) - (void) exit(EXIT_FAILURE); + if (!mkdirs(fullname)) + exit(EXIT_FAILURE); if ((fp = fopen(fullname, "wb")) == NULL) { const char *e = strerror(errno); - (void) fprintf(stderr, _("%s: Cannot create %s: %s\n"), - progname, fullname, e); + fprintf(stderr, _("%s: Cannot create %s: %s\n"), + progname, fullname, e); exit(EXIT_FAILURE); } } for (pass = 1; pass <= 2; ++pass) { - register int thistimei, + int thistimei, thistimecnt; - register int thisleapi, + int thisleapi, thisleapcnt; - register int thistimelim, + int thistimelim, thisleaplim; - int writetype[TZ_MAX_TIMES]; + int writetype[TZ_MAX_TYPES]; int typemap[TZ_MAX_TYPES]; - register int thistypecnt; + int thistypecnt; char thischars[TZ_MAX_CHARS]; char thischarcnt; int indmap[TZ_MAX_CHARS]; @@ -1697,24 +1910,79 @@ writezone(const char *name, const char *string) if (thistimecnt == 0) { /* - * * No transition times fall in the current * (32- or 64-bit) - * window. + * No transition times fall in the current (32- or 64-bit) window. */ if (typecnt != 0) - writetype[typecnt - 1] = TRUE; + writetype[typecnt - 1] = true; } else { for (i = thistimei - 1; i < thistimelim; ++i) if (i >= 0) - writetype[types[i]] = TRUE; + writetype[types[i]] = true; /* - * * For America/Godthab and Antarctica/Palmer + * For America/Godthab and Antarctica/Palmer */ if (thistimei == 0) - writetype[0] = TRUE; + writetype[0] = true; } +#ifndef LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH + + /* + * For some pre-2011 systems: if the last-to-be-written standard (or + * daylight) type has an offset different from the most recently used + * offset, append an (unused) copy of the most recently used type (to + * help get global "altzone" and "timezone" variables set correctly). + */ + { + int mrudst, + mrustd, + hidst, + histd, + type; + + hidst = histd = mrudst = mrustd = -1; + for (i = thistimei; i < thistimelim; ++i) + if (isdsts[types[i]]) + mrudst = types[i]; + else + mrustd = types[i]; + for (i = 0; i < typecnt; ++i) + if (writetype[i]) + { + if (isdsts[i]) + hidst = i; + else + histd = i; + } + if (hidst >= 0 && mrudst >= 0 && hidst != mrudst && + gmtoffs[hidst] != gmtoffs[mrudst]) + { + isdsts[mrudst] = -1; + type = addtype(gmtoffs[mrudst], + &chars[abbrinds[mrudst]], + true, + ttisstds[mrudst], + ttisgmts[mrudst]); + isdsts[mrudst] = 1; + writetype[type] = true; + } + if (histd >= 0 && mrustd >= 0 && histd != mrustd && + gmtoffs[histd] != gmtoffs[mrustd]) + { + isdsts[mrustd] = -1; + type = addtype(gmtoffs[mrustd], + &chars[abbrinds[mrustd]], + false, + ttisstds[mrustd], + ttisgmts[mrustd]); + isdsts[mrustd] = 0; + writetype[type] = true; + } + } +#endif /* !defined + * LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH */ thistypecnt = 0; for (i = 0; i < typecnt; ++i) typemap[i] = writetype[i] ? thistypecnt++ : -1; @@ -1723,7 +1991,7 @@ writezone(const char *name, const char *string) thischarcnt = 0; for (i = 0; i < typecnt; ++i) { - register char *thisabbr; + char *thisabbr; if (!writetype[i]) continue; @@ -1735,23 +2003,22 @@ writezone(const char *name, const char *string) break; if (j == thischarcnt) { - (void) strcpy(&thischars[(int) thischarcnt], - thisabbr); + strcpy(&thischars[(int) thischarcnt], + thisabbr); thischarcnt += strlen(thisabbr) + 1; } indmap[abbrinds[i]] = j; } -#define DO(field) (void) fwrite((void *) tzh.field, \ - (size_t) sizeof tzh.field, (size_t) 1, fp) +#define DO(field) fwrite(tzh.field, sizeof tzh.field, 1, fp) tzh = tzh0; - (void) strncpy(tzh.tzh_magic, TZ_MAGIC, sizeof tzh.tzh_magic); - tzh.tzh_version[0] = ZIC_VERSION; - convert(eitol(thistypecnt), tzh.tzh_ttisgmtcnt); - convert(eitol(thistypecnt), tzh.tzh_ttisstdcnt); - convert(eitol(thisleapcnt), tzh.tzh_leapcnt); - convert(eitol(thistimecnt), tzh.tzh_timecnt); - convert(eitol(thistypecnt), tzh.tzh_typecnt); - convert(eitol(thischarcnt), tzh.tzh_charcnt); + strncpy(tzh.tzh_magic, TZ_MAGIC, sizeof tzh.tzh_magic); + tzh.tzh_version[0] = version; + convert(thistypecnt, tzh.tzh_ttisgmtcnt); + convert(thistypecnt, tzh.tzh_ttisstdcnt); + convert(thisleapcnt, tzh.tzh_leapcnt); + convert(thistimecnt, tzh.tzh_timecnt); + convert(thistypecnt, tzh.tzh_typecnt); + convert(thischarcnt, tzh.tzh_charcnt); DO(tzh_magic); DO(tzh_version); DO(tzh_reserved); @@ -1764,7 +2031,12 @@ writezone(const char *name, const char *string) #undef DO for (i = thistimei; i < thistimelim; ++i) if (pass == 1) - puttzcode((long) ats[i], fp); + + /* + * Output an INT32_MIN "transition" if appropriate; see above. + */ + puttzcode(((ats[i] < PG_INT32_MIN) ? + PG_INT32_MIN : ats[i]), fp); else { puttzcode64(ats[i], fp); @@ -1779,7 +2051,7 @@ writezone(const char *name, const char *string) /* filter out assorted junk entries */ if (strcmp(thisabbrev, GRANDPARENTED) != 0 && strcmp(thisabbrev, "zzz") != 0) - fprintf(stdout, "%s\t%ld%s\n", + fprintf(stdout, "%s\t" INT64_FORMAT "%s\n", thisabbrev, gmtoffs[tm], isdsts[tm] ? "\tD" : ""); @@ -1790,25 +2062,21 @@ writezone(const char *name, const char *string) unsigned char uc; uc = typemap[types[i]]; - (void) fwrite((void *) &uc, - (size_t) sizeof uc, - (size_t) 1, - fp); + fwrite(&uc, sizeof uc, 1, fp); } for (i = 0; i < typecnt; ++i) if (writetype[i]) { puttzcode(gmtoffs[i], fp); - (void) putc(isdsts[i], fp); - (void) putc((unsigned char) indmap[abbrinds[i]], fp); + putc(isdsts[i], fp); + putc((unsigned char) indmap[abbrinds[i]], fp); } if (thischarcnt != 0) - (void) fwrite((void *) thischars, - (size_t) sizeof thischars[0], - (size_t) thischarcnt, fp); + fwrite(thischars, sizeof thischars[0], + thischarcnt, fp); for (i = thisleapi; i < thisleaplim; ++i) { - register zic_t todo; + zic_t todo; if (roll[i]) { @@ -1835,70 +2103,113 @@ writezone(const char *name, const char *string) else todo = trans[i]; if (pass == 1) - puttzcode((long) todo, fp); + puttzcode(todo, fp); else puttzcode64(todo, fp); puttzcode(corr[i], fp); } for (i = 0; i < typecnt; ++i) if (writetype[i]) - (void) putc(ttisstds[i], fp); + putc(ttisstds[i], fp); for (i = 0; i < typecnt; ++i) if (writetype[i]) - (void) putc(ttisgmts[i], fp); + putc(ttisgmts[i], fp); } - (void) fprintf(fp, "\n%s\n", string); - if (ferror(fp) || fclose(fp)) + fprintf(fp, "\n%s\n", string); + close_file(fp, fullname); + free(ats); + free(fullname); +} + +static char const * +abbroffset(char *buf, zic_t offset) +{ + char sign = '+'; + int seconds, + minutes; + + if (offset < 0) { - (void) fprintf(stderr, _("%s: Error writing %s\n"), - progname, fullname); - exit(EXIT_FAILURE); + offset = -offset; + sign = '-'; + } + + seconds = offset % SECSPERMIN; + offset /= SECSPERMIN; + minutes = offset % MINSPERHOUR; + offset /= MINSPERHOUR; + if (100 <= offset) + { + error(_("%%z UTC offset magnitude exceeds 99:59:59")); + return "%z"; + } + else + { + char *p = buf; + + *p++ = sign; + *p++ = '0' + offset / 10; + *p++ = '0' + offset % 10; + if (minutes | seconds) + { + *p++ = '0' + minutes / 10; + *p++ = '0' + minutes % 10; + if (seconds) + { + *p++ = '0' + seconds / 10; + *p++ = '0' + seconds % 10; + } + } + *p = '\0'; + return buf; } } -static void -doabbr(char *abbr, const char *format, const char *letters, int isdst, - int doquotes) +static size_t +doabbr(char *abbr, struct zone const * zp, char const * letters, + zic_t stdoff, bool doquotes) { char *cp; char *slashp; - int len; + size_t len; + char const *format = zp->z_format; slashp = strchr(format, '/'); if (slashp == NULL) { - if (letters == NULL) - (void) strcpy(abbr, format); - else - (void) sprintf(abbr, format, letters); + char letterbuf[PERCENT_Z_LEN_BOUND + 1]; + + if (zp->z_format_specifier == 'z') + letters = abbroffset(letterbuf, zp->z_gmtoff + stdoff); + else if (!letters) + letters = "%s"; + sprintf(abbr, format, letters); + } + else if (stdoff != 0) + { + strcpy(abbr, slashp + 1); } - else if (isdst) - (void) strcpy(abbr, slashp + 1); else { - if (slashp > format) - (void) strncpy(abbr, format, - (unsigned) (slashp - format)); + memcpy(abbr, format, slashp - format); abbr[slashp - format] = '\0'; } - if (!doquotes) - return; - for (cp = abbr; *cp != '\0'; ++cp) - if (strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZ", *cp) == NULL && - strchr("abcdefghijklmnopqrstuvwxyz", *cp) == NULL) - break; len = strlen(abbr); + if (!doquotes) + return len; + for (cp = abbr; is_alpha(*cp); cp++) + continue; if (len > 0 && *cp == '\0') - return; + return len; abbr[len + 2] = '\0'; abbr[len + 1] = '>'; - for (; len > 0; --len) - abbr[len] = abbr[len - 1]; + memmove(abbr + 1, abbr, len); abbr[0] = '<'; + return len + 2; } static void -updateminmax(int x) +updateminmax(const zic_t x) { if (min_year > x) min_year = x; @@ -1907,44 +2218,46 @@ updateminmax(int x) } static int -stringoffset(char *result, long offset) +stringoffset(char *result, zic_t offset) { int hours; int minutes; int seconds; + bool negative = offset < 0; + int len = negative; - result[0] = '\0'; - if (offset < 0) + if (negative) { - (void) strcpy(result, "-"); offset = -offset; + result[0] = '-'; } seconds = offset % SECSPERMIN; offset /= SECSPERMIN; minutes = offset % MINSPERHOUR; offset /= MINSPERHOUR; hours = offset; - if (hours >= HOURSPERDAY) + if (hours >= HOURSPERDAY * DAYSPERWEEK) { result[0] = '\0'; - return -1; + return 0; } - (void) sprintf(end(result), "%d", hours); + len += sprintf(result + len, "%d", hours); if (minutes != 0 || seconds != 0) { - (void) sprintf(end(result), ":%02d", minutes); + len += sprintf(result + len, ":%02d", minutes); if (seconds != 0) - (void) sprintf(end(result), ":%02d", seconds); + len += sprintf(result + len, ":%02d", seconds); } - return 0; + return len; } static int -stringrule(char *result, const struct rule * rp, long dstoff, long gmtoff) +stringrule(char *result, const struct rule * const rp, const zic_t dstoff, + const zic_t gmtoff) { - long tod; + zic_t tod = rp->r_tod; + int compat = 0; - result = end(result); if (rp->r_dycode == DC_DOM) { int month, @@ -1955,17 +2268,26 @@ stringrule(char *result, const struct rule * rp, long dstoff, long gmtoff) total = 0; for (month = 0; month < rp->r_month; ++month) total += len_months[0][month]; - (void) sprintf(result, "J%d", total + rp->r_dayofmonth); + /* Omit the "J" in Jan and Feb, as that's shorter. */ + if (rp->r_month <= 1) + result += sprintf(result, "%d", total + rp->r_dayofmonth - 1); + else + result += sprintf(result, "J%d", total + rp->r_dayofmonth); } else { int week; + int wday = rp->r_wday; + int wdayoff; if (rp->r_dycode == DC_DOWGEQ) { - week = 1 + rp->r_dayofmonth / DAYSPERWEEK; - if ((week - 1) * DAYSPERWEEK + 1 != rp->r_dayofmonth) - return -1; + wdayoff = (rp->r_dayofmonth - 1) % DAYSPERWEEK; + if (wdayoff) + compat = 2013; + wday -= wdayoff; + tod += wdayoff * SECSPERDAY; + week = 1 + (rp->r_dayofmonth - 1) / DAYSPERWEEK; } else if (rp->r_dycode == DC_DOWLEQ) { @@ -1973,37 +2295,64 @@ stringrule(char *result, const struct rule * rp, long dstoff, long gmtoff) week = 5; else { - week = 1 + rp->r_dayofmonth / DAYSPERWEEK; - if (week * DAYSPERWEEK - 1 != rp->r_dayofmonth) - return -1; + wdayoff = rp->r_dayofmonth % DAYSPERWEEK; + if (wdayoff) + compat = 2013; + wday -= wdayoff; + tod += wdayoff * SECSPERDAY; + week = rp->r_dayofmonth / DAYSPERWEEK; } } else return -1; /* "cannot happen" */ - (void) sprintf(result, "M%d.%d.%d", - rp->r_month + 1, week, rp->r_wday); + if (wday < 0) + wday += DAYSPERWEEK; + result += sprintf(result, "M%d.%d.%d", + rp->r_month + 1, week, wday); } - tod = rp->r_tod; if (rp->r_todisgmt) tod += gmtoff; if (rp->r_todisstd && rp->r_stdoff == 0) tod += dstoff; - if (tod < 0) - { - result[0] = '\0'; - return -1; - } if (tod != 2 * SECSPERMIN * MINSPERHOUR) { - (void) strcat(result, "/"); - if (stringoffset(end(result), tod) != 0) + *result++ = '/'; + if (!stringoffset(result, tod)) return -1; + if (tod < 0) + { + if (compat < 2013) + compat = 2013; + } + else if (SECSPERDAY <= tod) + { + if (compat < 1994) + compat = 1994; + } } - return 0; + return compat; } -static void -stringzone(char *result, const struct zone * zpfirst, int zonecount) +static int +rule_cmp(struct rule const * a, struct rule const * b) +{ + if (!a) + return -!!b; + if (!b) + return 1; + if (a->r_hiyear != b->r_hiyear) + return a->r_hiyear < b->r_hiyear ? -1 : 1; + if (a->r_month - b->r_month != 0) + return a->r_month - b->r_month; + return a->r_dayofmonth - b->r_dayofmonth; +} + +enum +{ +YEAR_BY_YEAR_ZONE = 1}; + +static int +stringzone(char *result, const struct zone * const zpfirst, const int zonecount) { const struct zone *zp; struct rule *rp; @@ -2011,6 +2360,12 @@ stringzone(char *result, const struct zone * zpfirst, int zonecount) struct rule *dstrp; int i; const char *abbrvar; + int compat = 0; + int c; + size_t len; + int offsetlen; + struct rule stdr, + dstr; result[0] = '\0'; zp = zpfirst + zonecount - 1; @@ -2018,7 +2373,7 @@ stringzone(char *result, const struct zone * zpfirst, int zonecount) for (i = 0; i < zp->z_nrules; ++i) { rp = &zp->z_rules[i]; - if (rp->r_hiwasnum || rp->r_hiyear != INT_MAX) + if (rp->r_hiwasnum || rp->r_hiyear != ZIC_MAX) continue; if (rp->r_yrtype != NULL) continue; @@ -2027,32 +2382,32 @@ stringzone(char *result, const struct zone * zpfirst, int zonecount) if (stdrp == NULL) stdrp = rp; else - return; + return -1; } else { if (dstrp == NULL) dstrp = rp; else - return; + return -1; } } if (stdrp == NULL && dstrp == NULL) { /* - * There are no rules running through "max". Let's find the latest - * rule. + * There are no rules running through "max". Find the latest std rule + * in stdabbrrp and latest rule of any type in stdrp. */ + struct rule *stdabbrrp = NULL; + for (i = 0; i < zp->z_nrules; ++i) { rp = &zp->z_rules[i]; - if (stdrp == NULL || rp->r_hiyear > stdrp->r_hiyear || - (rp->r_hiyear == stdrp->r_hiyear && - rp->r_month > stdrp->r_month)) + if (rp->r_stdoff == 0 && rule_cmp(stdabbrrp, rp) < 0) + stdabbrrp = rp; + if (rule_cmp(stdrp, rp) < 0) stdrp = rp; } - if (stdrp != NULL && stdrp->r_stdoff != 0) - return; /* We end up in DST (a POSIX no-no). */ /* * Horrid special case: if year is 2037, presume this is a zone @@ -2060,39 +2415,75 @@ stringzone(char *result, const struct zone * zpfirst, int zonecount) * zone. */ if (stdrp != NULL && stdrp->r_hiyear == 2037) - return; + return YEAR_BY_YEAR_ZONE; + + if (stdrp != NULL && stdrp->r_stdoff != 0) + { + /* Perpetual DST. */ + dstr.r_month = TM_JANUARY; + dstr.r_dycode = DC_DOM; + dstr.r_dayofmonth = 1; + dstr.r_tod = 0; + dstr.r_todisstd = dstr.r_todisgmt = false; + dstr.r_stdoff = stdrp->r_stdoff; + dstr.r_abbrvar = stdrp->r_abbrvar; + stdr.r_month = TM_DECEMBER; + stdr.r_dycode = DC_DOM; + stdr.r_dayofmonth = 31; + stdr.r_tod = SECSPERDAY + stdrp->r_stdoff; + stdr.r_todisstd = stdr.r_todisgmt = false; + stdr.r_stdoff = 0; + stdr.r_abbrvar + = (stdabbrrp ? stdabbrrp->r_abbrvar : ""); + dstrp = &dstr; + stdrp = &stdr; + } } if (stdrp == NULL && (zp->z_nrules != 0 || zp->z_stdoff != 0)) - return; + return -1; abbrvar = (stdrp == NULL) ? "" : stdrp->r_abbrvar; - doabbr(result, zp->z_format, abbrvar, FALSE, TRUE); - if (stringoffset(end(result), -zp->z_gmtoff) != 0) + len = doabbr(result, zp, abbrvar, 0, true); + offsetlen = stringoffset(result + len, -zp->z_gmtoff); + if (!offsetlen) { result[0] = '\0'; - return; + return -1; } + len += offsetlen; if (dstrp == NULL) - return; - doabbr(end(result), zp->z_format, dstrp->r_abbrvar, TRUE, TRUE); + return compat; + len += doabbr(result + len, zp, dstrp->r_abbrvar, dstrp->r_stdoff, true); if (dstrp->r_stdoff != SECSPERMIN * MINSPERHOUR) - if (stringoffset(end(result), - -(zp->z_gmtoff + dstrp->r_stdoff)) != 0) + { + offsetlen = stringoffset(result + len, + -(zp->z_gmtoff + dstrp->r_stdoff)); + if (!offsetlen) { result[0] = '\0'; - return; + return -1; } - (void) strcat(result, ","); - if (stringrule(result, dstrp, dstrp->r_stdoff, zp->z_gmtoff) != 0) + len += offsetlen; + } + result[len++] = ','; + c = stringrule(result + len, dstrp, dstrp->r_stdoff, zp->z_gmtoff); + if (c < 0) { result[0] = '\0'; - return; + return -1; } - (void) strcat(result, ","); - if (stringrule(result, stdrp, dstrp->r_stdoff, zp->z_gmtoff) != 0) + if (compat < c) + compat = c; + len += strlen(result + len); + result[len++] = ','; + c = stringrule(result + len, stdrp, dstrp->r_stdoff, zp->z_gmtoff); + if (c < 0) { result[0] = '\0'; - return; + return -1; } + if (compat < c) + compat = c; + return compat; } static void @@ -2102,28 +2493,34 @@ outzone(const struct zone * zpfirst, int zonecount) struct rule *rp; int i, j; - int usestart, + bool usestart, useuntil; - zic_t starttime = 0; - zic_t untiltime = 0; - long gmtoff; - long stdoff; - int year; - long startoff; - int startttisstd; - int startttisgmt; + zic_t starttime, + untiltime; + zic_t gmtoff; + zic_t stdoff; + zic_t year; + zic_t startoff; + bool startttisstd; + bool startttisgmt; int type; char *startbuf; char *ab; char *envvar; int max_abbr_len; int max_envvar_len; + bool prodstic; /* all rules are min to max */ + int compat; + bool do_extend; + char version; max_abbr_len = 2 + max_format_len + max_abbrvar_len; max_envvar_len = 2 * max_abbr_len + 5 * 9; startbuf = emalloc(max_abbr_len + 1); ab = emalloc(max_abbr_len + 1); envvar = emalloc(max_envvar_len + 1); + INITIALIZE(untiltime); + INITIALIZE(starttime); /* * Now. . .finally. . .generate some useful data! @@ -2131,18 +2528,19 @@ outzone(const struct zone * zpfirst, int zonecount) timecnt = 0; typecnt = 0; charcnt = 0; + prodstic = zonecount == 1; /* * Thanks to Earl Chew for noting the need to unconditionally initialize * startttisstd. */ - startttisstd = FALSE; - startttisgmt = FALSE; + startttisstd = false; + startttisgmt = false; min_year = max_year = EPOCH_YEAR; if (leapseen) { updateminmax(leapminyear); - updateminmax(leapmaxyear + (leapmaxyear < INT_MAX)); + updateminmax(leapmaxyear + (leapmaxyear < ZIC_MAX)); } for (i = 0; i < zonecount; ++i) { @@ -2156,33 +2554,71 @@ outzone(const struct zone * zpfirst, int zonecount) updateminmax(rp->r_loyear); if (rp->r_hiwasnum) updateminmax(rp->r_hiyear); + if (rp->r_lowasnum || rp->r_hiwasnum) + prodstic = false; } } /* * Generate lots of data if a rule can't cover all future times. */ - stringzone(envvar, zpfirst, zonecount); - if (noise && envvar[0] == '\0') + compat = stringzone(envvar, zpfirst, zonecount); + version = compat < 2013 ? ZIC_VERSION_PRE_2013 : ZIC_VERSION; + do_extend = compat < 0 || compat == YEAR_BY_YEAR_ZONE; + if (noise) { - char *wp; - - wp = ecpyalloc(_("no POSIX environment variable for zone")); - wp = ecatalloc(wp, " "); - wp = ecatalloc(wp, zpfirst->z_name); - warning(wp); - ifree(wp); + if (!*envvar) + warning("%s %s", + _("no POSIX environment variable for zone"), + zpfirst->z_name); + else if (compat != 0 && compat != YEAR_BY_YEAR_ZONE) + { + /* + * Circa-COMPAT clients, and earlier clients, might not work for + * this zone when given dates before 1970 or after 2038. + */ + warning(_("%s: pre-%d clients may mishandle" + " distant timestamps"), + zpfirst->z_name, compat); + } } - if (envvar[0] == '\0') + if (do_extend) { - if (min_year >= INT_MIN + YEARSPERREPEAT) - min_year -= YEARSPERREPEAT; + /* + * Search through a couple of extra years past the obvious 400, to + * avoid edge cases. For example, suppose a non-POSIX rule applies + * from 2012 onwards and has transitions in March and September, plus + * some one-off transitions in November 2013. If zic looked only at + * the last 400 years, it would set max_year=2413, with the intent + * that the 400 years 2014 through 2413 will be repeated. The last + * transition listed in the tzfile would be in 2413-09, less than 400 + * years after the last one-off transition in 2013-11. Two years + * might be overkill, but with the kind of edge cases available we're + * not sure that one year would suffice. + */ + enum + { + years_of_observations = YEARSPERREPEAT + 2}; + + if (min_year >= ZIC_MIN + years_of_observations) + min_year -= years_of_observations; else - min_year = INT_MIN; - if (max_year <= INT_MAX - YEARSPERREPEAT) - max_year += YEARSPERREPEAT; + min_year = ZIC_MIN; + if (max_year <= ZIC_MAX - years_of_observations) + max_year += years_of_observations; else - max_year = INT_MAX; + max_year = ZIC_MAX; + + /* + * Regardless of any of the above, for a "proDSTic" zone which + * specifies that its rules always have and always will be in effect, + * we only need one cycle to define the zone. + */ + if (prodstic) + { + min_year = 1900; + max_year = min_year + years_of_observations; + } } /* @@ -2199,9 +2635,9 @@ outzone(const struct zone * zpfirst, int zonecount) */ stdoff = 0; zp = &zpfirst[i]; - usestart = i > 0 && (zp - 1)->z_untiltime > min_time; + usestart = i > 0 && (zp - 1)->z_untiltime > big_bang_time; useuntil = i < (zonecount - 1); - if (useuntil && zp->z_untiltime <= min_time) + if (useuntil && zp->z_untiltime <= big_bang_time) continue; gmtoff = zp->z_gmtoff; eat(zp->z_filename, zp->z_linenum); @@ -2210,18 +2646,17 @@ outzone(const struct zone * zpfirst, int zonecount) if (zp->z_nrules == 0) { stdoff = zp->z_stdoff; - doabbr(startbuf, zp->z_format, - (char *) NULL, stdoff != 0, FALSE); + doabbr(startbuf, zp, NULL, stdoff, false); type = addtype(oadd(zp->z_gmtoff, stdoff), startbuf, stdoff != 0, startttisstd, startttisgmt); if (usestart) { addtt(starttime, type); - usestart = FALSE; + usestart = false; } - else if (stdoff != 0) - addtt(min_time, type); + else + addtt(big_bang_time, type); } else for (year = min_year; year <= max_year; ++year) @@ -2249,12 +2684,12 @@ outzone(const struct zone * zpfirst, int zonecount) int k; zic_t jtime, ktime = 0; - long offset; + zic_t offset; if (useuntil) { /* - * Turn untiltime into UTC assuming the current gmtoff + * Turn untiltime into UT assuming the current gmtoff * and stdoff values. */ untiltime = zp->z_untiltime; @@ -2291,43 +2726,55 @@ outzone(const struct zone * zpfirst, int zonecount) k = j; ktime = jtime; } + else if (jtime == ktime) + { + char const *dup_rules_msg = + _("two rules for same instant"); + + eats(zp->z_filename, zp->z_linenum, + rp->r_filename, rp->r_linenum); + warning("%s", dup_rules_msg); + rp = &zp->z_rules[k]; + eats(zp->z_filename, zp->z_linenum, + rp->r_filename, rp->r_linenum); + error("%s", dup_rules_msg); + } } if (k < 0) break; /* go on to next year */ rp = &zp->z_rules[k]; - rp->r_todo = FALSE; + rp->r_todo = false; if (useuntil && ktime >= untiltime) break; stdoff = rp->r_stdoff; if (usestart && ktime == starttime) - usestart = FALSE; + usestart = false; if (usestart) { if (ktime < starttime) { startoff = oadd(zp->z_gmtoff, stdoff); - doabbr(startbuf, zp->z_format, + doabbr(startbuf, zp, rp->r_abbrvar, - rp->r_stdoff != 0, - FALSE); + rp->r_stdoff, + false); continue; } if (*startbuf == '\0' && startoff == oadd(zp->z_gmtoff, stdoff)) { doabbr(startbuf, - zp->z_format, + zp, rp->r_abbrvar, - rp->r_stdoff != - 0, - FALSE); + rp->r_stdoff, + false); } } eats(zp->z_filename, zp->z_linenum, rp->r_filename, rp->r_linenum); - doabbr(ab, zp->z_format, rp->r_abbrvar, - rp->r_stdoff != 0, FALSE); + doabbr(ab, zp, rp->r_abbrvar, + rp->r_stdoff, false); offset = oadd(zp->z_gmtoff, rp->r_stdoff); type = addtype(offset, ab, rp->r_stdoff != 0, rp->r_todisstd, rp->r_todisgmt); @@ -2340,7 +2787,7 @@ outzone(const struct zone * zpfirst, int zonecount) zp->z_format != NULL && strchr(zp->z_format, '%') == NULL && strchr(zp->z_format, '/') == NULL) - (void) strcpy(startbuf, zp->z_format); + strcpy(startbuf, zp->z_format); eat(zp->z_filename, zp->z_linenum); if (*startbuf == '\0') error(_("cannot determine time zone abbreviation to use just after until time")); @@ -2366,62 +2813,82 @@ outzone(const struct zone * zpfirst, int zonecount) starttime = tadd(starttime, -gmtoff); } } - writezone(zpfirst->z_name, envvar); - ifree(startbuf); - ifree(ab); - ifree(envvar); + if (do_extend) + { + /* + * If we're extending the explicitly listed observations for 400 years + * because we can't fill the POSIX-TZ field, check whether we actually + * ended up explicitly listing observations through that period. If + * there aren't any near the end of the 400-year period, add a + * redundant one at the end of the final year, to make it clear that + * we are claiming to have definite knowledge of the lack of + * transitions up to that point. + */ + struct rule xr; + struct attype *lastat; + + xr.r_month = TM_JANUARY; + xr.r_dycode = DC_DOM; + xr.r_dayofmonth = 1; + xr.r_tod = 0; + for (lastat = &attypes[0], i = 1; i < timecnt; i++) + if (attypes[i].at > lastat->at) + lastat = &attypes[i]; + if (lastat->at < rpytime(&xr, max_year - 1)) + { + /* + * Create new type code for the redundant entry, to prevent it + * being optimized away. + */ + if (typecnt >= TZ_MAX_TYPES) + { + error(_("too many local time types")); + exit(EXIT_FAILURE); + } + gmtoffs[typecnt] = gmtoffs[lastat->type]; + isdsts[typecnt] = isdsts[lastat->type]; + ttisstds[typecnt] = ttisstds[lastat->type]; + ttisgmts[typecnt] = ttisgmts[lastat->type]; + abbrinds[typecnt] = abbrinds[lastat->type]; + ++typecnt; + addtt(rpytime(&xr, max_year + 1), typecnt - 1); + } + } + writezone(zpfirst->z_name, envvar, version); + free(startbuf); + free(ab); + free(envvar); } static void -addtt(const zic_t starttime, int type) +addtt(zic_t starttime, int type) { - if (starttime <= min_time || - (timecnt == 1 && attypes[0].at < min_time)) + if (starttime <= big_bang_time || + (timecnt == 1 && attypes[0].at < big_bang_time)) { gmtoffs[0] = gmtoffs[type]; isdsts[0] = isdsts[type]; ttisstds[0] = ttisstds[type]; ttisgmts[0] = ttisgmts[type]; if (abbrinds[type] != 0) - (void) strcpy(chars, &chars[abbrinds[type]]); + strcpy(chars, &chars[abbrinds[type]]); abbrinds[0] = 0; charcnt = strlen(chars) + 1; typecnt = 1; timecnt = 0; type = 0; } - if (timecnt >= TZ_MAX_TIMES) - { - error(_("too many transitions?!")); - exit(EXIT_FAILURE); - } + attypes = growalloc(attypes, sizeof *attypes, timecnt, &timecnt_alloc); attypes[timecnt].at = starttime; attypes[timecnt].type = type; ++timecnt; } static int -addtype(long gmtoff, const char *abbr, int isdst, - int ttisstd, int ttisgmt) +addtype(zic_t gmtoff, char const * abbr, bool isdst, bool ttisstd, bool ttisgmt) { - int i; - int j; - - if (isdst != TRUE && isdst != FALSE) - { - error(_("internal error - addtype called with bad isdst")); - exit(EXIT_FAILURE); - } - if (ttisstd != TRUE && ttisstd != FALSE) - { - error(_("internal error - addtype called with bad ttisstd")); - exit(EXIT_FAILURE); - } - if (ttisgmt != TRUE && ttisgmt != FALSE) - { - error(_("internal error - addtype called with bad ttisgmt")); - exit(EXIT_FAILURE); - } + int i, + j; /* * See if there's already an entry for this zone type. If so, just return @@ -2446,7 +2913,7 @@ addtype(long gmtoff, const char *abbr, int isdst, } if (!(-1L - 2147483647L <= gmtoff && gmtoff <= 2147483647L)) { - error(_("UTC offset out of range")); + error(_("UT offset out of range")); exit(EXIT_FAILURE); } gmtoffs[i] = gmtoff; @@ -2465,10 +2932,10 @@ addtype(long gmtoff, const char *abbr, int isdst, } static void -leapadd(const zic_t t, int positive, int rolling, int count) +leapadd(zic_t t, bool positive, int rolling, int count) { - int i; - int j; + int i, + j; if (leapcnt + (positive ? count : 1) > TZ_MAX_LEAPS) { @@ -2494,7 +2961,7 @@ leapadd(const zic_t t, int positive, int rolling, int count) roll[j] = roll[j - 1]; } trans[i] = t; - corr[i] = positive ? 1L : eitol(-count); + corr[i] = positive ? 1 : -count; roll[i] = rolling; ++leapcnt; } while (positive && --count != 0); @@ -2504,7 +2971,7 @@ static void adjleap(void) { int i; - long last = 0; + zic_t last = 0; /* * propagate leap seconds forward @@ -2516,61 +2983,201 @@ adjleap(void) } } -static int +static bool yearistype(int year, const char *type) { static char *buf; int result; if (type == NULL || *type == '\0') - return TRUE; - buf = erealloc(buf, (int) (132 + strlen(yitcommand) + strlen(type))); - (void) sprintf(buf, "%s %d %s", yitcommand, year, type); + return true; + buf = erealloc(buf, 132 + strlen(yitcommand) + strlen(type)); + sprintf(buf, "%s %d %s", yitcommand, year, type); result = system(buf); if (WIFEXITED(result)) switch (WEXITSTATUS(result)) { case 0: - return TRUE; + return true; case 1: - return FALSE; + return false; } error(_("Wild result from command execution")); - (void) fprintf(stderr, _("%s: command was '%s', result was %d\n"), - progname, buf, result); + fprintf(stderr, _("%s: command was '%s', result was %d\n"), + progname, buf, result); for (;;) exit(EXIT_FAILURE); } -static int -lowerit(int a) +/* Is A a space character in the C locale? */ +static bool +is_space(char a) { - a = (unsigned char) a; - return (isascii(a) && isupper(a)) ? tolower(a) : a; + switch (a) + { + default: + return false; + case ' ': + case '\f': + case '\n': + case '\r': + case '\t': + case '\v': + return true; + } } -static int +/* Is A an alphabetic character in the C locale? */ +static bool +is_alpha(char a) +{ + switch (a) + { + default: + return false; + case 'A': + case 'B': + case 'C': + case 'D': + case 'E': + case 'F': + case 'G': + case 'H': + case 'I': + case 'J': + case 'K': + case 'L': + case 'M': + case 'N': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'S': + case 'T': + case 'U': + case 'V': + case 'W': + case 'X': + case 'Y': + case 'Z': + case 'a': + case 'b': + case 'c': + case 'd': + case 'e': + case 'f': + case 'g': + case 'h': + case 'i': + case 'j': + case 'k': + case 'l': + case 'm': + case 'n': + case 'o': + case 'p': + case 'q': + case 'r': + case 's': + case 't': + case 'u': + case 'v': + case 'w': + case 'x': + case 'y': + case 'z': + return true; + } +} + +/* If A is an uppercase character in the C locale, return its lowercase + * counterpart. Otherwise, return A. */ +static char +lowerit(char a) +{ + switch (a) + { + default: + return a; + case 'A': + return 'a'; + case 'B': + return 'b'; + case 'C': + return 'c'; + case 'D': + return 'd'; + case 'E': + return 'e'; + case 'F': + return 'f'; + case 'G': + return 'g'; + case 'H': + return 'h'; + case 'I': + return 'i'; + case 'J': + return 'j'; + case 'K': + return 'k'; + case 'L': + return 'l'; + case 'M': + return 'm'; + case 'N': + return 'n'; + case 'O': + return 'o'; + case 'P': + return 'p'; + case 'Q': + return 'q'; + case 'R': + return 'r'; + case 'S': + return 's'; + case 'T': + return 't'; + case 'U': + return 'u'; + case 'V': + return 'v'; + case 'W': + return 'w'; + case 'X': + return 'x'; + case 'Y': + return 'y'; + case 'Z': + return 'z'; + } +} + +/* case-insensitive equality */ +static bool ciequal(const char *ap, const char *bp) { while (lowerit(*ap) == lowerit(*bp++)) if (*ap++ == '\0') - return TRUE; - return FALSE; + return true; + return false; } -static int +static bool itsabbr(const char *abbr, const char *word) { if (lowerit(*abbr) != lowerit(*word)) - return FALSE; + return false; ++word; while (*++abbr != '\0') do { if (*word == '\0') - return FALSE; + return false; } while (lowerit(*word++) != lowerit(*abbr)); - return TRUE; + return true; } static const struct lookup * @@ -2613,13 +3220,11 @@ getfields(char *cp) if (cp == NULL) return NULL; - array = (char **) (void *) - emalloc((int) ((strlen(cp) + 1) * sizeof *array)); + array = emalloc(size_product(strlen(cp) + 1, sizeof *array)); nsubs = 0; for (;;) { - while (isascii((unsigned char) *cp) && - isspace((unsigned char) *cp)) + while (is_space(*cp)) ++cp; if (*cp == '\0' || *cp == '#') break; @@ -2637,9 +3242,8 @@ getfields(char *cp) error(_("Odd number of quotation marks")); exit(1); } - } while (*cp != '\0' && *cp != '#' && - (!isascii(*cp) || !isspace((unsigned char) *cp))); - if (isascii(*cp) && isspace((unsigned char) *cp)) + } while (*cp && *cp != '#' && !is_space(*cp)); + if (is_space(*cp)) ++cp; *dp = '\0'; } @@ -2647,55 +3251,62 @@ getfields(char *cp) return array; } -static long -oadd(long t1, long t2) +static void +time_overflow(void) { - long t; - - t = t1 + t2; - if ((t2 > 0 && t <= t1) || (t2 < 0 && t >= t1)) - { - error(_("time overflow")); - exit(EXIT_FAILURE); - } - return t; + error(_("time overflow")); + exit(EXIT_FAILURE); } static zic_t -tadd(const zic_t t1, long t2) +oadd(zic_t t1, zic_t t2) { - zic_t t; + if (t1 < 0 ? t2 < ZIC_MIN - t1 : ZIC_MAX - t1 < t2) + time_overflow(); + return t1 + t2; +} - if (t1 == max_time && t2 > 0) - return max_time; - if (t1 == min_time && t2 < 0) - return min_time; - t = t1 + t2; - if ((t2 > 0 && t <= t1) || (t2 < 0 && t >= t1)) +static zic_t +tadd(zic_t t1, zic_t t2) +{ + if (t1 < 0) { - error(_("time overflow")); - exit(EXIT_FAILURE); + if (t2 < min_time - t1) + { + if (t1 != min_time) + time_overflow(); + return min_time; + } } - return t; + else + { + if (max_time - t1 < t2) + { + if (t1 != max_time) + time_overflow(); + return max_time; + } + } + return t1 + t2; } /* - * Given a rule, and a year, compute the date - in seconds since January 1, - * 1970, 00:00 LOCAL time - in that year that the rule refers to. + * Given a rule, and a year, compute the date (in seconds since January 1, + * 1970, 00:00 LOCAL time) in that year that the rule refers to. */ static zic_t -rpytime(const struct rule * rp, int wantedy) +rpytime(const struct rule * rp, zic_t wantedy) { - int y, - m, + int m, i; - long dayoff; /* with a nod to Margaret O. */ - zic_t t; + zic_t dayoff; /* with a nod to Margaret O. */ + zic_t t, + y; - if (wantedy == INT_MIN) + if (wantedy == ZIC_MIN) return min_time; - if (wantedy == INT_MAX) + if (wantedy == ZIC_MAX) return max_time; dayoff = 0; m = TM_JANUARY; @@ -2712,12 +3323,12 @@ rpytime(const struct rule * rp, int wantedy) --y; i = -len_years[isleap(y)]; } - dayoff = oadd(dayoff, eitol(i)); + dayoff = oadd(dayoff, i); } while (m != rp->r_month) { i = len_months[isleap(y)][m]; - dayoff = oadd(dayoff, eitol(i)); + dayoff = oadd(dayoff, i); ++m; } i = rp->r_dayofmonth; @@ -2732,13 +3343,13 @@ rpytime(const struct rule * rp, int wantedy) } } --i; - dayoff = oadd(dayoff, eitol(i)); + dayoff = oadd(dayoff, i); if (rp->r_dycode == DC_DOWGEQ || rp->r_dycode == DC_DOWLEQ) { - long wday; + zic_t wday; -#define LDAYSPERWEEK ((long) DAYSPERWEEK) - wday = eitol(EPOCH_WDAY); +#define LDAYSPERWEEK ((zic_t) DAYSPERWEEK) + wday = EPOCH_WDAY; /* * Don't trust mod of negative numbers. @@ -2751,17 +3362,17 @@ rpytime(const struct rule * rp, int wantedy) if (wday < 0) wday += LDAYSPERWEEK; } - while (wday != eitol(rp->r_wday)) + while (wday != rp->r_wday) if (rp->r_dycode == DC_DOWGEQ) { - dayoff = oadd(dayoff, (long) 1); + dayoff = oadd(dayoff, 1); if (++wday >= LDAYSPERWEEK) wday = 0; ++i; } else { - dayoff = oadd(dayoff, (long) -1); + dayoff = oadd(dayoff, -1); if (--wday < 0) wday = LDAYSPERWEEK - 1; --i; @@ -2769,7 +3380,7 @@ rpytime(const struct rule * rp, int wantedy) if (i < 0 || i >= len_months[isleap(y)][m]) { if (noise) - warning(_("rule goes past start/end of month--\ + warning(_("rule goes past start/end of month; \ will not work with pre-2004 versions of zic")); } } @@ -2790,28 +3401,21 @@ newabbr(const char *string) if (strcmp(string, GRANDPARENTED) != 0) { const char *cp; - char *wp; + const char *mp; cp = string; - wp = NULL; - while (isalpha((unsigned char) *cp) || ('0' <= *cp && *cp <= '9') + mp = NULL; + while (is_alpha(*cp) || ('0' <= *cp && *cp <= '9') || *cp == '-' || *cp == '+') ++cp; if (noise && cp - string < 3) - wp = _("time zone abbreviation has fewer than 3 characters"); + mp = _("time zone abbreviation has fewer than 3 characters"); if (cp - string > ZIC_MAX_ABBR_LEN_WO_WARN) - wp = _("time zone abbreviation has too many characters"); + mp = _("time zone abbreviation has too many characters"); if (*cp != '\0') - wp = _("time zone abbreviation differs from POSIX standard"); - if (wp != NULL) - { - wp = ecpyalloc(wp); - wp = ecatalloc(wp, " ("); - wp = ecatalloc(wp, string); - wp = ecatalloc(wp, ")"); - warning(wp); - ifree(wp); - } + mp = _("time zone abbreviation differs from POSIX standard"); + if (mp != NULL) + warning("%s (%s)", mp, string); } i = strlen(string) + 1; if (charcnt + i > TZ_MAX_CHARS) @@ -2819,18 +3423,18 @@ newabbr(const char *string) error(_("too many, or too long, time zone abbreviations")); exit(EXIT_FAILURE); } - (void) strcpy(&chars[charcnt], string); - charcnt += eitol(i); + strcpy(&chars[charcnt], string); + charcnt += i; } -static int +static bool mkdirs(char *argname) { char *name; char *cp; if (argname == NULL || *argname == '\0') - return 0; + return true; cp = name = ecpyalloc(argname); while ((cp = strchr(cp + 1, '/')) != NULL) { @@ -2840,60 +3444,38 @@ mkdirs(char *argname) /* * DOS drive specifier? */ - if (isalpha((unsigned char) name[0]) && - name[1] == ':' && name[2] == '\0') + if (is_alpha(name[0]) && name[1] == ':' && name[2] == '\0') { *cp = '/'; continue; } #endif /* WIN32 */ - if (!itsdir(name)) - { - /* - * It doesn't seem to exist, so we try to create it. Creation may - * fail because of the directory being created by some other - * multiprocessor, so we get to do extra checking. - */ - if (mkdir(name, MKDIR_UMASK) != 0) - { - const char *e = strerror(errno); - if (errno != EEXIST || !itsdir(name)) - { - (void) fprintf(stderr, - _("%s: Cannot create directory %s: %s\n"), - progname, name, e); - ifree(name); - return -1; - } + /* + * Try to create it. It's OK if creation fails because the directory + * already exists, perhaps because some other process just created it. + */ + if (mkdir(name, MKDIR_UMASK) != 0) + { + int err = errno; + + if (itsdir(name) <= 0) + { + char const *e = strerror(err); + + warning(_("%s: Can't create directory" + " %s: %s"), + progname, name, e); + free(name); + return false; } } *cp = '/'; } - ifree(name); - return 0; + free(name); + return true; } -static long -eitol(int i) -{ - long l; - - l = i; - if ((i < 0 && l >= 0) || (i == 0 && l != 0) || (i > 0 && l <= 0)) - { - (void) fprintf(stderr, - _("%s: %d did not sign extend correctly\n"), - progname, i); - exit(EXIT_FAILURE); - } - return l; -} - -/* - * UNIX was a registered trademark of The Open Group in 2003. - */ - #ifdef WIN32 /* @@ -2902,18 +3484,8 @@ eitol(int i) int link(const char *oldpath, const char *newpath) { - if (!CopyFile(oldpath, newpath, FALSE)) + if (!CopyFile(oldpath, newpath, false)) return -1; return 0; } #endif - -/* - * This allows zic to compile by just returning a dummy value. - * localtime.c references it, but no one uses it from zic. - */ -int -pg_open_tzfile(const char *name, char *canonname) -{ - return -1; -}