aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDag-Erling Smørgrav <des@FreeBSD.org>2026-09-13 13:18:34 +0000
committerDag-Erling Smørgrav <des@FreeBSD.org>2026-09-13 13:18:34 +0000
commit4d4fdb78dbcb075efa6bcd8f913a5bfb59246647 (patch)
tree637a366d51fed7a1e52e276d74df19eb396a1a96
parent2479b6921a20c7fe260ba1b30c5ca7f666e692a6 (diff)
-rw-r--r--CONTRIBUTING2
-rw-r--r--Makefile19
-rw-r--r--NEWS115
-rw-r--r--README2
-rw-r--r--localtime.c397
-rw-r--r--private.h15
-rw-r--r--strftime.c9
-rw-r--r--theory.html63
-rw-r--r--tz-art.html14
-rw-r--r--tz-how-to.html18
-rw-r--r--tz-link.html118
-rw-r--r--tzfile.5.txt3
-rw-r--r--version2
-rw-r--r--zdump.c4
-rw-r--r--zic.843
-rw-r--r--zic.8.txt44
-rw-r--r--zic.c89
17 files changed, 530 insertions, 427 deletions
diff --git a/CONTRIBUTING b/CONTRIBUTING
index c5fa803f7275..213ae67e962a 100644
--- a/CONTRIBUTING
+++ b/CONTRIBUTING
@@ -22,7 +22,7 @@ To email small changes, please run a POSIX shell command like
For more-elaborate or possibly controversial changes,
such as renaming, adding or removing zones, please read
“Theory and pragmatics of the tz code and data”
-<https://www.iana.org/time-zones/repository/theory.html>.
+<https://data.iana.org/time-zones/theory.html>
It is also good to browse the mailing list archives
<https://lists.iana.org/hyperkitty/list/tz@iana.org/>
for examples of patches that tend to work well.
diff --git a/Makefile b/Makefile
index f5a22f1896a4..96a7462e9a28 100644
--- a/Makefile
+++ b/Makefile
@@ -172,9 +172,9 @@ EXPIRES_LINE= 0
TZDATA_TEXT= leapseconds tzdata.zi
-# For backward-compatibility links for old zone names, use
+# For backward-compatibility links and zones for old names, use
# BACKWARD= backward
-# To omit these links, use
+# To omit these links and zones, use
# BACKWARD=
BACKWARD= backward
@@ -574,7 +574,7 @@ SAFE_CHAR= '[]'$(SAFE_CHARSET)'-]'
# These non-alphabetic, non-ASCII printable characters are
# used in commentary or in generated *.txt files
# and are not likely to cause confusion.
-UNUSUAL_OK_CHARSET= §«°±»½¾×–‘’“”•→−≤★⟨⟩⯪
+UNUSUAL_OK_CHARSET= §«°±»½¾¿×–‘’“”•→−≤★⟨⟩⯪
# Put this in a bracket expression to match spaces.
s = [:space:]
@@ -922,7 +922,7 @@ check_mild: check_web check_zishrink \
UTF8_LOCALE_MISSING = \
{ test ! '$(UTF8_LOCALE)' \
|| ! printf 'A\304\200B\n' \
- | LC_ALL='$(UTF8_LOCALE)' grep -q '^A.B$$' >/dev/null 2>&1 \
+ | LC_ALL='$(UTF8_LOCALE)' grep -q '^A[[:alpha:]]B$$' >/dev/null 2>&1 \
|| { export LC_ALL='$(UTF8_LOCALE)'; false; }; }
character-set.ck: $(ENCHILADA)
@@ -1006,11 +1006,11 @@ now.ck: checknow.awk date tzdata.zi zdump zic zone1970.tab zonenow.tab
now=$(CHECK_NOW_TIMESTAMP) && \
future=$$(($(CHECK_NOW_FUTURE_SECS) + $$now)) && \
./zdump -i -t $$now,$$future \
- $$(find "$$PWD/$@d"/????*/ -type f) \
+ $$(find "$$PWD/$@d"/????*/ -type f -o -type l) \
>$@d/zdump-now.tab && \
./zdump -i -t 0,$$future \
$$(find "$$PWD/$@d" -name Etc -prune \
- -o -type f ! -name '*.tab' -print) \
+ -o \( -type f -o -type l \) ! -name '*.tab' -print) \
>$@d/zdump-1970.tab && \
$(AWK) \
-v now=$$now \
@@ -1018,7 +1018,12 @@ now.ck: checknow.awk date tzdata.zi zdump zic zone1970.tab zonenow.tab
-v zdump_table=$@d/zdump-now.tab \
-f checknow.awk zonenow.tab
$(AWK) \
- 'BEGIN {print "-\t-\tUTC"} /^Zone/ {print "-\t-\t" $$2}' \
+ 'BEGIN { \
+ SysVzone["EST5EDT"] = 1; SysVzone["CST6CDT"] = 1; \
+ SysVzone["MST7MDT"] = 1; SysVzone["PST8PDT"] = 1; \
+ print "-\t-\tUTC" \
+ } \
+ $$1 == "Zone" && !SysVzone[$$2] {print "-\t-\t" $$2}' \
$(PRIMARY_YDATA) backward factory | \
$(AWK) \
-v zdump_table=$@d/zdump-1970.tab \
diff --git a/NEWS b/NEWS
index dd962b54d529..ae240d31b84e 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,87 @@
News for the tz database
+Release 2026d - 2026-09-11 15:21:07 -0700
+
+ Briefly:
+ Canada’s Northwest Territories moved to permanent -06 on 2026-08-21.
+ Obsolescent settings like TZ="EST5EDT" now conform better to POSIX.
+ Fix security, performance and porting bugs in zic and localtime.
+
+ Changes to future timestamps
+
+ Canada’s Northwest Territories will not fall back on 2026-11-01
+ and will stay on -06 year-round, matching Alberta’s recent change.
+ Model this with its traditional abbreviation CST. Although the
+ change to permanent -06 legally took place on 2026-08-21,
+ temporarily model the change to occur on 2026-11-01 at 02:00
+ for the same reason as other recent temporary hacks. (Caution:
+ see “NOTE FOR 2026b TEMPORARY HACK FOR CLDR AND CANADA” below.)
+ This affects only America/Inuvik as the rest of the territory is
+ covered by America/Edmonton, for which the equivalent change was
+ released in 2026c.
+
+ Changes to past timestamps
+
+ Colombia’s 1992-05-02 spring forward was at 00:00, not 24:00.
+
+ Iran’s 1979-05-26 spring forward was at 00:00, not 24:00.
+ (Thanks to N.F. Hase.)
+
+ The backward-compatibility names EST5EDT, CST6CDT, MST7MDT, and
+ PST8PDT now conform better to POSIX. For example, EST5EDT now
+ always uses the abbreviation "EST" for standard time (now always 5
+ hours behind UT) and "EDT" for daylight saving time, whereas it
+ formerly had different UT offsets before standard time was
+ introduced and sometimes used abbreviations like "LMT", "EWT" and
+ "EPT", all contrary to POSIX. Also, though not required by POSIX
+ these names now use US federal rules rather than rules of places
+ like New York, reverting to 2024a behavior. This change affects
+ only timestamps before 1966-10-30 at 01:00 standard time.
+
+ Other data changes
+
+ The temporary hacks used for North American timekeeping changes
+ now work around a libstdc++ std::chrono bug in GCC 14.1-14.4,
+ 15.1-15.2, and 16.1; see GCC bug 124851. This data change does
+ not affect TZif files or timestamps. The change does not work
+ around the related but less serious GCC bugs 116110 and 124513.
+ These GCC bugs are all fixed in GCC 16.2.
+
+ Changes to code
+
+ zic now rejects Link targets that would have invalid names, and
+ more efficiently processes Expires, Leap and Rule lines with years
+ far in the past or future. (Thanks to Darren Carreras.)
+
+ zic now ports to systems that report lack of link support via
+ EINVAL, ENOSYS or EPERM errno values. (Thanks to Tom Lane.)
+
+ When tzset and related functions encounter a TZif file that is too
+ large for them, they now consistently fail instead of sometimes
+ silently ignoring excess parts of the file.
+
+ localtime-related functions no longer mishandle extreme timestamps
+ when given TZif files holding some unlikely timezone histories.
+ (Problem reported by David Sarkisyan.)
+
+ localtime-related functions no longer check the values of TZif
+ files’ standard/wall and UT/local indicators, which these
+ functions have not used since 2026a’s removal of the old
+ posixrules feature.
+
+ tzcode has been ported to Haiku.
+
+ localtime.c now works again by default on AIX and DragonFly BSD.
+
+ zic now rejects ‘:’ and ‘\’ in Zone and Link names when running on
+ Microsoft Windows. (Problem reported by David Diaz.)
+
+ Changes to documentation
+
+ URLs for release tarballs in tz-link.html have been updated to
+ reflect their new canonical URLs on data.iana.org.
+
+
Release 2026c - 2026-07-08 10:23:58 -0700
Briefly:
@@ -15,7 +97,13 @@ Release 2026c - 2026-07-08 10:23:58 -0700
CST. Although the change to permanent -06 legally took place on
2026-06-18, temporarily model the change to occur on 2026-11-01 at
02:00 instead, for the same reason we introduced a similarly
- temporary hack for British Columbia in 2026b.
+ temporary hack for British Columbia in 2026b. (Caution: see
+ “NOTE FOR 2026b TEMPORARY HACK FOR CLDR AND CANADA” below.)
+
+ As the change affects both America/Edmonton and its backward
+ compatibility link, the obsolescent setting TZ="Canada/Mountain"
+ will now use the abbreviation CST for affected timestamps,
+ akin to TZ="Canada/Pacific" behavior introduced in 2026b.
Although another TZDB release will likely be needed soon because
Northwest Territories will likely follow Alberta, the legal
@@ -67,6 +155,7 @@ Release 2026b - 2026-04-22 23:06:43 -0700
Briefly:
British Columbia moved to permanent -07 on 2026-03-09.
+ zic no longer mishandles a last transition to a new time type.
Some more overflow bugs have been fixed in zic.
Changes to future timestamps
@@ -79,9 +168,33 @@ Release 2026b - 2026-04-22 23:06:43 -0700
limitation in CLDR 48.1 (2026-01-08). This temporary hack is
planned to be removed after CLDR is fixed.
+ To match longstanding practice for -07 standard time in North
+ America, such as for America/Dawson, America/Dawson_Creek,
+ America/Fort_Nelson and America/Whitehorse, affected BC
+ timestamps use the abbreviation MST. As the change affects both
+ America/Vancouver and its backward compatibility link, the
+ obsolescent setting TZ="Canada/Pacific" will now use the
+ abbreviation MST. Future TZDB releases may change the
+ abbreviation if something else becomes popular across areas
+ observing -07, and may change it to "-07" if no alphabetic
+ consensus emerges.
+
Changes to code
zic no longer mishandles a last transition to a new time type.
+ NOTE FOR 2026b TEMPORARY HACK FOR CLDR AND CANADA:
+ This zic fix is needed for the temporary hack (mentioned above)
+ that works around the Canadian timekeeping bug in Unicode CLDR.
+ Without the fix, the temporary hack causes zic versions 2023d
+ through 2026a, in their default mode that generates slim output,
+ to generate a TZif file that violates Internet RFC 9636 §3.3.
+ The buggy file in turn causes some TZif readers, including tzcode
+ itself, to ignore America/Vancouver’s 2026-11-01 02:00 transition
+ from PDT (tm_isdst=1) to MST (tm_isdst=0). Although the buggy
+ file does not cause any known TZif reader to mishandle UT offsets,
+ caution is advised when using zic 2023d through 2026a to compile
+ data from more-recent tz releases. To work around this problem
+ when using these older zic versions, use ‘zic -b fat’.
zic no longer overflows a buffer when generating a TZ string like
"PST-167:59:58PDT-167:59:59,M11.5.6/-167:59:59,M12.5.6/-167:59:59",
diff --git a/README b/README
index f22ec5492d09..0b69d8ea4a4d 100644
--- a/README
+++ b/README
@@ -10,7 +10,7 @@ locations around the globe. It is updated periodically to reflect
changes made by political bodies to time zone boundaries, UTC offsets,
and daylight-saving rules.
-See <https://www.iana.org/time-zones/repository/tz-link.html> or the
+See <https://data.iana.org/time-zones/tz-link.html> or the
file tz-link.html for how to acquire the code and data.
Once acquired, read the leading comments in the file ‘Makefile’
diff --git a/localtime.c b/localtime.c
index be3fe1d5d8f4..7c06e3707a46 100644
--- a/localtime.c
+++ b/localtime.c
@@ -64,7 +64,7 @@ struct stat { char st_ctime, st_dev, st_ino; };
# endif
# if THREAD_PREFER_SINGLE
# ifndef HAVE___ISTHREADED
-# if defined __FreeBSD__ || defined __OpenBSD__
+# if defined __FreeBSD__ || defined __OpenBSD__ || defined __DragonFly__
# define HAVE___ISTHREADED 1
# else
# define HAVE___ISTHREADED 0
@@ -348,6 +348,10 @@ static int fstatat(int dd, char const *path, struct stat *st, int flags)
# include <sys/auxv.h>
# endif
+/* Avoid clash if headers declare but libraries do not define issetugid. */
+# undef issetugid
+# define issetugid localtime_issetugid
+
/* Return 1 if the process is privileged, 0 otherwise. */
static int
issetugid(void)
@@ -475,8 +479,6 @@ struct ttinfo { /* time type information */
-2**31 + 1 .. 2**31 - 1 */
desigidx_type tt_desigidx; /* abbreviation list index */
bool tt_isdst; /* used to set tm_isdst */
- bool tt_ttisstd; /* transition is std time */
- bool tt_ttisut; /* transition is UT */
};
struct lsinfo { /* leap second information */
@@ -505,7 +507,6 @@ struct state {
int timecnt;
int typecnt;
int charcnt;
- bool goback;
bool goahead;
time_t ats[TZ_MAX_TIMES];
unsigned char types[TZ_MAX_TIMES];
@@ -569,12 +570,11 @@ struct rule {
};
static struct tm *gmtsub(struct state const *, time_t const *, int_fast32_t,
- struct tm *);
-static bool increment_overflow(int *, int);
+ struct tm *, time_t *);
static bool increment_overflow_time(time_t *, int_fast32_2s);
static int_fast32_2s leapcorr(struct state const *, time_t);
static struct tm *timesub(time_t const *, int_fast32_t, struct state const *,
- struct tm *);
+ struct tm *, time_t *);
static bool tzparse(char const *, struct state *, struct state const *);
#ifndef ALL_STATE
@@ -641,8 +641,6 @@ init_ttinfo(struct ttinfo *s, int_fast32_t utoff, bool isdst,
s->tt_utoff = utoff;
s->tt_isdst = isdst;
s->tt_desigidx = desigidx;
- s->tt_ttisstd = false;
- s->tt_ttisut = false;
}
/* Return true if SP's time type I does not specify local time. */
@@ -838,10 +836,11 @@ tzfile_changed(int fd, struct stat *st)
resolution if available, as this can help distinguish files on
non-POSIX platforms where st_dev and st_ino are unreliable. */
struct timespec ctim;
+ /* Copy via members, as AIX 7.3 defaults to an incompatible st_ctim. */
+ ctim.tv_sec = st->st_ctime;
#if HAVE_STRUCT_STAT_ST_CTIM
- ctim = st->st_ctim;
+ ctim.tv_nsec = st->st_ctim.tv_nsec;
#else
- ctim.tv_sec = st->st_ctime;
ctim.tv_nsec = 0;
#endif
@@ -896,9 +895,19 @@ union local_storage {
};
/* These tzload flags can be ORed together, and fit into 'char'. */
-enum { TZLOAD_FROMENV = 1 }; /* The TZ string came from the environment. */
-enum { TZLOAD_TZSTRING = 2 }; /* Read any newline-surrounded TZ string. */
-enum { TZLOAD_TZDIR_SUB = 4 }; /* TZ should be a file under TZDIR. */
+
+/* TZLOAD_FROMENV means the TZ string is a process-wide setting either
+ taken from the TZ environment variable or inferred from TZ being unset.
+ When 0 <= tz_change_interval, TZLOAD_FROMENV lets us skip reanalysis
+ of a TZif file that did not change during the interval. */
+enum { TZLOAD_FROMENV = 1 };
+
+/* Read any newline-surrounded TZ string. */
+enum { TZLOAD_TZSTRING = 2 };
+
+/* TZ should be a file under TZDIR. */
+enum { TZLOAD_TZDIR_SUB = 4 };
+
/* Load tz data from the file named NAME into *SP. Respect TZLOADFLAGS.
Use **LSPP for temporary storage. Return 0 on
@@ -923,7 +932,7 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags,
struct stat st;
st.st_ctime = 0;
- sp->goback = sp->goahead = false;
+ sp->goahead = false;
if (! name) {
name = TZDEFAULT;
@@ -1198,30 +1207,9 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags,
}
set_leapcount(sp, leapcnt);
- for (i = 0; i < sp->typecnt; ++i) {
- register struct ttinfo * ttisp;
-
- ttisp = &sp->ttis[i];
- if (ttisstdcnt == 0)
- ttisp->tt_ttisstd = false;
- else {
- if (*p != true && *p != false)
- return EFTYPE;
- ttisp->tt_ttisstd = *p++;
- }
- }
- for (i = 0; i < sp->typecnt; ++i) {
- register struct ttinfo * ttisp;
-
- ttisp = &sp->ttis[i];
- if (ttisutcnt == 0)
- ttisp->tt_ttisut = false;
- else {
- if (*p != true && *p != false)
- return EFTYPE;
- ttisp->tt_ttisut = *p++;
- }
- }
+ /* Do not bother to validate standard/wall and UT/local
+ indicators, as they are no longer used here. */
+ p += ttisstdcnt + ttisutcnt;
}
nread -= p - up->buf;
@@ -1232,13 +1220,13 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags,
break;
}
if ((tzloadflags & TZLOAD_TZSTRING) && nread > 2 &&
- up->buf[0] == '\n' && up->buf[nread - 1] == '\n' &&
- sp->typecnt + 2 <= TZ_MAX_TYPES) {
+ up->buf[0] == '\n' && up->buf[nread - 1] == '\n') {
struct state *ts = &lsp->u.st;
up->buf[nread - 1] = '\0';
- if (tzparse(&up->buf[1], ts, sp)) {
-
+ if (!tzparse(&up->buf[1], ts, sp))
+ return EFTYPE;
+ else {
/* Attempt to reuse existing abbreviations.
Without this, America/Anchorage would
consume 50 bytes for abbreviations, as
@@ -1246,7 +1234,6 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags,
AHDT YST AKDT AKST) and ts->charcnt equals 10
(for AKST AKDT). Reusing means sp->charcnt can
stay 40 in this example. */
- int gotabbr = 0;
int charcnt = sp->charcnt;
for (i = 0; i < ts->typecnt; i++) {
char *tsabbr = ts->chars + ts->ttis[i].tt_desigidx;
@@ -1254,22 +1241,25 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags,
for (j = 0; j < charcnt; j++)
if (strcmp(sp->chars + j, tsabbr) == 0) {
ts->ttis[i].tt_desigidx = j;
- gotabbr++;
break;
}
if (! (j < charcnt)) {
int tsabbrlen = strnlen(tsabbr, TZ_MAX_CHARS - j);
- if (j + tsabbrlen < TZ_MAX_CHARS) {
+ if (TZ_MAX_CHARS <= j + tsabbrlen)
+ return EOVERFLOW;
+ else {
char *cp = sp->chars + j;
cp = mempcpy(cp, tsabbr, tsabbrlen);
*cp = '\0';
charcnt = j + tsabbrlen + 1;
ts->ttis[i].tt_desigidx = j;
- gotabbr++;
}
}
}
- if (gotabbr == ts->typecnt) {
+
+ if (TZ_MAX_TYPES - sp->typecnt < ts->typecnt)
+ return EOVERFLOW;
+ else {
sp->charcnt = charcnt;
/* Ignore any trailing, no-op transitions generated
@@ -1288,10 +1278,8 @@ tzloadbody(char const *name, struct state *sp, char tzloadflags,
|| (0 < sp->timecnt
&& t <= sp->ats[sp->timecnt - 1]))
continue;
- if (TZ_MAX_TIMES <= sp->timecnt) {
- sp->goahead = false;
- break;
- }
+ if (TZ_MAX_TIMES <= sp->timecnt)
+ return EOVERFLOW;
sp->ats[sp->timecnt] = t;
sp->types[sp->timecnt] = (sp->typecnt
+ ts->types[i]);
@@ -1334,10 +1322,6 @@ static const int mon_lengths[2][MONSPERYEAR] = {
{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};
-static const int year_lengths[2] = {
- DAYSPERNYEAR, DAYSPERLYEAR
-};
-
/* Is C an ASCII digit? */
static bool
is_digit(char c)
@@ -1537,40 +1521,26 @@ getrule(const char *strp, register struct rule *const rulep)
*/
static int_fast32_t
-transtime(const int year, register const struct rule *const rulep,
+transtime(time_t year, register const struct rule *const rulep,
const int_fast32_t offset)
{
+ int d; /* Day of year (zero-origin). */
register bool leapyear;
- register int_fast32_t value;
- register int i;
- int d, m1, yy0, yy1, yy2, dow;
leapyear = isleap(year);
- switch (rulep->r_type) {
- case JULIAN_DAY:
+ if (rulep->r_type <= DAY_OF_YEAR) {
/*
** Jn - Julian day, 1 == January 1, 60 == March 1 even in leap
** years.
** In non-leap years, or if the day number is 59 or less, just
- ** add SECSPERDAY times the day number-1 to the time of
- ** January 1, midnight, to get the day.
- */
- value = (rulep->r_day - 1) * SECSPERDAY;
- if (leapyear && rulep->r_day >= 60)
- value += SECSPERDAY;
- break;
-
- case DAY_OF_YEAR:
- /*
+ ** use the day number - 1.
+ **
** n - day of year.
- ** Just add SECSPERDAY times the day number to the time of
- ** January 1, midnight, to get the day.
*/
- value = rulep->r_day * SECSPERDAY;
- break;
-
- case MONTH_NTH_DAY_OF_WEEK:
+ d = rulep->r_day - ((rulep->r_type < DAY_OF_YEAR)
+ & (!leapyear | (rulep->r_day <= 59)));
+ } else {
/*
** Mm.n.d - nth "dth day" of month m.
*/
@@ -1579,14 +1549,16 @@ transtime(const int year, register const struct rule *const rulep,
** Use Zeller's Congruence to get day-of-week of first day of
** month.
*/
- m1 = (rulep->r_mon + 9) % 12 + 1;
- yy0 = (rulep->r_mon <= 2) ? (year - 1) : year;
- yy1 = yy0 / 100;
- yy2 = yy0 % 100;
- dow = ((26 * m1 - 2) / 10 +
- 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7;
- if (dow < 0)
- dow += DAYSPERWEEK;
+ int i;
+ bool janfeb = rulep->r_mon <= 2;
+ int month = (rulep->r_mon
+ + (janfeb ? MONSPERYEAR : 0)); /* 3..14 */
+ int adjustment = (TYPE_SIGNED(time_t) ? 0 : 400) - janfeb;
+ int ay_rem = (year + adjustment) % YEARSPERREPEAT;
+ int y = ay_rem + (ay_rem < 0 ? YEARSPERREPEAT : 0);
+ int dow = (((13 * (month + 1)) / 5
+ + y + y / 4 - y / 100 + y / 400)
+ % DAYSPERWEEK);
/*
** "dow" is the day-of-week of the first day of the month. Get
@@ -1594,33 +1566,24 @@ transtime(const int year, register const struct rule *const rulep,
** month.
*/
d = rulep->r_day - dow;
- if (d < 0)
- d += DAYSPERWEEK;
- for (i = 1; i < rulep->r_week; ++i) {
- if (d + DAYSPERWEEK >=
- mon_lengths[leapyear][rulep->r_mon - 1])
- break;
- d += DAYSPERWEEK;
- }
+ d += (rulep->r_week - (0 <= d)) * DAYSPERWEEK;
+ if (mon_lengths[leapyear][rulep->r_mon - 1] <= d)
+ d -= DAYSPERWEEK;
/*
** "d" is the day-of-month (zero-origin) of the day we want.
*/
- value = d * SECSPERDAY;
for (i = 0; i < rulep->r_mon - 1; ++i)
- value += mon_lengths[leapyear][i] * SECSPERDAY;
- break;
-
- default: unreachable();
+ d += mon_lengths[leapyear][i];
}
/*
- ** "value" is the year-relative time of 00:00:00 UT on the day in
+ ** d is the origin-0 year-relative 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.
+ ** from UT to d * SECSPERDAY.
*/
- return value + rulep->r_time + offset;
+ return d * SECSPERDAY + rulep->r_time + offset;
}
/*
@@ -1670,11 +1633,11 @@ tzparse(const char *name, struct state *sp, struct state const *basep)
}
} else
set_leapcount(sp, 0); /* So, we're off a little. */
- sp->goback = sp->goahead = false;
+ sp->goahead = false;
if (*name != '\0') {
struct rule start, end;
- int year, yearbeg, yearlim, timecnt;
- time_t janfirst;
+ int timecnt;
+ time_t janfirst, repeatbeg, year, yearbeg, yearlim;
int_fast32_t janoffset = 0;
if (*name == '<') {
@@ -1712,18 +1675,20 @@ tzparse(const char *name, struct state *sp, struct state const *basep)
if (!name || *name)
return false;
sp->typecnt = 2; /* standard time and DST */
- /*
- ** Two transitions per year, from EPOCH_YEAR forward.
- */
+
+ /* Two transitions per year, from atlo forward, and going on
+ for years_of_observations past max(atlo, leaplo). */
init_ttinfo(&sp->ttis[0], -stdoffset, false, 0);
init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1);
timecnt = 0;
- janfirst = 0;
- yearbeg = EPOCH_YEAR;
+ repeatbeg = (atlo / SECSPERREPEAT
+ + (atlo < 0 && 0 < atlo % SECSPERREPEAT));
+ yearbeg = repeatbeg * YEARSPERREPEAT + EPOCH_YEAR;
+ janfirst = repeatbeg * SECSPERREPEAT;
do {
int_fast32_t yearsecs
- = year_lengths[isleap(yearbeg - 1)] * SECSPERDAY;
+ = year_days(yearbeg - 1) * SECSPERDAY;
time_t janfirst1 = janfirst;
yearbeg--;
if (increment_overflow_time(&janfirst1, -yearsecs)) {
@@ -1731,30 +1696,25 @@ tzparse(const char *name, struct state *sp, struct state const *basep)
break;
}
janfirst = janfirst1;
- } while (atlo < janfirst
- && EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg);
+ } while (atlo < janfirst);
while (true) {
int_fast32_t yearsecs
- = year_lengths[isleap(yearbeg)] * SECSPERDAY;
- int yearbeg1 = yearbeg;
+ = year_days(yearbeg) * SECSPERDAY;
time_t janfirst1 = janfirst;
if (increment_overflow_time(&janfirst1, yearsecs)
- || increment_overflow(&yearbeg1, 1)
|| atlo <= janfirst1)
break;
- yearbeg = yearbeg1;
+ yearbeg++;
janfirst = janfirst1;
}
- yearlim = yearbeg;
- if (increment_overflow(&yearlim, years_of_observations))
- yearlim = INT_MAX;
+ yearlim = yearbeg + years_of_observations;
for (year = yearbeg; year < yearlim; year++) {
int_fast32_t
starttime = transtime(year, &start, stdoffset),
endtime = transtime(year, &end, dstoffset),
- yearsecs = year_lengths[isleap(year)] * SECSPERDAY;
+ yearsecs = year_days(year) * SECSPERDAY;
bool reversed = endtime < starttime;
if (reversed) {
int_fast32_t swap = starttime;
@@ -1764,24 +1724,25 @@ tzparse(const char *name, struct state *sp, struct state const *basep)
if (reversed
|| (starttime < endtime
&& endtime - starttime < yearsecs)) {
- if (TZ_MAX_TIMES - 2 < timecnt)
- break;
- sp->ats[timecnt] = janfirst;
- if (! increment_overflow_time(&sp->ats[timecnt],
- janoffset + starttime)
- && atlo <= sp->ats[timecnt])
+ time_t at_added = TIME_T_MAX;
+ time_t at = janfirst;
+ if (! increment_overflow_time(&at, janoffset + starttime)
+ && atlo <= at) {
+ if (TZ_MAX_TIMES <= timecnt)
+ return false;
+ sp->ats[timecnt] = at_added = at;
sp->types[timecnt++] = !reversed;
- sp->ats[timecnt] = janfirst;
- if (! increment_overflow_time(&sp->ats[timecnt],
- janoffset + endtime)
- && atlo <= sp->ats[timecnt]) {
+ }
+ at = janfirst;
+ if (! increment_overflow_time(&at, janoffset + endtime)
+ && atlo <= at) {
+ if (TZ_MAX_TIMES <= timecnt)
+ return false;
+ sp->ats[timecnt] = at_added = at;
sp->types[timecnt++] = reversed;
}
- }
- if (endtime < leaplo) {
- yearlim = year;
- if (increment_overflow(&yearlim, years_of_observations))
- yearlim = INT_MAX;
+ if (at_added < leaplo)
+ yearlim = year + years_of_observations;
}
if (increment_overflow_time(&janfirst, janoffset + yearsecs))
break;
@@ -1792,7 +1753,7 @@ tzparse(const char *name, struct state *sp, struct state const *basep)
sp->ttis[0] = sp->ttis[1];
sp->typecnt = 1; /* Perpetual DST. */
} else if (years_of_observations <= year - yearbeg)
- sp->goback = sp->goahead = true;
+ sp->goahead = true;
} else {
dstlen = 0;
sp->typecnt = 1; /* only standard time */
@@ -1849,7 +1810,7 @@ zoneinit(struct state *sp, char const *name, char tzloadflags)
sp->timecnt = 0;
sp->typecnt = 0;
sp->charcnt = 0;
- sp->goback = sp->goahead = false;
+ sp->goahead = false;
init_ttinfo(&sp->ttis[0], 0, false, 0);
strcpy(sp->chars, utc);
return 0;
@@ -2069,12 +2030,15 @@ tzfree(timezone_t sp)
** since in that case tzset should have already done this step correctly.
** SETNAME's type is int_fast32_t for compatibility with gmtsub,
** but it is actually a boolean and its value should be 0 or 1.
+**
+** If LTM_YEAR, store the resulting year-1900 into *LTM_YEAR rather
+** than into the default TMP->tm_year; this prevents year overflow.
*/
/*ARGSUSED*/
static struct tm *
localsub(struct state const *sp, time_t const *timep, int_fast32_t setname,
- struct tm *const tmp)
+ struct tm *tmp, time_t *ltm_year)
{
register const struct ttinfo * ttisp;
register int i;
@@ -2083,52 +2047,39 @@ localsub(struct state const *sp, time_t const *timep, int_fast32_t setname,
if (sp == NULL) {
/* Don't bother to set tzname etc.; tzset has already done it. */
- return gmtsub(gmtptr, timep, 0, tmp);
+ return gmtsub(gmtptr, timep, 0, tmp, ltm_year);
}
- if ((sp->goback && t < sp->ats[0]) ||
- (sp->goahead && t > sp->ats[sp->timecnt - 1])) {
- time_t newt;
- register time_t seconds;
- register time_t years;
-
- if (t < sp->ats[0])
- seconds = sp->ats[0] - t;
- else seconds = t - sp->ats[sp->timecnt - 1];
- --seconds;
-
- /* Beware integer overflow, as SECONDS might
- be close to the maximum time_t. */
- years = seconds / SECSPERREPEAT * YEARSPERREPEAT;
- seconds = years * AVGSECSPERYEAR;
- years += YEARSPERREPEAT;
- if (t < sp->ats[0])
- newt = t + seconds + SECSPERREPEAT;
- else
- newt = t - seconds - SECSPERREPEAT;
+ if (sp->goahead && sp->ats[sp->timecnt - 1] < t) {
+ /* Avoid integer overflow when time_t is signed, by
+ using secs_div_2 twice; the full value would
+ always be even, so halving does not round. */
+ time_t
+ tlo = sp->ats[sp->timecnt - 1],
+ diffyears = ((t / 2 - tlo / 2
+ + ((t % 2 - tlo % 2 + 2) / 2 - 1))
+ / (SECSPERREPEAT / 2)
+ * YEARSPERREPEAT),
+ years = diffyears + YEARSPERREPEAT,
+ secs_div_2 = (diffyears * (AVGSECSPERYEAR / 2)
+ + SECSPERREPEAT / 2),
+ newt = t - secs_div_2 - secs_div_2,
+ ryear;
- if (newt < sp->ats[0] ||
- newt > sp->ats[sp->timecnt - 1])
- return NULL; /* "cannot happen" */
- result = localsub(sp, &newt, setname, tmp);
+ result = localsub(sp, &newt, setname, tmp, &ryear);
if (result) {
-# if defined ckd_add && defined ckd_sub
- if (t < sp->ats[0]
- ? ckd_sub(&result->tm_year,
- result->tm_year, years)
- : ckd_add(&result->tm_year,
- result->tm_year, years))
+ if (ltm_year)
+ *ltm_year = ryear + years;
+ else {
+# ifdef ckd_add
+ if (ckd_add(&result->tm_year, ryear, years))
return NULL;
# else
- register int_fast64_t newy;
-
- newy = result->tm_year;
- if (t < sp->ats[0])
- newy -= years;
- else newy += years;
+ time_t newy = ryear + years;
if (! (INT_MIN <= newy && newy <= INT_MAX))
return NULL;
result->tm_year = newy;
# endif
+ }
}
return result;
}
@@ -2152,9 +2103,9 @@ localsub(struct state const *sp, time_t const *timep, int_fast32_t setname,
** To get (wrong) behavior that's compatible with System V Release 2.0
** you'd replace the statement below with
** t += ttisp->tt_utoff;
- ** timesub(&t, 0, sp, tmp);
+ ** timesub(&t, 0, sp, tmp, ltm_year);
*/
- result = timesub(&t, ttisp->tt_utoff, sp, tmp);
+ result = timesub(&t, ttisp->tt_utoff, sp, tmp, ltm_year);
if (result) {
result->tm_isdst = ttisp->tt_isdst;
# ifdef TM_ZONE
@@ -2204,7 +2155,7 @@ struct tm *
localtime_rz(struct state *restrict sp, time_t const *restrict timep,
struct tm *restrict tmp)
{
- return localsub(sp, timep, 0, tmp);
+ return localsub(sp, timep, 0, tmp, NULL);
}
# endif
@@ -2219,7 +2170,7 @@ localtime_tzset(time_t const *timep, struct tm *tmp, bool setname)
}
if (0 <= tz_change_interval || setname || !lcl_is_set)
tzset_unlocked(!err, false, now);
- tmp = localsub(lclptr, timep, setname, tmp);
+ tmp = localsub(lclptr, timep, setname, tmp, NULL);
unlock(!err);
return tmp;
}
@@ -2246,11 +2197,11 @@ localtime_r(const time_t *restrict timep, struct tm *restrict tmp)
static struct tm *
gmtsub(ATTRIBUTE_MAYBE_UNUSED struct state const *sp, time_t const *timep,
- int_fast32_t offset, struct tm *tmp)
+ int_fast32_t offset, struct tm *tmp, time_t *ltm_year)
{
register struct tm * result;
- result = timesub(timep, offset, gmtptr, tmp);
+ result = timesub(timep, offset, gmtptr, tmp, ltm_year);
#ifdef TM_ZONE
/*
** Could get fancy here and deliver something such as
@@ -2273,7 +2224,7 @@ struct tm *
gmtime_r(time_t const *restrict timep, struct tm *restrict tmp)
{
gmtcheck();
- return gmtsub(gmtptr, timep, 0, tmp);
+ return gmtsub(gmtptr, timep, 0, tmp, NULL);
}
struct tm *
@@ -2294,7 +2245,7 @@ struct tm *
offtime_r(time_t const *restrict timep, long offset, struct tm *restrict tmp)
{
gmtcheck();
- return gmtsub(gmtptr, timep, offset, tmp);
+ return gmtsub(gmtptr, timep, offset, tmp, NULL);
}
struct tm *
@@ -2330,7 +2281,7 @@ leaps_thru_end_of(time_t y)
static struct tm *
timesub(const time_t *timep, int_fast32_t offset,
- const struct state *sp, struct tm *tmp)
+ const struct state *sp, struct tm *tmp, time_t *ltm_year)
{
register time_t tdays;
register const int * ip;
@@ -2380,7 +2331,7 @@ timesub(const time_t *timep, int_fast32_t offset,
idays += dayoff % DAYSPERREPEAT + 2 * DAYSPERREPEAT;
idays %= DAYSPERREPEAT;
/* Increase Y and decrease IDAYS until IDAYS is in range for Y. */
- while (year_lengths[isleap(y)] <= idays) {
+ while (year_days(y) <= idays) {
int tdelta = idays / DAYSPERLYEAR;
int_fast32_t ydelta = tdelta + !tdelta;
time_t newy = y + ydelta;
@@ -2392,29 +2343,34 @@ timesub(const time_t *timep, int_fast32_t offset,
y = newy;
}
+ if (ltm_year) {
+ *ltm_year = y - TM_YEAR_BASE;
+ } else {
#ifdef ckd_add
- if (ckd_add(&tmp->tm_year, y, -TM_YEAR_BASE)) {
- errno = EOVERFLOW;
- return NULL;
- }
+ if (ckd_add(&tmp->tm_year, y, -TM_YEAR_BASE)) {
+ errno = EOVERFLOW;
+ return NULL;
+ }
#else
- if (!TYPE_SIGNED(time_t) && y < TM_YEAR_BASE) {
- int signed_y = y;
- tmp->tm_year = signed_y - TM_YEAR_BASE;
- } else if ((!TYPE_SIGNED(time_t) || INT_MIN + TM_YEAR_BASE <= y)
- && y - TM_YEAR_BASE <= INT_MAX)
- tmp->tm_year = y - TM_YEAR_BASE;
- else {
- errno = EOVERFLOW;
- return NULL;
- }
+ if (!TYPE_SIGNED(time_t) && y < TM_YEAR_BASE) {
+ int signed_y = y;
+ tmp->tm_year = signed_y - TM_YEAR_BASE;
+ } else if ((!TYPE_SIGNED(time_t) || INT_MIN + TM_YEAR_BASE <= y)
+ && y - TM_YEAR_BASE <= INT_MAX)
+ tmp->tm_year = y - TM_YEAR_BASE;
+ else {
+ errno = EOVERFLOW;
+ return NULL;
+ }
#endif
+ }
tmp->tm_yday = idays;
/*
** The "extra" mods below avoid overflow problems.
*/
tmp->tm_wday = (TM_WDAY_BASE
- + ((tmp->tm_year % DAYSPERWEEK)
+ + ((y % DAYSPERWEEK - TM_YEAR_BASE % DAYSPERWEEK)
+ % DAYSPERWEEK
* (DAYSPERNYEAR % DAYSPERWEEK))
+ leaps_thru_end_of(y - 1)
- leaps_thru_end_of(TM_YEAR_BASE - 1)
@@ -2460,27 +2416,6 @@ timesub(const time_t *timep, int_fast32_t offset,
*/
static bool
-increment_overflow(int *ip, int j)
-{
-#ifdef ckd_add
- return ckd_add(ip, *ip, j);
-#else
- register int const i = *ip;
-
- /*
- ** 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;
-#endif
-}
-
-static bool
increment_overflow_64(int *ip, int_fast64_t j)
{
#ifdef ckd_add
@@ -2588,7 +2523,7 @@ mktmcpy(struct tm *dest, struct tm const *src)
static time_t
time2sub(struct tm *const tmp,
struct tm *funcp(struct state const *, time_t const *,
- int_fast32_t, struct tm *),
+ int_fast32_t, struct tm *, time_t *),
struct state const *sp,
const int_fast32_t offset,
bool *okayp,
@@ -2648,12 +2583,12 @@ time2sub(struct tm *const tmp,
while (mday <= 0) {
iinntt li = y - (yourtm.tm_mon <= 1);
- mday += year_lengths[isleap(li)];
+ mday += year_days(li);
y--;
}
while (DAYSPERLYEAR < mday) {
iinntt li = y + (1 < yourtm.tm_mon);
- mday -= year_lengths[isleap(li)];
+ mday -= year_days(li);
y++;
}
yourtm.tm_mday = mday;
@@ -2705,7 +2640,7 @@ time2sub(struct tm *const tmp,
t = lo;
else if (t > hi)
t = hi;
- if (! funcp(sp, &t, offset, &mytm)) {
+ if (! funcp(sp, &t, offset, &mytm, NULL)) {
/*
** Assume that t is too extreme to be represented in
** a struct tm; arrange things so that it is less
@@ -2761,7 +2696,7 @@ time2sub(struct tm *const tmp,
# endif
if (!v && !increment_overflow_time_64(&altt, offdiff)) {
struct tm alttm;
- if (funcp(sp, &altt, offset, &alttm)
+ if (funcp(sp, &altt, offset, &alttm, NULL)
&& alttm.tm_isdst == mytm.tm_isdst
&& alttm.TM_GMTOFF == yourtm.TM_GMTOFF
&& tmcomp(&alttm, &yourtm) == 0) {
@@ -2795,7 +2730,7 @@ time2sub(struct tm *const tmp,
utoff_diff(sp->ttis[j].tt_utoff,
sp->ttis[i].tt_utoff)))
continue;
- if (! funcp(sp, &newt, offset, &mytm))
+ if (! funcp(sp, &newt, offset, &mytm, NULL))
continue;
if (tmcomp(&mytm, &yourtm) != 0)
continue;
@@ -2813,7 +2748,7 @@ time2sub(struct tm *const tmp,
label:
if (increment_overflow_time_iinntt(&t, saved_seconds))
return WRONG;
- if (funcp(sp, &t, offset, tmp))
+ if (funcp(sp, &t, offset, tmp, NULL))
*okayp = true;
return t;
}
@@ -2821,7 +2756,7 @@ label:
static time_t
time2(struct tm * const tmp,
struct tm *funcp(struct state const *, time_t const *,
- int_fast32_t, struct tm *),
+ int_fast32_t, struct tm *, time_t *),
struct state const *sp,
const int_fast32_t offset,
bool *okayp)
@@ -2840,7 +2775,7 @@ time2(struct tm * const tmp,
static time_t
time1(struct tm *const tmp,
struct tm *funcp(struct state const *, time_t const *,
- int_fast32_t, struct tm *),
+ int_fast32_t, struct tm *, time_t *),
struct state const *sp,
const int_fast32_t offset)
{
diff --git a/private.h b/private.h
index 7918082dca1b..f2861c94ff53 100644
--- a/private.h
+++ b/private.h
@@ -255,6 +255,9 @@ strnlen (char const *s, size_t maxlen)
#ifndef ENOMEM
# define ENOMEM EINVAL
#endif
+#ifndef ENOSYS
+# define ENOSYS EINVAL
+#endif
#ifndef ENOTCAPABLE
# define ENOTCAPABLE EINVAL
#endif
@@ -264,6 +267,9 @@ strnlen (char const *s, size_t maxlen)
#ifndef EOVERFLOW
# define EOVERFLOW EINVAL
#endif
+#ifndef EPERM
+# define EPERM EINVAL
+#endif
#if HAVE_GETTEXT
# include <libintl.h>
@@ -305,6 +311,7 @@ extern int optind;
#ifndef HAVE_ISSETUGID
# if (defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
+ || defined __DragonFly__ \
|| (defined __linux__ && !defined __GLIBC__) /* Android, musl, etc. */ \
|| (defined __APPLE__ && defined __MACH__) || defined __sun)
# define HAVE_ISSETUGID 1
@@ -813,6 +820,7 @@ void tzset(void);
# if (202311 <= __STDC_VERSION__ \
|| defined __GLIBC__ || defined __tm_zone /* musl */ \
|| defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
+ || defined __DragonFly__ || defined __HAIKU__ \
|| (defined __APPLE__ && defined __MACH__))
# define HAVE_DECL_TIMEGM 1
# else
@@ -841,7 +849,8 @@ extern char **environ;
#ifndef HAVE_MEMPCPY
# if (defined mempcpy \
- || defined __FreeBSD__ || defined __NetBSD__ || defined __linux__)
+ || defined __FreeBSD__ || defined __NetBSD__ || defined __DragonFly__ \
+ || defined __linux__)
# define HAVE_MEMPCPY 1
# else
# define HAVE_MEMPCPY 0
@@ -906,6 +915,7 @@ time_t posix2time(time_t);
|| defined __GLIBC__ \
|| defined __tm_zone /* musl */ \
|| defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
+ || defined __DragonFly__ || defined __HAIKU__ \
|| (defined __APPLE__ && defined __MACH__))
# if !defined TM_GMTOFF && !defined NO_TM_GMTOFF
# define TM_GMTOFF tm_gmtoff
@@ -1170,6 +1180,7 @@ enum {
};
#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
+#define year_days(y) (DAYSPERNYEAR + isleap(y))
/*
** Since everything in isleap is modulo 400 (or a factor of 400), we know that
@@ -1183,6 +1194,6 @@ enum {
** We use this to avoid addition overflow problems.
*/
-#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400)
+#define year_sum_days(a, b) (DAYSPERNYEAR + isleap((a) % 400 + (b) % 400))
#endif /* !defined PRIVATE_H */
diff --git a/strftime.c b/strftime.c
index c249010561d1..a7d488499fdd 100644
--- a/strftime.c
+++ b/strftime.c
@@ -464,9 +464,7 @@ label:
int bot;
int top;
- len = isleap_sum(year, base) ?
- DAYSPERLYEAR :
- DAYSPERNYEAR;
+ len = year_sum_days(year, base);
/*
** What yday (-3 ... 3) does
** the ISO year begin on?
@@ -493,9 +491,8 @@ label:
break;
}
--base;
- yday += isleap_sum(year, base) ?
- DAYSPERLYEAR :
- DAYSPERNYEAR;
+ yday += year_sum_days(year,
+ base);
}
#ifdef XPG4_1994_04_09
if ((w == 52 &&
diff --git a/theory.html b/theory.html
index 8f76aaf0b3c8..d56e1a79df46 100644
--- a/theory.html
+++ b/theory.html
@@ -12,6 +12,7 @@
</head>
<body>
+<main>
<h1>Theory and pragmatics of the <code><abbr>tz</abbr></code> code and data</h1>
<nav>
<ul>
@@ -26,10 +27,10 @@
<li><a href="#leapsec">Leap seconds</a></li>
<li><a href="#calendar">Calendrical issues</a></li>
<li><a href="#planets">Time and time zones off earth</a></li>
+ <li><a href="#see-also">See also</a></li>
</ul>
</nav>
-<section>
<h2 id="scope">Scope of the <code><abbr>tz</abbr></code> database</h2>
<p>
The <a
@@ -112,9 +113,6 @@ It does not always make sense to talk about a timezone’s
“base offset”, which is not necessarily a single number.
</p>
-</section>
-
-<section>
<h2 id="naming">Timezone identifiers</h2>
<p>
Each timezone has a name that uniquely identifies the timezone.
@@ -345,8 +343,7 @@ These legacy names are mostly defined in the file
"<code>etcetera</code>".
Also, the file "<code>backward</code>" defines the legacy names
<code>Etc/GMT0</code>, <code>Etc/GMT-0</code>, <code>Etc/GMT+0</code>,
-<code>GMT0</code>, <code>GMT-0</code> and <code>GMT+0</code>,
-and the file "<code>northamerica</code>" defines the legacy names
+<code>GMT0</code>, <code>GMT-0</code>, <code>GMT+0</code>,
<code>EST5EDT</code>, <code>CST6CDT</code>,
<code>MST7MDT</code>, and <code>PST8PDT</code>.
</li>
@@ -387,11 +384,11 @@ and <code>zone.tab</code>.
<p>
The database defines each timezone name to be a zone, or a link to a zone.
-The source file <code>backward</code> defines links for backward
-compatibility; it does not define zones.
+The source file <code>backward</code> defines links
+and a few zones for backward compatibility.
Although <code>backward</code> was originally designed to be optional,
nowadays distributions typically use it
-and no great weight should be attached to whether a link
+and no great weight should be attached to whether a link or zone
is defined in <code>backward</code> or in some other file.
The source file <code>etcetera</code> defines names that may be useful
on platforms that do not support proleptic <code>TZ</code> strings
@@ -404,9 +401,7 @@ second information on platforms that support leap seconds.
Another <code>etcetera</code> name, <code>GMT</code>,
is used by older code releases.
</p>
-</section>
-<section>
<h2 id="abbreviations">Time zone abbreviations</h2>
<p>
When this package is installed, it generates time zone abbreviations
@@ -610,9 +605,7 @@ Israel.
To avoid ambiguity, use numeric <abbr>UT</abbr> offsets like
<code>-</code>0600 instead of time zone abbreviations like CST.
</p>
-</section>
-<section>
<h2 id="accuracy">Accuracy of the <code><abbr>tz</abbr></code> database</h2>
<p>
The <code><abbr>tz</abbr></code> database is not authoritative, and it
@@ -876,9 +869,7 @@ merely because two locations
differ in <abbr>LMT</abbr> or transitioned to standard time at
different dates.
</p>
-</section>
-<section>
<h2 id="functions">Time and date functions</h2>
<p>
The <code><abbr>tz</abbr></code> code contains time and date functions
@@ -1284,9 +1275,7 @@ The vestigial <abbr>API</abbr>s are:
much the better.
</li>
</ul>
-</section>
-<section>
<h2 id="stability">Interface stability</h2>
<p>
The <code><abbr>tz</abbr></code> code and data supply the following interfaces:
@@ -1353,9 +1342,7 @@ than Bangkok by putting <samp>Asia/Bangkok</samp> in the event’s record,
the application should be robust in the presence of timezone splits
between now and the future time.
</p>
-</section>
-<section>
<h2 id="leapsec">Leap seconds</h2>
<p>
Leap seconds were introduced in 1972 to accommodate the
@@ -1430,9 +1417,7 @@ Alternatively, you can install just one set of <abbr>TZif</abbr> files
in the first place; see the <code>REDO</code> variable in this package’s
<a href="https://en.wikipedia.org/wiki/Makefile">makefile</a>.
</p>
-</section>
-<section>
<h2 id="calendar">Calendrical issues</h2>
<p>
Calendrical issues are a bit out of scope for a time zone database,
@@ -1446,22 +1431,30 @@ Other information and sources are given in the file "<code>calendars</code>"
in the <code><abbr>tz</abbr></code> distribution.
They sometimes disagree.
</p>
-</section>
-<section>
<h2 id="planets">Time and time zones off Earth</h2>
<p>
-The European Space Agency is <a
-href="https://www.esa.int/Applications/Satellite_navigation/Telling_time_on_the_Moon">considering</a>
-the establishment of a reference timescale for the Moon, which has
+Efforts are underway to establish a reference timescale for the Moon, which has
days roughly equivalent to 29.5 Earth days, and where relativistic
effects cause clocks to tick slightly faster than on Earth.
-Also, <abbr title="National Aeronautics and Space Administration">NASA</abbr>
-has been <a
+The European Space Agency <a
+href="https://www.esa.int/Applications/Satellite_navigation/Telling_time_on_the_Moon">began
+considering</a> the problem in 2023, and in 2024
+<abbr title="National Aeronautics and Space Administration">NASA</abbr>
+was <a
href="https://bidenwhitehouse.archives.gov/wp-content/uploads/2024/04/Celestial-Time-Standardization-Policy.pdf">ordered</a>
to consider the establishment of Coordinated Lunar Time (<abbr>LTC</abbr>).
-It is not yet known whether the US and European efforts will result in
-multiple timescales on the Moon.
+In 2025 a team at China’s
+<a href="https://en.wikipedia.org/wiki/Purple_Mountain_Observatory">Purple
+Mountain Observatory</a> published
+<a href="https://doi.org/10.1051/0004-6361/202557345">Lunar Time Ephemeris
+440</a>, the first formal proposal for a standard
+for Temps Coordonné Lunaire (<abbr>TCL</abbr>).
+A <a href="https://www.bipm.org/documents/20126/284836054/CGPM-2026-Draft-Resolutions.pdf">draft
+resolution of the 13–15 October 2026 General Conference on Weights
+and Measures</a> recommends that the <a href="https://www.bipm.org/en/">Bureau
+International des Poids et Mesures</a> collaborate with space agencies
+and other organizations on this matter.
</p>
<p>
@@ -1583,7 +1576,15 @@ Sources for time on other planets:
(2016-01-20).
</li>
</ul>
-</section>
+</main>
+
+<aside>
+<h2 id="see-also">See also</h2>
+<ul>
+<li><a href="tz-link.html">Time zone and daylight saving time data</a></li>
+<li><a href="tz-art.html">Time and the arts</a></li>
+</ul>
+</aside>
<footer>
<hr>
diff --git a/tz-art.html b/tz-art.html
index 77c7983c49fd..57d64e5c53f9 100644
--- a/tz-art.html
+++ b/tz-art.html
@@ -9,6 +9,7 @@ ul {padding-left: 1.3rem;}
<title>Time and the arts</title>
</head>
<body>
+<main>
<h1>Time and the arts</h1>
<h2>Documentaries</h2>
<ul>
@@ -466,8 +467,10 @@ Unlike minimalism, it does not assume that the listener has plenty of time.
<li>
The webcomic <em>xkcd</em> has the strips
“<a href="https://xkcd.com/673/">The Sun</a>” (2009-12-09),
-“<a href="https://xkcd.com/1655/">Doomsday Clock</a>” (2016-03-14) and
+“<a href="https://xkcd.com/1655/">Doomsday Clock</a>” (2016-03-14),
“<a href="https://xkcd.com/2549/">Edge Cake</a>” (2021-12-01),
+“<a href="https://xkcd.com/2867/">DateTime</a>” (2023-12-13),
+and “<a href="https://xkcd.com/3272/">Time Change</a>” (2026-07-15),
along with the panels
“<a href="https://xkcd.com/448/">Good Morning</a>” (2008-07-11),
“<a href="https://xkcd.com/1017/">Backward in Time</a>” (2012-02-14),
@@ -482,8 +485,7 @@ along with the panels
“<a href="https://xkcd.com/2266/">Leap Smearing</a>” (2020-02-10),
“<a href="https://xkcd.com/2594/">Consensus Time</a>” (2022-03-16),
“<a href="https://xkcd.com/2846/">Daylight Saving Choice</a>” (2023-10-25),
-“<a href="https://xkcd.com/2854/">Date Line</a>” (2023-11-13),
-and “<a href="https://xkcd.com/2867/">DateTime</a>” (2023-12-13).
+and “<a href="https://xkcd.com/2854/">Date Line</a>” (2023-11-13).
The related book <em>What If?</em> has an entry
“<a href="https://what-if.xkcd.com/26/">Leap Seconds</a>” (2012-12-31).
</li>
@@ -611,10 +613,16 @@ but maybe in your time zone I haven’t finished yet. So stay tuned!”
(Goldie Hawn, <em>Rowan &amp; Martin’s Laugh-In</em> No. 65, 1970-03-09)
</li>
</ul>
+</main>
+
+<aside>
<h2>See also</h2>
<ul>
<li><a href="tz-link.html">Time zone and daylight saving time data</a></li>
+<li><a href="theory.html">Theory and pragmatics of the
+<code><abbr>tz</abbr></code> code and data</a></li>
</ul>
+</aside>
<footer>
<hr>
diff --git a/tz-how-to.html b/tz-how-to.html
index 82a6176bef25..5a7a4388fe1f 100644
--- a/tz-how-to.html
+++ b/tz-how-to.html
@@ -15,6 +15,7 @@ ul {padding-left: 1.3rem;}
</style>
</head>
<body>
+<main>
<h1>How to Read the <a href="https://en.wikipedia.org/wiki/Tz_database">tz
Database</a> Source Files</h1>
<h2>by Bill Seymour</h2>
@@ -415,17 +416,19 @@ Line. For example:</p>
<pre>
#Zone NAME STDOFF RULES FORMAT [UNTIL]
-Zone America/Juneau 15:02:19 - LMT 1867 Oct 18
+Zone America/Juneau 15:02:19 - LMT 1867 Oct 19 0:31:13u
-8:57:41 - LMT ...
</pre>
<p>When Alaska was purchased from Russia in 1867, the Date Line moved
from the Alaska/Canada border to the Bering Strait; and the time in
Alaska was then 24 hours earlier than it had
-been. <code>&lt;aside&gt;</code>(6 October in the Julian calendar,
-which Russia was still using then for religious reasons, was followed
-by <i>a second instance of the same day with a different name</i>, 18
-October in the Gregorian calendar. Isn’t civil time
+been. <code>&lt;aside&gt;</code>(This also means that, although the sun had set
+on Friday 6 October and risen on Saturday 7 October in the Julian calendar,
+which Russia was still using then for religious reasons, upon the transfer
+Saturday afternoon became Friday afternoon, and then the sun set and rose once
+more on <i>second instances of the same days with different names</i>,
+Friday 18 and Saturday 19 October in the Gregorian calendar. Isn’t civil time
wonderful? <code>8-)</code>)<code>&lt;/aside&gt;</code></p>
<p>The abbreviation, “LMT” stands for “local mean
@@ -660,14 +663,15 @@ Zone Pacific/Honolulu -10:31:26 - LMT 1896 Jan 13 12:00
</table>
<p>There will be a short quiz later. <code>8-)</code></p>
+</main>
+<footer>
<hr>
-<address>
This web page is in the public domain, so clarified as of
2015-10-20 by Bill Seymour.
<br>
All suggestions and corrections will be welcome; all flames will be amusing.
Mail to was at pobox dot com.
-</address>
+</footer>
</body>
</html>
diff --git a/tz-link.html b/tz-link.html
index 2a7da95e9d27..8f836909d7e8 100644
--- a/tz-link.html
+++ b/tz-link.html
@@ -11,6 +11,7 @@ ul {padding-left: 1.3rem;}
</style>
</head>
<body>
+<main>
<h1>Time zone and daylight saving time data</h1>
<p>
<a href="https://en.wikipedia.org/wiki/Time_zone">Time zone</a> and
@@ -61,7 +62,6 @@ area.
</ul>
</nav>
-<section>
<h2 id="tzdb">The <code><abbr title="time zone">tz</abbr></code> database</h2>
<p>
The <a href="https://en.wikipedia.org/wiki/Public_domain">public-domain</a>
@@ -84,6 +84,7 @@ title="Berkeley Software Distribution">BSD</abbr></a>,
<a href="https://netbsd.org">Net<abbr>BSD</abbr></a>,
<a href="https://www.openbsd.org">Open<abbr>BSD</abbr></a>,
<a href="https://www.chromium.org/chromium-os/">ChromiumOS</a>,
+<a href="https://en.wikipedia.org/wiki/OpenHarmony">OpenHarmony</a>,
<a href="https://cygwin.com">Cygwin</a>,
<a href="https://mariadb.org">MariaDB</a>,
<a href="https://musl.libc.org">musl libc</a>,
@@ -133,9 +134,7 @@ Universal Time</a> (<abbr>UTC</abbr>).
The database also records when daylight saving time was in use,
along with some time zone abbreviations such as <abbr>EST</abbr>
for Eastern Standard Time in the <abbr>US</abbr>.</p>
-</section>
-<section>
<h2 id="download">Downloading the <code><abbr>tz</abbr></code> database</h2>
<p>
The following <a
@@ -145,15 +144,15 @@ the latest release’s two
to a <abbr>GNU</abbr>/Linux or similar host.</p>
<pre><code>mkdir tzdb
cd tzdb
-<a href="https://www.gnu.org/software/wget/">wget</a> https://www.iana.org/time-zones/repository/tzcode-latest.tar.gz
-wget https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz
+<a href="https://www.gnu.org/software/wget/">wget</a> https://data.iana.org/time-zones/tzcode-latest.tar.gz
+wget https://data.iana.org/time-zones/tzdata-latest.tar.gz
<a href="https://www.gnu.org/software/gzip/">gzip</a> -dc tzcode-latest.tar.gz | <a href="https://www.gnu.org/software/tar/">tar</a> -xf -
gzip -dc tzdata-latest.tar.gz | tar -xf -
</code></pre>
<p>Alternatively, the following shell commands download the same
release in a single-tarball format containing extra data
useful for regression testing:</p>
-<pre><code>wget <a href="https://www.iana.org/time-zones/repository/tzdb-latest.tar.lz">https://www.iana.org/time-zones/repository/tzdb-latest.tar.lz</a>
+<pre><code>wget <a href="https://data.iana.org/time-zones/tzdb-latest.tar.lz">https://data.iana.org/time-zones/tzdb-latest.tar.lz</a>
<a href="https://www.nongnu.org/lzip/">lzip</a> -dc tzdb-latest.tar.lz | tar -xf -
</code></pre>
<p>These commands use convenience links to the latest release
@@ -183,7 +182,7 @@ title="Hypertext Transfer Protocol Secure">HTTPS</abbr></a>,
<a href="https://en.wikipedia.org/wiki/Rsync"><abbr
title="remote sync">rsync</abbr></a>, and
<a href="https://en.wikipedia.org/wiki/FTP"><abbr
-title="File Transfer Protocol">FTP</abbr></a>.
+title="File Transfer Protocol">FTP</abbr></a>.</p>
<p>Alternatively, a development repository of code and data can be
retrieved from <a href="https://github.com">GitHub</a> via the shell
command:</p>
@@ -205,9 +204,7 @@ Time Zone Information Format (<abbr>TZif</abbr>)</a>
The code also lets
you read a <abbr>TZif</abbr> file and interpret timestamps for that
location.</p>
-</section>
-<section>
<h2 id="changes">Changes to the <code><abbr>tz</abbr></code> database</h2>
<p>
The <code><abbr>tz</abbr></code> code and data
@@ -234,7 +231,10 @@ the process by tailoring the generic instructions in
the <code><abbr>tz</abbr> README</code> file and installing the latest
data yourself. System-specific instructions for installing the
latest <code><abbr>tz</abbr></code> data have also been published
-for <a href="https://www.ibm.com/support/pages/aix-time-zone-olson-tzdata-updates"><abbr>AIX</abbr></a>,
+for <a href="https://community.ibm.com/community/user/blogs/ravindra-shinde/2024/12/13/time-zone-update-tool-tz"><abbr>AIX</abbr></a>
+(and
+<a href="https://www.ibm.com/support/pages/aix-time-zone-olson-tzdata-updates">older
+<abbr>AIX</abbr></a>),
<a
href="https://source.android.com/docs/core/permissions/timezone-rules">Android</a>,
<a
@@ -276,16 +276,14 @@ found in <a href="theory.html">Theory and pragmatics of the
<a href="https://a0.github.io/a0-tzmigration/">A0 TimeZone Migration</a>
displays changes between recent <code><abbr>tzdb</abbr></code> versions.
</p>
-</section>
-<section>
<h2 id="coordinating">Coordinating with governments and distributors</h2>
<p>
As discussed in
“<a href="https://www.icann.org/en/blogs/details/how-time-zones-are-coordinated-13-03-2023-en">How
Time Zones Are Coordinated</a>”, the time zone database relies on
collaboration among governments, the time zone database volunteer
-community, and data distributors downstream.
+community, and data distributors downstream.</p>
<p>
If your government plans to change its time zone boundaries or
daylight saving rules, please send email as described in
@@ -326,9 +324,7 @@ The shorter the notice, the more likely clock problems will arise; see “<a
href="https://codeofmatt.com/on-the-timing-of-time-zone-changes/">On
the Timing of Time Zone Changes</a>” for examples.
</p>
-</section>
-<section>
<h2 id="commentary">Commentary on the <code><abbr>tz</abbr></code> database</h2>
<ul>
<li>The article
@@ -344,9 +340,7 @@ database’s style.</li>
<li><a href="https://doi.org/10.1145/3340301.3341125">What time is it:
managing time in the internet</a> analyzes the database longitudinally.</li>
</ul>
-</section>
-<section>
<h2 id="web">Web sites using recent versions of the
<code><abbr>tz</abbr></code> database</h2>
<p>
@@ -380,9 +374,7 @@ calculates the current time difference between locations.</li>
<li><a href="https://www.wx-now.com">Weather Now</a> and
<a href="https://www.thetimenow.com">The Time Now</a> list the weather too.</li>
</ul>
-</section>
-<section>
<h2 id="protocols">Network protocols for <code><abbr>tz</abbr></code> data</h2>
<ul>
<li><a href="https://www.rfc-editor.org/info/rfc7808/">Time Zone
@@ -405,9 +397,7 @@ title="Extensible Markup Language">XML</abbr></a> format, and a variant
uses <a href="https://www.json.org/json-en.html"><abbr
title="JavaScript Object Notation">JSON</abbr></a> format.</li>
</ul>
-</section>
-<section>
<h2 id="compilers">Other <code><abbr>tz</abbr></code> compilers</h2>
<p>Although some of these do not fully support
<code><abbr>tz</abbr></code> data, in recent <code><abbr>tzdb</abbr></code>
@@ -416,7 +406,7 @@ running the command <code>make rearguard_tarballs</code> and compiling
from the resulting tarballs instead.</p>
<ul>
<li><a href="https://github.com/libical">Libical</a>
-contains <code>vzic</code>, <a
+contains <code>vzic</code>, a <a
href="https://en.wikipedia.org/wiki/C_(programming_language)">C</a>
program that compiles
<code><abbr>tz</abbr></code> source into iCalendar-compatible VTIMEZONE files.
@@ -425,6 +415,23 @@ available under the <a
href="https://www.gnu.org/copyleft/gpl.html"><abbr>GNU</abbr>
General Public License (<abbr
title="General Public License">GPL</abbr>)</a>.</li>
+<li>The <a id="TZDP"
+href="https://howardhinnant.github.io/date/tz.html">Time Zone
+Database Parser</a> contains a
+<a href="https://en.wikipedia.org/wiki/C++">C++</a> runtime parser
+supporting the <a
+href="https://en.cppreference.com/cpp/chrono#Time_zone"><code>std::chrono::time_zone</code> API</a>
+that is standardized in
+<a href="https://en.wikipedia.org/wiki/C%2B%2B20">C++20</a> and later.
+It is freely available under the
+<a href="https://en.wikipedia.org/wiki/MIT_License"><abbr
+title="Massachusetts Institute of Technology">MIT</abbr> license</a>.
+This approach is used by the <code>std::chrono::time_zone</code> implementation
+of <a href="https://gcc.gnu.org/"><abbr
+title="the GNU Compiler Collection">GCC</abbr></a>
+<code>libstdc++</code>, which is available under the GPL combined
+with the <a href="https://www.gnu.org/licenses/gcc-exception.html">GCC
+Runtime Library Exception</a>.</li>
<li><a
href="https://metacpan.org/release/DateTime-TimeZone">DateTime::TimeZone</a>
contains a script <code>parse_olson</code> that compiles
@@ -436,14 +443,6 @@ available under both the <abbr>GPL</abbr> and the Perl Artistic
License. DateTime::TimeZone also contains a script
<code>tests_from_zdump</code> that generates test cases for each clock
transition in the <code><abbr>tz</abbr></code> database.</li>
-<li>The <a href="https://howardhinnant.github.io/date/tz.html">Time Zone
-Database Parser</a> is a
-<a href="https://en.wikipedia.org/wiki/C++">C++</a> parser and
-runtime library with a <a
-href="https://en.cppreference.com/cpp/chrono"><code>std::chrono</code> API</a>
-that is a standard part of C++.
-It is freely available under the
-<abbr title="Massachusetts Institute of Technology">MIT</abbr> license.</li>
<li><a id="ICU" href="https://icu.unicode.org">International Components for
Unicode (<abbr>ICU</abbr>)</a> contains C/C++ and <a
href="https://en.wikipedia.org/wiki/Java_(programming_language)">Java</a>
@@ -498,6 +497,7 @@ License">LGPL</abbr>)</a>.</li>
<li><abbr>ICU</abbr> (mentioned <a href="#ICU">above</a>) contains compilers and
Java-based libraries.</li>
</ul>
+</li>
<li><a href="https://nodatime.org">Noda Time – Date and
time <abbr>API</abbr> for .NET</a>
is like Joda-Time and Time4J, but for the .NET framework instead of Java.
@@ -549,6 +549,7 @@ objects</a> let programs access an abstract view of
<code><abbr>tzdb</abbr></code> data, and are designed to replace <a
href="https://codeofmatt.com/javascript-date-type-is-horribly-broken/">JavaScript’s
problematic <code>Date</code> objects</a> when working with dates and times.
+</li>
<li><a href="https://github.com/JuliaTime">JuliaTime</a> contains a
compiler from <code><abbr>tz</abbr></code> source into
<a href="https://julialang.org">Julia</a>. It is freely available
@@ -587,9 +588,7 @@ source into text files, along with a runtime that can read those
files. Tcl is freely available under a <abbr>BSD</abbr>-style
license.</li>
</ul>
-</section>
-<section>
<h2 id="TZif">Other <abbr>TZif</abbr> readers</h2>
<ul>
<li>The <a
@@ -604,6 +603,17 @@ a <abbr>TZif</abbr> file reader written in C that
creates a <code>GTimeZone</code> object representing sets
of <abbr>UT</abbr> offsets.
It is freely available under the <abbr>LGPL</abbr>.</li>
+<li>The <a href="https://en.wikipedia.org/wiki/Musl">musl</a> C library
+also has an <abbr>TZif</abbr> reader.
+It is freely available under the <abbr>MIT</abbr> license.</li>
+<li>The Time Zone Database Parser for C++, mentioned <a href="#TZDP">above,</a>
+can be configured to read TZif binary files.
+A similar approach is taken in
+<a href="https://www.llvm.org/"><abbr>LLVM</abbr></a>
+<code>std::chrono::time_zone</code>, which is available
+under the Apache License with
+<a href="https://llvm.org/docs/DeveloperPolicy.html#open-source-licensing-terms">LLVM
+Exceptions</a>.</li>
<li>The
<a href="https://github.com/bloomberg/bde/wiki">BDE Standard Library</a>’s
<code>baltzo::TimeZoneUtil</code> component contains a C++
@@ -659,15 +669,20 @@ converts <abbr>TZif</abbr> data into
<abbr>JSON</abbr>-format data suitable for use
in its JavaScript library for time zone conversion. Dates before 1970
are not supported.</li>
+<li>The <a href="https://crates.io/crates/jiff">jiff</a> datetime library
+for <a href="https://rust-lang.org/">Rust</a> either compiles
+<code><abbr>tz</abbr></code> source into Rust,
+or consults <abbr>TZif</abbr> data at runtime.
+It is in the public domain.
+It intends to supplant the soft-deprecated
+<a href="https://crates.io/crates/chrono-tz">Chrono-TZ</a>.</li>
<li>The <a
href="https://hackage.haskell.org/package/timezone-olson">timezone-olson</a>
package contains <a href="https://www.haskell.org">Haskell</a> code that
parses and uses <abbr>TZif</abbr> data. It is freely
available under a <abbr>BSD</abbr>-style license.</li>
</ul>
-</section>
-<section>
<h2 id="software">Other <code><abbr>tz</abbr></code>-based time zone software</h2>
<ul>
<li><a href="https://foxclocks.org">FoxClocks</a>
@@ -684,7 +699,10 @@ data (mentioned <a href="#CLDR">below</a>) used by the
<a href="https://en.wikipedia.org/wiki/Windows_Runtime">Windows Runtime</a> /
<a href="https://en.wikipedia.org/wiki/Universal_Windows_Platform">Universal Windows Platform</a> classes
<a href="https://learn.microsoft.com/en-us/uwp/api/Windows.Globalization.DateTimeFormatting.DateTimeFormatter"><code>DateTimeFormatter</code></a> and
-<a href="https://learn.microsoft.com/en-us/uwp/api/windows.globalization.calendar"><code>Calendar</code></a>.
+<a href="https://learn.microsoft.com/en-us/uwp/api/windows.globalization.calendar"><code>Calendar</code></a>,
+and with updates discussed in the
+<a href="https://techcommunity.microsoft.com/category/windows/blog/dstblog">Daylight
+Saving Time &amp; Time Zone Blog</a>.
<a id="System.TimeZoneInfo"
href="https://learn.microsoft.com/en-us/archive/blogs/bclteam/exploring-windows-time-zones-with-system-timezoneinfo-josh-free">Exploring
Windows Time Zones with <code>System.TimeZoneInfo</code></a> describes
@@ -700,16 +718,14 @@ to <code><abbr>tz</abbr></code> names.
These mappings can be performed programmatically via the
<a href="https://github.com/mattjohnsonpint/TimeZoneConverter">TimeZoneConverter</a>
.NET library,
-or the ICU Java and C++ libraries mentioned <a href="#ICU">above</a>.
+or the ICU Java and C++ libraries mentioned <a href="#ICU">above</a>.</li>
<li><a
href="https://www.oracle.com/java/">Oracle
Java</a> contains a copy of a subset of a recent
<code><abbr>tz</abbr></code> database in a
Java-specific format.</li>
</ul>
-</section>
-<section>
<h2 id="other-dbs">Other time zone databases</h2>
<ul>
<li><a href="https://www.astro.com/atlas">Time-zone Atlas</a>
@@ -733,9 +749,7 @@ Schedules Information Manual</a> of the
International Air Transport Association
gives current time zone rules for airports served by commercial aviation.</li>
</ul>
-</section>
-<section>
<h2 id="maps">Maps</h2>
<ul>
<li><a href="https://www.worldtimezone.com">World Time Zone Map
@@ -757,9 +771,7 @@ World Factbook</em></a>, formerly published by the
Although the maps’ pictorial quality is good,
the maps do not indicate daylight saving time.</li>
</ul>
-</section>
-<section>
<h2 id="boundaries">Time zone boundaries</h2>
<p>Geographical boundaries between timezones are available
from several <a
@@ -810,9 +822,7 @@ for land and territorial waters only). A captain can change ship’s
clocks any time after entering a new time zone; midnight changes are
common.</li>
</ul>
-</section>
-<section>
<h2 id="civil">Civil time concepts and history</h2>
<ul>
<li><a href="https://www.nist.gov/pml/time-and-frequency-division/popular-links/walk-through-time">A
@@ -835,9 +845,7 @@ time zone boundary.</li>
<li><a href="https://statoids.com/tconcept.html">Basic Time
Zone Concepts</a> discusses terminological issues behind time zones.</li>
</ul>
-</section>
-<section>
<h2 id="national">National histories of legal time</h2>
<dl>
<dt>Australia</dt>
@@ -935,9 +943,7 @@ Navy (SOHMA) publishes an annual <a
href="https://sohma.armada.mil.uy/index.php/servicios/datos-astronomicos" hreflang="es">almanac
(in Spanish)</a>.</dd>
</dl>
-</section>
-<section>
<h2 id="costs">Costs and benefits of time shifts</h2>
<p>Various sources argue for and against daylight saving time and time
zone shifts, and many scientific studies have been conducted. This
@@ -1016,9 +1022,7 @@ opposes <abbr>DST</abbr> changes and permanent <abbr>DST</abbr>,
and advocates that governments adopt
“permanent Standard Time for the health and safety of their citizens”.</li>
</ul>
-</section>
-<section>
<h2 id="precision">Precision timekeeping</h2>
<ul>
<li><a
@@ -1053,7 +1057,7 @@ a client’s time zone and daylight saving settings automatically.</li>
Scales</a> describes astronomical time scales like
<abbr title="Terrestrial Dynamic Time">TDT</abbr>,
<abbr title="Geocentric Coordinate Time">TCG</abbr>, and
-<abbr title="Barycentric Dynamic Time">TDB</abbr>.
+<abbr title="Barycentric Dynamic Time">TDB</abbr>.</li>
<li>The <a href="https://www.iau.org"><abbr
title="International Astronomical Union">IAU</abbr></a>’s <a
href="https://www.iausofa.org"><abbr
@@ -1170,9 +1174,7 @@ However, there is still no consensus on whether this is the best way
to replace leap seconds.
</li>
</ul>
-</section>
-<section>
<h2 id="notation">Time notation</h2>
<ul>
<li>The <a id="CLDR" href="https://cldr.unicode.org">Unicode Common Locale Data
@@ -1183,7 +1185,7 @@ contains French translations for “Eastern European Summer Time”,
<a href="https://unicode.org/cldr/charts/latest/by_type/">by-type
charts</a> show these values for many locales. Data values are available in
both <abbr title="Locale Data Markup Language">LDML</abbr>
-(an <abbr>XML</abbr> format) and <abbr>JSON</abbr>.
+(an <abbr>XML</abbr> format) and <abbr>JSON</abbr>.</li>
<li>
<a href="https://www.cl.cam.ac.uk/~mgk25/iso-time.html">A summary of
the international standard date and time notation</a> covers
@@ -1209,7 +1211,7 @@ An extension, <a href="https://www.rfc-editor.org/info/rfc9557/">Date
and Time on the Internet: Timestamps with Additional Information</a>
(Internet <abbr>RFC</abbr> 9557) extends this profile
to let you specify the <code><abbr>tzdb</abbr></code> timezone of a timestamp
-via suffixes like <code>[Asia/Tokyo]</code>.
+via suffixes like <code>[Asia/Tokyo]</code>.</li>
<li>
<a href="https://web.archive.org/web/20190130042457/https://www.hackcraft.net/web/datetime/">Date &amp; Time
Formats on the Web</a> surveys web- and Internet-oriented date and time
@@ -1243,16 +1245,16 @@ any future changes to the rules. One should never set
local time is nine hours ahead of <abbr>UT</abbr> and the time zone
is called “<abbr>GMT</abbr>”.</li>
</ul>
-</section>
+</main>
-<section>
+<aside>
<h2 id="see-also">See also</h2>
<ul>
<li><a href="theory.html">Theory and pragmatics of the
<code><abbr>tz</abbr></code> code and data</a></li>
<li><a href="tz-art.html">Time and the arts</a></li>
</ul>
-</section>
+</aside>
<footer>
<hr>
diff --git a/tzfile.5.txt b/tzfile.5.txt
index d812b0ea6003..00c2e62d398d 100644
--- a/tzfile.5.txt
+++ b/tzfile.5.txt
@@ -402,7 +402,6 @@ SEE ALSO
time(2), localtime(3), tzset(3), tzselect(8), zdump(8), zic(8).
Olson A, Eggert P, Murchison K. The Time Zone Information Format
- (TZif). October 2024. Internet RFC 9636 ⟨https://www.rfc-editor.org/
- rfc/rfc9636⟩ doi:10.17487/RFC9636 ⟨https://doi.org/10.17487/RFC9636⟩.
+ (TZif). October 2024. Internet RFC 9636 doi:10.17487/RFC9636.
Time Zone Database tzfile(5)
diff --git a/version b/version
index 9217a2da674e..b62e2687afbb 100644
--- a/version
+++ b/version
@@ -1 +1 @@
-2026c
+2026d
diff --git a/zdump.c b/zdump.c
index 54ef63c0dd29..c627f8bd4206 100644
--- a/zdump.c
+++ b/zdump.c
@@ -719,7 +719,7 @@ delta_nonneg(struct tm *newp, struct tm *oldp)
intmax_t sec = SECSPERREPEAT, result = cycles * sec;
int tmy = oldp->tm_year + cycles * YEARSPERREPEAT;
for ( ; tmy < newp->tm_year; ++tmy)
- result += DAYSPERNYEAR + isleap_sum(tmy, TM_YEAR_BASE);
+ result += year_sum_days(tmy, TM_YEAR_BASE);
result += newp->tm_yday - oldp->tm_yday;
result *= HOURSPERDAY;
result += newp->tm_hour - oldp->tm_hour;
@@ -746,7 +746,7 @@ adjusted_yday(struct tm const *a, struct tm const *b)
{
int yday = a->tm_yday;
if (b->tm_year < a->tm_year)
- yday += 365 + isleap_sum(b->tm_year, TM_YEAR_BASE);
+ yday += year_sum_days(b->tm_year, TM_YEAR_BASE);
return yday;
}
#endif
diff --git a/zic.8 b/zic.8
index 3e32e85c4796..fcdbb6736dd6 100644
--- a/zic.8
+++ b/zic.8
@@ -330,12 +330,8 @@ for the
POSIX Portable Character Set (PPCS)
.UE
and the encoding's non-unibyte characters should consist entirely of
-non-PPCS bytes. Non-PPCS characters typically occur only in comments:
-although output file names and time zone abbreviations can contain
-nearly any character, other software will work better if these are
-limited to the restricted syntax described under the
-.B \-v
-option.
+non-PPCS bytes.
+Non-PPCS characters typically occur only in comments.
.PP
Input lines are made up of fields.
Fields are separated from one another by one or more white space characters.
@@ -350,15 +346,16 @@ Any line that is blank (after comment stripping) is ignored.
Nonblank lines are expected to be of one of three types:
rule lines, zone lines, and link lines.
.PP
-Names must be in English and are case insensitive.
+Keywords are in English and are case insensitive.
+They are not reserved words.
They appear in several contexts, and include month and weekday names
-and keywords such as
+and other words such as
.BR "maximum" ,
.BR "only" ,
.BR "Rolling" ,
and
.BR "Zone" .
-A name can be abbreviated by omitting all but an initial prefix; any
+A keyword can be abbreviated by omitting all but an initial prefix; any
abbreviation must be unambiguous in context.
.PP
A rule line has the form
@@ -586,11 +583,26 @@ The name of the timezone.
This is the name used in creating the time conversion information file for the
timezone.
It should not contain a file name component
+that is empty or is
.q ".\&"
or
.q ".." ;
a file name component is a maximal substring that does not contain
.q "/" .
+Names can be further restricted by the operating system or file system.
+Most systems reject a name
+.IB A / B
+if there is another name
+.IR A ,
+some reject names differing only in case,
+and some reject names containing special characters like
+.q ":"
+and
+.q "\e" .
+Other software will work better if names are
+limited to the restricted syntax described under the
+.B \-v
+option.
.TP
.B STDOFF
The amount of time to add to UT to get standard time,
@@ -737,8 +749,16 @@ A link line has the form
.ta \w'Link\0\0'u +\w'Europe/Istanbul\0\0'u
Link TARGET LINK-NAME
.sp
+.fi
+where
+.B TARGET
+and
+.B LINK-NAME
+both have the same syntax as a zone line's
+.BR NAME .
For example:
.sp
+.nf
.ti +2
Link Europe/Istanbul Asia/Istanbul
.sp
@@ -752,10 +772,7 @@ field in some zone line or as the
field in some link line.
The
.B LINK-NAME
-field is used as an alternative name for that zone;
-it has the same syntax as a zone line's
-.B NAME
-field.
+field is an alternative name for that zone.
Links can chain together, although the behavior is unspecified if a
chain of one or more links does not terminate in a Zone name.
A link line can appear before the line that defines the link target.
diff --git a/zic.8.txt b/zic.8.txt
index 77b5c62f6878..945a810dcb50 100644
--- a/zic.8.txt
+++ b/zic.8.txt
@@ -186,13 +186,9 @@ FILES
zero or more lines, each ending in a newline byte and containing at
most 2048 bytes counting the newline, and without any NUL bytes. The
input text's encoding is typically UTF-8 or ASCII; it should have a
- unibyte representation for the POSIX Portable Character Set (PPCS)
- ⟨https://pubs.opengroup.org/onlinepubs/9799919799/basedefs/V1_chap06
- .html⟩ and the encoding's non-unibyte characters should consist
- entirely of non-PPCS bytes. Non-PPCS characters typically occur only
- in comments: although output file names and time zone abbreviations can
- contain nearly any character, other software will work better if these
- are limited to the restricted syntax described under the -v option.
+ unibyte representation for the POSIX Portable Character Set (PPCS) and
+ the encoding's non-unibyte characters should consist entirely of non-
+ PPCS bytes. Non-PPCS characters typically occur only in comments.
Input lines are made up of fields. Fields are separated from one
another by one or more white space characters. The white space
@@ -206,11 +202,11 @@ FILES
lines are expected to be of one of three types: rule lines, zone lines,
and link lines.
- Names must be in English and are case insensitive. They appear in
- several contexts, and include month and weekday names and keywords such
- as maximum, only, Rolling, and Zone. A name can be abbreviated by
- omitting all but an initial prefix; any abbreviation must be
- unambiguous in context.
+ Keywords are in English and are case insensitive. They are not
+ reserved words. They appear in several contexts, and include month and
+ weekday names and other words such as maximum, only, Rolling, and Zone.
+ A keyword can be abbreviated by omitting all but an initial prefix; any
+ abbreviation must be unambiguous in context.
A rule line has the form
@@ -330,8 +326,14 @@ FILES
NAME The name of the timezone. This is the name used in creating the
time conversion information file for the timezone. It should
- not contain a file name component “.” or “..”; a file name
- component is a maximal substring that does not contain “/”.
+ not contain a file name component that is empty or is “.” or
+ “..”; a file name component is a maximal substring that does not
+ contain “/”. Names can be further restricted by the operating
+ system or file system. Most systems reject a name A/B if there
+ is another name A, some reject names differing only in case, and
+ some reject names containing special characters like “:” and
+ “\”. Other software will work better if names are limited to
+ the restricted syntax described under the -v option.
STDOFF The amount of time to add to UT to get standard time, without
any adjustment for daylight saving. This field has the same
@@ -418,17 +420,17 @@ FILES
Link TARGET LINK-NAME
- For example:
+ where TARGET and LINK-NAME both have the same syntax as a zone line's
+ NAME. For example:
Link Europe/Istanbul Asia/Istanbul
The TARGET field should appear as the NAME field in some zone line or
- as the LINK-NAME field in some link line. The LINK-NAME field is used
- as an alternative name for that zone; it has the same syntax as a zone
- line's NAME field. Links can chain together, although the behavior is
- unspecified if a chain of one or more links does not terminate in a
- Zone name. A link line can appear before the line that defines the
- link target. For example:
+ as the LINK-NAME field in some link line. The LINK-NAME field is an
+ alternative name for that zone. Links can chain together, although the
+ behavior is unspecified if a chain of one or more links does not
+ terminate in a Zone name. A link line can appear before the line that
+ defines the link target. For example:
Link Greenwich G_M_T
Link Etc/GMT Greenwich
diff --git a/zic.c b/zic.c
index d5e521d6b664..6e18893af095 100644
--- a/zic.c
+++ b/zic.c
@@ -528,10 +528,6 @@ static const int len_months[2][MONSPERYEAR] = {
{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};
-static const int len_years[2] = {
- DAYSPERNYEAR, DAYSPERLYEAR
-};
-
static struct attype {
zic_t at;
bool dontmerge;
@@ -783,6 +779,7 @@ arg2num(char const *arg, int base, unsigned long maxval, char const *msgid)
#ifndef HAVE_SETMODE
# if (defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
+ || defined __DragonFly__ \
|| (defined __APPLE__ && defined __MACH__))
# define HAVE_SETMODE 1
# else
@@ -1467,6 +1464,12 @@ namecheck(const char *name)
register char const *component = name;
for (cp = name; *cp; cp++) {
unsigned char c = *cp;
+#if defined _WIN32 || defined __CYGWIN__
+ if (c == ':' || c == '\\') {
+ error(N_("file name '%s' contains '%c'"), name, c);
+ return false;
+ }
+#endif
if (noise && !strchr(benign, c)) {
warning((strchr(printable_and_not_benign, c)
? N_("file name '%s' contains byte '%c'")
@@ -1781,8 +1784,20 @@ dolink(char const *target, char const *linkname, bool staysymlink)
break;
}
link_errno = errno;
+ /* When hard links are not supported, some MS-Windows file system
+ drivers fail with EINVAL, contrary to the intent of
+ MS-FSA 42.0 (2025) section 2.1.5.15.7. */
+ if (link_errno == EINVAL)
+ link_errno = ENOTSUP;
}
#endif
+ /* On platforms like AIX, the Linux kernel, macOS, and Solaris,
+ link/linkat can fail with ENOSYS or EPERM if the file system
+ does not support hard links, or if other problems occur.
+ It is too much trouble to suss out the other problems. */
+ if (link_errno == ENOSYS || link_errno == EPERM)
+ link_errno = ENOTSUP;
+
if (link_errno == EXDEV || link_errno == ENOTSUP)
break;
@@ -1849,7 +1864,8 @@ dolink(char const *target, char const *linkname, bool staysymlink)
strerror(link_errno));
else if (symlink_errno < 0)
warning(N_("copy used because symbolic link not obvious"));
- else if (symlink_errno != ENOTSUP)
+ else if (symlink_errno != ENOSYS && symlink_errno != ENOTSUP
+ && symlink_errno != EPERM)
warning(N_("copy used because symbolic link failed: %s"),
strerror(symlink_errno));
}
@@ -2307,18 +2323,13 @@ inzsub(char **fields, int nfields, bool iscont)
static zic_t
getleapdatetime(char **fields, bool expire_line)
{
- register const char * cp;
register const struct lookup * lp;
- register zic_t i, j;
zic_t year;
- int month, day;
- zic_t dayoff, tod;
zic_t t;
char xs;
+ struct rule r;
- dayoff = 0;
- cp = fields[LP_YEAR];
- if (sscanf(cp, "%"SCNdZIC"%c", &year, &xs) != 1) {
+ if (sscanf(fields[LP_YEAR], "%"SCNdZIC"%c", &year, &xs) != 1) {
/*
** Leapin' Lizards!
*/
@@ -2332,38 +2343,20 @@ getleapdatetime(char **fields, bool expire_line)
leapminyear = year;
leapseen = true;
}
- j = EPOCH_YEAR;
- while (j != year) {
- if (year > j) {
- i = len_years[isleap(j)];
- ++j;
- } else {
- --j;
- i = -len_years[isleap(j)];
- }
- dayoff = oadd(dayoff, i);
- }
if ((lp = byword(fields[LP_MONTH], mon_names)) == NULL) {
error(N_("invalid month name"));
return -1;
}
- month = lp->l_value;
- j = TM_JANUARY;
- while (j != month) {
- i = len_months[isleap(year)][j];
- dayoff = oadd(dayoff, i);
- ++j;
- }
- cp = fields[LP_DAY];
- if (sscanf(cp, "%d%c", &day, &xs) != 1 ||
- day <= 0 || day > len_months[isleap(year)][month]) {
+ r.r_month = lp->l_value;
+ if (! (sscanf(fields[LP_DAY], "%d%c", &r.r_dayofmonth, &xs) == 1
+ && 0 < r.r_dayofmonth
+ && r.r_dayofmonth <= len_months[isleap(year)][r.r_month])) {
error(N_("invalid day of month"));
return -1;
}
- dayoff = oadd(dayoff, day - 1);
- t = omul(dayoff, SECSPERDAY);
- tod = gethms(fields[LP_TIME], N_("invalid time of day"));
- t = tadd(t, tod);
+ r.r_dycode = DC_DOM;
+ r.r_tod = gethms(fields[LP_TIME], N_("invalid time of day"));
+ t = rpytime(&r, year);
if (t < 0)
error(N_("leap second precedes Epoch"));
return t;
@@ -2419,6 +2412,8 @@ inlink(char **fields, int nfields)
error(N_("blank TARGET field on Link line"));
return;
}
+ if (! namecheck(fields[LF_TARGET]))
+ return;
if (! namecheck(fields[LF_LINKNAME]))
return;
l.l_filenum = filenum;
@@ -3552,10 +3547,13 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
} else
defaulttype = type;
} else {
- zic_t year;
- for (year = min_year; year <= max_year; ++year) {
+ zic_t year, next_year;
+ bool try_next_year = true;
+ for (year = min_year; try_next_year; year = next_year) {
if (useuntil && year > zp->z_untilrule.r_hiyear)
break;
+ try_next_year = false;
+ next_year = max_year;
/*
** Mark which rules to do in the current year.
** For those to do, calculate rpytime(rp, year);
@@ -3567,6 +3565,17 @@ outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
struct rule *rp = &zp->z_rules[j];
eats(zp->z_filenum, zp->z_linenum,
rp->r_filenum, rp->r_linenum);
+
+ /* Keep track of the earliest year after YEAR
+ in which some rule <= J applies. */
+ if (year < rp->r_hiyear) {
+ zic_t next = max(year + 1, rp->r_loyear);
+ if (next <= next_year) {
+ next_year = next;
+ try_next_year = true;
+ }
+ }
+
rp->r_todo = year >= rp->r_loyear &&
year <= rp->r_hiyear;
if (rp->r_todo) {
@@ -4147,7 +4156,7 @@ rpytime(const struct rule *rp, zic_t wantedy)
wantedy = y + (yrem + 2 * YEARSPERREPEAT) % YEARSPERREPEAT;
while (wantedy != y) {
- i = len_years[isleap(y)];
+ i = year_days(y);
dayoff = oadd(dayoff, i);
y++;
}