diff --git a/contrib/tzcode/CONTRIBUTING b/contrib/tzcode/CONTRIBUTING --- a/contrib/tzcode/CONTRIBUTING +++ b/contrib/tzcode/CONTRIBUTING @@ -10,26 +10,27 @@ 1970, and undoubtedly errors remain in the code and data. Feel free to fill gaps or fix mistakes, and please email improvements to for use in the future. In your email, please give -reliable sources that reviewers can check. +reliable sources that reviewers can check. The mailing list and its +archives are public, so please do not send confidential information. ## Contributing technical changes To email small changes, please run a POSIX shell command like -'diff -u old/europe new/europe >myfix.patch', and attach -'myfix.patch' to the email. +‘diff -u old/europe new/europe >myfix.patch’, and attach +‘myfix.patch’ to the email. 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" +“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. Changes should contain commentary citing reliable sources. -Citations should use "https:" URLs if available. +Citations should use ‘https:’ URLs if available. For changes that fix sensitive security-related bugs, please see the -distribution's 'SECURITY' file. +distribution’s SECURITY file. Please submit changes against either the latest release or the main branch of the development @@ -54,11 +55,11 @@ git checkout -b mybranch - * Sleuth by using 'git blame'. For example, when fixing data for - Africa/Sao_Tome, if the command 'git blame africa' outputs a line - '2951fa3b (Paul Eggert 2018-01-08 09:03:13 -0800 1068) Zone - Africa/Sao_Tome 0:26:56 - LMT 1884', commit 2951fa3b should - provide some justification for the 'Zone Africa/Sao_Tome' line. + * Sleuth by using ‘git blame’. For example, when fixing data for + Africa/Sao_Tome, if the command ‘git blame africa’ outputs a line + ‘2951fa3b (Paul Eggert 2018-01-08 09:03:13 -0800 1068) Zone + Africa/Sao_Tome 0:26:56 - LMT 1884’, commit 2951fa3b should + provide some justification for the ‘Zone Africa/Sao_Tome’ line. * Edit source files. Include commentary that justifies the changes by citing reliable sources. @@ -69,28 +70,31 @@ ./zdump -v America/Los_Angeles Although builds assume only basic POSIX, they use extra features - if available. 'make check' accesses validator.w3.org unless you - lack 'curl' or use 'make CURL=:'. If you have the latest GCC, - "make CFLAGS='$(GCC_DEBUG_FLAGS)'" does extra checking. + if available. ‘make check’ accesses validator.w3.org unless you + lack ‘curl’ or use ‘make CURL=:’. If you have the latest GCC, + ‘make CFLAGS='$(GCC_DEBUG_FLAGS)'’ does extra checking. * For each separable change, commit it in the new branch, e.g.: git add northamerica git commit - See recent 'git log' output for the commit-message style. + See recent ‘git log’ output for the commit-message style. * Create patch files 0001-..., 0002-..., ... git format-patch main + * Check that the patch files and your email setup contain only + information that you want to make public. + * After reviewing the patch files, send the patches to for others to review. git send-email main For an archived example of such an email, see - "[PROPOSED] Fix off-by-1 error for Jamaica and T&C before 1913" + “[PROPOSED] Fix off-by-1 error for Jamaica and T&C before 1913” . * Start anew by getting current with the main branch again diff --git a/contrib/tzcode/Makefile b/contrib/tzcode/Makefile --- a/contrib/tzcode/Makefile +++ b/contrib/tzcode/Makefile @@ -3,17 +3,30 @@ # 2009-05-17 by Arthur David Olson. # Request POSIX conformance; this must be the first non-comment line. .POSIX: +# By default, builds of code and data assume POSIX.1-2001 or later; +# this assumption can be relaxed by tailoring the build as described below. # On older platforms you may need to scrounge for POSIX conformance. # For example, on Solaris 10 (2005) with Sun Studio 12 aka Sun C 5.9 (2007), # use 'PATH=/usr/xpg4/bin:$PATH make CC=c99'. +# Reproducible builds of distribution tarballs also need a copy of the +# Git repository, and assume the behavior of the following programs +# (or later versions): +# Git 2.7.0 (2016) +# GNU Coreutils 6.3 (2006) +# GNU Tar 1.14 (2004) +# GnuPG 1.4 (2004) +# Although tzdb does not come with a software bill of materials, +# you should be able to construct one based on the above information, +# your platform, and the way you use this Makefile. # To affect how this Makefile works, you can run a shell script like this: # # #!/bin/sh -# make CC='gcc -std=gnu23' "$@" +# make CFLAGS='-O2 -DHAVE_GETTEXT=0' "$@" # -# This example script is appropriate for a circa 2024 GNU/Linux system -# where a non-default setting enables this package's optional use of C23. +# This example script is appropriate for a GNU/Linux system +# which needs more optimization than default, and which does not want +# gettext's internationalization of diagnostics. # # Alternatively, you can simply edit this Makefile to tailor the following # macro definitions. @@ -53,28 +66,6 @@ LOCALTIME= Factory -# The POSIXRULES macro controls interpretation of POSIX-like TZ -# settings like TZ='EET-2EEST' that lack DST transition rules. -# If POSIXRULES is '-', no template is installed; this is the default. -# Any other value for POSIXRULES is obsolete and should not be relied on, as: -# * It does not work correctly in popular implementations such as GNU/Linux. -# * It does not work even in tzcode, except for historical timestamps -# that precede the last explicit transition in the POSIXRULES file. -# Hence it typically does not work for current and future timestamps. -# If, despite the above, you want a template for handling these settings, -# you can change the line below (after finding the timezone you want in the -# one of the $(TDATA) source files, or adding it to a source file). -# Alternatively, if you discover you've got the wrong timezone, you can just -# 'zic -p -' to remove it, or 'zic -p rightzone' to change it. -# Use the command -# make zonenames -# to get a list of the values you can use for POSIXRULES. - -POSIXRULES= - - -# Also see TZDEFRULESTRING below, which takes effect only -# if POSIXRULES is '-' or if the template file cannot be accessed. - # Installation locations. # @@ -150,13 +141,14 @@ # below. If you want both sets of data available, with leap seconds counted # normally, use # REDO= right_posix -# below. POSIX mandates that leap seconds not be counted; for compatibility -# with it, use "posix_only" or "posix_right". Use POSIX time on systems with +# below. POSIX mandates that leap seconds not be counted, and a +# nonnegative TZ_CHANGE_INTERVAL also assumes this, so to be compatible with +# these, use "posix_only" or "posix_right". Use POSIX time on systems with # leap smearing; this can work better than unsmeared "right" time with # applications that are not leap second aware, and is closer to unsmeared # "right" time than unsmeared POSIX time is (e.g., 0.5 vs 1.0 s max error). -REDO= posix_right +REDO= posix_only # Whether to put an "Expires" line in the leapseconds file. # Use EXPIRES_LINE=1 to put the line in, 0 to omit it. @@ -206,6 +198,12 @@ UTF8_LOCALE= en_US.utf8 +# Extra flags for producing man page files like tzfile.5.txt. +# These flags are used only if groff (or mandoc) is present. +# Each option should begin with "-" and should lack shell metacharacters. +# Plausible options include -Tascii and -Tutf8. +MANFLAGS= -Tutf8 + # Non-default libraries needed to link. # On some hosts, this should have -lintl unless CFLAGS has -DHAVE_GETTEXT=0. LDLIBS= @@ -219,14 +217,19 @@ # -DEPOCH_OFFSET=N if the 'time' function returns a value N greater # than what POSIX specifies, assuming local time is UT. # For example, N is 252460800 on AmigaOS. +# -DFREE_PRESERVES_ERRNO=[01] if the 'free' function munges or preserves errno +# (default is guessed) # -DHAVE_DECL_ASCTIME_R=0 if does not declare asctime_r # on POSIX platforms predating POSIX.1-2024 # -DHAVE_DECL_ENVIRON if declares 'environ' # -DHAVE_DECL_TIMEGM=0 if does not declare timegm # -DHAVE_DIRECT_H if mkdir needs (MS-Windows) +# -DHAVE_FCHMOD=0 if your system lacks the fchmod function # -DHAVE__GENERIC=0 if _Generic does not work* +# -DHAVE_GETEUID=0 if gete?[ug]id do not work # -DHAVE_GETRANDOM if getrandom works (e.g., GNU/Linux), # -DHAVE_GETRANDOM=0 to avoid using getrandom +# -DHAVE_GETRESUID=0 if getres[ug]id do not work # -DHAVE_GETTEXT if gettext works (e.g., GNU/Linux, FreeBSD, Solaris), # where LDLIBS also needs to contain -lintl on some hosts; # -DHAVE_GETTEXT=0 to avoid using gettext @@ -234,28 +237,46 @@ # ctime_r and asctime_r incompatibly with POSIX.1-2017 and earlier # (Solaris when _POSIX_PTHREAD_SEMANTICS is not defined). # -DHAVE_INTTYPES_H=0 if does not work*+ +# -DHAVE_ISSETUGID=1 if issetugid works, 0 otherwise (default is guessed) +# If 0, you may also use -DHAVE_SYS_AUXV_H=1 if works, +# 0 otherwise (default is guessed). # -DHAVE_LINK=0 if your system lacks a link function # -DHAVE_LOCALTIME_R=0 if your system lacks a localtime_r function # -DHAVE_LOCALTIME_RZ=0 if you do not want zdump to use localtime_rz # localtime_rz can make zdump significantly faster, but is nonstandard. # -DHAVE_MALLOC_ERRNO=0 if malloc etc. do not set errno on failure. +# -DHAVE_MEMPCPY=1 if your system has mempcpy, 0 if not (default is guessed) # -DHAVE_POSIX_DECLS=0 if your system's include files do not declare -# functions like 'link' or variables like 'tzname' required by POSIX +# variables like 'tzname' required by POSIX +# -DHAVE_PWD_H=0 if your system lacks pwd.h, grp.h and corresponding functions +# If 0, you may also need -Dgid_t=G -Duid_t=U +# to define gid_t and uid_t to be types G and U. # -DHAVE_SETENV=0 if your system lacks the setenv function +# -DHAVE_SETMODE=[01] if your system lacks or has the setmode and getmode +# functions (default is guessed) # -DHAVE_SNPRINTF=0 if your system lacks the snprintf function+ # -DHAVE_STDCKDINT_H=0 if neither nor substitutes like # __builtin_add_overflow work* # -DHAVE_STDINT_H=0 if does not work*+ # -DHAVE_STRFTIME_L if declares locale_t and strftime_l # -DHAVE_STRDUP=0 if your system lacks the strdup function +# -DHAVE_STRNLEN=0 if your system lacks the strnlen function+ # -DHAVE_STRTOLL=0 if your system lacks the strtoll function+ +# -DHAVE_STRUCT_STAT_ST_CTIM=0 if struct stat lacks a status-change member +# of type struct timespec, so code should use st_ctime instead; +# but if the status-change member name is st_ctimespec, +# use -Dst_ctim=st_ctimespec instead (default is guessed)+ +# -DHAVE_STRUCT_TIMESPEC=0 if your system lacks struct timespec+ # -DHAVE_SYMLINK=0 if your system lacks the symlink function # -DHAVE_SYS_STAT_H=0 if does not work* +# If 0, you may also need -Dmode_t=M to define mode_t to be type M. # -DHAVE_TZSET=0 if your system lacks a tzset function # -DHAVE_UNISTD_H=0 if does not work* # -DHAVE_UTMPX_H=0 if does not work* # -Dlocale_t=XXX if your system uses XXX instead of locale_t # -DMKTIME_MIGHT_OVERFLOW if mktime might fail due to time_t overflow +# -DOPENAT_TZDIR if tzset should use openat on TZDIR then a relative open. +# See localtime.c for details. # -DPORT_TO_C89 if tzcode should also run on mostly-C89 platforms+ # Typically it is better to use a later standard. For example, # with GCC 4.9.4 (2016), prefer '-std=gnu11' to '-DPORT_TO_C89'. @@ -274,15 +295,46 @@ # -DTHREAD_SAFE to make localtime.c thread-safe, as POSIX requires; # not needed by the main-program tz code, which is single-threaded. # Append other compiler flags as needed, e.g., -pthread on GNU/Linux. +# The following options can also be used: +# -DTHREAD_PREFER_SINGLE to prefer speed in single-threaded apps, +# at some cost in CPU time and energy in multi-threaded apps. +# The following options can also be used: +# -DHAVE___ISTHREADED=1 if there is an extern int __isthreaded +# variable, 0 otherwise (default is guessed) +# -DHAVE_SYS_SINGLE_THREADED_H=0 if works, +# 0 otherwise (default is guessed) +# -DTHREAD_RWLOCK to use read-write locks instead of mutexes. +# This can improve parallelism and thus save real time +# if many threads call tzcode functions simultaneously. +# It also costs CPU time and thus energy. +# -DTHREAD_TM_MULTI to have gmtime, localtime, and offtime +# return different struct tm * addresses in different threads. +# This supports nonportable programs that call +# gmtime/localtime/offtime when they should call +# gmtime_r/localtime_r/offtime_r to avoid races. +# Because the corresponding storage is freed on thread exit, +# this option is incompatible with POSIX.1-2024 and earlier. +# It also costs CPU time and memory. # -Dtime_tz=\"T\" to use T as the time_t type, rather than the system time_t # This is intended for internal use only; it mangles external names. +# -DTZ_CHANGE_INTERVAL=N if functions depending on TZ should check +# no more often than every N seconds for TZif file changes. +# If N is negative (the default), no such checking is done. +# This option is intended for platforms that want localtime etc. +# to respond to changes to a file selected by TZ, including to +# TZDEFAULT (normally /etc/localtime) if TZ is unset. +# On these platforms, REDO should be "posix_only" or "posix_right". +# This option does not affect tzalloc-allocated objects. # -DTZ_DOMAIN=\"foo\" to use "foo" for gettext domain name; default is "tz" # -DTZ_DOMAINDIR=\"/path\" to use "/path" for gettext directory; # the default is system-supplied, typically "/usr/lib/locale" +# -DTZ_RUNTIME_LEAPS=0 to disable runtime support for leap seconds. +# This conforms to POSIX, shrinks tzcode's attack surface, +# and is more efficient. However, it fails to support Internet +# RFC 9636's leap seconds. # -DTZDEFRULESTRING=\",date/time,date/time\" to default to the specified -# DST transitions for proleptic format TZ strings lacking them, -# in the usual case where POSIXRULES is '-'. If not specified, -# TZDEFRULESTRING defaults to US rules for future DST transitions. +# DST transitions for proleptic format TZ strings lacking them. +# If not specified, it defaults to US rules for future DST transitions. # This mishandles some past timestamps, as US DST rules have changed. # It also mishandles settings like TZ='EET-2EEST' for eastern Europe, # as Europe and US DST rules differ. @@ -314,7 +366,7 @@ # Select instrumentation via "make GCC_INSTRUMENT='whatever'". GCC_INSTRUMENT = \ -fsanitize=undefined -fsanitize-address-use-after-scope \ - -fsanitize-undefined-trap-on-error -fstack-protector + -fsanitize-trap=all -fstack-protector # Omit -fanalyzer from GCC_DEBUG_FLAGS, as it makes GCC too slow. GCC_DEBUG_FLAGS = -DGCC_LINT -g3 -O3 \ $(GCC_INSTRUMENT) \ @@ -332,12 +384,13 @@ -Wold-style-definition -Woverlength-strings -Wpointer-arith \ -Wshadow -Wshift-overflow=2 -Wstrict-overflow \ -Wstrict-prototypes -Wstringop-overflow=4 \ - -Wstringop-truncation -Wsuggest-attribute=cold \ + -Wsuggest-attribute=cold \ -Wsuggest-attribute=const -Wsuggest-attribute=format \ -Wsuggest-attribute=malloc \ -Wsuggest-attribute=noreturn -Wsuggest-attribute=pure \ -Wtrampolines -Wundef -Wunused-macros -Wuse-after-free=3 \ -Wvariadic-macros -Wvla -Wwrite-strings \ + -Wzero-as-null-pointer-constant \ -Wno-format-nonliteral -Wno-sign-compare -Wno-type-limits # # If your system has a "GMT offset" field in its "struct tm"s @@ -393,7 +446,9 @@ # functions to be added to the time conversion library. # "offtime" is like "gmtime" except that it accepts a second (long) argument # that gives an offset to add to the time_t when converting it. -# I.e., "offtime" is like calling "localtime_rz" with a fixed-offset zone. +# "offtime_r" is to "offtime" what "gmtime_r" is to "gmtime". +# I.e., "offtime" and "offtime_r" are like calling "localtime_rz" +# with a fixed-offset zone. # "timelocal" is nearly equivalent to "mktime". # "timeoff" is like "timegm" except that it accepts a second (long) argument # that gives an offset to use when converting to a time_t. @@ -451,6 +506,11 @@ https://hpiers.obspm.fr/iers/bul/bulc/ntp/leap-seconds.list # The file is generated by the IERS Earth Orientation Centre, in Paris. leaplist_TZ = Europe/Paris +# +# To fetch leap-seconds.list from NIST via a less-secure protocol +# and with less-volatile metadata, use these settings: +#leaplist_URI = ftp://ftp.boulder.nist.gov/pub/time/leap-seconds.list +#leaplist_TZ = America/Denver # The zic command and its arguments. @@ -510,13 +570,10 @@ SAFE_CHARSET= $(SAFE_CHARSET1)$(SAFE_CHARSET2)$(SAFE_CHARSET3) SAFE_CHAR= '[]'$(SAFE_CHARSET)'-]' -# These non-alphabetic, non-ASCII printable characters are Latin-1, -# and so are likely displayable even in editors like XEmacs 21 -# that have limited display capabilities. -UNUSUAL_OK_LATIN_1 = ¡¢£¤¥¦§¨©«¬®¯°±²³´¶·¸¹»¼½¾¿×÷ -# Non-ASCII non-letters that OK_CHAR allows, as these characters are -# useful in commentary. -UNUSUAL_OK_CHARSET= $(UNUSUAL_OK_LATIN_1) +# 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= §«°±»½¾×–‘’“”•→−≤★⟨⟩⯪ # Put this in a bracket expression to match spaces. s = [:space:] @@ -525,9 +582,6 @@ # This is the same as SAFE_CHAR, except that UNUSUAL_OK_CHARSET and # multibyte letters are also allowed so that commentary can contain a # few safe symbols and people's names and can quote non-English sources. -# Other non-letters are limited to ASCII renderings for the -# convenience of maintainers using XEmacs 21.5.34, which by default -# mishandles Unicode characters U+0100 and greater. OK_CHAR= '[][:alpha:]$(UNUSUAL_OK_CHARSET)'$(SAFE_CHARSET)'-]' # SAFE_LINE matches a line of safe characters. @@ -654,7 +708,6 @@ '$(DESTDIR)$(MANDIR)/man3' '$(DESTDIR)$(MANDIR)/man5' \ '$(DESTDIR)$(MANDIR)/man8' $(ZIC_INSTALL) -l $(LOCALTIME) \ - -p $(POSIXRULES) \ -t '$(DESTDIR)$(TZDEFAULT)' cp -f $(TABDATA) '$(DESTDIR)$(TZDIR)/.' cp tzselect '$(DESTDIR)$(BINDIR)/.' @@ -874,9 +927,9 @@ character-set.ck: $(ENCHILADA) $(UTF8_LOCALE_MISSING) || { \ sharp='#' && \ - ! grep -Env $(SAFE_LINE) $(MANS) date.1 $(MANTXTS) \ - $(MISC) $(SOURCES) $(WEB_PAGES) \ - CONTRIBUTING LICENSE README SECURITY \ + ! grep -Env $(SAFE_LINE) $(MANS) date.1 \ + $(MISC) $(SOURCES) \ + LICENSE \ version tzdata.zi && \ ! grep -Env $(SAFE_LINE)'|^UNUSUAL_OK_'$(OK_CHAR)'*$$' \ Makefile && \ @@ -888,11 +941,9 @@ white-space.ck: $(ENCHILADA) $(UTF8_LOCALE_MISSING) || { \ - enchilada='$(ENCHILADA)' && \ patfmt=' \t|[\f\r\v]' && pat=$$(printf "$$patfmt\\n") && \ ! grep -En "$$pat|[$s]\$$" \ - $${enchilada%leap-seconds.list*} \ - $${enchilada#*leap-seconds.list}; \ + $(ENCHILADA:leap-seconds.list=); \ } touch $@ @@ -959,8 +1010,10 @@ ./zdump -i -t 0,$$future \ $$(find "$$PWD/$@d" -name Etc -prune \ -o -type f ! -name '*.tab' -print) \ - >$@d/zdump-1970.tab + >$@d/zdump-1970.tab && \ $(AWK) \ + -v now=$$now \ + -v now_out=$@.out \ -v zdump_table=$@d/zdump-now.tab \ -f checknow.awk zonenow.tab $(AWK) \ @@ -970,7 +1023,8 @@ -v zdump_table=$@d/zdump-1970.tab \ -f checknow.awk rm -fr $@d - touch $@ + touch $@.out + mv $@.out $@ tables.ck: checktab.awk $(YDATA) backward zone.tab zone1970.tab for tab in $(ZONETABLES); do \ @@ -1031,7 +1085,7 @@ clean_misc: rm -fr *.ckd *.dir - rm -f *.ck *.core *.o *.out core core.* \ + rm -f *.ck *.core *.o *.out *.t core core.* \ date tzdir.h tzselect version.h zdump zic libtz.a clean: clean_misc rm -fr tzdb-*/ @@ -1059,7 +1113,7 @@ zic.8.txt: zic.8 $(MANTXTS): workman.sh - LC_ALL=C sh workman.sh $(@:.txt=) >$@.out + LC_ALL=C sh workman.sh $(MANFLAGS) $(@:.txt=) >$@.out mv $@.out $@ # Set file timestamps deterministically if possible, @@ -1108,7 +1162,7 @@ if git diff --quiet HEAD $$file; then \ time=$$(TZ=UTC0 git log -1 \ --format='tformat:%cd' \ - --date='format:%Y-%m-%dT%H:%M:%SZ' \ + --date='format-local:%Y-%m-%dT%H:%M:%SZ' \ $$file) && \ echo "+ touch -md $$time $$file" && \ touch -md $$time $$file; \ @@ -1207,12 +1261,12 @@ touch $@ TRADITIONAL_ASC = \ - tzcode$(VERSION).tar.gz.asc \ - tzdata$(VERSION).tar.gz.asc + tzcode$(VERSION).tar.gz.asc.t \ + tzdata$(VERSION).tar.gz.asc.t REARGUARD_ASC = \ - tzdata$(VERSION)-rearguard.tar.gz.asc + tzdata$(VERSION)-rearguard.tar.gz.asc.t ALL_ASC = $(TRADITIONAL_ASC) $(REARGUARD_ASC) \ - tzdb-$(VERSION).tar.lz.asc + tzdb-$(VERSION).tar.lz.asc.t tarballs rearguard_tarballs tailored_tarballs traditional_tarballs \ signatures rearguard_signatures traditional_signatures: \ @@ -1224,29 +1278,31 @@ # other means. Ordinarily these rules are used only by the above # non-_version rules, which set VERSION on the 'make' command line. tarballs_version: traditional_tarballs_version rearguard_tarballs_version \ - tzdb-$(VERSION).tar.lz + tzdb-$(VERSION).tar.lz.t rearguard_tarballs_version: \ - tzdata$(VERSION)-rearguard.tar.gz + tzdata$(VERSION)-rearguard.tar.gz.t traditional_tarballs_version: \ - tzcode$(VERSION).tar.gz tzdata$(VERSION).tar.gz + tzcode$(VERSION).tar.gz.t tzdata$(VERSION).tar.gz.t tailored_tarballs_version: \ - tzdata$(VERSION)-tailored.tar.gz + tzdata$(VERSION)-tailored.tar.gz.t signatures_version: $(ALL_ASC) rearguard_signatures_version: $(REARGUARD_ASC) traditional_signatures_version: $(TRADITIONAL_ASC) -tzcode$(VERSION).tar.gz: set-timestamps.out +tzcode$(VERSION).tar.gz.t: set-timestamps.out $(SETUP_TAR) && \ $$TAR -cf - \ $(COMMON) $(DOCS) $(SOURCES) | \ - gzip $(GZIPFLAGS) >$@.out - mv $@.out $@ + gzip $(GZIPFLAGS) >$(@:.t=) + $(SET_TIMESTAMP) $(@:.t=) $(COMMON) $(DOCS) $(SOURCES) + touch $@ -tzdata$(VERSION).tar.gz: set-timestamps.out +tzdata$(VERSION).tar.gz.t: set-timestamps.out $(SETUP_TAR) && \ $$TAR -cf - $(TZDATA_DIST) | \ - gzip $(GZIPFLAGS) >$@.out - mv $@.out $@ + gzip $(GZIPFLAGS) >$(@:.t=) + $(SET_TIMESTAMP) $(@:.t=) $(TZDATA_DIST) + touch $@ # Create empty files with a reproducible timestamp. CREATE_EMPTY = TZ=UTC0 touch -mt 202010122253.00 @@ -1255,7 +1311,7 @@ # for backwards compatibility with tz releases 2018e through 2022a. # They should go away eventually. To build rearguard tarballs you # can instead use 'make DATAFORM=rearguard tailored_tarballs'. -tzdata$(VERSION)-rearguard.tar.gz: rearguard.zi set-timestamps.out +tzdata$(VERSION)-rearguard.tar.gz.t: rearguard.zi set-timestamps.out rm -fr $@.dir mkdir $@.dir ln $(TZDATA_DIST) $@.dir @@ -1273,8 +1329,11 @@ (cd $@.dir && \ $$TAR -cf - \ $(TZDATA_DIST) pacificnew | \ - gzip $(GZIPFLAGS)) >$@.out - mv $@.out $@ + gzip $(GZIPFLAGS)) >$(@:.t=) + $(SET_TIMESTAMP) $(@:.t=) \ + $$(cd $@.dir && \ + ls $(TZDATA_DIST) pacificnew | sed 's,^,$@.dir/,') + touch $@ # Create a tailored tarball suitable for TZUpdater and compatible tools. # For example, 'make DATAFORM=vanguard tailored_tarballs' makes a tarball @@ -1283,7 +1342,7 @@ # traditional tarball, as data entries are put into 'etcetera' even if they # came from some other source file. However, the effect should be the same # for ordinary use, which reads all the source files. -tzdata$(VERSION)-tailored.tar.gz: set-timestamps.out +tzdata$(VERSION)-tailored.tar.gz.t: set-timestamps.out rm -fr $@.dir mkdir $@.dir : The dummy pacificnew pacifies TZUpdater 2.3.1 and earlier. @@ -1295,7 +1354,7 @@ cd $@.dir && \ $(CREATE_EMPTY) $(PRIMARY_YDATA) $(NDATA) backward \ $$pacificnew - (grep '^#' tzdata.zi && echo && cat $(DATAFORM).zi) \ + (sed '/^#/!d' tzdata.zi && echo && cat $(DATAFORM).zi) \ >$@.dir/etcetera touch -mr tzdata.zi $@.dir/etcetera sed -n \ @@ -1316,24 +1375,29 @@ ln $$links $@.dir $(SETUP_TAR) && \ (cd $@.dir && \ - $$TAR -cf - * | gzip $(GZIPFLAGS)) >$@.out - mv $@.out $@ + $$TAR -cf - *) | gzip $(GZIPFLAGS) >$(@:.t=) + $(SET_TIMESTAMP) $(@:.t=) \ + $$(cd $@.dir && ls * | sed 's,^,$@.dir/,') + touch $@ -tzdb-$(VERSION).tar.lz: set-timestamps.out set-tzs-timestamp.out +tzdb-$(VERSION).tar.lz.t: set-timestamps.out set-tzs-timestamp.out rm -fr tzdb-$(VERSION) mkdir tzdb-$(VERSION) ln $(ENCHILADA) tzdb-$(VERSION) $(SET_TIMESTAMP) tzdb-$(VERSION) tzdb-$(VERSION)/* $(SETUP_TAR) && \ - $$TAR -cf - tzdb-$(VERSION) | lzip -9 >$@.out - mv $@.out $@ + $$TAR -cf - tzdb-$(VERSION) | lzip -9 >$(@:.t=) + $(SET_TIMESTAMP) $(@:.t=) tzdb-$(VERSION) + touch $@ -tzcode$(VERSION).tar.gz.asc: tzcode$(VERSION).tar.gz -tzdata$(VERSION).tar.gz.asc: tzdata$(VERSION).tar.gz -tzdata$(VERSION)-rearguard.tar.gz.asc: tzdata$(VERSION)-rearguard.tar.gz -tzdb-$(VERSION).tar.lz.asc: tzdb-$(VERSION).tar.lz +tzcode$(VERSION).tar.gz.asc.t: tzcode$(VERSION).tar.gz.t +tzdata$(VERSION).tar.gz.asc.t: tzdata$(VERSION).tar.gz.t +tzdata$(VERSION)-rearguard.tar.gz.asc.t: tzdata$(VERSION)-rearguard.tar.gz.t +tzdb-$(VERSION).tar.lz.asc.t: tzdb-$(VERSION).tar.lz.t $(ALL_ASC): - $(GPG) --armor --detach-sign $? + $(GPG) --armor --detach-sign $(?:.t=) + $(SET_TIMESTAMP) $(@:.t=) $(?:.t=) + touch $@ TYPECHECK_CFLAGS = $(CFLAGS) -DTYPECHECK -D__time_t_defined -D_TIME_T typecheck: long-long.ck unsigned.ck diff --git a/contrib/tzcode/NEWS b/contrib/tzcode/NEWS --- a/contrib/tzcode/NEWS +++ b/contrib/tzcode/NEWS @@ -1,5 +1,237 @@ News for the tz database +Release 2026a - 2026-03-01 22:59:49 -0800 + + Briefly: + Moldova has used EU transition times since 2022. + The "right" TZif files are no longer installed by default. + -DTZ_RUNTIME_LEAPS=0 disables runtime support for leap seconds. + TZif files are no longer limited to 50 bytes of abbreviations. + zic is no longer limited to 50 leap seconds. + Several integer overflow bugs have been fixed. + + Changes to past and future timestamps + + Since 2022 Moldova has observed EU transition times, that is, it + has sprung forward at 03:00, not 02:00, and has fallen back at + 04:00, not 03:00. (Thanks to Heitor David Pinto.) + + Changes to data + + Remove Europe/Chisinau from zonenow.tab, as it now agrees with + Europe/Athens for future timestamps. + + Changes to build procedure + + The Makefile no longer by default installs an alternate set + of TZif files for system clocks that count leap seconds. + Install with 'make REDO=posix_right' to get the old default, + which is rarely used in major downstream distributions. + If your system clock counts leap seconds (contrary to POSIX), + it is better to install with 'make REDO=right_only'. + This change does not affect the leapseconds file, which is still + installed as before. + + The Makefile's POSIXRULES option, which was declared obsolete in + release 2019b, has been removed. The Makefile's build procedure + thus no longer optionally installs the obsolete posixrules file. + + Changes to code + + Compiling with the new option -DTZ_RUNTIME_LEAPS=0 disables + runtime support for leap seconds. Although this conforms to + POSIX, shrinks tzcode's attack surface, and is more efficient, + it fails to support Internet RFC 9636's leap seconds. + + zic now can generate, and localtime.c can now use, TZif files that + hold up to 256 bytes of abbreviations, counting trailing NULs. + The previous limit was 50 bytes, and some tzdata TZif files were + already consuming 40 bytes. zic -v warns if it generates a file + that exceeds the old 50-byte limit. + + zic -L can now generate TZif files with more than 50 leap seconds. + This helps test TZif readers not limited to 50 leap seconds, as + tzcode's localtime.c is; it has little immediate need for + practical timekeeping as there have been only 27 leap seconds and + possibly there will be no more, due to planned changes to UTC. + zic -v warns if its output exceeds the old 50-second limit. + + localtime.c no longer accesses the posixrules file generated by + zic -p. Hence for obsolete and nonconforming settings like + TZ="AST4ADT" it now typically falls back on US DST rules, rather + than attempting to override this fallback with the contents of the + posixrules file. This removes library support that was declared + obsolete in release 2019b, and fixes some undefined behavior. + (Undefined behavior reported by GitHub user Naveed8951.) + + The posix2time, posix2time_z, time2posix, and time2posix_z + functions now set errno=EOVERFLOW and return ((time_t) -1) if the + result is not representable. Formerly they had undefined behavior + that could in practice result in crashing, looping indefinitely, + or returning an incorrect result. As before, these functions are + defined only when localtime.c is compiled with the -DSTD_INSPIRED + option. + + Some other undefined behavior, triggered by TZif files containing + outlandish but conforming UT offsets or leap second corrections, + has also been fixed. (Some of these bugs reported by Naveed8951.) + + localtime.c no longer rejects TZif files that exactly fit in its + internal structures, fixing off-by-one typos introduced in 2014g. + + zic no longer generates a no-op transition when + simultaneous Rule and Zone changes cancel each other out. + This occurs in tzdata only in Asia/Tbilisi on 1997-03-30. + (Thanks to Renchunhui for a test case showing the bug.) + + zic no longer assumes you can fflush a read-only stream. + (Problem reported by Christos Zoulas.) + + zic no longer generates UT offsets equal to -2**31 and localtime.c + no longer accepts them, as they can cause trouble in both + localtime.c and its callers. RFC 9636 prohibits such offsets. + + zic -p now warns that the -p option is obsolete and likely + ineffective. + + +Release 2025c - 2025-12-10 14:42:37 -0800 + + Briefly: + Several code changes for compatibility with FreeBSD. + + Changes to past timestamps + + Baja California agreed with California’s DST rules in 1953 and in + 1961 through 1975, instead of observing standard time all year. + (Thanks to Alois Treindl.) + + Changes to build procedure + + Files in distributed tarballs now have correct commit times. + Formerly, the committer’s time zone was incorrectly ignored. + + Distribution products (*.asc, *.gz, and *.lz) now have + reproducible timestamps. Formerly, only the contents of the + compressed tarballs had reproducible timestamps. + + By default, distributed formatted man pages (*.txt) now use UTF-8 + and are left-adjusted more consistently. A new Makefile macro + MANFLAGS can override these defaults. (Thanks to G. Branden + Robinson for inspiring these changes.) + + Changes to code + + An unset TZ is no longer invalid when /etc/localtime is missing, + and is abbreviated "UTC" not "-00". This reverts to 2024b behavior. + (Problem and patch reported by Dag-Erling Smørgrav.) + + New function offtime_r, short for fixed-offset localtime_rz. + It is defined if STD_INSPIRED is defined. + (Patch from Dag-Erling Smørgrav.) + + tzset etc. are now more cautious about questionable TZ settings. + Privileged programs now reject TZ settings that start with '/', + unless they are TZDEFAULT (default "/etc/localtime") or + start with TZDIR then '/' (default "/usr/share/zoneinfo/"). + Unprivileged programs now require files to be regular files + and reject relative names containing ".." directory components; + formerly, only privileged programs did those two things. + These changes were inspired by similar behavior in FreeBSD. + On NetBSD, unprivileged programs now use O_REGULAR to check + whether a TZ setting starting with '/' names a regular file, + avoiding a minor security race still present elsewhere. + TZ strings taken from tzalloc arguments are now treated with + no less caution than TZ strings taken from the environment, as + the old undocumented behavior would have been hard to explain. + tzset etc. no longer use the ‘access’ system call to check access; + instead they now use the system calls issetugid, getauxval, + getresuid/getresgid, and geteuid/getegid/getuid/getgid (whichever + first works) to test whether a program is privileged. + Compile with -DHAVE_SYS_AUXV_H=[01] to enable or disable + which (if it defines AT_SECURE) enables getauxval, + and compile with -DHAVE_ISSETUGID=[01], -DHAVE_GETRESUID=[01], and + -DHAVE_GETEUID=[01] to enable or disable the other calls’ use. + + The new CFLAGS option -DTZ_CHANGE_INTERVAL=N makes tzset etc. + check for TZif file changes if the in-memory data are N seconds + old or more, and are derived from the TZ environment variable. + This is intended for platforms that want tzset etc. to reflect + changes to whatever file TZ selects (including changes to + /etc/localtime if TZ is unset). If N is negative (the default) + these checks are omitted; this is the traditional behavior. + + The new CFLAGS options -DHAVE_STRUCT_STAT_ST_CTIM=0 and + -DHAVE_STRUCT_TIMESPEC=0 port to non-POSIX.1-2008 platforms + that lack st_ctim and struct timespec, respectively. + On these platforms, the code falls back on st_ctime to + implement -DTZ_CHANGE_INTERVAL=N. + + tzset etc. now treat ' ' like '_' in time zone abbreviations, + just as they treat other invalid bytes. This continues the + transition begun in release 96k, which removed spaces in tzdata + because the spaces break time string parsers. + + The new CFLAGS option -DTHREAD_PREFER_SINGLE causes tzcode + in single-threaded processes to avoid locks, as FreeBSD does. + This can save time in single-threaded apps. The threadedness + testing costs CPU time and energy in multi-threaded apps. + New options -DHAVE___ISTHREADED and -DHAVE_SYS_SINGLE_THREADED_H + can help configure how to test for single-threadedness. + + The new CFLAGS option -DTHREAD_RWLOCK uses read-write locks, as + macOS does, instead of mutexes. This saves real time when TZ is + rarely changing and many threads call tzcode simultaneously. + It costs more CPU time and energy. + + The new CFLAGS option -TTHREAD_TM_MULTI causes localtime to return + a pointer to thread-specific memory, as FreeBSD does, instead of + to the same memory in all threads. This supports nonportable + programs that incorrectly use localtime instead of localtime_r. + This option affects gmtime and offtime similarly to localtime. + Because the corresponding storage is freed on thread exit, this + option is incompatible with POSIX.1-2024 and earlier. It also + costs CPU time and memory. + + tzfree now preserves errno, consistently with POSIX.1-2024 ‘free’. + + tzcode now uses mempcpy if available, guessing its availability. + Compile with -DHAVE_MEMPCPY=1 or 0 to override the guess. + + tzcode now uses strnlen to improve asymptotic performance a bit. + Compile with -DHAVE_STRNLEN=0 if your platform lacks it. + + tzcode now hand-declares unistd.h-provided symbols like getopt + if HAVE_UNISTD_H=0, not if HAVE_POSIX_DECLS=0. + + tzset etc. now have an experimental OPENAT_TZDIR option; + see Makefile and localtime.c for details. + + On platforms like GNU/Hurd that do not define PATH_MAX, + exceedingly long TZ strings no longer fail merely because they + exceed an arbitrary file name length limit imposed by tzcode. + + zic has new options inspired by FreeBSD. ‘-D’ skips creation of + output ancestor directories, ‘-m MODE’ sets output files’ mode, + and ‘-u OWNER[:GROUP]’ sets output files’ owner and group. + + zic now uses the fdopen function, which was standardized by + POSIX.1-1988 and is now safe to use in portable code. + This replaces its use of the older umask function, which + complicated maintenance. + + Changes to commentary + + The leapseconds file contains commentary about the IERS and NIST + last-modified and expiration timestamps for leap second data. + (Thanks to Judah Levine.) + + Commentary now also uses characters from the set –‘’“”•≤ as this + can be useful and should work with current applications. This + also affects data in iso3166.tab and zone1970.tab, which now + contain strings like “Côte d’Ivoire” instead of “Côte d'Ivoire”. + + Release 2025b - 2025-03-22 13:40:46 -0700 Briefly: diff --git a/contrib/tzcode/README b/contrib/tzcode/README --- a/contrib/tzcode/README +++ b/contrib/tzcode/README @@ -1,8 +1,8 @@ README for the tz distribution -"Where do I set the hands of the clock?" -- Les Tremayne as The King -"Oh that--you can set them any place you want." -- Frank Baxter as The Scientist - (from the Bell System film "About Time") +“Where do I set the hands of the clock?” – Les Tremayne as The King +“Oh that – you can set them any place you want.” – Frank Baxter as The Scientist + (from the Bell System film “About Time”) The Time Zone Database (called tz, tzdb or zoneinfo) contains code and data that represent the history of local time for many representative @@ -13,12 +13,12 @@ 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" +Once acquired, read the leading comments in the file ‘Makefile’ and make any changes needed to make things right for your system, especially when using a platform other than current GNU/Linux. Then run the following commands, substituting your desired -installation directory for "$HOME/tzdir": +installation directory for ‘$HOME/tzdir’: make TOPDIR="$HOME/tzdir" install "$HOME/tzdir/usr/bin/zdump" -v America/Los_Angeles @@ -39,12 +39,12 @@ fixes and enhancements are welcome. Please see the file CONTRIBUTING for details. -Thanks to these Time Zone Caballeros who've made major contributions to the +Thanks to these Time Zone Caballeros who’ve made major contributions to the time conversion package: Keith Bostic; Bob Devine; Paul Eggert; Robert Elz; Guy Harris; Mark Horton; John Mackin; and Bradley White. Thanks also to Michael Bloom, Art Neilson, Stephen Prince, John Sovereign, and Frank Wales for testing work, and to Gwillim Law for checking local mean time data. -Thanks in particular to Arthur David Olson, the project's founder and first +Thanks in particular to Arthur David Olson, the project’s founder and first maintainer, to whom the time zone community owes the greatest debt of all. None of them are responsible for remaining errors. diff --git a/contrib/tzcode/SECURITY b/contrib/tzcode/SECURITY --- a/contrib/tzcode/SECURITY +++ b/contrib/tzcode/SECURITY @@ -1,7 +1,7 @@ Please report any sensitive security-related bugs via email to the tzdb designated coordinators, currently Paul Eggert and Tim Parenti . -Put "tzdb security" at the start of your email's subject line. +Put “tzdb security” at the start of your email’s subject line. We prefer communications to be in English. You should receive a response within a week. If not, please follow up diff --git a/contrib/tzcode/calendars b/contrib/tzcode/calendars --- a/contrib/tzcode/calendars +++ b/contrib/tzcode/calendars @@ -16,30 +16,9 @@ Russia -From Chris Carrier (1996-12-02): -On 1929-10-01 the Soviet Union instituted an "Eternal Calendar" -with 30-day months plus 5 holidays, with a 5-day week. -On 1931-12-01 it changed to a 6-day week; in 1934 it reverted to the -Gregorian calendar while retaining the 6-day week; on 1940-06-27 it -reverted to the 7-day week. With the 6-day week the usual days -off were the 6th, 12th, 18th, 24th and 30th of the month. -(Source: Evitiar Zerubavel, _The Seven Day Circle_) - - -Mark Brader reported a similar story in "The Book of Calendars", edited -by Frank Parise (1982, Facts on File, ISBN 0-8719-6467-8), page 377. But: - -From: Petteri Sulonen (via Usenet) -Date: 14 Jan 1999 00:00:00 GMT -... - -If your source is correct, how come documents between 1929 and 1940 were -still dated using the conventional, Gregorian calendar? - -I can post a scan of a document dated December 1, 1934, signed by -Yenukidze, the secretary, on behalf of Kalinin, the President of the -Executive Committee of the Supreme Soviet, if you like. - +Soviet Russia adopted the Gregorian calendar on 1918-02-14. +It also used 5- and 6-day work weeks at times, in parallel with the +Gregorian calendar; see . Sweden (and Finland) diff --git a/contrib/tzcode/date.1 b/contrib/tzcode/date.1 --- a/contrib/tzcode/date.1 +++ b/contrib/tzcode/date.1 @@ -4,8 +4,6 @@ .SH NAME date \- show and set date and time .SH SYNOPSIS -.if n .nh -.if n .na .B date [ .B \-u @@ -79,6 +77,6 @@ .br /usr/share/zoneinfo timezone directory .br -/usr/share/zoneinfo/Etc/UTC for UTC leap seconds +/usr/share/zoneinfo/Etc/UTC for UTC leap seconds, if supported .SH SEE ALSO .BR strftime (3). diff --git a/contrib/tzcode/date.c b/contrib/tzcode/date.c --- a/contrib/tzcode/date.c +++ b/contrib/tzcode/date.c @@ -31,11 +31,6 @@ #include #include -#if !HAVE_POSIX_DECLS -extern char * optarg; -extern int optind; -#endif - static int retval = EXIT_SUCCESS; static void display(const char *, time_t); @@ -64,7 +59,7 @@ textdomain(TZ_DOMAIN); #endif /* HAVE_GETTEXT */ t = time(NULL); - while ((ch = getopt(argc, argv, "ucr:")) != EOF && ch != -1) { + while ((ch = getopt(argc, argv, "ucr:")) != -1) { switch (ch) { default: usage(); diff --git a/contrib/tzcode/localtime.c b/contrib/tzcode/localtime.c --- a/contrib/tzcode/localtime.c +++ b/contrib/tzcode/localtime.c @@ -13,45 +13,174 @@ /*LINTLIBRARY*/ #define LOCALTIME_IMPLEMENTATION -#ifdef __FreeBSD__ -#include -#endif /* __FreeBSD__ */ -#ifdef DETECT_TZ_CHANGES -# ifndef DETECT_TZ_CHANGES_INTERVAL -# define DETECT_TZ_CHANGES_INTERVAL 61 -# endif -int __tz_change_interval = DETECT_TZ_CHANGES_INTERVAL; -# include -#endif /* DETECT_TZ_CHANGES */ #include "private.h" #include "tzdir.h" #include "tzfile.h" #include -#ifdef __FreeBSD__ -#include "libc_private.h" -#endif /* __FreeBSD__ */ #if HAVE_SYS_STAT_H # include +# ifndef S_ISREG +# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) /* Ancient UNIX. */ +# endif +#else +struct stat { char st_ctime, st_dev, st_ino; }; +# define dev_t char +# define ino_t char +# define fstat(fd, st) (memset(st, 0, sizeof *(st)), 0) +# define stat(name, st) fstat(0, st) +# define S_ISREG(mode) 1 +#endif + +#ifndef HAVE_STRUCT_STAT_ST_CTIM +# define HAVE_STRUCT_STAT_ST_CTIM 1 +#endif +#if !defined st_ctim && defined __APPLE__ && defined __MACH__ +# define st_ctim st_ctimespec +#endif + +#ifndef THREAD_SAFE +# define THREAD_SAFE 0 +#endif + +#ifndef THREAD_RWLOCK +# define THREAD_RWLOCK 0 #endif -#if !defined S_ISREG && defined S_IFREG -/* Ancient UNIX or recent MS-Windows. */ -# define S_ISREG(mode) (((mode) & S_IFMT) == S_IFREG) + +#ifndef THREAD_TM_MULTI +# define THREAD_TM_MULTI 0 #endif -#if defined THREAD_SAFE && THREAD_SAFE +#ifndef USE_TIMEX_T +# define USE_TIMEX_T false +#endif + +#if THREAD_SAFE # include -#ifdef __FreeBSD__ -# define pthread_mutex_lock(l) (__isthreaded ? pthread_mutex_lock(l) : 0) -# define pthread_mutex_unlock(l) (__isthreaded ? pthread_mutex_unlock(l) : 0) -#endif /* __FreeBSD__ */ + +# ifndef THREAD_PREFER_SINGLE +# define THREAD_PREFER_SINGLE 0 +# endif +# if THREAD_PREFER_SINGLE +# ifndef HAVE___ISTHREADED +# if defined __FreeBSD__ || defined __OpenBSD__ +# define HAVE___ISTHREADED 1 +# else +# define HAVE___ISTHREADED 0 +# endif +# endif +# if HAVE___ISTHREADED +extern int __isthreaded; +# else +# if !defined HAVE_SYS_SINGLE_THREADED_H && defined __has_include +# if __has_include() +# define HAVE_SYS_SINGLE_THREADED_H 1 +# else +# define HAVE_SYS_SINGLE_THREADED_H 0 +# endif +# endif +# ifndef HAVE_SYS_SINGLE_THREADED_H +# if defined __GLIBC__ && 2 < __GLIBC__ + (32 <= __GLIBC_MINOR__) +# define HAVE_SYS_SINGLE_THREADED_H 1 +# else +# define HAVE_SYS_SINGLE_THREADED_H 0 +# endif +# endif +# if HAVE_SYS_SINGLE_THREADED_H +# include +# endif +# endif +# endif +#endif + +#if !defined TM_GMTOFF || !USE_TIMEX_T +# if THREAD_SAFE + +/* True if the current process might be multi-threaded, + false if it is definitely single-threaded. + If false, it will be false the next time it is called + unless the caller creates a thread in the meantime. + If true, it might become false the next time it is called + if all other threads exit in the meantime. */ +static bool +is_threaded(void) +{ +# if THREAD_PREFER_SINGLE && HAVE___ISTHREADED + return !!__isthreaded; +# elif THREAD_PREFER_SINGLE && HAVE_SYS_SINGLE_THREADED_H + return !__libc_single_threaded; +# else + return true; +# endif +} + +# if THREAD_RWLOCK +static pthread_rwlock_t locallock = PTHREAD_RWLOCK_INITIALIZER; +static int dolock(void) { return pthread_rwlock_rdlock(&locallock); } +static void dounlock(void) { pthread_rwlock_unlock(&locallock); } +# else static pthread_mutex_t locallock = PTHREAD_MUTEX_INITIALIZER; -static int lock(void) { return pthread_mutex_lock(&locallock); } -static void unlock(void) { pthread_mutex_unlock(&locallock); } +static int dolock(void) { return pthread_mutex_lock(&locallock); } +static void dounlock(void) { pthread_mutex_unlock(&locallock); } +# endif + +/* Get a lock. Return 0 on success, a positive errno value on failure, + negative if known to be single-threaded so no lock is needed. */ +static int +lock(void) +{ + if (!is_threaded()) + return -1; + return dolock(); +} +static void +unlock(bool threaded) +{ + if (threaded) + dounlock(); +} +# else +static int lock(void) { return -1; } +static void unlock(ATTRIBUTE_MAYBE_UNUSED bool threaded) { } +# endif +#endif + +#if THREAD_SAFE +typedef pthread_once_t once_t; +# define ONCE_INIT PTHREAD_ONCE_INIT +#else +typedef bool once_t; +# define ONCE_INIT false +#endif + +static void +once(once_t *once_control, void init_routine(void)) +{ +#if THREAD_SAFE + pthread_once(once_control, init_routine); #else -static int lock(void) { return 0; } -static void unlock(void) { } + if (!*once_control) { + *once_control = true; + init_routine(); + } +#endif +} + +enum tm_multi { LOCALTIME_TM_MULTI, GMTIME_TM_MULTI, OFFTIME_TM_MULTI }; + +#if THREAD_SAFE && THREAD_TM_MULTI + +enum { N_TM_MULTI = OFFTIME_TM_MULTI + 1 }; +static pthread_key_t tm_multi_key; +static int tm_multi_key_err; + +static void +tm_multi_key_init(void) +{ + tm_multi_key_err = pthread_key_create(&tm_multi_key, free); +} + #endif /* Unless intptr_t is missing, pacify gcc -Wcast-qual on char const * exprs. @@ -81,6 +210,47 @@ #endif static_assert(IINNTT_MIN < INT_MIN && INT_MAX < IINNTT_MAX); +#ifndef HAVE_STRUCT_TIMESPEC +# define HAVE_STRUCT_TIMESPEC 1 +#endif +#if !HAVE_STRUCT_TIMESPEC +struct timespec { time_t tv_sec; long tv_nsec; }; +#endif + +#if !defined CLOCK_MONOTONIC_COARSE && defined CLOCK_MONOTONIC +# define CLOCK_MONOTONIC_COARSE CLOCK_MONOTONIC +#endif +#ifndef CLOCK_MONOTONIC_COARSE +# undef clock_gettime +# define clock_gettime(id, t) ((t)->tv_sec = time(NULL), (t)->tv_nsec = 0, 0) +#endif + +/* How many seconds to wait before checking the default TZif file again. + Negative means no checking. Default to 61 if DETECT_TZ_CHANGES + (as circa 2025 FreeBSD builds its localtime.c with -DDETECT_TZ_CHANGES), + and to -1 otherwise. */ +#ifndef TZ_CHANGE_INTERVAL +# ifdef DETECT_TZ_CHANGES +# define TZ_CHANGE_INTERVAL 61 +# else +# define TZ_CHANGE_INTERVAL (-1) +# endif +#endif +static_assert(TZ_CHANGE_INTERVAL < 0 || HAVE_SYS_STAT_H); + +/* The change detection interval. */ +#if TZ_CHANGE_INTERVAL < 0 || !defined __FreeBSD__ +enum { tz_change_interval = TZ_CHANGE_INTERVAL }; +#else +/* FreeBSD uses this private-but-extern var in its internal test suite. */ +int __tz_change_interval = TZ_CHANGE_INTERVAL; +# define tz_change_interval __tz_change_interval +#endif + +/* The type of monotonic times. + This is the system time_t, even if USE_TIMEX_T #defines time_t below. */ +typedef time_t monotime_t; + /* On platforms where offtime or mktime might overflow, strftime.c defines USE_TIMEX_T to be true and includes us. This tells us to #define time_t to an internal type timex_t that is @@ -89,9 +259,6 @@ to a static function that returns the redefined time_t. It also tells us to define only data and code needed to support the offtime or mktime variant. */ -#ifndef USE_TIMEX_T -# define USE_TIMEX_T false -#endif #if USE_TIMEX_T # undef TIME_T_MIN # undef TIME_T_MAX @@ -99,16 +266,16 @@ # define time_t timex_t # if MKTIME_FITS_IN(LONG_MIN, LONG_MAX) typedef long timex_t; -# define TIME_T_MIN LONG_MIN -# define TIME_T_MAX LONG_MAX +# define TIME_T_MIN LONG_MIN +# define TIME_T_MAX LONG_MAX # elif MKTIME_FITS_IN(LLONG_MIN, LLONG_MAX) typedef long long timex_t; -# define TIME_T_MIN LLONG_MIN -# define TIME_T_MAX LLONG_MAX +# define TIME_T_MIN LLONG_MIN +# define TIME_T_MAX LLONG_MAX # else typedef intmax_t timex_t; -# define TIME_T_MIN INTMAX_MIN -# define TIME_T_MAX INTMAX_MAX +# define TIME_T_MIN INTMAX_MIN +# define TIME_T_MAX INTMAX_MAX # endif # ifdef TM_GMTOFF @@ -121,14 +288,11 @@ # endif #endif -#ifndef TZ_ABBR_CHAR_SET -# define TZ_ABBR_CHAR_SET \ - "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789 :+-._" -#endif /* !defined TZ_ABBR_CHAR_SET */ - -#ifndef TZ_ABBR_ERR_CHAR -# define TZ_ABBR_ERR_CHAR '_' -#endif /* !defined TZ_ABBR_ERR_CHAR */ +/* Placeholders for platforms lacking openat. */ +#ifndef AT_FDCWD +# define AT_FDCWD (-1) /* any negative value will do */ +static int openat(int dd, char const *path, int oflag) { unreachable (); } +#endif /* Port to platforms that lack some O_* flags. Unless otherwise specified, the flags are standardized by POSIX. */ @@ -142,12 +306,76 @@ #ifndef O_CLOFORK # define O_CLOFORK 0 #endif +#ifndef O_DIRECTORY +# define O_DIRECTORY 0 +#endif #ifndef O_IGNORE_CTTY # define O_IGNORE_CTTY 0 /* GNU/Hurd */ #endif #ifndef O_NOCTTY # define O_NOCTTY 0 #endif +#ifndef O_PATH +# define O_PATH 0 +#endif +#ifndef O_REGULAR +# define O_REGULAR 0 +#endif +#ifndef O_RESOLVE_BENEATH +# define O_RESOLVE_BENEATH 0 +#endif +#ifndef O_SEARCH +# define O_SEARCH 0 +#endif + +#if !HAVE_ISSETUGID + +# if !defined HAVE_SYS_AUXV_H && defined __has_include +# if __has_include() +# define HAVE_SYS_AUXV_H 1 +# endif +# endif +# ifndef HAVE_SYS_AUXV_H +# if defined __GLIBC__ && 2 < __GLIBC__ + (19 <= __GLIBC_MINOR__) +# define HAVE_SYS_AUXV_H 1 +# else +# define HAVE_SYS_AUXV_H 0 +# endif +# endif +# if HAVE_SYS_AUXV_H +# include +# endif + +/* Return 1 if the process is privileged, 0 otherwise. */ +static int +issetugid(void) +{ +# if HAVE_SYS_AUXV_H && defined AT_SECURE + unsigned long val; + errno = 0; + val = getauxval(AT_SECURE); + if (val || errno != ENOENT) + return !!val; +# endif +# if HAVE_GETRESUID + { + uid_t ruid, euid, suid; + gid_t rgid, egid, sgid; + if (0 <= getresuid (&ruid, &euid, &suid)) { + if ((ruid ^ euid) | (ruid ^ suid)) + return 1; + if (0 <= getresgid (&rgid, &egid, &sgid)) + return !!((rgid ^ egid) | (rgid ^ sgid)); + } + } +# endif +# if HAVE_GETEUID + return geteuid() != getuid() || getegid() != getgid(); +# else + return 0; +# endif +} +#endif #ifndef WILDABBR /* @@ -181,7 +409,7 @@ #endif /* -** The DST rules to use if TZ has no rules and we can't load TZDEFRULES. +** The DST rules to use if TZ has no rules. ** Default to US rules as of 2017-05-07. ** POSIX does not specify the default DST rules; ** for historical reasons, US rules are a common default. @@ -190,6 +418,26 @@ # define TZDEFRULESTRING ",M3.2.0,M11.1.0" #endif +/* If compiled with -DOPENAT_TZDIR, then when accessing a relative + name like "America/Los_Angeles", first open TZDIR (default + "/usr/share/zoneinfo") as a directory and then use the result in + openat with "America/Los_Angeles", rather than the traditional + approach of opening "/usr/share/zoneinfo/America/Los_Angeles". + Although the OPENAT_TZDIR approach is less efficient, suffers from + spurious EMFILE and ENFILE failures, and is no more secure in practice, + it is how bleeding edge FreeBSD did things from August 2025 + through at least September 2025. */ +#ifndef OPENAT_TZDIR +# define OPENAT_TZDIR 0 +#endif + +/* If compiled with -DSUPPRESS_TZDIR, do not prepend TZDIR to relative TZ. + This is intended for specialized applications only, due to its + security implications. */ +#ifndef SUPPRESS_TZDIR +# define SUPPRESS_TZDIR 0 +#endif + /* Limit to time zone abbreviation length in proleptic TZ strings. This is distinct from TZ_MAX_CHARS, which limits TZif file contents. It defaults to 254, not 255, so that desigidx_type can be an unsigned char. @@ -211,8 +459,19 @@ # error "TZNAME_MAXIMUM too large" #endif +/* A type that can represent any 32-bit two's complement integer, + i.e., any integer in the range -2**31 .. 2**31 - 1. + Ordinarily this is int_fast32_t, but on non-C23 hosts + that are not two's complement it is int_fast64_t. */ +#if INT_FAST32_MIN < -TWO_31_MINUS_1 +typedef int_fast32_t int_fast32_2s; +#else +typedef int_fast64_t int_fast32_2s; +#endif + struct ttinfo { /* time type information */ - int_least32_t tt_utoff; /* UT offset in seconds */ + int_least32_t tt_utoff; /* UT offset in seconds; in the range + -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 */ @@ -220,8 +479,8 @@ }; struct lsinfo { /* leap second information */ - time_t ls_trans; /* transition time */ - int_fast32_t ls_corr; /* correction to apply */ + time_t ls_trans; /* transition time (positive) */ + int_fast32_2s ls_corr; /* correction to apply */ }; /* This abbreviation means local time is unspecified. */ @@ -239,7 +498,9 @@ are put on the stack and stacks are relatively small on some platforms. See tzfile.h for more about the sizes. */ struct state { +#if TZ_RUNTIME_LEAPS int leapcnt; +#endif int timecnt; int typecnt; int charcnt; @@ -250,9 +511,48 @@ struct ttinfo ttis[TZ_MAX_TYPES]; char chars[max(max(TZ_MAX_CHARS + CHARS_EXTRA, sizeof "UTC"), 2 * (TZNAME_MAXIMUM + 1))]; +#if TZ_RUNTIME_LEAPS struct lsinfo lsis[TZ_MAX_LEAPS]; +#endif }; +static int +leapcount(ATTRIBUTE_MAYBE_UNUSED struct state const *sp) +{ +#if TZ_RUNTIME_LEAPS + return sp->leapcnt; +#else + return 0; +#endif +} +static void +set_leapcount(ATTRIBUTE_MAYBE_UNUSED struct state *sp, + ATTRIBUTE_MAYBE_UNUSED int leapcnt) +{ +#if TZ_RUNTIME_LEAPS + sp->leapcnt = leapcnt; +#endif +} +static struct lsinfo +lsinfo(ATTRIBUTE_MAYBE_UNUSED struct state const *sp, + ATTRIBUTE_MAYBE_UNUSED int i) +{ +#if TZ_RUNTIME_LEAPS + return sp->lsis[i]; +#else + unreachable(); +#endif +} +static void +set_lsinfo(ATTRIBUTE_MAYBE_UNUSED struct state *sp, + ATTRIBUTE_MAYBE_UNUSED int i, + ATTRIBUTE_MAYBE_UNUSED struct lsinfo lsinfo) +{ +#if TZ_RUNTIME_LEAPS + sp->lsis[i] = lsinfo; +#endif +} + enum r_type { JULIAN_DAY, /* Jn = Julian day */ DAY_OF_YEAR, /* n = day of year */ @@ -267,30 +567,31 @@ int_fast32_t r_time; /* transition time of rule */ }; -#ifdef __FreeBSD__ -static void tzset_unlocked_name(char const *); -#endif /* __FreeBSD__ */ static struct tm *gmtsub(struct state const *, time_t const *, int_fast32_t, struct tm *); static bool increment_overflow(int *, int); -static bool increment_overflow_time(time_t *, int_fast32_t); -static int_fast32_t leapcorr(struct state const *, 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 *); static bool tzparse(char const *, struct state *, struct state const *); -#ifdef ALL_STATE +#ifndef ALL_STATE +# define ALL_STATE 0 +#endif + +#if ALL_STATE static struct state * lclptr; static struct state * gmtptr; -#endif /* defined ALL_STATE */ - -#ifndef ALL_STATE +#else static struct state lclmem; static struct state gmtmem; static struct state *const lclptr = &lclmem; static struct state *const gmtptr = &gmtmem; #endif /* State Farm */ +/* Maximum number of bytes in an efficiently-handled TZ string. + Longer strings work, albeit less efficiently. */ #ifndef TZ_STRLEN_MAX # define TZ_STRLEN_MAX 255 #endif /* !defined TZ_STRLEN_MAX */ @@ -299,18 +600,6 @@ static char lcl_TZname[TZ_STRLEN_MAX + 1]; static int lcl_is_set; #endif -#ifdef __FreeBSD__ -static pthread_once_t gmt_once = PTHREAD_ONCE_INIT; -static pthread_once_t gmtime_once = PTHREAD_ONCE_INIT; -static pthread_key_t gmtime_key; -static int gmtime_key_error; -static pthread_once_t offtime_once = PTHREAD_ONCE_INIT; -static pthread_key_t offtime_key; -static int offtime_key_error; -static pthread_once_t localtime_once = PTHREAD_ONCE_INIT; -static pthread_key_t localtime_key; -static int localtime_key_error; -#endif /* __FreeBSD__ */ /* ** Section 4.12.3 of X3.159-1989 requires that @@ -328,7 +617,7 @@ # if SUPPORT_C89 static struct tm tm; -#endif +# endif # if 2 <= HAVE_TZNAME + TZ_TIME_T char *tzname[2] = { UNCONST(wildabbr), UNCONST(wildabbr) }; @@ -364,15 +653,14 @@ return memcmp(abbr, UNSPEC, sizeof UNSPEC) == 0; } -static int_fast32_t +static int_fast32_2s detzcode(const char *const codep) { - register int_fast32_t result; register int i; - int_fast32_t one = 1; - int_fast32_t halfmaxval = one << (32 - 2); - int_fast32_t maxval = halfmaxval - 1 + halfmaxval; - int_fast32_t minval = -1 - maxval; + int_fast32_2s + maxval = TWO_31_MINUS_1, + minval = -1 - maxval, + result; result = codep[0] & 0x7f; for (i = 1; i < 4; ++i) @@ -380,8 +668,7 @@ if (codep[0] & 0x80) { /* Do two's-complement negation even on non-two's-complement machines. - If the result would be minval - 1, return minval. */ - result -= !TWOS_COMPLEMENT(int_fast32_t) && result != 0; + This cannot overflow, as int_fast32_2s is wide enough. */ result += minval; } return result; @@ -492,7 +779,7 @@ /* Reject overlong abbreviations. */ for (i = 0; i < sp->charcnt - (TZNAME_MAXIMUM + 1); ) { - int len = strlen(&sp->chars[i]); + int len = strnlen(&sp->chars[i], TZNAME_MAXIMUM + 1); if (TZNAME_MAXIMUM < len) return EOVERFLOW; i += len + 1; @@ -500,49 +787,74 @@ /* Replace bogus characters. */ for (i = 0; i < sp->charcnt; ++i) - if (strchr(TZ_ABBR_CHAR_SET, sp->chars[i]) == NULL) - sp->chars[i] = TZ_ABBR_ERR_CHAR; + switch (sp->chars[i]) { + case '\0': + case '+': case '-': case '.': + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case ':': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + break; + + default: + sp->chars[i] = '_'; + break; + } return 0; } #endif -#ifdef DETECT_TZ_CHANGES -/* - * Check whether either the time zone name or the file it refers to has - * changed since the last time we checked. - * Returns: -1 on error - * 0 if the time zone has not changed - * 1 if the time zone has changed - */ -static int -tzfile_changed(const char *name, int fd) +/* Return true if the TZif file with descriptor FD changed, + or may have changed, since the last time we were called. + Return false if it did not change. + If *ST is valid it is the file's current status; + otherwise, update *ST to the status if possible. */ +static bool +tzfile_changed(int fd, struct stat *st) { - static char old_name[PATH_MAX]; - static struct stat old_sb; - struct stat sb; - - if (fstat(fd, &sb) != 0) - return -1; + /* If old_ctim.tv_sec, these variables hold the corresponding part + of the file's metadata the last time this function was called. */ + static struct timespec old_ctim; + static dev_t old_dev; + static ino_t old_ino; - if (strcmp(name, old_name) != 0) { - strlcpy(old_name, name, sizeof(old_name)); - old_sb = sb; - return 1; - } + if (!st->st_ctime && fstat(fd, st) < 0) { + /* We do not know the file's state, so reset. */ + old_ctim.tv_sec = 0; + return true; + } else { + /* Use the change time, as it changes more reliably; mod time can + be set back with futimens etc. Use subsecond timestamp + resolution if available, as this can help distinguish files on + non-POSIX platforms where st_dev and st_ino are unreliable. */ + struct timespec ctim; +#if HAVE_STRUCT_STAT_ST_CTIM + ctim = st->st_ctim; +#else + ctim.tv_sec = st->st_ctime; + ctim.tv_nsec = 0; +#endif - if (sb.st_dev != old_sb.st_dev || - sb.st_ino != old_sb.st_ino || - sb.st_ctime != old_sb.st_ctime || - sb.st_mtime != old_sb.st_mtime) { - old_sb = sb; - return 1; - } + if ((ctim.tv_sec ^ old_ctim.tv_sec) | (ctim.tv_nsec ^ old_ctim.tv_nsec) + | (st->st_dev ^ old_dev) | (st->st_ino ^ old_ino)) { + old_ctim = ctim; + old_dev = st->st_dev; + old_ino = st->st_ino; + return true; + } - return 0; + return false; + } } -#endif /* DETECT_TZ_CHANGES */ /* Input buffer for data read from a compiled tz file. */ union input_buffer { @@ -555,10 +867,15 @@ + 4 * TZ_MAX_TIMES]; }; -#ifndef __FreeBSD__ -/* TZDIR with a trailing '/' rather than a trailing '\0'. */ -static char const tzdirslash[sizeof TZDIR] = TZDIR "/"; -#endif /* !__FreeBSD__ */ +/* TZDIR with a trailing '/'. It is null-terminated if OPENAT_TZDIR. */ +#if !OPENAT_TZDIR +ATTRIBUTE_NONSTRING +#endif +static char const tzdirslash[sizeof TZDIR + OPENAT_TZDIR] = TZDIR "/"; +enum { tzdirslashlen = sizeof TZDIR }; +#ifdef PATH_MAX +static_assert(tzdirslashlen <= PATH_MAX); /* Sanity check; assumed below. */ +#endif /* Local storage needed for 'tzloadbody'. */ union local_storage { @@ -571,41 +888,38 @@ struct state st; } u; -#ifndef __FreeBSD__ - /* The name of the file to be opened. Ideally this would have no - size limits, to support arbitrarily long Zone names. - Limiting Zone names to 1024 bytes should suffice for practical use. - However, there is no need for this to be smaller than struct - file_analysis as that struct is allocated anyway, as the other - union member. */ - char fullname[max(sizeof(struct file_analysis), sizeof tzdirslash + 1024)]; -#endif /* !__FreeBSD__ */ +#if defined PATH_MAX && !OPENAT_TZDIR && !SUPPRESS_TZDIR + /* The name of the file to be opened. */ + char fullname[PATH_MAX]; +#endif }; /* 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. */ /* Load tz data from the file named NAME into *SP. Respect TZLOADFLAGS. - Use *LSP for temporary storage. Return 0 on + Use **LSPP for temporary storage. Return 0 on success, an errno value on failure. */ static int tzloadbody(char const *name, struct state *sp, char tzloadflags, - union local_storage *lsp) + union local_storage **lspp) { register int i; register int fid; register int stored; register ssize_t nread; -#ifdef __FreeBSD__ - struct stat sb; - const char *relname; - int dd, serrno; -#else /* !__FreeBSD__ */ - register bool doaccess; -#endif /* !__FreeBSD__ */ - register union input_buffer *up = &lsp->u.u; + char const *relname; + union local_storage *lsp = *lspp; + union input_buffer *up; register int tzheadsize = sizeof(struct tzhead); + int dd = AT_FDCWD; + int oflags = (O_RDONLY | O_BINARY | O_CLOEXEC | O_CLOFORK + | O_IGNORE_CTTY | O_NOCTTY | O_REGULAR); + int err; + struct stat st; + st.st_ctime = 0; sp->goback = sp->goahead = false; @@ -613,148 +927,149 @@ name = TZDEFAULT; if (! name) return EINVAL; +#ifdef __FreeBSD__ tzloadflags &= ~TZLOAD_FROMENV; +#endif /* __FreeBSD__ */ } if (name[0] == ':') ++name; -#ifndef __FreeBSD__ -#ifdef SUPPRESS_TZDIR - /* Do not prepend TZDIR. This is intended for specialized - applications only, due to its security implications. */ - doaccess = true; -#else - doaccess = name[0] == '/'; -#endif - if (!doaccess) { - char const *dot; - if (sizeof lsp->fullname - sizeof tzdirslash <= strlen(name)) - return ENAMETOOLONG; - /* Create a string "TZDIR/NAME". Using sprintf here - would pull in stdio (and would fail if the - resulting string length exceeded INT_MAX!). */ - memcpy(lsp->fullname, tzdirslash, sizeof tzdirslash); - strcpy(lsp->fullname + sizeof tzdirslash, name); - - /* Set doaccess if NAME contains a ".." file name - component, as such a name could read a file outside - the TZDIR virtual subtree. */ - for (dot = name; (dot = strchr(dot, '.')); dot++) - if ((dot == name || dot[-1] == '/') && dot[1] == '.' - && (dot[2] == '/' || !dot[2])) { - doaccess = true; - break; - } + relname = name; - name = lsp->fullname; - } - if (doaccess && (tzloadflags & TZLOAD_FROMENV)) { - /* Check for security violations and for devices whose mere - opening could have unwanted side effects. Although these - checks are racy, they're better than nothing and there is - no portable way to fix the races. */ - if (access(name, R_OK) < 0) - return errno; -#ifdef S_ISREG - { - struct stat st; + /* If the program is privileged, NAME is TZDEFAULT or + subsidiary to TZDIR. Also, NAME is not a device. */ + if (name[0] == '/' && strcmp(name, TZDEFAULT) != 0) { + if (!SUPPRESS_TZDIR + && strncmp(relname, tzdirslash, tzdirslashlen) == 0) + for (relname += tzdirslashlen; *relname == '/'; relname++) + continue; + else if (issetugid()) + return ENOTCAPABLE; + else if (!O_REGULAR) { + /* Check for devices, as their mere opening could have + unwanted side effects. Though racy, there is no + portable way to fix the races. This check is needed + only for files not otherwise known to be non-devices. */ if (stat(name, &st) < 0) return errno; if (!S_ISREG(st.st_mode)) return EINVAL; } -#endif } - fid = open(name, (O_RDONLY | O_BINARY | O_CLOEXEC | O_CLOFORK - | O_IGNORE_CTTY | O_NOCTTY)); -#else /* __FreeBSD__ */ - if ((tzloadflags & TZLOAD_FROMENV) && strcmp(name, TZDEFAULT) == 0) - tzloadflags &= ~TZLOAD_FROMENV; - relname = name; - if (strncmp(relname, TZDIR "/", strlen(TZDIR) + 1) == 0) { - relname += strlen(TZDIR) + 1; - while (*relname == '/') - relname++; - } - dd = open(TZDIR, O_DIRECTORY | O_SEARCH | O_CLOEXEC); - if ((tzloadflags & TZLOAD_FROMENV) && issetugid()) { - if (dd < 0) - return errno; - if (fstatat(dd, relname, &sb, AT_RESOLVE_BENEATH) < 0) { - fid = -1; - } else if (!S_ISREG(sb.st_mode)) { - fid = -1; - errno = EINVAL; - } else { - fid = openat(dd, relname, O_RDONLY | O_CLOEXEC | O_RESOLVE_BENEATH); + + if (relname[0] != '/') { + if (!OPENAT_TZDIR || !O_RESOLVE_BENEATH) { + /* Fail if a relative name contains a non-terminal ".." component, + as such a name could read a non-directory outside TZDIR + when AT_FDCWD and O_RESOLVE_BENEATH are not available. */ + char const *component; + for (component = relname; component[0]; component++) + if (component[0] == '.' && component[1] == '.' + && component[2] == '/' + && (component == relname || component[-1] == '/')) + return ENOTCAPABLE; } - } else { - if (dd < 0) { - relname = name; - dd = AT_FDCWD; + + if (OPENAT_TZDIR && !SUPPRESS_TZDIR) { + /* Prefer O_SEARCH or O_PATH if available; + O_RDONLY should be OK too, as TZDIR is invariably readable. + O_DIRECTORY should be redundant but might help + on old platforms that mishandle trailing '/'. */ + dd = open(tzdirslash, + ((O_SEARCH ? O_SEARCH : O_PATH ? O_PATH : O_RDONLY) + | O_BINARY | O_CLOEXEC | O_CLOFORK | O_DIRECTORY)); + if (dd < 0) + return errno; +#ifdef __FreeBSD__ + if (issetugid()) +#endif /* __FreeBSD__ */ + oflags |= O_RESOLVE_BENEATH; } - fid = openat(dd, relname, O_RDONLY | O_CLOEXEC); } - if (dd != AT_FDCWD && dd >= 0) { - serrno = errno; - close(dd); - errno = serrno; + + if (!OPENAT_TZDIR && !SUPPRESS_TZDIR && name[0] != '/') { + char *cp; + size_t fullnamesize; +#ifdef PATH_MAX + size_t namesizemax = PATH_MAX - tzdirslashlen; + size_t namelen = strnlen (name, namesizemax); + if (namesizemax <= namelen) + return ENAMETOOLONG; +#else + size_t namelen = strlen (name); +#endif + fullnamesize = tzdirslashlen + namelen + 1; + + /* Create a string "TZDIR/NAME". Using sprintf here + would pull in stdio (and would fail if the + resulting string length exceeded INT_MAX!). */ + if (ALL_STATE || sizeof *lsp < fullnamesize) { + lsp = malloc(max(sizeof *lsp, fullnamesize)); + if (!lsp) + return HAVE_MALLOC_ERRNO ? errno : ENOMEM; + *lspp = lsp; + } + cp = mempcpy(lsp, tzdirslash, tzdirslashlen); + cp = mempcpy(cp, name, namelen); + *cp = '\0'; +#if defined PATH_MAX && !OPENAT_TZDIR && !SUPPRESS_TZDIR + name = lsp->fullname; +#else + name = (char *) lsp; +#endif } -#endif /* __FreeBSD__ */ + + fid = OPENAT_TZDIR ? openat(dd, relname, oflags) : open(name, oflags); + err = errno; + if (0 <= dd) + close(dd); if (fid < 0) - return errno; - -#ifdef DETECT_TZ_CHANGES - if (tzloadflags) { - /* - * Detect if the timezone file has changed. Check tzloadflags - * to ignore TZDEFRULES; the tzfile_changed() function can only - * keep state for a single file. - */ - switch (tzfile_changed(name, fid)) { - case -1: - serrno = errno; - close(fid); - return serrno; - case 0: - close(fid); - return 0; - case 1: - break; - } - } -#endif /* DETECT_TZ_CHANGES */ - nread = read(fid, up->buf, sizeof up->buf); - if (nread < tzheadsize) { - int err = nread < 0 ? errno : EINVAL; - close(fid); return err; + + /* If detecting changes to the the primary TZif file's state and + the file's status is unchanged, save time by returning now. + Otherwise read the file's contents. Close the file either way. */ + if (0 <= tz_change_interval && (tzloadflags & TZLOAD_FROMENV) + && !tzfile_changed(fid, &st)) + err = -1; + else { + if (ALL_STATE && !lsp) { + lsp = malloc(sizeof *lsp); + if (!lsp) + return HAVE_MALLOC_ERRNO ? errno : ENOMEM; + *lspp = lsp; + } + up = &lsp->u.u; + nread = read(fid, up->buf, sizeof up->buf); + err = tzheadsize <= nread ? 0 : nread < 0 ? errno : EINVAL; } - if (close(fid) < 0) - return errno; + close(fid); + if (err) + return err < 0 ? 0 : err; + for (stored = 4; stored <= 8; stored *= 2) { char version = up->tzhead.tzh_version[0]; bool skip_datablock = stored == 4 && version; int_fast32_t datablock_size; - int_fast32_t ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt); - int_fast32_t ttisutcnt = detzcode(up->tzhead.tzh_ttisutcnt); - int_fast64_t prevtr = -1; - int_fast32_t prevcorr; - int_fast32_t leapcnt = detzcode(up->tzhead.tzh_leapcnt); - int_fast32_t timecnt = detzcode(up->tzhead.tzh_timecnt); - int_fast32_t typecnt = detzcode(up->tzhead.tzh_typecnt); - int_fast32_t charcnt = detzcode(up->tzhead.tzh_charcnt); + int_fast32_2s + ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt), + ttisutcnt = detzcode(up->tzhead.tzh_ttisutcnt), + leapcnt = detzcode(up->tzhead.tzh_leapcnt), + timecnt = detzcode(up->tzhead.tzh_timecnt), + typecnt = detzcode(up->tzhead.tzh_typecnt), + charcnt = detzcode(up->tzhead.tzh_charcnt); char const *p = up->buf + tzheadsize; /* Although tzfile(5) currently requires typecnt to be nonzero, support future formats that may allow zero typecnt in files that have a TZ string and no transitions. */ - if (! (0 <= leapcnt && leapcnt < TZ_MAX_LEAPS - && 0 <= typecnt && typecnt < TZ_MAX_TYPES - && 0 <= timecnt && timecnt < TZ_MAX_TIMES - && 0 <= charcnt && charcnt < TZ_MAX_CHARS - && 0 <= ttisstdcnt && ttisstdcnt < TZ_MAX_TYPES - && 0 <= ttisutcnt && ttisutcnt < TZ_MAX_TYPES)) + if (! (0 <= leapcnt + && leapcnt <= (TZ_RUNTIME_LEAPS ? TZ_MAX_LEAPS : 0) + && 0 <= typecnt && typecnt <= TZ_MAX_TYPES + && 0 <= timecnt && timecnt <= TZ_MAX_TIMES + && 0 <= charcnt && charcnt <= TZ_MAX_CHARS + && 0 <= ttisstdcnt && ttisstdcnt <= TZ_MAX_TYPES + && 0 <= ttisutcnt && ttisutcnt <= TZ_MAX_TYPES)) return EINVAL; datablock_size = (timecnt * stored /* ats */ @@ -768,12 +1083,13 @@ return EINVAL; if (skip_datablock) p += datablock_size; + else if (! ((ttisstdcnt == typecnt || ttisstdcnt == 0) + && (ttisutcnt == typecnt || ttisutcnt == 0))) + return EINVAL; else { - if (! ((ttisstdcnt == typecnt || ttisstdcnt == 0) - && (ttisutcnt == typecnt || ttisutcnt == 0))) - return EINVAL; - - sp->leapcnt = leapcnt; + int_fast64_t prevtr = -1; + int_fast32_2s prevcorr; + set_leapcount(sp, leapcnt); sp->timecnt = timecnt; sp->typecnt = typecnt; sp->charcnt = charcnt; @@ -813,9 +1129,16 @@ for (i = 0; i < sp->typecnt; ++i) { register struct ttinfo * ttisp; unsigned char isdst, desigidx; + int_fast32_2s utoff = detzcode(p); + + /* Reject a UT offset equal to -2**31, as it might + cause trouble both in this file and in callers. + Also, it violates RFC 9636 section 3.2. */ + if (utoff < -TWO_31_MINUS_1) + return EINVAL; ttisp = &sp->ttis[i]; - ttisp->tt_utoff = detzcode(p); + ttisp->tt_utoff = utoff; p += 4; isdst = *p++; if (! (isdst < 2)) @@ -834,9 +1157,9 @@ /* Read leap seconds, discarding those out of time_t range. */ leapcnt = 0; - for (i = 0; i < sp->leapcnt; ++i) { + for (i = 0; i < leapcount(sp); i++) { int_fast64_t tr = stored == 4 ? detzcode(p) : detzcode64(p); - int_fast32_t corr = detzcode(p + stored); + int_fast32_2s corr = detzcode(p + stored); p += stored + 4; /* Leap seconds cannot occur before the Epoch, @@ -859,12 +1182,14 @@ prevcorr = corr; if (tr <= TIME_T_MAX) { - sp->lsis[leapcnt].ls_trans = tr; - sp->lsis[leapcnt].ls_corr = corr; + struct lsinfo ls; + ls.ls_trans = tr; + ls.ls_corr = corr; + set_lsinfo(sp, leapcnt, ls); leapcnt++; } } - sp->leapcnt = leapcnt; + set_leapcount(sp, leapcnt); for (i = 0; i < sp->typecnt; ++i) { register struct ttinfo * ttisp; @@ -908,8 +1233,8 @@ if (tzparse(&up->buf[1], ts, sp)) { /* Attempt to reuse existing abbreviations. - Without this, America/Anchorage would be right on - the edge after 2037 when TZ_MAX_CHARS is 50, as + Without this, America/Anchorage would + consume 50 bytes for abbreviations, as sp->charcnt equals 40 (for LMT AST AWT APT AHST AHDT YST AKDT AKST) and ts->charcnt equals 10 (for AKST AKDT). Reusing means sp->charcnt can @@ -926,9 +1251,11 @@ break; } if (! (j < charcnt)) { - int tsabbrlen = strlen(tsabbr); + int tsabbrlen = strnlen(tsabbr, TZ_MAX_CHARS - j); if (j + tsabbrlen < TZ_MAX_CHARS) { - strcpy(sp->chars + j, tsabbr); + char *cp = sp->chars + j; + cp = mempcpy(cp, tsabbr, tsabbrlen); + *cp = '\0'; charcnt = j + tsabbrlen + 1; ts->ttis[i].tt_desigidx = j; gotabbr++; @@ -979,19 +1306,20 @@ static int tzload(char const *name, struct state *sp, char tzloadflags) { -#ifdef ALL_STATE - union local_storage *lsp = malloc(sizeof *lsp); - if (!lsp) { - return HAVE_MALLOC_ERRNO ? errno : ENOMEM; - } else { - int err = tzloadbody(name, sp, tzloadflags, lsp); - free(lsp); - return err; - } + int r; + union local_storage *lsp0; + union local_storage *lsp; +#if ALL_STATE + lsp = NULL; #else union local_storage ls; - return tzloadbody(name, sp, tzloadflags, &ls); + lsp = &ls; #endif + lsp0 = lsp; + r = tzloadbody(name, sp, tzloadflags, &lsp); + if (lsp != lsp0) + free(lsp); + return r; } static const int mon_lengths[2][MONSPERYEAR] = { @@ -1301,7 +1629,6 @@ int_fast32_t stdoffset; int_fast32_t dstoffset; register char * cp; - register bool load_ok; ptrdiff_t stdlen, dstlen, charcnt; time_t atlo = TIME_T_MIN, leaplo = TIME_T_MIN; @@ -1327,18 +1654,22 @@ if (basep) { if (0 < basep->timecnt) atlo = basep->ats[basep->timecnt - 1]; - load_ok = false; - sp->leapcnt = basep->leapcnt; - memcpy(sp->lsis, basep->lsis, sp->leapcnt * sizeof *sp->lsis); - } else { - load_ok = tzload(TZDEFRULES, sp, 0) == 0; - if (!load_ok) - sp->leapcnt = 0; /* So, we're off a little. */ - } - if (0 < sp->leapcnt) - leaplo = sp->lsis[sp->leapcnt - 1].ls_trans; + set_leapcount(sp, leapcount(basep)); + if (0 < leapcount(sp)) { + int i; + for (i = 0; i < leapcount(sp); i++) + set_lsinfo(sp, i, lsinfo(basep, i)); + leaplo = lsinfo(sp, leapcount(sp) - 1).ls_trans; + } + } else + set_leapcount(sp, 0); /* So, we're off a little. */ sp->goback = sp->goahead = false; if (*name != '\0') { + struct rule start, end; + int year, yearbeg, yearlim, timecnt; + time_t janfirst; + int_fast32_t janoffset = 0; + if (*name == '<') { dstname = ++name; name = getqzname(name, '>'); @@ -1359,194 +1690,102 @@ if (name == NULL) return false; } else dstoffset = stdoffset - SECSPERHOUR; - if (*name == '\0' && !load_ok) + + if (*name == '\0') name = TZDEFRULESTRING; - if (*name == ',' || *name == ';') { - struct rule start; - struct rule end; - register int year; - register int timecnt; - time_t janfirst; - int_fast32_t janoffset = 0; - int yearbeg, yearlim; - - ++name; - if ((name = getrule(name, &start)) == NULL) - return false; - if (*name++ != ',') - return false; - if ((name = getrule(name, &end)) == NULL) - return false; - if (*name != '\0') - return false; - sp->typecnt = 2; /* standard time and DST */ - /* - ** Two transitions per year, from EPOCH_YEAR forward. - */ - init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); - init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); - timecnt = 0; - janfirst = 0; - yearbeg = EPOCH_YEAR; - - do { - int_fast32_t yearsecs - = year_lengths[isleap(yearbeg - 1)] * SECSPERDAY; - time_t janfirst1 = janfirst; - yearbeg--; - if (increment_overflow_time(&janfirst1, -yearsecs)) { - janoffset = -yearsecs; - break; - } - janfirst = janfirst1; - } while (atlo < janfirst - && EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg); - - while (true) { - int_fast32_t yearsecs - = year_lengths[isleap(yearbeg)] * SECSPERDAY; - int yearbeg1 = yearbeg; - time_t janfirst1 = janfirst; - if (increment_overflow_time(&janfirst1, yearsecs) - || increment_overflow(&yearbeg1, 1) - || atlo <= janfirst1) - break; - yearbeg = yearbeg1; - janfirst = janfirst1; - } + if (! (*name == ',' || *name == ';')) + return false; - yearlim = yearbeg; - if (increment_overflow(&yearlim, years_of_observations)) - yearlim = INT_MAX; - for (year = yearbeg; year < yearlim; year++) { - int_fast32_t - starttime = transtime(year, &start, stdoffset), - endtime = transtime(year, &end, dstoffset); - int_fast32_t - yearsecs = (year_lengths[isleap(year)] - * SECSPERDAY); - bool reversed = endtime < starttime; - if (reversed) { - int_fast32_t swap = starttime; - starttime = endtime; - endtime = swap; - } - 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]) - sp->types[timecnt++] = !reversed; - sp->ats[timecnt] = janfirst; - if (! increment_overflow_time - (&sp->ats[timecnt], - janoffset + endtime) - && atlo <= sp->ats[timecnt]) { - sp->types[timecnt++] = reversed; - } - } - if (endtime < leaplo) { - yearlim = year; - if (increment_overflow(&yearlim, - years_of_observations)) - yearlim = INT_MAX; - } - if (increment_overflow_time - (&janfirst, janoffset + yearsecs)) - break; - janoffset = 0; - } - sp->timecnt = timecnt; - if (! timecnt) { - sp->ttis[0] = sp->ttis[1]; - sp->typecnt = 1; /* Perpetual DST. */ - } else if (years_of_observations <= year - yearbeg) - sp->goback = sp->goahead = true; - } else { - register int_fast32_t theirstdoffset; - register int_fast32_t theirdstoffset; - register int_fast32_t theiroffset; - register bool isdst; - register int i; - register int j; - - if (*name != '\0') - return false; - /* - ** Initial values of theirstdoffset and theirdstoffset. - */ - theirstdoffset = 0; - for (i = 0; i < sp->timecnt; ++i) { - j = sp->types[i]; - if (!sp->ttis[j].tt_isdst) { - theirstdoffset = - - sp->ttis[j].tt_utoff; - break; - } - } - theirdstoffset = 0; - for (i = 0; i < sp->timecnt; ++i) { - j = sp->types[i]; - if (sp->ttis[j].tt_isdst) { - theirdstoffset = - - sp->ttis[j].tt_utoff; - break; - } - } - /* - ** Initially we're assumed to be in standard time. - */ - isdst = false; - /* - ** Now juggle transition times and types - ** tracking offsets as you do. - */ - for (i = 0; i < sp->timecnt; ++i) { - j = sp->types[i]; - sp->types[i] = sp->ttis[j].tt_isdst; - if (sp->ttis[j].tt_ttisut) { - /* No adjustment to transition time */ - } else { - /* - ** If daylight saving time is in - ** effect, and the transition time was - ** not specified as standard time, add - ** the daylight saving time offset to - ** the transition time; otherwise, add - ** the standard time offset to the - ** transition time. - */ - /* - ** Transitions from DST to DDST - ** will effectively disappear since - ** proleptic TZ strings have only one - ** DST offset. - */ - if (isdst && !sp->ttis[j].tt_ttisstd) { - sp->ats[i] += dstoffset - - theirdstoffset; - } else { - sp->ats[i] += stdoffset - - theirstdoffset; - } - } - theiroffset = -sp->ttis[j].tt_utoff; - if (sp->ttis[j].tt_isdst) - theirdstoffset = theiroffset; - else theirstdoffset = theiroffset; - } - /* - ** Finally, fill in ttis. - */ - init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); - init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); - sp->typecnt = 2; + name = getrule(name + 1, &start); + if (!name) + return false; + if (*name++ != ',') + return false; + name = getrule(name, &end); + if (!name || *name) + return false; + sp->typecnt = 2; /* standard time and DST */ + /* + ** Two transitions per year, from EPOCH_YEAR forward. + */ + init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); + init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); + timecnt = 0; + janfirst = 0; + yearbeg = EPOCH_YEAR; + + do { + int_fast32_t yearsecs + = year_lengths[isleap(yearbeg - 1)] * SECSPERDAY; + time_t janfirst1 = janfirst; + yearbeg--; + if (increment_overflow_time(&janfirst1, -yearsecs)) { + janoffset = -yearsecs; + break; + } + janfirst = janfirst1; + } while (atlo < janfirst + && EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg); + + while (true) { + int_fast32_t yearsecs + = year_lengths[isleap(yearbeg)] * SECSPERDAY; + int yearbeg1 = yearbeg; + time_t janfirst1 = janfirst; + if (increment_overflow_time(&janfirst1, yearsecs) + || increment_overflow(&yearbeg1, 1) + || atlo <= janfirst1) + break; + yearbeg = yearbeg1; + janfirst = janfirst1; + } + + yearlim = yearbeg; + if (increment_overflow(&yearlim, years_of_observations)) + yearlim = INT_MAX; + 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; + bool reversed = endtime < starttime; + if (reversed) { + int_fast32_t swap = starttime; + starttime = endtime; + endtime = swap; + } + 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]) + sp->types[timecnt++] = !reversed; + sp->ats[timecnt] = janfirst; + if (! increment_overflow_time(&sp->ats[timecnt], + janoffset + endtime) + && atlo <= sp->ats[timecnt]) { + sp->types[timecnt++] = reversed; + } + } + if (endtime < leaplo) { + yearlim = year; + if (increment_overflow(&yearlim, years_of_observations)) + yearlim = INT_MAX; + } + if (increment_overflow_time(&janfirst, janoffset + yearsecs)) + break; + janoffset = 0; } + sp->timecnt = timecnt; + if (! timecnt) { + sp->ttis[0] = sp->ttis[1]; + sp->typecnt = 1; /* Perpetual DST. */ + } else if (years_of_observations <= year - yearbeg) + sp->goback = sp->goahead = true; } else { dstlen = 0; sp->typecnt = 1; /* only standard time */ @@ -1555,12 +1794,11 @@ } sp->charcnt = charcnt; cp = sp->chars; - memcpy(cp, stdname, stdlen); - cp += stdlen; + cp = mempcpy(cp, stdname, stdlen); *cp++ = '\0'; if (dstlen != 0) { - memcpy(cp, dstname, dstlen); - *(cp + dstlen) = '\0'; + cp = mempcpy(cp, dstname, dstlen); + *cp = '\0'; } return true; } @@ -1568,34 +1806,27 @@ static void gmtload(struct state *const sp) { - if (tzload(etc_utc, sp, TZLOAD_TZSTRING) != 0) + if (!TZ_RUNTIME_LEAPS || tzload(etc_utc, sp, TZLOAD_TZSTRING) != 0) tzparse("UTC0", sp, NULL); } -#ifdef DETECT_TZ_CHANGES -/* - * Check if the time zone data we have is still fresh. - */ -static int -tzdata_is_fresh(void) -{ - static time_t last_checked; - struct timespec now; - - if (clock_gettime(CLOCK_MONOTONIC, &now) < 0) - return 1; +#if !USE_TIMEX_T || !defined TM_GMTOFF - if (last_checked == 0 || last_checked > now.tv_sec || - now.tv_sec - last_checked >= __tz_change_interval) { - last_checked = now.tv_sec; - return 0; - } +/* Return true if primary cached time zone data are fresh, + i.e., if this function is known to have recently returned false. + A call is recent if it occurred less than tz_change_interval seconds ago. + NOW should be the current time. */ +static bool +fresh_tzdata(monotime_t now) +{ + /* If nonzero, the time of the last false return. */ + static monotime_t last_checked; - return 1; + if (last_checked && now - last_checked < tz_change_interval) + return true; + last_checked = now; + return false; } -#endif /* DETECT_TZ_CHANGES */ - -#if !USE_TIMEX_T || !defined TM_GMTOFF /* Initialize *SP to a value appropriate for the TZ setting NAME. Respect TZLOADFLAGS. @@ -1607,7 +1838,7 @@ /* ** User wants it fast rather than right. */ - sp->leapcnt = 0; /* so, we're off a little */ + set_leapcount(sp, 0); /* so, we're off a little */ sp->timecnt = 0; sp->typecnt = 0; sp->charcnt = 0; @@ -1617,7 +1848,8 @@ return 0; } else { int err = tzload(name, sp, tzloadflags); - if (err != 0 && name && name[0] != ':' && tzparse(name, sp, NULL)) + if (err != 0 && name && name[0] != ':' && !(tzloadflags & TZLOAD_TZDIR_SUB) + && tzparse(name, sp, NULL)) err = 0; if (err == 0) err = scrub_abbrs(sp); @@ -1625,32 +1857,78 @@ } } -static void -tzset_unlocked(void) +/* If THREADED, upgrade a read lock to a write lock. + Return 0 on success, a positive errno value otherwise. */ +static int +rd2wrlock(ATTRIBUTE_MAYBE_UNUSED bool threaded) { - char const *name = getenv("TZ"); -#ifdef __FreeBSD__ - tzset_unlocked_name(name); +# if THREAD_RWLOCK + if (threaded) { + dounlock(); + return pthread_rwlock_wrlock(&locallock); + } +# endif + return 0; } + +/* Like tzset(), but in a critical section. + If THREADED && THREAD_RWLOCK the caller has a read lock, + and this function might upgrade it to a write lock. + If WALL, act as if TZ is unset; although always false in this file, + a wrapper .c file's obsolete and ineffective tzsetwall function can use it. + If tz_change_interval is positive the time is NOW; otherwise ignore NOW. */ static void -tzset_unlocked_name(char const *name) -{ -#endif - struct state *sp = lclptr; - int lcl = name ? strlen(name) < sizeof lcl_TZname : -1; - if (lcl < 0 - ? lcl_is_set < 0 - : 0 < lcl_is_set && strcmp(lcl_TZname, name) == 0) -#ifdef DETECT_TZ_CHANGES - if (tzdata_is_fresh()) -#endif /* DETECT_TZ_CHANGES */ - return; -# ifdef ALL_STATE +tzset_unlocked(bool threaded, bool wall, monotime_t now) +{ + char const *name; + struct state *sp; + char tzloadflags; + size_t namelen; + bool writing = false; + + for (;;) { + name = wall ? NULL : getenv("TZ"); + sp = lclptr; + tzloadflags = TZLOAD_FROMENV | TZLOAD_TZSTRING; + namelen = sizeof lcl_TZname + 1; /* placeholder for no name */ + + if (name) { + namelen = strnlen(name, sizeof lcl_TZname); + + /* Abbreviate a string like "/usr/share/zoneinfo/America/Los_Angeles" + to its shorter equivalent "America/Los_Angeles". */ + if (!SUPPRESS_TZDIR && tzdirslashlen < namelen + && memcmp(name, tzdirslash, tzdirslashlen) == 0) { + char const *p = name + tzdirslashlen; + while (*p == '/') + p++; + if (*p && *p != ':') { + name = p; + namelen = strnlen(name, sizeof lcl_TZname); + tzloadflags |= TZLOAD_TZDIR_SUB; + } + } + } + + if ((tz_change_interval <= 0 ? tz_change_interval < 0 : fresh_tzdata(now)) + && (name + ? 0 < lcl_is_set && strcmp(lcl_TZname, name) == 0 + : lcl_is_set < 0)) + return; + + if (!THREAD_RWLOCK || writing) + break; + if (rd2wrlock(threaded) != 0) + return; + writing = true; + } + +# if ALL_STATE if (! sp) lclptr = sp = malloc(sizeof *lclptr); # endif if (sp) { - int err = zoneinit(sp, name, TZLOAD_FROMENV | TZLOAD_TZSTRING); + int err = zoneinit(sp, name, tzloadflags); if (err != 0) { zoneinit(sp, "", 0); /* Abbreviate with "-00" if there was an error. @@ -1658,23 +1936,47 @@ if (name || err != ENOENT) strcpy(sp->chars, UNSPEC); } - if (0 < lcl) - strcpy(lcl_TZname, name); + if (namelen < sizeof lcl_TZname) { + char *cp = lcl_TZname; + cp = mempcpy(cp, name, namelen); + *cp = '\0'; + } } settzname(); - lcl_is_set = lcl; + lcl_is_set = (sizeof lcl_TZname > namelen) - (sizeof lcl_TZname < namelen); } #endif +#if !defined TM_GMTOFF || !USE_TIMEX_T + +/* If tz_change_interval is positive, + return the current time as a monotonically nondecreasing value. + Otherwise the return value does not matter. */ +static monotime_t +get_monotonic_time(void) +{ + struct timespec now; + now.tv_sec = 0; + if (0 < tz_change_interval) + clock_gettime(CLOCK_MONOTONIC_COARSE, &now); + return now.tv_sec; +} +#endif + #if !USE_TIMEX_T + void tzset(void) { - if (lock() != 0) + monotime_t now = get_monotonic_time(); + int err = lock(); + if (0 < err) { + errno = err; return; - tzset_unlocked(); - unlock(); + } + tzset_unlocked(!err, false, now); + unlock(!err); } #endif @@ -1682,34 +1984,35 @@ void freebsd13_tzsetwall(void) { - if (lock() != 0) + monotime_t now = get_monotonic_time(); + int err = lock(); + if (0 < err) { + errno = err; return; - tzset_unlocked_name(NULL); - unlock(); + } + tzset_unlocked(!err, true, now); + unlock(!err); } __sym_compat(tzsetwall, freebsd13_tzsetwall, FBSD_1.0); __warn_references(tzsetwall, "warning: tzsetwall() is deprecated, use tzset() instead."); #endif /* __FreeBSD__ */ static void -gmtcheck(void) +gmtcheck1(void) { - static bool gmt_is_set; - if (lock() != 0) - return; - if (! gmt_is_set) { -#ifdef ALL_STATE - gmtptr = malloc(sizeof *gmtptr); +#if ALL_STATE + gmtptr = malloc(sizeof *gmtptr); #endif - if (gmtptr) - gmtload(gmtptr); - gmt_is_set = true; - } - unlock(); + if (gmtptr) + gmtload(gmtptr); +} + +static void +gmtcheck(void) +{ + static once_t gmt_once = ONCE_INIT; + once(&gmt_once, gmtcheck1); } -#ifdef __FreeBSD__ -#define gmtcheck() _once(&gmt_once, gmtcheck) -#endif #if NETBSD_INSPIRED && !USE_TIMEX_T @@ -1729,10 +2032,25 @@ return sp; } +#ifndef FREE_PRESERVES_ERRNO +# if ((defined _POSIX_VERSION && 202405 <= _POSIX_VERSION) \ + || (defined __GLIBC__ && 2 < __GLIBC__ + (33 <= __GLIBC_MINOR__)) \ + || defined __OpenBSD__ || defined __sun) +# define FREE_PRESERVES_ERRNO 1 +# else +# define FREE_PRESERVES_ERRNO 0 +# endif +#endif + void tzfree(timezone_t sp) { + int err; + if (!FREE_PRESERVES_ERRNO) + err = errno; free(sp); + if (!FREE_PRESERVES_ERRNO) + errno = err; } /* @@ -1844,7 +2162,7 @@ ** 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, 0L, sp, tmp); + ** timesub(&t, 0, sp, tmp); */ result = timesub(&t, ttisp->tt_utoff, sp, tmp); if (result) { @@ -1861,6 +2179,36 @@ #if !USE_TIMEX_T +/* Return TMP, or a thread-specific struct tm * selected by WHICH. */ +static struct tm * +tm_multi(struct tm *tmp, ATTRIBUTE_MAYBE_UNUSED enum tm_multi which) +{ +# if THREAD_SAFE && THREAD_TM_MULTI + /* It is OK to check is_threaded() separately here; even if it + returns a different value in other places in the caller, + this function's behavior is still valid. */ + if (is_threaded()) { + /* Try to get a thread-specific struct tm *. + Fall back on TMP if this fails. */ + static pthread_once_t tm_multi_once = PTHREAD_ONCE_INIT; + pthread_once(&tm_multi_once, tm_multi_key_init); + if (!tm_multi_key_err) { + struct tm *p = pthread_getspecific(tm_multi_key); + if (!p) { + p = malloc(N_TM_MULTI * sizeof *p); + if (p && pthread_setspecific(tm_multi_key, p) != 0) { + free(p); + p = NULL; + } + } + if (p) + return &p[which]; + } + } +# endif + return tmp; +} + # if NETBSD_INSPIRED struct tm * localtime_rz(struct state *restrict sp, time_t const *restrict timep, @@ -1873,54 +2221,26 @@ static struct tm * localtime_tzset(time_t const *timep, struct tm *tmp, bool setname) { + monotime_t now = get_monotonic_time(); int err = lock(); - if (err) { + if (0 < err) { errno = err; return NULL; } -#ifndef DETECT_TZ_CHANGES - if (setname || !lcl_is_set) -#endif /* DETECT_TZ_CHANGES */ - tzset_unlocked(); + if (0 <= tz_change_interval || setname || !lcl_is_set) + tzset_unlocked(!err, false, now); tmp = localsub(lclptr, timep, setname, tmp); - unlock(); + unlock(!err); return tmp; } -#ifdef __FreeBSD__ -static void -localtime_key_init(void) -{ - localtime_key_error = pthread_key_create(&localtime_key, free); -} -#endif /* __FreeBSD__ */ struct tm * localtime(const time_t *timep) { # if !SUPPORT_C89 static struct tm tm; # endif -#ifdef __FreeBSD__ - struct tm *p_tm = &tm; - - if (__isthreaded != 0) { - pthread_once(&localtime_once, localtime_key_init); - if (localtime_key_error != 0) { - errno = localtime_key_error; - return (NULL); - } - if ((p_tm = pthread_getspecific(localtime_key)) == NULL) { - if ((p_tm = malloc(sizeof(*p_tm))) == NULL) { - return (NULL); - } - if (pthread_setspecific(localtime_key, p_tm) != 0) { - free(p_tm); - return (NULL); - } - } - } -#endif /* __FreeBSD__ */ - return localtime_tzset(timep, p_tm, true); + return localtime_tzset(timep, tm_multi(&tm, LOCALTIME_TM_MULTI), true); } struct tm * @@ -1966,40 +2286,13 @@ return gmtsub(gmtptr, timep, 0, tmp); } -#ifdef __FreeBSD__ -static void -gmtime_key_init(void) -{ - gmtime_key_error = pthread_key_create(&gmtime_key, free); -} -#endif /* __FreeBSD__ */ struct tm * gmtime(const time_t *timep) { # if !SUPPORT_C89 static struct tm tm; # endif -#ifdef __FreeBSD__ - struct tm *p_tm = &tm; - - if (__isthreaded != 0) { - pthread_once(&gmtime_once, gmtime_key_init); - if (gmtime_key_error != 0) { - errno = gmtime_key_error; - return (NULL); - } - if ((p_tm = pthread_getspecific(gmtime_key)) == NULL) { - if ((p_tm = malloc(sizeof(*p_tm))) == NULL) { - return (NULL); - } - if (pthread_setspecific(gmtime_key, p_tm) != 0) { - free(p_tm); - return (NULL); - } - } - } -#endif /* __FreeBSD__ */ - return gmtime_r(timep, p_tm); + return gmtime_r(timep, tm_multi(&tm, GMTIME_TM_MULTI)); } # if STD_INSPIRED @@ -2014,40 +2307,13 @@ return gmtsub(gmtptr, timep, offset, tmp); } -#ifdef __FreeBSD__ -static void -offtime_key_init(void) -{ - offtime_key_error = pthread_key_create(&offtime_key, free); -} -#endif /* __FreeBSD__ */ struct tm * offtime(time_t const *timep, long offset) { # if !SUPPORT_C89 static struct tm tm; # endif -#ifdef __FreeBSD__ - struct tm *p_tm = &tm; - - if (__isthreaded != 0) { - pthread_once(&offtime_once, offtime_key_init); - if (offtime_key_error != 0) { - errno = offtime_key_error; - return (NULL); - } - if ((p_tm = pthread_getspecific(offtime_key)) == NULL) { - if ((p_tm = malloc(sizeof(*p_tm))) == NULL) { - return (NULL); - } - if (pthread_setspecific(offtime_key, p_tm) != 0) { - free(p_tm); - return (NULL); - } - } - } -#endif - return offtime_r(timep, offset, p_tm); + return offtime_r(timep, offset, tm_multi(&tm, OFFTIME_TM_MULTI)); } # endif @@ -2076,10 +2342,9 @@ timesub(const time_t *timep, int_fast32_t offset, const struct state *sp, struct tm *tmp) { - register const struct lsinfo * lp; register time_t tdays; register const int * ip; - register int_fast32_t corr; + int_fast32_2s corr; register int i; int_fast32_t idays, rem, dayoff, dayrem; time_t y; @@ -2090,13 +2355,13 @@ time_t secs_since_posleap = SECSPERMIN; corr = 0; - i = (sp == NULL) ? 0 : sp->leapcnt; + i = sp ? leapcount(sp) : 0; while (--i >= 0) { - lp = &sp->lsis[i]; - if (*timep >= lp->ls_trans) { - corr = lp->ls_corr; - if ((i == 0 ? 0 : lp[-1].ls_corr) < corr) - secs_since_posleap = *timep - lp->ls_trans; + struct lsinfo ls = lsinfo(sp, i); + if (ls.ls_trans <= *timep) { + corr = ls.ls_corr; + if ((i == 0 ? 0 : lsinfo(sp, i - 1).ls_corr) < corr) + secs_since_posleap = *timep - ls.ls_trans; break; } } @@ -2225,6 +2490,19 @@ #endif } +static bool +increment_overflow_64(int *ip, int_fast64_t j) +{ +#ifdef ckd_add + return ckd_add(ip, *ip, j); +#else + if (j < 0 ? *ip < INT_MIN - j : INT_MAX - j < *ip) + return true; + *ip += j; + return false; +#endif +} + static bool increment_overflow_time_iinntt(time_t *tp, iinntt j) { @@ -2241,7 +2519,22 @@ } static bool -increment_overflow_time(time_t *tp, int_fast32_t j) +increment_overflow_time_64(time_t *tp, int_fast64_t j) +{ +#ifdef ckd_add + return ckd_add(tp, *tp, j); +#else + if (j < 0 + ? (TYPE_SIGNED(time_t) ? *tp < TIME_T_MIN - j : *tp <= -1 - j) + : TIME_T_MAX - j < *tp) + return true; + *tp += j; + return false; +#endif +} + +static bool +increment_overflow_time(time_t *tp, int_fast32_2s j) { #ifdef ckd_add return ckd_add(tp, *tp, j); @@ -2260,6 +2553,15 @@ #endif } +/* Return A - B, where both are in the range -2**31 + 1 .. 2**31 - 1. + The result cannot overflow. */ +static int_fast64_t +utoff_diff (int_fast32_t a, int_fast32_t b) +{ + int_fast64_t aa = a; + return aa - b; +} + static int tmcomp(register const struct tm *const atmp, register const struct tm *const btmp) @@ -2295,8 +2597,8 @@ static time_t time2sub(struct tm *const tmp, - struct tm *(*funcp)(struct state const *, time_t const *, - int_fast32_t, struct tm *), + struct tm *funcp(struct state const *, time_t const *, + int_fast32_t, struct tm *), struct state const *sp, const int_fast32_t offset, bool *okayp, @@ -2456,8 +2758,18 @@ It's OK if YOURTM.TM_GMTOFF contains uninitialized data, since the guess gets checked. */ time_t altt = t; - int_fast32_t diff = mytm.TM_GMTOFF - yourtm.TM_GMTOFF; - if (!increment_overflow_time(&altt, diff)) { + int_fast64_t offdiff; + bool v; +# ifdef ckd_sub + v = ckd_sub(&offdiff, mytm.TM_GMTOFF, yourtm.TM_GMTOFF); +# else + /* A ckd_sub approximation that is good enough here. */ + v = !(-TWO_31_MINUS_1 <= yourtm.TM_GMTOFF + && yourtm.TM_GMTOFF <= TWO_31_MINUS_1); + if (!v) + offdiff = utoff_diff(mytm.TM_GMTOFF, yourtm.TM_GMTOFF); +# endif + if (!v && !increment_overflow_time_64(&altt, offdiff)) { struct tm alttm; if (funcp(sp, &altt, offset, &alttm) && alttm.tm_isdst == mytm.tm_isdst @@ -2487,8 +2799,12 @@ continue; if (ttunspecified(sp, j)) continue; - newt = (t + sp->ttis[j].tt_utoff - - sp->ttis[i].tt_utoff); + newt = t; + if (increment_overflow_time_64 + (&newt, + utoff_diff(sp->ttis[j].tt_utoff, + sp->ttis[i].tt_utoff))) + continue; if (! funcp(sp, &newt, offset, &mytm)) continue; if (tmcomp(&mytm, &yourtm) != 0) @@ -2514,8 +2830,8 @@ static time_t time2(struct tm * const tmp, - struct tm *(*funcp)(struct state const *, time_t const *, - int_fast32_t, struct tm *), + struct tm *funcp(struct state const *, time_t const *, + int_fast32_t, struct tm *), struct state const *sp, const int_fast32_t offset, bool *okayp) @@ -2533,8 +2849,8 @@ static time_t time1(struct tm *const tmp, - struct tm *(*funcp)(struct state const *, time_t const *, - int_fast32_t, struct tm *), + struct tm *funcp(struct state const *, time_t const *, + int_fast32_t, struct tm *), struct state const *sp, const int_fast32_t offset) { @@ -2587,17 +2903,20 @@ continue; for (otherind = 0; otherind < nseen; ++otherind) { otheri = types[otherind]; - if (sp->ttis[otheri].tt_isdst == tmp->tm_isdst) - continue; - tmp->tm_sec += (sp->ttis[otheri].tt_utoff - - sp->ttis[samei].tt_utoff); - tmp->tm_isdst = !tmp->tm_isdst; - t = time2(tmp, funcp, sp, offset, &okay); - if (okay) - return t; - tmp->tm_sec -= (sp->ttis[otheri].tt_utoff - - sp->ttis[samei].tt_utoff); - tmp->tm_isdst = !tmp->tm_isdst; + if (sp->ttis[otheri].tt_isdst != tmp->tm_isdst) { + int sec = tmp->tm_sec; + if (!increment_overflow_64 + (&tmp->tm_sec, + utoff_diff(sp->ttis[otheri].tt_utoff, + sp->ttis[samei].tt_utoff))) { + tmp->tm_isdst = !tmp->tm_isdst; + t = time2(tmp, funcp, sp, offset, &okay); + if (okay) + return t; + tmp->tm_isdst = !tmp->tm_isdst; + } + tmp->tm_sec = sec; + } } } return WRONG; @@ -2622,15 +2941,16 @@ time_t mktime(struct tm *tmp) { + monotime_t now = get_monotonic_time(); time_t t; int err = lock(); - if (err) { + if (0 < err) { errno = err; return -1; } - tzset_unlocked(); + tzset_unlocked(!err, false, now); t = mktime_tzname(lclptr, tmp, true); - unlock(); + unlock(!err); return t; } @@ -2692,17 +3012,16 @@ } #endif -static int_fast32_t +static int_fast32_2s leapcorr(struct state const *sp, time_t t) { - register struct lsinfo const * lp; register int i; - i = sp->leapcnt; + i = leapcount(sp); while (--i >= 0) { - lp = &sp->lsis[i]; - if (t >= lp->ls_trans) - return lp->ls_corr; + struct lsinfo ls = lsinfo(sp, i); + if (ls.ls_trans <= t) + return ls.ls_corr; } return 0; } @@ -2714,6 +3033,21 @@ #if !USE_TIMEX_T # if STD_INSPIRED +static bool +decrement_overflow_time(time_t *tp, int_fast32_2s j) +{ +#ifdef ckd_sub + return ckd_sub(tp, *tp, j); +#else + if (! (j < 0 + ? *tp <= TIME_T_MAX + j + : (TYPE_SIGNED(time_t) ? TIME_T_MIN + j <= *tp : j <= *tp))) + return true; + *tp -= j; + return false; +#endif +} + /* NETBSD_INSPIRED_EXTERN functions are exported to callers if NETBSD_INSPIRED is defined, and are private otherwise. */ # if NETBSD_INSPIRED @@ -2733,71 +3067,72 @@ NETBSD_INSPIRED_EXTERN time_t time2posix_z(struct state *sp, time_t t) { - return t - leapcorr(sp, t); + if (decrement_overflow_time(&t, leapcorr(sp, t))) { + /* Overflow near maximum time_t value with negative correction. + This can happen with unrealistic-but-valid TZif files. */ + errno = EOVERFLOW; + return -1; + } + return t; } time_t time2posix(time_t t) { + monotime_t now = get_monotonic_time(); int err = lock(); - if (err) { + if (0 < err) { errno = err; return -1; } -#ifndef DETECT_TZ_CHANGES - if (!lcl_is_set) -#endif /* DETECT_TZ_CHANGES */ - tzset_unlocked(); + if (0 <= tz_change_interval || !lcl_is_set) + tzset_unlocked(!err, false, now); if (lclptr) t = time2posix_z(lclptr, t); - unlock(); + unlock(!err); return t; } NETBSD_INSPIRED_EXTERN time_t posix2time_z(struct state *sp, time_t t) { - time_t x; - time_t y; - /* - ** For a positive leap second hit, the result - ** is not unique. For a negative leap second - ** hit, the corresponding time doesn't exist, - ** so we return an adjacent second. - */ - x = t + leapcorr(sp, t); - y = x - leapcorr(sp, x); - if (y < t) { - do { - x++; - y = x - leapcorr(sp, x); - } while (y < t); - x -= y != t; - } else if (y > t) { - do { - --x; - y = x - leapcorr(sp, x); - } while (y > t); - x += y != t; - } - return x; + int i; + for (i = leapcount(sp); 0 <= --i; ) { + struct lsinfo ls = lsinfo(sp, i); + time_t t_corr = t; + + if (increment_overflow_time(&t_corr, ls.ls_corr)) { + if (0 <= ls.ls_corr) { + /* Overflow near maximum time_t value with positive correction. + This can happen with ordinary TZif files with leap seconds. */ + errno = EOVERFLOW; + return -1; + } else { + /* A negative correction overflowed, so keep going. + This can happen with unrealistic-but-valid TZif files. */ + } + } else if (ls.ls_trans <= t_corr) + return (t_corr + - (ls.ls_trans == t_corr + && (i == 0 ? 0 : lsinfo(sp, i - 1).ls_corr) < ls.ls_corr)); + } + return t; } time_t posix2time(time_t t) { + monotime_t now = get_monotonic_time(); int err = lock(); - if (err) { + if (0 < err) { errno = err; return -1; } -#ifndef DETECT_TZ_CHANGES - if (!lcl_is_set) -#endif /* DETECT_TZ_CHANGES */ - tzset_unlocked(); + if (0 <= tz_change_interval || !lcl_is_set) + tzset_unlocked(!err, false, now); if (lclptr) t = posix2time_z(lclptr, t); - unlock(); + unlock(!err); return t; } @@ -2823,7 +3158,7 @@ time_t time(time_t *p) { - time_t r = sys_time(0); + time_t r = sys_time(NULL); if (r != (time_t) -1) { iinntt offset = EPOCH_LOCAL ? timezone : 0; if (offset < IINNTT_MIN + EPOCH_OFFSET diff --git a/contrib/tzcode/newctime.3 b/contrib/tzcode/newctime.3 --- a/contrib/tzcode/newctime.3 +++ b/contrib/tzcode/newctime.3 @@ -181,7 +181,7 @@ .PP The .B ctime -function is equivalent to calliing +function is equivalent to calling .B localtime and then calling .B asctime @@ -246,17 +246,17 @@ .PP .nf .ta 2n +\w'long tm_gmtoff;nn'u - int tm_sec; /\(** seconds (0\*(en60) \(**/ - int tm_min; /\(** minutes (0\*(en59) \(**/ - int tm_hour; /\(** hours (0\*(en23) \(**/ - int tm_mday; /\(** day of month (1\*(en31) \(**/ - int tm_mon; /\(** month of year (0\*(en11) \(**/ - int tm_year; /\(** year \- 1900 \(**/ - int tm_wday; /\(** day of week (Sunday = 0) \(**/ - int tm_yday; /\(** day of year (0\*(en365) \(**/ - int tm_isdst; /\(** is daylight saving time in effect? \(**/ - char \(**tm_zone; /\(** time zone abbreviation (optional) \(**/ - long tm_gmtoff; /\(** offset from UT in seconds (optional) \(**/ + int tm_sec; /* seconds (0\*(en60) */ + int tm_min; /* minutes (0\*(en59) */ + int tm_hour; /* hours (0\*(en23) */ + int tm_mday; /* day of month (1\*(en31) */ + int tm_mon; /* month of year (0\*(en11) */ + int tm_year; /* year \- 1900 */ + int tm_wday; /* day of week (Sunday = 0) */ + int tm_yday; /* day of year (0\*(en365) */ + int tm_isdst; /* is daylight saving time in effect? */ + char *tm_zone; /* time zone abbreviation (optional) */ + long tm_gmtoff; /* offset from UT in seconds (optional) */ .fi .RE .PP @@ -311,13 +311,11 @@ functions might (or might not) also behave this way. This is for compatibility with older platforms, as required by POSIX. .SH FILES -.ta \w'/usr/share/zoneinfo/posixrules\0\0'u +.ta \w'/usr/share/zoneinfo/GMT\0\0'u /etc/localtime local timezone file .br /usr/share/zoneinfo timezone directory .br -/usr/share/zoneinfo/posixrules default DST rules (obsolete) -.br /usr/share/zoneinfo/GMT for UTC leap seconds .PP If /usr/share/zoneinfo/GMT is absent, diff --git a/contrib/tzcode/newstrftime.3 b/contrib/tzcode/newstrftime.3 --- a/contrib/tzcode/newstrftime.3 +++ b/contrib/tzcode/newstrftime.3 @@ -74,7 +74,7 @@ ordinary characters. All ordinary characters are copied directly into the array. A conversion specification consists of a percent sign -.Ql % +.q % and one other character. .PP No more than diff --git a/contrib/tzcode/newtzset.3 b/contrib/tzcode/newtzset.3 --- a/contrib/tzcode/newtzset.3 +++ b/contrib/tzcode/newtzset.3 @@ -13,7 +13,7 @@ .PP .B void tzset(void); .PP -/\(** Optional and obsolescent: \(**/ +/* Optional and obsolescent: */ .br .B extern char *tzname[]; .br @@ -78,7 +78,17 @@ is used as-is; otherwise the pathname is relative to a system time conversion information directory. -The file must be in the format specified in +In a privileged program the pathname must be relative. +Relative pathnames must not contain +.q "..\&" +components. +For the purpose of these checks, a file name beginning with +.q "/" +is considered to be relative if it is the +.B localtime +file's name, or if it starts with the +system timezone directory's name followed by one more more slashes. +The file must be a regular file in the format specified in .BR tzfile (5). .PP When @@ -267,7 +277,7 @@ .TP .B <\-03>3<\-02>,M3.5.0/\-2,M10.5.0/\-1 stands for time in western Greenland, 3 hours behind UT, where clocks -follow the EU rules of +follow the EU rule of springing forward on March's last Sunday at 01:00 UT (\-02:00 local time, i.e., 22:00 the previous day) and falling back on October's last Sunday at 01:00 UT (\-01:00 local time, i.e., 23:00 the previous day). @@ -280,28 +290,13 @@ .I TZ specifies daylight saving time but does not specify a .IR rule , -and the optional -.BR tzfile (5)-format -file -.B posixrules -is present in the system time conversion information directory, the -rules in -.B posixrules -are used, with the -.B posixrules -standard and daylight saving time offsets from UT -replaced by those specified by the -.I offset -values in -.IR TZ . -However, the -.B posixrules -file is obsolete: if it is present it is only for backward compatibility, -and it does not work reliably. +the rule typically defaults to the current US daylight-saving rule, +although such a default is not guaranteed and +is incorrect for many locations outside the US. Therefore, if a .I TZ string directly specifies a timezone with daylight saving time, -it should specify the daylight saving rules explicitly. +it should specify the daylight saving rule explicitly. .PP For compatibility with System V Release 3.1, a semicolon .RB ( ; ) @@ -369,6 +364,10 @@ function returns a nonnull pointer to the newly allocated object. Otherwise, it returns a null pointer and sets .IR errno . +The +.B tzfree +function does not modify +.IR errno . .SH ERRORS .TP .B EOVERFLOW @@ -389,13 +388,11 @@ and .BR read (2). .SH FILES -.ta \w'/usr/share/zoneinfo/posixrules\0\0'u +.ta \w'/usr/share/zoneinfo/GMT\0\0'u /etc/localtime local timezone file .br /usr/share/zoneinfo timezone directory .br -/usr/share/zoneinfo/posixrules default DST rules (obsolete) -.br /usr/share/zoneinfo/GMT for UTC leap seconds .PP If /usr/share/zoneinfo/GMT is absent, diff --git a/contrib/tzcode/private.h b/contrib/tzcode/private.h --- a/contrib/tzcode/private.h +++ b/contrib/tzcode/private.h @@ -82,8 +82,9 @@ # include #endif -#if __STDC_VERSION__ < 202311 -# undef static_assert +/* For pre-C23 compilers, a substitute for static_assert. + Some of these compilers may warn if it is used outside the top level. */ +#if __STDC_VERSION__ < 202311 && !defined static_assert # define static_assert(cond) extern int static_assert_check[(cond) ? 1 : -1] #endif @@ -98,7 +99,9 @@ #endif /* This string was in the Factory zone through version 2016f. */ -#define GRANDPARENTED "Local time zone must be set--use tzsetup" +#ifndef GRANDPARENTED +# define GRANDPARENTED "Local time zone must be set--see zic manual page" +#endif /* ** Defaults for preprocessor symbols. @@ -118,6 +121,14 @@ # define HAVE__GENERIC (201112 <= __STDC_VERSION__) #endif +#ifndef HAVE_GETEUID +# define HAVE_GETEUID 1 +#endif + +#ifndef HAVE_GETRESUID +# define HAVE_GETRESUID 1 +#endif + #if !defined HAVE_GETTEXT && defined __has_include # if __has_include() # define HAVE_GETTEXT 1 @@ -182,37 +193,44 @@ # define ctime_r _incompatible_ctime_r #endif /* HAVE_INCOMPATIBLE_CTIME_R */ +#ifndef TZ_RUNTIME_LEAPS +# define TZ_RUNTIME_LEAPS 1 +#endif + /* ** Nested includes */ -/* Avoid clashes with NetBSD by renaming NetBSD's declarations. - If defining the 'timezone' variable, avoid a clash with FreeBSD's - 'timezone' function by renaming its declaration. */ -#define localtime_rz sys_localtime_rz -#define mktime_z sys_mktime_z -#define posix2time_z sys_posix2time_z -#define time2posix_z sys_time2posix_z -#if defined USG_COMPAT && USG_COMPAT == 2 +#include + +/* If defining the 'timezone' variable a la POSIX, avoid clashing with the old + 'timezone' function of FreeBSD <= 14, by renaming the latter's declaration. + This hack can be removed after 2028-11-30, FreeBSD 14's expected EOL. */ +#if (defined __FreeBSD__ && __FreeBSD__ < 15 && defined __BSD_VISIBLE \ + && defined USG_COMPAT && USG_COMPAT == 2) # define timezone sys_timezone +# define timezone_defined #endif -#define timezone_t sys_timezone_t -#define tzalloc sys_tzalloc -#define tzfree sys_tzfree + #include -#undef localtime_rz -#undef mktime_z -#undef posix2time_z -#undef time2posix_z -#if defined USG_COMPAT && USG_COMPAT == 2 + +#ifdef timezone_defined # undef timezone +# undef timezone_defined #endif -#undef timezone_t -#undef tzalloc -#undef tzfree -#include #include +#if defined HAVE_STRNLEN && !HAVE_STRNLEN +static size_t +strnlen (char const *s, size_t maxlen) +{ + size_t i; + for (i = 0; i < maxlen && s[i]; i++) + continue; + return i; +} +#endif + #if !PORT_TO_C89 # include #endif @@ -234,6 +252,9 @@ #ifndef ENOMEM # define ENOMEM EINVAL #endif +#ifndef ENOTCAPABLE +# define ENOTCAPABLE EINVAL +#endif #ifndef ENOTSUP # define ENOTSUP EINVAL #endif @@ -246,7 +267,12 @@ #endif /* HAVE_GETTEXT */ #if HAVE_UNISTD_H -# include /* for R_OK, and other POSIX goodness */ +# include +#else +/* Assume getopt.o or equivalent is linked via Makefile configuration. */ +int getopt(int, char *const[], char const *); +extern char *optarg; +extern int optind; #endif /* HAVE_UNISTD_H */ /* SUPPORT_POSIX2008 means the tzcode library should support @@ -274,6 +300,16 @@ # endif #endif +#ifndef HAVE_ISSETUGID +# if (defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \ + || (defined __linux__ && !defined __GLIBC__) /* Android, musl, etc. */ \ + || (defined __APPLE__ && defined __MACH__) || defined __sun) +# define HAVE_ISSETUGID 1 +# else +# define HAVE_ISSETUGID 0 +# endif +#endif + #ifndef HAVE_SNPRINTF # define HAVE_SNPRINTF (!PORT_TO_C89 || 199901 <= __STDC_VERSION__) #endif @@ -310,10 +346,6 @@ # endif #endif -#ifndef R_OK -# define R_OK 4 -#endif /* !defined R_OK */ - #if PORT_TO_C89 /* @@ -322,150 +354,144 @@ ** previously included files. glibc 2.1 and Solaris 10 and later have ** stdint.h, even with pre-C99 compilers. */ -#if !defined HAVE_STDINT_H && defined __has_include -# define HAVE_STDINT_H 1 /* C23 __has_include implies C99 stdint.h. */ -#endif -#ifndef HAVE_STDINT_H -# define HAVE_STDINT_H \ - (199901 <= __STDC_VERSION__ \ - || 2 < __GLIBC__ + (1 <= __GLIBC_MINOR__) \ - || __CYGWIN__ || INTMAX_MAX) -#endif /* !defined HAVE_STDINT_H */ - -#if HAVE_STDINT_H -# include -#endif /* !HAVE_STDINT_H */ - -#ifndef HAVE_INTTYPES_H -# define HAVE_INTTYPES_H HAVE_STDINT_H -#endif -#if HAVE_INTTYPES_H -# include -#endif - -/* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX. */ -#if defined __LONG_LONG_MAX__ && !defined __STRICT_ANSI__ -# ifndef LLONG_MAX -# define LLONG_MAX __LONG_LONG_MAX__ +# if !defined HAVE_STDINT_H && defined __has_include +# define HAVE_STDINT_H 1 /* C23 __has_include implies C99 stdint.h. */ # endif -# ifndef LLONG_MIN -# define LLONG_MIN (-1 - LLONG_MAX) +# ifndef HAVE_STDINT_H +# define HAVE_STDINT_H \ + (199901 <= __STDC_VERSION__ \ + || 2 < __GLIBC__ + (1 <= __GLIBC_MINOR__) \ + || __CYGWIN__ || INTMAX_MAX) +# endif /* !defined HAVE_STDINT_H */ + +# if HAVE_STDINT_H +# include +# endif /* !HAVE_STDINT_H */ + +# ifndef HAVE_INTTYPES_H +# define HAVE_INTTYPES_H HAVE_STDINT_H # endif -# ifndef ULLONG_MAX -# define ULLONG_MAX (LLONG_MAX * 2ull + 1) +# if HAVE_INTTYPES_H +# include +# endif + +/* Pre-C99 GCC compilers define __LONG_LONG_MAX__ instead of LLONG_MAX. */ +# if defined __LONG_LONG_MAX__ && !defined __STRICT_ANSI__ +# ifndef LLONG_MAX +# define LLONG_MAX __LONG_LONG_MAX__ +# endif +# ifndef LLONG_MIN +# define LLONG_MIN (-1 - LLONG_MAX) +# endif +# ifndef ULLONG_MAX +# define ULLONG_MAX (LLONG_MAX * 2ull + 1) +# endif # endif -#endif -#ifndef INT_FAST64_MAX -# if 1 <= LONG_MAX >> 31 >> 31 +# ifndef INT_FAST64_MAX +# if 1 <= LONG_MAX >> 31 >> 31 typedef long int_fast64_t; -# define INT_FAST64_MIN LONG_MIN -# define INT_FAST64_MAX LONG_MAX -# else +# define INT_FAST64_MIN LONG_MIN +# define INT_FAST64_MAX LONG_MAX +# else /* If this fails, compile with -DHAVE_STDINT_H or with a better compiler. */ typedef long long int_fast64_t; -# define INT_FAST64_MIN LLONG_MIN -# define INT_FAST64_MAX LLONG_MAX +# define INT_FAST64_MIN LLONG_MIN +# define INT_FAST64_MAX LLONG_MAX +# endif # endif -#endif -#ifndef PRIdFAST64 -# if INT_FAST64_MAX == LONG_MAX -# define PRIdFAST64 "ld" -# else -# define PRIdFAST64 "lld" +# ifndef PRIdFAST64 +# if INT_FAST64_MAX == LONG_MAX +# define PRIdFAST64 "ld" +# else +# define PRIdFAST64 "lld" +# endif # endif -#endif -#ifndef SCNdFAST64 -# define SCNdFAST64 PRIdFAST64 -#endif +# ifndef SCNdFAST64 +# define SCNdFAST64 PRIdFAST64 +# endif -#ifndef INT_FAST32_MAX -# if INT_MAX >> 31 == 0 +# ifndef INT_FAST32_MAX typedef long int_fast32_t; # define INT_FAST32_MAX LONG_MAX # define INT_FAST32_MIN LONG_MIN -# else -typedef int int_fast32_t; -# define INT_FAST32_MAX INT_MAX -# define INT_FAST32_MIN INT_MIN # endif -#endif -#ifndef INT_LEAST32_MAX +# ifndef INT_LEAST32_MAX typedef int_fast32_t int_least32_t; -#endif +# endif -#ifndef INTMAX_MAX -# ifdef LLONG_MAX +# ifndef INTMAX_MAX +# ifdef LLONG_MAX typedef long long intmax_t; -# ifndef HAVE_STRTOLL -# define HAVE_STRTOLL 1 +# ifndef HAVE_STRTOLL +# define HAVE_STRTOLL 1 +# endif +# if HAVE_STRTOLL +# define strtoimax strtoll +# endif +# define INTMAX_MAX LLONG_MAX +# define INTMAX_MIN LLONG_MIN +# else +typedef long intmax_t; +# define INTMAX_MAX LONG_MAX +# define INTMAX_MIN LONG_MIN # endif -# if HAVE_STRTOLL -# define strtoimax strtoll +# ifndef strtoimax +# define strtoimax strtol # endif -# define INTMAX_MAX LLONG_MAX -# define INTMAX_MIN LLONG_MIN -# else -typedef long intmax_t; -# define INTMAX_MAX LONG_MAX -# define INTMAX_MIN LONG_MIN -# endif -# ifndef strtoimax -# define strtoimax strtol # endif -#endif -#ifndef PRIdMAX -# if INTMAX_MAX == LLONG_MAX -# define PRIdMAX "lld" -# else -# define PRIdMAX "ld" +# ifndef PRIdMAX +# if INTMAX_MAX == LLONG_MAX +# define PRIdMAX "lld" +# else +# define PRIdMAX "ld" +# endif # endif -#endif -#ifndef PTRDIFF_MAX -# define PTRDIFF_MAX MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t)) -#endif +# ifndef PTRDIFF_MAX +# define PTRDIFF_MAX MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t)) +# endif -#ifndef UINT_FAST32_MAX +# ifndef UINT_FAST32_MAX typedef unsigned long uint_fast32_t; -#endif +# endif -#ifndef UINT_FAST64_MAX -# if 3 <= ULONG_MAX >> 31 >> 31 +# ifndef UINT_FAST64_MAX +# if 3 <= ULONG_MAX >> 31 >> 31 typedef unsigned long uint_fast64_t; -# define UINT_FAST64_MAX ULONG_MAX -# else +# define UINT_FAST64_MAX ULONG_MAX +# else /* If this fails, compile with -DHAVE_STDINT_H or with a better compiler. */ typedef unsigned long long uint_fast64_t; -# define UINT_FAST64_MAX ULLONG_MAX +# define UINT_FAST64_MAX ULLONG_MAX +# endif # endif -#endif -#ifndef UINTMAX_MAX -# ifdef ULLONG_MAX +# ifndef UINTMAX_MAX +# ifdef ULLONG_MAX typedef unsigned long long uintmax_t; -# define UINTMAX_MAX ULLONG_MAX -# else +# define UINTMAX_MAX ULLONG_MAX +# else typedef unsigned long uintmax_t; -# define UINTMAX_MAX ULONG_MAX +# define UINTMAX_MAX ULONG_MAX +# endif # endif -#endif -#ifndef PRIuMAX -# ifdef ULLONG_MAX -# define PRIuMAX "llu" -# else -# define PRIuMAX "lu" +# ifndef PRIuMAX +# ifdef ULLONG_MAX +# define PRIuMAX "llu" +# else +# define PRIuMAX "lu" +# endif # endif -#endif -#ifndef SIZE_MAX -# define SIZE_MAX ((size_t) -1) -#endif +# ifndef SIZE_MAX +# define SIZE_MAX ((size_t) -1) +# endif #endif /* PORT_TO_C89 */ @@ -513,6 +539,15 @@ # define HAVE___HAS_C_ATTRIBUTE false #endif +#ifdef __has_attribute +# if __has_attribute (nonstring) +# define ATTRIBUTE_NONSTRING __attribute__((__nonstring__)) +# endif +#endif +#ifndef ATTRIBUTE_NONSTRING +# define ATTRIBUTE_NONSTRING +#endif + #if HAVE___HAS_C_ATTRIBUTE # if __has_c_attribute(deprecated) # define ATTRIBUTE_DEPRECATED [[deprecated]] @@ -585,11 +620,11 @@ # define ATTRIBUTE_UNSEQUENCED /* empty */ #endif -/* GCC attributes that are useful in tzcode. - __attribute__((const)) is stricter than [[unsequenced]], - so the latter is an adequate substitute in non-GCC C23 platforms. - __attribute__((pure)) is stricter than [[reproducible]], - so the latter is an adequate substitute in non-GCC C23 platforms. */ +/* GNU C attributes that are useful in tzcode. + Although neither __attribute__((const)) nor __attribute__((pure)) are + stricter than their C23 counterparts [[unsequenced]] and [[reproducible]], + the C23 attributes happen to work in each tzcode use of ATTRIBUTE_CONST + and ATTRIBUTE_PURE. (This might not work outside of tzcode!) */ #if __GNUC__ < 3 # define ATTRIBUTE_CONST ATTRIBUTE_UNSEQUENCED # define ATTRIBUTE_FORMAT(spec) /* empty */ @@ -608,6 +643,12 @@ #else # define ATTRIBUTE_PURE_114833 /* empty */ #endif +/* GCC_LINT hack to pacify GCC bug 114833 even though the attribute is + not strictly correct, as the function might not return whereas pure + functions are supposed to return exactly once. This hack is not + known to generate wrong code for tzcode on any platform. + Remove this macro and its uses when the bug is fixed in a GCC release. */ +#define ATTRIBUTE_PURE_114833_HACK ATTRIBUTE_PURE_114833 #if (__STDC_VERSION__ < 199901 && !defined restrict \ && (PORT_TO_C89 || defined _MSC_VER)) @@ -744,10 +785,10 @@ # endif DEPRECATED_IN_C23 char *asctime(struct tm const *); DEPRECATED_IN_C23 char *ctime(time_t const *); -#if SUPPORT_POSIX2008 +# if SUPPORT_POSIX2008 char *asctime_r(struct tm const *restrict, char *restrict); char *ctime_r(time_t const *, char *); -#endif +# endif ATTRIBUTE_CONST double difftime(time_t, time_t); size_t strftime(char *restrict, size_t, char const *restrict, struct tm const *restrict); @@ -795,6 +836,23 @@ extern char **environ; #endif +#ifndef HAVE_MEMPCPY +# if (defined mempcpy \ + || defined __FreeBSD__ || defined __NetBSD__ || defined __linux__) +# define HAVE_MEMPCPY 1 +# else +# define HAVE_MEMPCPY 0 +# endif +#endif +#if !HAVE_MEMPCPY +static void * +mempcpy(void *restrict s1, void const *restrict s2, size_t n) +{ + char *p = memcpy(s1, s2, n); + return p + n; +} +#endif + #if 2 <= HAVE_TZNAME + (TZ_TIME_T || !HAVE_POSIX_DECLS) extern char *tzname[]; #endif @@ -855,26 +913,32 @@ #endif /* -** Define functions that are ABI compatible with NetBSD but have -** better prototypes. NetBSD 6.1.4 defines a pointer type timezone_t -** and labors under the misconception that 'const timezone_t' is a -** pointer to a constant. This use of 'const' is ineffective, so it -** is not done here. What we call 'struct state' NetBSD calls +** Define functions that are ABI compatible with NetBSD. +** What we call 'struct state' NetBSD calls ** 'struct __state', but this is a private name so it doesn't matter. */ #if NETBSD_INSPIRED +# ifdef _NETBSD_SOURCE +# define state __state +# else typedef struct state *timezone_t; +# endif struct tm *localtime_rz(timezone_t restrict, time_t const *restrict, struct tm *restrict); time_t mktime_z(timezone_t restrict, struct tm *restrict); timezone_t tzalloc(char const *); void tzfree(timezone_t); # if STD_INSPIRED +# if TZ_RUNTIME_LEAPS +# define ATTRIBUTE_POSIX2TIME ATTRIBUTE_PURE +# else +# define ATTRIBUTE_POSIX2TIME ATTRIBUTE_CONST +# endif # if TZ_TIME_T || !defined posix2time_z -ATTRIBUTE_PURE time_t posix2time_z(timezone_t, time_t); +ATTRIBUTE_POSIX2TIME time_t posix2time_z(timezone_t, time_t); # endif # if TZ_TIME_T || !defined time2posix_z -ATTRIBUTE_PURE time_t time2posix_z(timezone_t, time_t); +ATTRIBUTE_POSIX2TIME time_t time2posix_z(timezone_t, time_t); # endif # endif #endif @@ -885,7 +949,7 @@ #define TYPE_BIT(type) (CHAR_BIT * (ptrdiff_t) sizeof(type)) #define TYPE_SIGNED(type) (((type) -1) < 0) -#define TWOS_COMPLEMENT(t) ((t) ~ (t) 0 < 0) +#define TWOS_COMPLEMENT(type) (TYPE_SIGNED (type) && (! ~ (type) -1)) /* Minimum and maximum of two values. Use lower case to avoid naming clashes with standard include files. */ @@ -1008,23 +1072,27 @@ */ #if HAVE_GETTEXT -#define _(msgid) gettext(msgid) +# define _(msgid) gettext(msgid) #else /* !HAVE_GETTEXT */ -#define _(msgid) msgid +# define _(msgid) (msgid) #endif /* !HAVE_GETTEXT */ +#define N_(msgid) (msgid) #if !defined TZ_DOMAIN && defined HAVE_GETTEXT # define TZ_DOMAIN "tz" #endif #if HAVE_INCOMPATIBLE_CTIME_R -#undef asctime_r -#undef ctime_r +# undef asctime_r +# undef ctime_r char *asctime_r(struct tm const *restrict, char *restrict); char *ctime_r(time_t const *, char *); #endif /* HAVE_INCOMPATIBLE_CTIME_R */ -/* Handy macros that are independent of tzfile implementation. */ +/* Handy constants that are independent of tzfile implementation. */ + +/* 2**31 - 1 as a signed integer, and usable in #if. */ +#define TWO_31_MINUS_1 2147483647 enum { SECSPERMIN = 60, diff --git a/contrib/tzcode/strftime.c b/contrib/tzcode/strftime.c --- a/contrib/tzcode/strftime.c +++ b/contrib/tzcode/strftime.c @@ -49,8 +49,9 @@ and account for the tm_year origin (1900) and time_t origin (1970). */ #define MKTIME_FITS_IN(min, max) \ ((min) < 0 \ - && ((min) + 0x7fffffff) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900 < INT_MIN \ - && INT_MAX < ((max) - 0x7fffffff) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900) + && (((min) + TWO_31_MINUS_1) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900 \ + < INT_MIN) \ + && INT_MAX < ((max) - TWO_31_MINUS_1) / 366 / 24 / 60 / 60 / 2 + 1970 - 1900) /* MKTIME_MIGHT_OVERFLOW is true if mktime can fail due to time_t overflow or if it is not known whether mktime can fail, diff --git a/contrib/tzcode/theory.html b/contrib/tzcode/theory.html --- a/contrib/tzcode/theory.html +++ b/contrib/tzcode/theory.html @@ -3,14 +3,16 @@ Theory and pragmatics of the tz code and data +

Theory and pragmatics of the tz code and data

-

Outline