git: 36c6d5275677 - stable/15 - tzcode: Update to 2026d
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
Date: Sun, 20 Sep 2026 17:07:38 UTC
The branch stable/15 has been updated by des:
URL: https://cgit.FreeBSD.org/src/commit/?id=36c6d5275677cd54e46880509fcc5a5dcebaa743
commit 36c6d5275677cd54e46880509fcc5a5dcebaa743
Author: Dag-Erling Smørgrav <des@FreeBSD.org>
AuthorDate: 2026-09-13 13:29:59 +0000
Commit: Dag-Erling Smørgrav <des@FreeBSD.org>
CommitDate: 2026-09-20 16:51:12 +0000
tzcode: Update to 2026d
MFC after: 1 week
(cherry picked from commit 212e3524943222650688bd1f49d5eb4c9326de7f)
libc/stdtime: Catch up with tzcode 2026d
Fixes: 212e35249432 ("tzcode: Update to 2026c")
(cherry picked from commit 1689b875b4b80c152cf7d3652c72335a2149173e)
---
contrib/tzcode/CONTRIBUTING | 2 +-
contrib/tzcode/Makefile | 19 +-
contrib/tzcode/NEWS | 115 +++++++++++-
contrib/tzcode/README | 2 +-
contrib/tzcode/localtime.c | 399 ++++++++++++++++++------------------------
contrib/tzcode/private.h | 15 +-
contrib/tzcode/strftime.c | 9 +-
contrib/tzcode/theory.html | 63 +++----
contrib/tzcode/tz-art.html | 14 +-
contrib/tzcode/tz-how-to.html | 18 +-
contrib/tzcode/tz-link.html | 118 +++++++------
contrib/tzcode/version | 2 +-
contrib/tzcode/zdump.c | 4 +-
contrib/tzcode/zic.8 | 45 +++--
contrib/tzcode/zic.c | 89 +++++-----
lib/libc/stdtime/strftime.c | 8 +-
16 files changed, 507 insertions(+), 415 deletions(-)
diff --git a/contrib/tzcode/CONTRIBUTING b/contrib/tzcode/CONTRIBUTING
index c5fa803f7275..213ae67e962a 100644
--- a/contrib/tzcode/CONTRIBUTING
+++ b/contrib/tzcode/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/contrib/tzcode/Makefile b/contrib/tzcode/Makefile
index f5a22f1896a4..96a7462e9a28 100644
--- a/contrib/tzcode/Makefile
+++ b/contrib/tzcode/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/contrib/tzcode/NEWS b/contrib/tzcode/NEWS
index dd962b54d529..ae240d31b84e 100644
--- a/contrib/tzcode/NEWS
+++ b/contrib/tzcode/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/contrib/tzcode/README b/contrib/tzcode/README
index f22ec5492d09..0b69d8ea4a4d 100644
--- a/contrib/tzcode/README
+++ b/contrib/tzcode/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/contrib/tzcode/localtime.c b/contrib/tzcode/localtime.c
index cd1bc8bd7523..67eba0fb7459 100644
--- a/contrib/tzcode/localtime.c
+++ b/contrib/tzcode/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;
@@ -1201,30 +1210,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;
@@ -1235,13 +1223,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
@@ -1249,7 +1237,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;
@@ -1257,22 +1244,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
@@ -1291,10 +1281,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]);
@@ -1337,10 +1325,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)
@@ -1540,40 +1524,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.
*/
@@ -1582,14 +1552,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
@@ -1597,33 +1569,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;
}
/*
@@ -1673,11 +1636,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 == '<') {
@@ -1715,18 +1678,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)) {
@@ -1734,30 +1699,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;
@@ -1767,24 +1727,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;
@@ -1795,7 +1756,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 */
@@ -1852,7 +1813,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;
@@ -2089,12 +2050,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;
@@ -2103,52 +2067,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 (newt < sp->ats[0] ||
- newt > sp->ats[sp->timecnt - 1])
- return NULL; /* "cannot happen" */
- result = localsub(sp, &newt, setname, tmp);
+ 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;
+
+ 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;
}
@@ -2172,9 +2123,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
@@ -2224,7 +2175,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
@@ -2239,7 +2190,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;
}
@@ -2266,11 +2217,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
@@ -2293,7 +2244,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 *
@@ -2314,7 +2265,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 *
@@ -2350,7 +2301,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;
@@ -2400,7 +2351,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;
@@ -2412,29 +2363,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)
@@ -2479,27 +2435,6 @@ timesub(const time_t *timep, int_fast32_t offset,
** Normalize logic courtesy Paul Eggert.
*/
-static bool
-increment_overflow(int *ip, int j)
-{
-#ifdef ckd_add
- return ckd_add(ip, *ip, j);
*** 1226 LINES SKIPPED ***