From nobody Sun Sep 20 17:07:38 2026 X-Original-To: dev-commits-src-branches@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4hnt8W4G8xz6s9mQ for ; Sun, 20 Sep 2026 17:07:39 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR2" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4hnt8W0PSMz4Jcn for ; Sun, 20 Sep 2026 17:07:39 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1789924059; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=OR4uM/CFJ5zr72hE/yg0NXaqnsRlnTXCo5tTYMmpUhI=; b=bJgHX9coP2V6dqVBvoO1uMxCPvNvCuW8ZP37xFMOBHlDp0Xsu9BhajgDgGZ5Y73qKh8Buz UajEOX4McwUdpCt7a4UCh8U+O5Epb4Z/KzcTPhFZT0LDG0LZ4KIFS13iyi0dE/qhf7FN5/ EjLx8GwochkcrdVp7353o1anjKIQ1HbKzLgBsXCBDHY5fh6ur0HKR45BoZQpf11ceJi0yW ze0CGxe/lOXAEYCBtkui3Jc03XL/VBEsvoyeerOdQF+0kWO7/0WGlwTwJMYDjH10/nd0vF gCI+2A0o/sDKqVTuNyK3oxcVOvLJHiH+uqgitMpfha+UJF8DqszraGYEEYZOPg== ARC-Seal: i=1; a=rsa-sha256; d=freebsd.org; s=dkim; cv=none; t=1789924059; b=mwyPnCI1rHsQAsqMZwNrCETU3s9kYGa0DS/p5+JToV4Qsa3tTyFFzRY52I1usQxcuzzASf Ym+5efTX2wk4bgZZdO4Cs95zpdrkILjCfZtl+GWbOAxl45c+pPApzulgq2Jz7+SFGoJAnt nDxdz3b2W8B47RmAuQ3lEyuv8ofPV/QavdJpnXRIB+V0ZtIMgJbMokiqMrdC1ic6/2l659 yaoBc3D2KXcOwBkWhSsI+p8OobG/blXibCDj+VTxOyJBJ0QK+GaBsho/VgpzQB/RwTUgtr xtBVKk0biVhAM7BZ9A+QlH3vAHaPQq2dNmEhjpfl5AS2sXpM6/U2/KwXuFgXpg== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1789924059; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=OR4uM/CFJ5zr72hE/yg0NXaqnsRlnTXCo5tTYMmpUhI=; b=vC6TUUoYe/RoSNA5r8xvtlmJyglrPRWZ/3HiokZamxLMjhtE5QC+TTQ8bptSObmYJHQ05Q spuQ67O5w4P+MqFegGmqU/JURYi0wis8BFMoAcNgKKNDvyWlIVzWbs0uh0VwoAm1ESDVVw SNCaJ7GqqcWk/34OiPU+58IblHpm//6edhcPhcHWp3R/3XKtoyH1mYca9kJxx2b9rC4oQK lk7suxeGdQXeCxKcRxx7Xi35c41no1JGLL+XjZxWQT24Jg3tNcw7IvNHChgGy6YazUMxbG o5u6Q+04OHC+n6jAoCytbhzzxCTzv6G8AXvwvYSz1ouOlA0z5ClQXlQ030rYsQ== ARC-Authentication-Results: i=1; mx1.freebsd.org; none Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4hnt8V6WhFzkJH for ; Sun, 20 Sep 2026 17:07:38 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 3128e by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Sun, 20 Sep 2026 17:07:38 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-branches@FreeBSD.org From: Dag-Erling=?utf-8?Q? Sm=C3=B8rg?=rav Subject: git: 36c6d5275677 - stable/15 - tzcode: Update to 2026d List-Id: Commits to the stable branches of the FreeBSD src repository List-Archive: https://lists.freebsd.org/archives/dev-commits-src-branches List-Help: List-Post: List-Subscribe: List-Unsubscribe: X-BeenThere: dev-commits-src-branches@freebsd.org Sender: owner-dev-commits-src-branches@FreeBSD.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: des X-Git-Repository: src X-Git-Refname: refs/heads/stable/15 X-Git-Reftype: branch X-Git-Commit: 36c6d5275677cd54e46880509fcc5a5dcebaa743 Auto-Submitted: auto-generated Date: Sun, 20 Sep 2026 17:07:38 +0000 Message-Id: <6ab012da.3128e.984ece4@gitrepo.freebsd.org> 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 AuthorDate: 2026-09-13 13:29:59 +0000 Commit: Dag-Erling Smørgrav 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” -. + It is also good to browse the mailing list archives 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 or the +See 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 # 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 ***