summaryrefslogtreecommitdiff
path: root/tz/zic.c
diff options
context:
space:
mode:
Diffstat (limited to 'tz/zic.c')
-rw-r--r--tz/zic.c264
1 files changed, 192 insertions, 72 deletions
diff --git a/tz/zic.c b/tz/zic.c
index 2ebc66a..4bafe5d 100644
--- a/tz/zic.c
+++ b/tz/zic.c
@@ -575,7 +575,8 @@ usage(FILE *stream, int status)
fprintf(stream,
_("%s: usage is %s [ --version ] [ --help ] [ -v ] \\\n"
"\t[ -l localtime ] [ -p posixrules ] [ -d directory ] \\\n"
- "\t[ -t localtime-link ] [ -L leapseconds ] [ filename ... ]\n\n"
+ "\t[ -t localtime-link ] [ -L leapseconds ] [ -r '[@lo][/@hi]' ] \\\n"
+ "\t[ filename ... ]\n\n"
"Report bugs to %s.\n"),
progname, progname, REPORT_BUGS_TO);
if (status == EXIT_SUCCESS)
@@ -603,6 +604,45 @@ change_directory (char const *dir)
}
}
+#define TIME_T_BITS_IN_FILE 64
+
+/* The minimum and maximum values representable in a TZif file. */
+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);
+
+/* The minimum, and one less than the maximum, values specified by
+ the -r option. These default to MIN_TIME and MAX_TIME. */
+static zic_t lo_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE);
+static zic_t hi_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE);
+
+/* Set the time range of the output to TIMERANGE.
+ Return true if successful. */
+static bool
+timerange_option(char *timerange)
+{
+ intmax_t lo = min_time, hi = max_time;
+ char *lo_end = timerange, *hi_end;
+ if (*timerange == '@') {
+ errno = 0;
+ lo = strtoimax (timerange + 1, &lo_end, 10);
+ if (lo_end == timerange + 1 || (lo == INTMAX_MAX && errno == ERANGE))
+ return false;
+ }
+ hi_end = lo_end;
+ if (lo_end[0] == '/' && lo_end[1] == '@') {
+ errno = 0;
+ hi = strtoimax (lo_end + 2, &hi_end, 10);
+ if (hi_end == lo_end + 2 || hi == INTMAX_MIN)
+ return false;
+ hi -= ! (hi == INTMAX_MAX && errno == ERANGE);
+ }
+ if (*hi_end || hi < lo || max_time < lo || hi < min_time)
+ return false;
+ lo_time = lo < min_time ? min_time : lo;
+ hi_time = max_time < hi ? max_time : hi;
+ return true;
+}
+
static const char * psxrules;
static const char * lcltime;
static const char * directory;
@@ -615,6 +655,7 @@ main(int argc, char **argv)
{
register int c, k;
register ptrdiff_t i, j;
+ bool timerange_given = false;
#ifdef S_IWGRP
umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH));
@@ -640,7 +681,7 @@ main(int argc, char **argv)
} else if (strcmp(argv[k], "--help") == 0) {
usage(stdout, EXIT_SUCCESS);
}
- while ((c = getopt(argc, argv, "d:l:L:p:st:vy:")) != EOF && c != -1)
+ while ((c = getopt(argc, argv, "d:l:L:p:r:st:vy:")) != EOF && c != -1)
switch (c) {
default:
usage(stderr, EXIT_FAILURE);
@@ -708,6 +749,21 @@ _("%s: More than one -L option specified\n"),
case 'v':
noise = true;
break;
+ case 'r':
+ if (timerange_given) {
+ fprintf(stderr,
+_("%s: More than one -r option specified\n"),
+ progname);
+ return EXIT_FAILURE;
+ }
+ if (! timerange_option(optarg)) {
+ fprintf(stderr,
+_("%s: invalid time range: %s\n"),
+ progname, optarg);
+ return EXIT_FAILURE;
+ }
+ timerange_given = true;
+ break;
case 's':
warning(_("-s ignored"));
break;
@@ -961,11 +1017,6 @@ dolink(char const *fromfield, char const *tofield, bool staysymlink)
}
}
-#define TIME_T_BITS_IN_FILE 64
-
-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);
-
/* Return true if NAME is a directory. */
static bool
itsdir(char const *name)
@@ -1714,12 +1765,16 @@ puttzcode(const int_fast32_t val, FILE *const fp)
}
static void
-puttzcode64(const zic_t val, FILE *const fp)
+puttzcodepass(zic_t val, FILE *fp, int pass)
{
+ if (pass == 1)
+ puttzcode(val, fp);
+ else {
char buf[8];
convert64(val, buf);
fwrite(buf, sizeof buf, 1, fp);
+ }
}
static int
@@ -1742,14 +1797,42 @@ swaptypes(int i, int j)
{ bool t = ttisgmts[i]; ttisgmts[i] = ttisgmts[j]; ttisgmts[j] = t; }
}
+struct timerange {
+ int defaulttype;
+ ptrdiff_t base, count;
+ int leapbase, leapcount;
+};
+
+static struct timerange
+limitrange(struct timerange r, zic_t lo, zic_t hi,
+ zic_t const *ats, unsigned char const *types)
+{
+ while (0 < r.count && ats[r.base] < lo) {
+ r.defaulttype = types[r.base];
+ r.count--;
+ r.base++;
+ }
+ while (0 < r.leapcount && trans[r.leapbase] < lo) {
+ r.leapcount--;
+ r.leapbase++;
+ }
+
+ if (hi < ZIC_MAX) {
+ while (0 < r.count && hi + 1 < ats[r.base + r.count - 1])
+ r.count--;
+ while (0 < r.leapcount && hi + 1 < trans[r.leapbase + r.leapcount - 1])
+ r.leapcount--;
+ }
+
+ return r;
+}
+
static void
writezone(const char *const name, const char *const string, char version,
int defaulttype)
{
register FILE * fp;
register ptrdiff_t i, j;
- register int leapcnt32, leapi32;
- register ptrdiff_t timecnt32, timei32;
register int pass;
static const struct tzhead tzh0;
static struct tzhead tzh;
@@ -1764,6 +1847,7 @@ writezone(const char *const name, const char *const string, char version,
_Alignof(zic_t)));
void *typesptr = ats + nats;
unsigned char *types = typesptr;
+ struct timerange rangeall, range32, range64;
/*
** Sort.
@@ -1839,32 +1923,13 @@ writezone(const char *const name, const char *const string, char version,
timecnt++;
}
- /*
- ** Figure out 32-bit-limited starts and counts.
- */
- timecnt32 = timecnt;
- timei32 = 0;
- leapcnt32 = leapcnt;
- leapi32 = 0;
- while (0 < timecnt32 && INT32_MAX < ats[timecnt32 - 1])
- --timecnt32;
- while (1 < timecnt32 && ats[timei32] < INT32_MIN
- && ats[timei32 + 1] <= INT32_MIN) {
- /* Discard too-low transitions, except keep any last too-low
- transition if no transition is exactly at INT32_MIN.
- The kept transition will be output as an INT32_MIN
- "transition" appropriate for buggy 32-bit clients that do
- not use time type 0 for timestamps before the first
- transition; see below. */
- --timecnt32;
- ++timei32;
- }
- while (0 < leapcnt32 && INT32_MAX < trans[leapcnt32 - 1])
- --leapcnt32;
- while (0 < leapcnt32 && trans[leapi32] < INT32_MIN) {
- --leapcnt32;
- ++leapi32;
- }
+ rangeall.defaulttype = defaulttype;
+ rangeall.base = rangeall.leapbase = 0;
+ rangeall.count = timecnt;
+ rangeall.leapcount = leapcnt;
+ range64 = limitrange(rangeall, lo_time, hi_time, ats, types);
+ range32 = limitrange(range64, INT32_MIN, INT32_MAX, ats, types);
+
/*
** Remove old file, if any, to snap links.
*/
@@ -1894,6 +1959,9 @@ writezone(const char *const name, const char *const string, char version,
for (pass = 1; pass <= 2; ++pass) {
register ptrdiff_t thistimei, thistimecnt, thistimelim;
register int thisleapi, thisleapcnt, thisleaplim;
+ int currenttype, thisdefaulttype;
+ bool locut, hicut;
+ zic_t lo;
int old0;
char omittype[TZ_MAX_TYPES];
int typemap[TZ_MAX_TYPES];
@@ -1904,33 +1972,72 @@ writezone(const char *const name, const char *const string, char version,
int indmap[TZ_MAX_CHARS];
if (pass == 1) {
- thistimei = timei32;
- thistimecnt = timecnt32;
+ /* Arguably the default time type in the 32-bit data
+ should be range32.defaulttype, which is suited for
+ timestamps just before INT32_MIN. However, zic
+ traditionally used the time type of the indefinite
+ past instead. Internet RFC 8532 says readers should
+ ignore 32-bit data, so this discrepancy matters only
+ to obsolete readers where the traditional type might
+ be more appropriate even if it's "wrong". So, use
+ the historical zic value, unless -r specifies a low
+ cutoff that excludes some 32-bit timestamps. */
+ thisdefaulttype = (lo_time <= INT32_MIN
+ ? range64.defaulttype
+ : range32.defaulttype);
+
+ thistimei = range32.base;
+ thistimecnt = range32.count;
toomanytimes = thistimecnt >> 31 >> 1 != 0;
- thisleapi = leapi32;
- thisleapcnt = leapcnt32;
+ thisleapi = range32.leapbase;
+ thisleapcnt = range32.leapcount;
+ locut = INT32_MIN < lo_time;
+ hicut = hi_time < INT32_MAX;
} else {
- thistimei = 0;
- thistimecnt = timecnt;
+ thisdefaulttype = range64.defaulttype;
+ thistimei = range64.base;
+ thistimecnt = range64.count;
toomanytimes = thistimecnt >> 31 >> 31 >> 2 != 0;
- thisleapi = 0;
- thisleapcnt = leapcnt;
+ thisleapi = range64.leapbase;
+ thisleapcnt = range64.leapcount;
+ locut = min_time < lo_time;
+ hicut = hi_time < max_time;
}
if (toomanytimes)
error(_("too many transition times"));
+
+ /* Keep the last too-low transition if no transition is
+ exactly at LO. The kept transition will be output as
+ a LO "transition"; see "Output a LO_TIME transition"
+ below. This is needed when the output is truncated at
+ the start, and is also useful when catering to buggy
+ 32-bit clients that do not use time type 0 for
+ timestamps before the first transition. */
+ if (0 < thistimei && ats[thistimei] != lo_time) {
+ thistimei--;
+ thistimecnt++;
+ locut = false;
+ }
+
thistimelim = thistimei + thistimecnt;
thisleaplim = thisleapi + thisleapcnt;
+ if (thistimecnt != 0) {
+ if (ats[thistimei] == lo_time)
+ locut = false;
+ if (hi_time < ZIC_MAX && ats[thistimelim - 1] == hi_time + 1)
+ hicut = false;
+ }
memset(omittype, true, typecnt);
- omittype[defaulttype] = false;
+ omittype[thisdefaulttype] = false;
for (i = thistimei; i < thistimelim; i++)
omittype[types[i]] = false;
- /* Reorder types to make DEFAULTTYPE type 0.
- Use TYPEMAP to swap OLD0 and DEFAULTTYPE so that
- DEFAULTTYPE appears as type 0 in the output instead
+ /* Reorder types to make THISDEFAULTTYPE type 0.
+ Use TYPEMAP to swap OLD0 and THISDEFAULTTYPE so that
+ THISDEFAULTTYPE appears as type 0 in the output instead
of OLD0. TYPEMAP also omits unused types. */
old0 = strlen(omittype);
- swaptypes(old0, defaulttype);
+ swaptypes(old0, thisdefaulttype);
#ifndef LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH
/*
@@ -1982,8 +2089,8 @@ writezone(const char *const name, const char *const string, char version,
thistypecnt = 0;
for (i = old0; i < typecnt; i++)
if (!omittype[i])
- typemap[i == old0 ? defaulttype
- : i == defaulttype ? old0 : i]
+ typemap[i == old0 ? thisdefaulttype
+ : i == thisdefaulttype ? old0 : i]
= thistypecnt++;
for (i = 0; i < sizeof indmap / sizeof indmap[0]; ++i)
@@ -2013,7 +2120,7 @@ writezone(const char *const name, const char *const string, char version,
convert(thistypecnt, tzh.tzh_ttisgmtcnt);
convert(thistypecnt, tzh.tzh_ttisstdcnt);
convert(thisleapcnt, tzh.tzh_leapcnt);
- convert(thistimecnt, tzh.tzh_timecnt);
+ convert(locut + thistimecnt + hicut, tzh.tzh_timecnt);
convert(thistypecnt, tzh.tzh_typecnt);
convert(thischarcnt, tzh.tzh_charcnt);
DO(tzh_magic);
@@ -2026,21 +2133,29 @@ writezone(const char *const name, const char *const string, char version,
DO(tzh_typecnt);
DO(tzh_charcnt);
#undef DO
- for (i = thistimei; i < thistimelim; ++i)
- if (pass == 1)
- /*
- ** Output an INT32_MIN "transition"
- ** if appropriate; see above.
- */
- puttzcode(((ats[i] < INT32_MIN) ?
- INT32_MIN : ats[i]), fp);
- else puttzcode64(ats[i], fp);
- for (i = thistimei; i < thistimelim; ++i) {
- unsigned char uc;
+ /* Output a LO_TIME transition if needed; see limitrange.
+ But do not go below the minimum representable value
+ for this pass. */
+ lo = pass == 1 && lo_time < INT32_MIN ? INT32_MIN : lo_time;
- uc = typemap[types[i]];
- fwrite(&uc, sizeof uc, 1, fp);
+ if (locut)
+ puttzcodepass(lo, fp, pass);
+ for (i = thistimei; i < thistimelim; ++i) {
+ zic_t at = ats[i] < lo ? lo : ats[i];
+ puttzcodepass(at, fp, pass);
+ }
+ if (hicut)
+ puttzcodepass(hi_time + 1, fp, pass);
+ currenttype = 0;
+ if (locut)
+ putc(currenttype, fp);
+ for (i = thistimei; i < thistimelim; ++i) {
+ currenttype = typemap[types[i]];
+ putc(currenttype, fp);
}
+ if (hicut)
+ putc(currenttype, fp);
+
for (i = old0; i < typecnt; i++)
if (!omittype[i]) {
puttzcode(gmtoffs[i], fp);
@@ -2070,9 +2185,7 @@ writezone(const char *const name, const char *const string, char version,
}
todo = tadd(trans[i], -gmtoffs[j]);
} else todo = trans[i];
- if (pass == 1)
- puttzcode(todo, fp);
- else puttzcode64(todo, fp);
+ puttzcodepass(todo, fp, pass);
puttzcode(corr[i], fp);
}
for (i = old0; i < typecnt; i++)
@@ -2081,7 +2194,7 @@ writezone(const char *const name, const char *const string, char version,
for (i = old0; i < typecnt; i++)
if (!omittype[i])
putc(ttisgmts[i], fp);
- swaptypes(old0, defaulttype);
+ swaptypes(old0, thisdefaulttype);
}
fprintf(fp, "\n%s\n", string);
close_file(fp, directory, name);
@@ -2301,6 +2414,12 @@ stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
struct rule stdr, dstr;
result[0] = '\0';
+
+ /* Internet RFC 8536 section 5.1 says to use an empty TZ string if
+ future timestamps are truncated. */
+ if (hi_time < max_time)
+ return -1;
+
zp = zpfirst + zonecount - 1;
stdrp = dstrp = NULL;
for (i = 0; i < zp->z_nrules; ++i) {
@@ -2737,11 +2856,12 @@ error(_("can't determine time zone abbreviation to use just after until time"));
xr.r_dycode = DC_DOM;
xr.r_dayofmonth = 1;
xr.r_tod = 0;
- for (lastat = &attypes[0], i = 1; i < timecnt; i++)
+ for (lastat = attypes, i = 1; i < timecnt; i++)
if (attypes[i].at > lastat->at)
lastat = &attypes[i];
- if (lastat->at < rpytime(&xr, max_year - 1)) {
- addtt(rpytime(&xr, max_year + 1), lastat->type);
+ if (!lastat || lastat->at < rpytime(&xr, max_year - 1)) {
+ addtt(rpytime(&xr, max_year + 1),
+ lastat ? lastat->type : defaulttype);
attypes[timecnt - 1].dontmerge = true;
}
}