" directives to time zone data files and reflect
changes to warning message logic in "zdump.c" (but with calls to
"gettext" kept unbundled at the suggestion of Ken Pizzini).
Release 2005q - 2005-12-13 09:17:09 -0500
Nothing earth-shaking here:
1. Electronic mail addresses have been removed.
2. Casts of the return value of exit have been removed.
3. Casts of the argument of is.* macros have been added.
4. Indentation in one section of zic.c has been fixed.
5. References to dead URLs in the data files have been dealt with.
Release 2005p - 2005-12-05 10:30:53 -0500
"systemv", "tz-link.htm", and "zdump.c" changes
(less the casts of arguments to the is* macros)
Release 2005o - 2005-11-28 10:55:26 -0500
Georgia, Cuba, Nicaragua, and Jordan changes by Paul Eggert
zdump.c lint fixes by Arthur David Olson
Release 2005n - 2005-10-03 09:44:09 -0400
changes by Paul Eggert (both the Uruguay changes and the Kyrgyzstan
et al. changes)
Release 2005m - 2005-08-29 12:15:40 -0400
changes by Paul Eggert (with a small tweak to the tz-art change)
a declaration of an unused variable has been removed from zdump.c
Release 2005l - 2005-08-22 12:06:39 -0400
changes by Paul Eggert
overflow/underflow checks by Arthur David Olson, minus changes to
the "Theory" file about the pending addition of 64-bit data (I grow
less confident of the changes being accepted with each passing day,
and the changes no longer increase the data files nine-fold--there's
less than a doubling in size by my local Sun's reckoning)
Release 2005k - 2005-07-14 14:14:24 -0400
The "leapseconds" file has been edited to reflect the recently
announced leap second at the end of 2005.
I've also deleted electronic mail addresses from the files as an
anti-spam measure.
Release 2005j - 2005-06-13 14:34:13 -0400
These reflect changes to limit the length of time zone abbreviations
and the characters used in those abbreviations.
There are also changes to handle POSIX-style "quoted" timezone
environment variables.
The changes were circulated on the time zone mailing list; the only
change since then was the removal of a couple of minimum-length of
abbreviation checks.
Release data2005i - 2005-04-21 15:04:16 -0400
changes (most importantly to Nicaragua and Haiti) by Paul Eggert
Release 2005h - 2005-04-04 11:24:47 -0400
changes by Paul Eggert
minor changes to Makefile and zdump.c to produce more useful output
when doing a "make typecheck"
Release 2005g - 2005-03-14 10:11:21 -0500
changes by Paul Eggert (a change to current DST rules in Uruguay and
an update to a link to time zone software)
Release 2005f - 2005-03-01 08:45:32 -0500
data and documentation changes by Paul Eggert
Release 2005e - 2005-02-10 15:59:44 -0500
[not summarized]
Release code2005d - 2005-01-31 09:21:47 -0500
make zic complain about links to links if the -v flag is used
have "make public" do more code checking
add an include to "localtime.c" for the benefit of gcc systems
Release 2005c - 2005-01-17 18:36:29 -0500
get better results when mktime runs on a system where time_t is double
changes to the data files (most importantly to Paraguay)
Release 2005b - 2005-01-10 09:19:54 -0500
Get localtime and gmtime working on systems with exotic time_t types.
Update the leap second commentary in the "leapseconds" file.
Release 2005a - 2005-01-01 13:13:44 -0500
[not summarized]
Release code2004i - 2004-12-14 13:42:58 -0500
Deal with systems where time_t is unsigned.
Release code2004h - 2004-12-07 11:40:18 -0500
64-bit-time_t changes
Release 2004g - 2004-11-02 09:06:01 -0500
update to Cuba (taking effect this weekend)
other changes by Paul Eggert
correction of the spelling of Oslo
changed versions of difftime.c and private.h
Release code2004f - 2004-10-21 10:25:22 -0400
Cope with wide-ranging tm_year values.
Release 2004e - 2004-10-11 14:47:21 -0400
Brazil/Argentina/Israel changes by Paul Eggert
changes to tz-link.htm by Paul
one small fix to Makefile
Release 2004d - 2004-09-22 08:27:29 -0400
Avoid overflow problems when TM_YEAR_BASE is added to an integer.
Release 2004c - 2004-08-11 12:06:26 -0400
asctime-related changes
(variants of) some of the documentation changes suggested by Paul Eggert
Release 2004b - 2004-07-19 14:33:35 -0400
data changes by Paul Eggert - most importantly, updates for Argentina
Release 2004a - 2004-05-27 12:00:47 -0400
changes by Paul Eggert
Handle DST transitions that occur at the end of a month in some
years but at the start of the following month in other years.
Add a copy of the correspondence that's the basis for claims about
DST in the Navajo Nation.
Release 2003e - 2003-12-15 09:36:47 -0500
changes by Arthur David Olson (primarily code changes)
changes by Paul Eggert (primarily data changes)
minor changes to "Makefile" and "northamerica" (in the latter case,
optimization of the "Toronto" rules)
Release 2003d - 2003-10-06 09:34:44 -0400
changes by Paul Eggert
Release 2003c - 2003-09-16 10:47:05 -0400
Fix bad returns in zic.c's inleap function.
Thanks to Bradley White for catching the problem!
Release 2003b - 2003-09-16 07:13:44 -0400
Add a "--version" option (and documentation) to the zic and zdump commands.
changes to overflow/underflow checking in zic
a localtime typo fix.
Update the leapseconds and tz-art.htm files.
Release 2003a - 2003-03-24 09:30:54 -0500
changes by Paul Eggert
a few additions and modifications to the tz-art.htm file
Release 2002d - 2002-10-15 13:12:42 -0400
changes by Paul Eggert, less the "Britain (UK)" change in iso3166.tab
There's also a new time zone quote in "tz-art.htm".
Release 2002c - 2002-04-04 11:55:20 -0500
changes by Paul Eggert
Change zic.c to avoid creating symlinks to files that don't exist.
Release 2002b - 2002-01-28 12:56:03 -0500
[These change notes are for Release 2002a, which was corrupted.
2002b was a corrected version of 2002a.]
changes by Paul Eggert
Update the "leapseconds" file to note that there'll be no leap
second at the end of June, 2002.
Change "zic.c" to deal with a problem in handling the "Asia/Bishkek" zone.
Change to "difftime.c" to avoid sizeof problems.
Release 2001d - 2001-10-09 13:31:32 -0400
changes by Paul Eggert
Release 2001c - 2001-06-05 13:59:55 -0400
changes by Paul Eggert and Andrew Brown
Release 2001b - 2001-04-05 16:44:38 -0400
changes by Paul Eggert (modulo jnorgard's typo fix)
tz-art.htm has been HTMLified.
Release 2001a - 2001-03-13 12:57:44 -0500
changes by Paul Eggert
An addition to the "leapseconds" file: comments with the text of the
latest IERS leap second notice.
Trailing white space has been removed from data file lines, and
repeated spaces in "Rule Jordan" lines in the "asia" file have been
converted to tabs.
Release 2000h - 2000-12-14 15:33:38 -0500
changes by Paul Eggert
one typo fix in the "art" file
With providence, this is the last update of the millennium.
Release 2000g - 2000-10-10 11:35:22 -0400
changes by Paul Eggert
correction of John Mackin's name submitted by Robert Elz
Garry Shandling's Daylight Saving Time joke (!?!) from the recent
Emmy Awards broadcast.
Release 2000f - 2000-08-10 09:31:58 -0400
changes by Paul Eggert
Added information in "tz-art.htm" on a Seinfeld reference to DST.
Error checking and messages in the "yearistype" script have been
improved.
Release 2000e - 2000-07-31 09:27:54 -0400
data changes by Paul Eggert
a change to the default value of the defined constant HAVE_STRERROR
the addition of a Dave Barry quote on DST to the tz-arts file
Release 2000d - 2000-04-20 15:43:04 -0400
changes to the documentation and code of strftime for C99 conformance
a bug fix for date.c
These are based on (though modified from) changes by Paul Eggert.
Release 2000c - 2000-03-04 10:31:43 -0500
changes by Paul Eggert
Release 2000b - 2000-02-21 12:16:29 -0500
changes by Paul Eggert and Joseph Myers
modest tweaks to the tz-art.htm and tz-link.htm files
Release 2000a - 2000-01-18 09:21:26 -0500
changes by Paul Eggert
The two hypertext documents have also been renamed.
Release code1999i-data1999j - 1999-11-15 18:43:22 -0500
Paul Eggert's changes
additions to the "zic" manual page and the "Arts.htm" file
Release code1999h-data1999i - 1999-11-08 14:55:21 -0500
[not summarized]
Release data1999h - 1999-10-07 03:50:29 -0400
changes by Paul Eggert to "europe" (most importantly, fixing
Lithuania and Estonia)
Release 1999g - 1999-09-28 11:06:18 -0400
data changes by Paul Eggert (most importantly, the change for
Lebanon that buys correctness for this coming Sunday)
The "code" file contains changes to "Makefile" and "checktab.awk" to
allow better checking of time zone files before they are published.
Release 1999f - 1999-09-23 09:48:14 -0400
changes by Arthur David Olson and Paul Eggert
Release 1999e - 1999-08-17 15:20:54 -0400
changes circulated by Paul Eggert, although the change to handling
of DST-specifying timezone names has been commented out for now
(search for "XXX" in "localtime.c" for details). These files also
do not make any changes to the start of DST in Brazil.
In addition to Paul's changes, there are updates to "Arts.htm" and
cleanups of URLs.
Release 1999d - 1999-03-30 11:31:07 -0500
changes by Paul Eggert
The Makefile's "make public" rule has also been changed to do a test
compile of each individual time zone data file (which should help
avoid problems such as the one we had with Nicosia).
Release 1999c - 1999-03-25 09:47:47 -0500
changes by Paul Eggert, most importantly the change for Chile.
Release 1999b - 1999-02-01 17:51:44 -0500
changes by Paul Eggert
code changes (suggested by Mani Varadarajan, mani at be.com) for
correct handling of symbolic links when building using a relative directory
code changes to generate correct messages for failed links
updates to the URLs in Arts.htm
Release 1999a - 1999-01-19 16:20:29 -0500
error message internationalizations and corrections in zic.c and
zdump.c (as suggested by Vladimir Michl, vladimir.michl at upol.cz,
to whom thanks!)
Release code1998h-data1998i - 1998-10-01 09:56:10 -0400
changes for Brazil, Chile, and Germany
support for use of "24:00" in the input files for the time zone compiler
Release code1998g-data1998h - 1998-09-24 10:50:28 -0400
changes by Paul Eggert
correction to a define in the "private.h" file
Release data1998g - 1998-08-11 03:28:35 -0000
[tzdata1998g.tar.gz is missing!]
Lithuanian change provided by mgedmin at pub.osf.it
Move creation of the GMT link with Etc/GMT to "etcetera" (from
"backward") to ensure that the GMT file is created even where folks
don't want the "backward" links (as suggested by Paul Eggert).
Release data1998f - 1998-07-20 13:50:00 -0000
[tzdata1998f.tar.gz is missing!]
- Update the "leapseconds" file to include the newly-announced
+ Update the "leapseconds" file to include the newly announced
insertion at the end of 1998.
Release code1998f - 1998-06-01 10:18:31 -0400
addition to localtime.c by Guy Harris
Release 1998e - 1998-05-28 09:56:26 -0400
The Makefile is changed to produce zoneinfo-posix rather than
zoneinfo/posix, and to produce zoneinfo-leaps rather than
zoneinfo/right.
data changes by Paul Eggert
changes from Guy Harris to provide asctime_r and ctime_r
A usno1998 file (substantially identical to usno1997) has been added.
Release 1998d - 1998-05-14 11:58:34 -0400
changes to comments (in particular, elimination of references to CIA maps).
"Arts.htm", "WWW.htm", "asia", and "australasia" are the only places
where changes occur.
Release 1998c - 1998-02-28 12:32:26 -0500
changes by Paul Eggert (save the "French correction," on which I'll
wait for the dust to settle)
symlink changes
changes and additions to Arts.htm
Release 1998b - 1998-01-17 14:31:51 -0500
URL cleanups and additions
Release 1998a - 1998-01-13 12:37:35 -0500
changes by Paul Eggert
Release code1997i-data1997k - 1997-12-29 09:53:41 -0500
changes by Paul Eggert, with minor modifications from Arthur David
Olson to make the files more browser friendly
Release code1997h-data1997j - 1997-12-18 17:47:35 -0500
minor changes to put "TZif" at the start of each timezone information file
a rule has also been added to the Makefile so you can
make zones
to just recompile the zone information files (rather than doing a
full "make install" with its other effects).
Release data1997i - 1997-10-07 08:45:38 -0400
changes to Africa by Paul Eggert
Release code1997g-data1997h - 1997-09-04 16:56:54 -0400
corrections for Uruguay (and other locations)
Arthur David Olson's simple-minded fix allowing mktime to both
correctly handle leap seconds and correctly handle tm_sec values
upon which arithmetic has been performed.
Release code1997f-data1997g - 1997-07-19 13:15:02 -0400
Paul Eggert's updates
a small change to a function prototype;
"Music" has been renamed "Arts.htm", HTMLified, and augmented to
include information on Around the World in Eighty Days.
Release code1997e-data1997f - 1997-05-03 18:52:34 -0400
fixes to zic's error handling
changes inspired by the item circulated on Slovenia
The description of Web resources has been HTMLified for browsing
convenience.
A new piece of tz-related music has been added to the "Music" file.
Release code1997d-data1997e - 1997-03-29 12:48:52 -0500
Paul Eggert's latest suggestions
Release code1997c-data1997d - 1997-03-07 20:37:54 -0500
changes to "zic.c" to correct performance of the "-s" option
a new file "usno1997"
Release data1997c - 1997-03-04 09:58:18 -0500
changes in Israel
Release 1997b - 1997-02-27 18:34:19 -0500
The data file incorporates the 1997 leap second.
The code file incorporates Arthur David Olson's take on the
zic/multiprocessor/directory-creation situation.
Release 1997a - 1997-01-21 09:11:10 -0500
Paul Eggert's Antarctica (and other changes)
Arthur David Olson finessed the "getopt" issue by checking against
both -1 and EOF (regardless of POSIX, SunOS 4.1.1's manual says -1
is returned while SunOS 5.5's manual says EOF is returned).
Release code1996o-data1996n - 1996-12-27 21:42:05 -0500
Paul Eggert's latest changes
Release code1996n - 1996-12-16 09:42:02 -0500
link snapping fix from Bruce Evans (via Garrett Wollman)
Release data1996m - 1996-11-24 02:37:34 -0000
[tzdata1996m.tar.gz is missing!]
Paul Eggert's batch of changes
Release code1996m-data1996l - 1996-11-05 14:00:12 -0500
No functional changes here; the files have simply been changed to
make more use of ISO style dates in comments. The names of the above
files now include the year in full.
Release code96l - 1996-09-08 17:12:20 -0400
tzcode96k was missing a couple of pieces.
Release 96k - 1996-09-08 16:06:22 -0400
the latest round of changes from Paul Eggert
the recent Year 2000 material
Release code96j - 1996-07-30 13:18:53 -0400
Set sp->typecnt as suggested by Timothy Patrick Murphy.
Release code96i - 1996-07-27 20:11:35 -0400
Paul's suggested patch for strftime %V week numbers
Release data96i - 1996-07-01 18:13:04 -0400
"northamerica" and "europe" changes by Paul Eggert
Release code96h - 1996-06-05 08:02:21 -0400
fix for handling transitions specified in Universal Time
Some "public domain" notices have also been added.
Release code96g - 1996-05-16 14:00:26 -0400
fix for the simultaneous-DST-and-zone-change challenge
Release data96h - 1996-05-09 17:40:51 -0400
changes by Paul Eggert
Release code96f-data96g - 1996-05-03 03:09:59 -0000
[tzcode96f.tar.gz + tzdata96g.tar.gz are both missing!]
The changes get us some of the way to fixing the problems noted in Paul
Eggert's letter yesterday (in addition to a few others). The approach
has been to make zic a bit smarter about figuring out what time zone
abbreviations apply just after the time specified in the "UNTIL" part
of a zone line. Putting the smarts in zic means avoiding having
transition times show up in both "Zone" lines and "Rule" lines, which
in turn avoids multiple transition time entries in time zone files.
(This also makes the zic input files such as "europe" a bit shorter and
should ease maintenance.)
Release data96f - 1996-04-19 19:20:03 -0000
[tzdata96f.tar.gz is missing!]
The only changes are to the "northamerica" file; the time zone
abbreviation for Denver is corrected to MST (and MDT), and the
comments for Mexico have been updated.
Release data96e - 1996-03-19 17:37:26 -0500
Proposals by Paul Eggert, in particular the Portugal change that
comes into play at the end of this month.
Release data96d - 1996-03-18 20:49:39 -0500
[not summarized]
Release code96e - 1996-02-29 15:43:27 -0000
[tzcode96e.tar.gz is missing!]
internationalization changes and the fix to the documentation for strftime
Release code96d-data96c - 1996-02-12 11:05:27 -0500
The "code" file simply updates Bob Kridle's electronic address.
The "data" file updates rules for Mexico.
Release data96b - 1996-01-27 15:44:42 -0500
Kiribati change
Release code96c - 1996-01-16 16:58:15 -0500
leap-year streamlining and binary-search changes
fix to newctime.3
Release code96b - 1996-01-10 20:42:39 -0500
fixes and enhancements from Paul Eggert, including code that
emulates the behavior of recent versions of the SunOS "date"
command.
Release 96a - 1996-01-06 09:08:24 -0500
Israel updates
fixes to strftime.c for correct ISO 8601 week number generation,
plus support for two new formats ('G' and 'g') to give ISO 8601 year
numbers (which are not necessarily the same as calendar year numbers)
Release code95i-data95m - 1995-12-21 12:46:47 -0500
The latest revisions from Paul Eggert are included, the usno1995
file has been updated, and a new file ("WWW") covering useful URLs
has been added.
Release code95h-data95l - 1995-12-19 18:10:12 -0500
A simplification of a macro definition, a change to data for Sudan,
and (for last minute shoppers) notes in the "Music" file on the CD
"Old Man Time".
Release code95g-data95k - 1995-10-30 10:32:47 -0500
(slightly reformatted) 8-bit-clean proposed patch
minor patch: US/Eastern -> America/New_York
snapshot of the USNO's latest data ("usno1995")
some other minor cleanups
Release code95f-data95j - 1995-10-28 21:01:34 -0000
[tzcode95f.tar.gz + tzdata95j.tar.gz are both missing!]
European cleanups
support for 64-bit time_t's
optimization in localtime.c
Release code95e - 1995-10-13 13:23:57 -0400
the mktime change to scan from future to past when trying to find time zone
offsets
Release data95i - 1995-09-26 10:43:26 -0400
For Canada/Central, guess that the Sun customer's "one week too
early" was just a approximation, and the true error is one month
too early. This is consistent with the rest of Canada.
Release data95h - 1995-09-21 11:26:48 -0400
latest changes from Paul Eggert
Release code95d - 1995-09-14 11:14:45 -0400
the addition of a "Music" file, which documents four recorded
versions of the tune "Save That Time".
Release data95g - 1995-09-01 17:21:36 -0400
"yearistype" correction
Release data95f - 1995-08-28 20:46:56 -0400
Paul Eggert's change to the australasia file
Release data95e - 1995-07-08 18:02:34 -0400
The only change is a leap second at the end of this year.
Thanks to Bradley White for forwarding news on the leap second.
Release data95d - 1995-07-03 13:26:22 -0400
Paul Eggert's changes
Release data95c - 1995-07-02 19:19:28 -0400
changes to "asia", "backward", "europe", and "southamerica"
(read: northamericacentrics need not apply)
Release code95c - 1995-03-13 14:00:46 -0500
one-line fix for sign extension problems in detzcode
Release 95b - 1995-03-04 11:22:38 -0500
Minor changes in both:
The "code" file contains a workaround for the lack of "unistd.h" in
Microsoft C++ version 7.
The "data" file contains a fixed "Link" for America/Shiprock.
Release 94h - 1994-12-10 12:51:14 -0500
The files:
* incorporate the changes to "zdump" and "date" to make changes to
the "TZ" environment variable permanent;
* incorporate the table changes by Paul Eggert;
* include (and document) support for universal time specifications in
data files - but do not (yet) include use of this feature in the
data files.
Think of this as "TZ Classic" - the software has been set up not to break if
universal time shows up in its input, and data entries have been
left as is so as not to break existing implementations.
Release data94f - 1994-08-20 12:56:09 -0400
(with thanks!) the latest data updates from Paul Eggert
Release data94e - 1994-06-04 13:13:53 -0400
[not summarized]
Release code94g - 1994-05-05 12:14:07 -0400
fix missing "optind.c" and a reference to it in the Makefile
Release code94f - 1994-05-05 13:00:33 -0000
[tzcode94f.tar.gz is missing!]
changes to avoid overflow in difftime, as well as changes to cope
with the 52/53 challenge in strftime
Release code94e - 1994-03-30 23:32:59 -0500
change for the benefit of PCTS
Release 94d - 1994-02-24 15:42:25 -0500
Avoid clashes with POSIX semantics for zones such as GMT+4.
Some other very minor housekeeping is also present.
Release code94c - 1994-02-10 08:52:40 -0500
Fix bug where mkdirs was broken unless you compile with
-fwritable-strings (which is generally losing to do).
Release 94b - 1994-02-07 10:04:33 -0500
work by Paul Eggert who notes:
I found another book of time zone histories by E W Whitman; it's not
as extensive as Shanks but has a few goodies of its own. I used it
to update the tables. I also fixed some more as a result of
correspondence with Adam David and Peter Ilieve, and move some stray
links from 'europe' to 'backward'. I corrected some scanning errors
in usno1989.
As far as the code goes, I fixed zic to allow years in the range
INT_MIN to INT_MAX; this fixed a few boundary conditions around 1900.
And I cleaned up the zic documentation a little bit.
Release data94a - 1994-02-03 08:58:54 -0500
It simply incorporates the recently announced leap second into the
"leapseconds" file.
Release 93g - 1993-11-22 17:28:27 -0500
Paul Eggert has provided a good deal of historic information (based
on Shanks), and there are some code changes to deal with the buglets
that crawled out in dealing with the new information.
Release 93f - 1993-10-15 12:27:46 -0400
Paul Eggert's changes
Release 93e - 1993-09-05 21:21:44 -0400
This has updated data for Israel, England, and Kwajalein. There's
also an update to "zdump" to cope with Kwajalein's 24-hour jump.
Thanks to Paul Eggert and Peter Ilieve for the changes.
Release 93d - 1993-06-17 23:34:17 -0400
new fix and new data on Israel
Release 93c - 1993-06-06 19:31:55 -0400
[not summarized]
Release 93b - 1993-02-02 14:53:58 -0500
updated "leapseconds" file
Release 93 - 1993-01-08 07:01:06 -0500
At kre's suggestion, the package has been split in two - a code piece
(which also includes documentation) that's only of use to folks who
want to recompile things and a data piece useful to anyone who can
run "zic".
The new version has a few changes to the data files, a few
portability changes, and an off-by-one fix (with thanks to
Tom Karzes at deshaw.com for providing a description and a
solution).
Release 92c - 1992-11-21 17:35:36 -0000
[tz92c.tar.Z is missing!]
The fallout from the latest round of DST transitions.
There are changes for Portugal, Saskatchewan, and "Pacific-New";
there's also a change to "zic.c" that makes it portable to more systems.
Release 92 - 1992-04-25 18:17:03 -0000
[tz92.tar.Z is missing!]
By popular demand (well, at any rate, following a request by kre at munnari)
The 1989 update of the time zone package featured:
* POSIXization (including interpretation of POSIX-style TZ environment
variables, provided by Guy Harris),
* ANSIfication (including versions of "mktime" and "difftime"),
* SVIDulation (an "altzone" variable)
* MACHination (the "gtime" function)
* corrections to some time zone data (including corrections to the rules
for Great Britain and New Zealand)
* reference data from the United States Naval Observatory for folks who
want to do additional time zones
* and the 1989 data for Saudi Arabia.
(Since this code will be treated as "part of the implementation" in some
places and as "part of the application" in others, there's no good way to
name functions, such as timegm, that are not part of the proposed ANSI C
standard; such functions have kept their old, underscore-free names in this
update.)
And the "dysize" function has disappeared; it was present to allow
compilation of the "date" command on old BSD systems, and a version of "date"
is now provided in the package. The "date" command is not created when you
"make all" since it may lack options provided by the version distributed with
your operating system, or may not interact with the system in the same way
the native version does.
Since POSIX frowns on correct leap second handling, the default behavior of
the "zic" command (in the absence of a "-L" option) has been changed to omit
leap second information from its output files.
-----
Notes
This file contains copies of the part of each release announcement
that talks about the changes in that release. The text has been
adapted and reformatted for the purposes of this file.
Traditionally a release R consists of a pair of tarball files,
tzcodeR.tar.gz and tzdataR.tar.gz. However, some releases (e.g.,
code2010a, data2012c) consist of just one or the other tarball, and a
few (e.g., code2012c-data2012d) have tarballs with mixed version
numbers. Recent releases also come in an experimental format
consisting of a single tarball tzdb-R.tar.lz with extra data.
Release timestamps are taken from the release's commit (for newer,
Git-based releases), from the newest file in the tarball (for older
releases, where this info is available) or from the email announcing
the release (if all else fails; these are marked with a time zone
abbreviation of -0000 and an "is missing!" comment).
Earlier versions of the code and data were not announced on the tz
list and are not summarized here.
This file is in the public domain.
Local Variables:
coding: utf-8
End:
diff --git a/contrib/tzcode/asctime.c b/contrib/tzcode/asctime.c
index 1a6486f38163..b129ea22dd1b 100644
--- a/contrib/tzcode/asctime.c
+++ b/contrib/tzcode/asctime.c
@@ -1,108 +1,133 @@
/* asctime and asctime_r a la POSIX and ISO C, except pad years before 1000. */
/*
** This file is in the public domain, so clarified as of
** 1996-06-05 by Arthur David Olson.
*/
/*
** Avoid the temptation to punt entirely to strftime;
** the output of strftime is supposed to be locale specific
** whereas the output of asctime is supposed to be constant.
*/
/*LINTLIBRARY*/
#include "namespace.h"
#include "private.h"
#include "un-namespace.h"
#include
/*
** All years associated with 32-bit time_t values are exactly four digits long;
** some years associated with 64-bit time_t values are not.
** Vintage programs are coded for years that are always four digits long
** and may assume that the newline always lands in the same place.
** For years that are less than four digits, we pad the output with
** leading zeroes to get the newline in the traditional place.
** The -4 ensures that we get four characters of output even if
** we call a strftime variant that produces fewer characters for some years.
** The ISO C and POSIX standards prohibit padding the year,
** but many implementations pad anyway; most likely the standards are buggy.
*/
static char const ASCTIME_FMT[] = "%s %s%3d %.2d:%.2d:%.2d %-4s\n";
/*
** For years that are more than four digits we put extra spaces before the year
** so that code trying to overwrite the newline won't end up overwriting
** a digit within a year and truncating the year (operating on the assumption
** that no output is better than wrong output).
*/
static char const ASCTIME_FMT_B[] = "%s %s%3d %.2d:%.2d:%.2d %s\n";
enum { STD_ASCTIME_BUF_SIZE = 26 };
/*
** Big enough for something such as
** ??? ???-2147483648 -2147483648:-2147483648:-2147483648 -2147483648\n
** (two three-character abbreviations, five strings denoting integers,
** seven explicit spaces, two explicit colons, a newline,
** and a trailing NUL byte).
** The values above are for systems where an int is 32 bits and are provided
** as an example; the size expression below is a bound for the system at
** hand.
*/
static char buf_asctime[2*3 + 5*INT_STRLEN_MAXIMUM(int) + 7 + 2 + 1 + 1];
+/* A similar buffer for ctime.
+ C89 requires that they be the same buffer.
+ This requirement was removed in C99, so support it only if requested,
+ as support is more likely to lead to bugs in badly written programs. */
+#if SUPPORT_C89
+# define buf_ctime buf_asctime
+#else
+static char buf_ctime[sizeof buf_asctime];
+#endif
+
char *
-asctime_r(register const struct tm *timeptr, char *buf)
+asctime_r(struct tm const *restrict timeptr, char *restrict buf)
{
static const char wday_name[][4] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
static const char mon_name[][4] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
const char * wn;
const char * mn;
char year[INT_STRLEN_MAXIMUM(int) + 2];
char result[sizeof buf_asctime];
if (timeptr == NULL) {
errno = EINVAL;
return strcpy(buf, "??? ??? ?? ??:??:?? ????\n");
}
if (timeptr->tm_wday < 0 || timeptr->tm_wday >= DAYSPERWEEK)
wn = "???";
else wn = wday_name[timeptr->tm_wday];
if (timeptr->tm_mon < 0 || timeptr->tm_mon >= MONSPERYEAR)
mn = "???";
else mn = mon_name[timeptr->tm_mon];
/*
** Use strftime's %Y to generate the year, to avoid overflow problems
** when computing timeptr->tm_year + TM_YEAR_BASE.
** Assume that strftime is unaffected by other out-of-range members
** (e.g., timeptr->tm_mday) when processing "%Y".
*/
strftime(year, sizeof year, "%Y", timeptr);
/*
** We avoid using snprintf since it's not available on all systems.
*/
sprintf(result,
((strlen(year) <= 4) ? ASCTIME_FMT : ASCTIME_FMT_B),
wn, mn,
timeptr->tm_mday, timeptr->tm_hour,
timeptr->tm_min, timeptr->tm_sec,
year);
- if (strlen(result) < STD_ASCTIME_BUF_SIZE || buf == buf_asctime)
+ if (strlen(result) < STD_ASCTIME_BUF_SIZE
+ || buf == buf_ctime || buf == buf_asctime)
return strcpy(buf, result);
else {
errno = EOVERFLOW;
return NULL;
}
}
char *
asctime(register const struct tm *timeptr)
{
return asctime_r(timeptr, buf_asctime);
}
+
+char *
+ctime_r(const time_t *timep, char *buf)
+{
+ struct tm mytm;
+ struct tm *tmp = localtime_r(timep, &mytm);
+ return tmp ? asctime_r(tmp, buf) : NULL;
+}
+
+char *
+ctime(const time_t *timep)
+{
+ return ctime_r(timep, buf_ctime);
+}
diff --git a/contrib/tzcode/date.1 b/contrib/tzcode/date.1
index 043e568117df..e8107212364d 100644
--- a/contrib/tzcode/date.1
+++ b/contrib/tzcode/date.1
@@ -1,167 +1,168 @@
.\" This file is in the public domain, so clarified as of
.\" 2009-05-17 by Arthur David Olson.
-.TH date 1
+.TH date 1 "" "Time Zone Database"
.SH NAME
date \- show and set date and time
.SH SYNOPSIS
.if n .nh
.if n .na
.ie \n(.g .ds - \f(CR-\fP
.el .ds - \-
.B date
[
.B \*-u
] [
.B \*-c
] [
.B \*-r
.I seconds
] [
.BI + format
] [
\fR[\fIyyyy\fR]\fImmddhhmm\fR[\fIyy\fR][\fB.\fIss\fR]
]
.SH DESCRIPTION
.ie '\(lq'' .ds lq \&"\"
.el .ds lq \(lq\"
.ie '\(rq'' .ds rq \&"\"
.el .ds rq \(rq\"
.de q
\\$3\*(lq\\$1\*(rq\\$2
..
The
.B date
command
without arguments writes the date and time to the standard output in
the form
.ce 1
Wed Mar 8 14:54:40 EST 1989
.br
with
.B EST
replaced by the local time zone's abbreviation
(or by the abbreviation for the time zone specified in the
.B TZ
environment variable if set).
The exact output format depends on the locale.
.PP
If a command-line argument starts with a plus sign (\c
.q "\fB+\fP" ),
the rest of the argument is used as a
.I format
that controls what appears in the output.
In the format, when a percent sign (\c
.q "\fB%\fP"
appears,
it and the character after it are not output,
but rather identify part of the date or time
to be output in a particular way
(or identify a special character to output):
.nf
.sp
.if t .in +.5i
.if n .in +2
.ta \w'%M\0\0'u +\w'Wed Mar 8 14:54:40 EST 1989\0\0'u
Sample output Explanation
%a Wed Abbreviated weekday name*
%A Wednesday Full weekday name*
%b Mar Abbreviated month name*
%B March Full month name*
%c Wed Mar 08 14:54:40 1989 Date and time*
%C 19 Century
%d 08 Day of month (always two digits)
%D 03/08/89 Month/day/year (eight characters)
%e 8 Day of month (leading zero blanked)
%h Mar Abbreviated month name*
%H 14 24-hour-clock hour (two digits)
%I 02 12-hour-clock hour (two digits)
%j 067 Julian day number (three digits)
%k 2 12-hour-clock hour (leading zero blanked)
%l 14 24-hour-clock hour (leading zero blanked)
%m 03 Month number (two digits)
%M 54 Minute (two digits)
%n \\n newline character
%p PM AM/PM designation
%r 02:54:40 PM Hour:minute:second AM/PM designation
%R 14:54 Hour:minute
%S 40 Second (two digits)
%t \\t tab character
%T 14:54:40 Hour:minute:second
%U 10 Sunday-based week number (two digits)
%w 3 Day number (one digit, Sunday is 0)
%W 10 Monday-based week number (two digits)
%x 03/08/89 Date*
%X 14:54:40 Time*
%y 89 Last two digits of year
%Y 1989 Year in full
%z -0500 Numeric time zone
%Z EST Time zone abbreviation
%+ Wed Mar 8 14:54:40 EST 1989 Default output format*
.if t .in -.5i
.if n .in -2
* The exact output depends on the locale.
.sp
.fi
If a character other than one of those shown above appears after
a percent sign in the format,
that following character is output.
All other characters in the format are copied unchanged to the output;
a newline character is always added at the end of the output.
.PP
In Sunday-based week numbering,
the first Sunday of the year begins week 1;
days preceding it are part of
.q "week 0" .
In Monday-based week numbering,
the first Monday of the year begins week 1.
.PP
To set the date, use a command line argument with one of the following forms:
.nf
.if t .in +.5i
.if n .in +2
.ta \w'198903081454\0'u
1454 24-hour-clock hours (first two digits) and minutes
081454 Month day (first two digits), hours, and minutes
03081454 Month (two digits, January is 01), month day, hours, minutes
8903081454 Year, month, month day, hours, minutes
0308145489 Month, month day, hours, minutes, year
(on System V-compatible systems)
030814541989 Month, month day, hours, minutes, four-digit year
198903081454 Four-digit year, month, month day, hours, minutes
.if t .in -.5i
.if n .in -2
.fi
If the century, year, month, or month day is not given,
the current value is used.
Any of the above forms may be followed by a period and two digits that give
the seconds part of the new time; if no seconds are given, zero is assumed.
.PP
These options are available:
.TP
.BR \*-u " or " \*-c
Use Universal Time when setting and showing the date and time.
.TP
.BI "\*-r " seconds
Output the date that corresponds to
.I seconds
past the epoch of 1970-01-01 00:00:00 UTC, where
.I seconds
should be an integer, either decimal, octal (leading 0), or
hexadecimal (leading 0x), preceded by an optional sign.
.SH FILES
.ta \w'/usr/share/zoneinfo/posixrules\0\0'u
/etc/localtime local timezone file
.br
/usr/lib/locale/\f2L\fP/LC_TIME description of time locale \f2L\fP
.br
/usr/share/zoneinfo timezone information directory
.br
-/usr/share/zoneinfo/posixrules used with POSIX-style TZ's
+/usr/share/zoneinfo/posixrules default DST rules (obsolete,
+ and can cause bugs if present)
.br
/usr/share/zoneinfo/GMT for UTC leap seconds
.sp
If
.B /usr/share/zoneinfo/GMT
is absent,
UTC leap seconds are loaded from
.BR /usr/share/zoneinfo/posixrules .
diff --git a/contrib/tzcode/date.c b/contrib/tzcode/date.c
index 11c5e5fe8d49..b62f04d768bc 100644
--- a/contrib/tzcode/date.c
+++ b/contrib/tzcode/date.c
@@ -1,216 +1,217 @@
/* Display or set the current time and date. */
/* Copyright 1985, 1987, 1988 The Regents of the University of California.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the University nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE. */
#include "private.h"
#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);
static void dogmt(void);
static void errensure(void);
static void timeout(FILE *, const char *, const struct tm *);
-static ATTRIBUTE_NORETURN void usage(void);
+ATTRIBUTE_NORETURN static void usage(void);
int
main(const int argc, char *argv[])
{
register const char * format = "+%+";
register int ch;
register bool rflag = false;
time_t t;
intmax_t secs;
char * endarg;
#ifdef LC_ALL
setlocale(LC_ALL, "");
#endif /* defined(LC_ALL) */
#if HAVE_GETTEXT
# ifdef TZ_DOMAINDIR
bindtextdomain(TZ_DOMAIN, TZ_DOMAINDIR);
# endif /* defined(TEXTDOMAINDIR) */
textdomain(TZ_DOMAIN);
#endif /* HAVE_GETTEXT */
t = time(NULL);
while ((ch = getopt(argc, argv, "ucr:")) != EOF && ch != -1) {
switch (ch) {
default:
usage();
case 'u': /* do it in UT */
case 'c':
dogmt();
break;
case 'r': /* seconds since 1970 */
if (rflag) {
fprintf(stderr,
_("date: error: multiple -r's used"));
usage();
}
rflag = true;
errno = 0;
secs = strtoimax(optarg, &endarg, 0);
if (*endarg || optarg == endarg)
errno = EINVAL;
else if (! (TIME_T_MIN <= secs && secs <= TIME_T_MAX))
errno = ERANGE;
if (errno) {
- perror(optarg);
+ char const *e = strerror(errno);
+ fprintf(stderr, _("date: %s: %s\n"),
+ optarg, e);
errensure();
exit(retval);
}
t = secs;
break;
}
}
if (optind < argc) {
if (argc - optind != 1) {
fprintf(stderr,
_("date: error: multiple operands in command line\n"));
usage();
}
format = argv[optind];
if (*format != '+') {
fprintf(stderr, _("date: unknown operand: %s\n"), format);
usage();
}
}
display(format, t);
return retval;
}
static void
dogmt(void)
{
static char ** fakeenv;
if (fakeenv == NULL) {
static char tzeutc0[] = "TZ=UTC0";
ptrdiff_t from, to, n;
for (n = 0; environ[n] != NULL; ++n)
continue;
#if defined ckd_add && defined ckd_mul
if (!ckd_add(&n, n, 2) && !ckd_mul(&n, n, sizeof *fakeenv)
- && n <= SIZE_MAX)
+ && n <= INDEX_MAX)
fakeenv = malloc(n);
#else
- if (n <= min(PTRDIFF_MAX, SIZE_MAX) / sizeof *fakeenv - 2)
+ if (n <= INDEX_MAX / sizeof *fakeenv - 2)
fakeenv = malloc((n + 2) * sizeof *fakeenv);
#endif
if (fakeenv == NULL) {
fprintf(stderr, _("date: Memory exhausted\n"));
errensure();
exit(retval);
}
to = 0;
fakeenv[to++] = tzeutc0;
for (from = 1; environ[from] != NULL; ++from)
if (strncmp(environ[from], "TZ=", 3) != 0)
fakeenv[to++] = environ[from];
fakeenv[to] = NULL;
environ = fakeenv;
}
}
static void
errensure(void)
{
if (retval == EXIT_SUCCESS)
retval = EXIT_FAILURE;
}
static void
usage(void)
{
fprintf(stderr,
_("date: usage: date [-u] [-c] [-r seconds]"
" [+format]\n"));
errensure();
exit(retval);
}
static void
display(char const *format, time_t now)
{
struct tm *tmp;
tmp = localtime(&now);
if (!tmp) {
fprintf(stderr,
_("date: error: time out of range\n"));
errensure();
return;
}
timeout(stdout, format, tmp);
putchar('\n');
fflush(stdout);
fflush(stderr);
if (ferror(stdout) || ferror(stderr)) {
fprintf(stderr,
_("date: error: couldn't write results\n"));
errensure();
}
}
static void
timeout(FILE *fp, char const *format, struct tm const *tmp)
{
char *cp = NULL;
ptrdiff_t result;
ptrdiff_t size = 1024 / 2;
for ( ; ; ) {
#ifdef ckd_mul
- bool bigger = !ckd_mul(&size, size, 2) && size <= SIZE_MAX;
+ bool bigger = !ckd_mul(&size, size, 2) && size <= INDEX_MAX;
#else
- bool bigger = (size <= min(PTRDIFF_MAX, SIZE_MAX) / 2
- && (size *= 2, true));
+ bool bigger = size <= INDEX_MAX / 2 && (size *= 2, true);
#endif
char *newcp = bigger ? realloc(cp, size) : NULL;
if (!newcp) {
fprintf(stderr,
_("date: error: can't get memory\n"));
errensure();
exit(retval);
}
cp = newcp;
result = strftime(cp, size, format, tmp);
if (result != 0)
break;
}
fwrite(cp + 1, 1, result - 1, fp);
free(cp);
}
diff --git a/contrib/tzcode/localtime.c b/contrib/tzcode/localtime.c
index a8beaf47319a..3bf9378fe673 100644
--- a/contrib/tzcode/localtime.c
+++ b/contrib/tzcode/localtime.c
@@ -1,2671 +1,2691 @@
/* Convert timestamp from time_t to struct tm. */
/*
** This file is in the public domain, so clarified as of
** 1996-06-05 by Arthur David Olson.
*/
/*
** Leap second handling from Bradley White.
** POSIX-style TZ environment variable handling from Guy Harris.
*/
/*LINTLIBRARY*/
#define LOCALTIME_IMPLEMENTATION
#include "namespace.h"
#ifdef DETECT_TZ_CHANGES
#ifndef DETECT_TZ_CHANGES_INTERVAL
#define DETECT_TZ_CHANGES_INTERVAL 61
#endif
#include
#endif
#include
#if THREAD_SAFE
#include
#endif
#include "private.h"
#include "un-namespace.h"
#include "tzfile.h"
#include "libc_private.h"
#if defined THREAD_SAFE && THREAD_SAFE
static pthread_mutex_t locallock = PTHREAD_MUTEX_INITIALIZER;
static int lock(void) {
if (__isthreaded)
return _pthread_mutex_lock(&locallock);
return 0;
}
static void unlock(void) {
if (__isthreaded)
_pthread_mutex_unlock(&locallock);
}
#else
static int lock(void) { return 0; }
static void unlock(void) { }
#endif
-#ifndef TZ_ABBR_MAX_LEN
-# define TZ_ABBR_MAX_LEN 16
-#endif /* !defined TZ_ABBR_MAX_LEN */
-
#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 */
/*
** Support non-POSIX platforms that distinguish between text and binary files.
*/
#ifndef O_BINARY
# define O_BINARY 0
#endif
#ifndef WILDABBR
/*
** Someone might make incorrect use of a time zone abbreviation:
** 1. They might reference tzname[0] before calling tzset (explicitly
** or implicitly).
** 2. They might reference tzname[1] before calling tzset (explicitly
** or implicitly).
** 3. They might reference tzname[1] after setting to a time zone
** in which Daylight Saving Time is never observed.
** 4. They might reference tzname[0] after setting to a time zone
** in which Standard Time is never observed.
** 5. They might reference tm.TM_ZONE after calling offtime.
** What's best to do in the above cases is open to debate;
** for now, we just set things up so that in any of the five cases
** WILDABBR is used. Another possibility: initialize tzname[0] to the
** string "tzname[0] used before set", and similarly for the other cases.
** And another: initialize tzname[0] to "ERA", with an explanation in the
** manual page of what this "time zone abbreviation" means (doing this so
** that tzname[0] has the "normal" length of three characters).
*/
# define WILDABBR " "
#endif /* !defined WILDABBR */
static const char wildabbr[] = WILDABBR;
static char const etc_utc[] = "Etc/UTC";
static char const *utc = etc_utc + sizeof "Etc/" - 1;
/*
** The DST rules to use if TZ has no rules and we can't load TZDEFRULES.
** 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.
*/
#ifndef TZDEFRULESTRING
# define TZDEFRULESTRING ",M3.2.0,M11.1.0"
#endif
struct ttinfo { /* time type information */
int_fast32_t tt_utoff; /* UT offset in seconds */
bool tt_isdst; /* used to set tm_isdst */
int tt_desigidx; /* abbreviation list index */
bool tt_ttisstd; /* transition is std time */
bool tt_ttisut; /* transition is UT */
};
struct lsinfo { /* leap second information */
time_t ls_trans; /* transition time */
int_fast32_t ls_corr; /* correction to apply */
};
/* This abbreviation means local time is unspecified. */
static char const UNSPEC[] = "-00";
/* How many extra bytes are needed at the end of struct state's chars array.
This needs to be at least 1 for null termination in case the input
data isn't properly terminated, and it also needs to be big enough
for ttunspecified to work without crashing. */
enum { CHARS_EXTRA = max(sizeof UNSPEC, 2) - 1 };
-#ifdef TZNAME_MAX
-# define MY_TZNAME_MAX TZNAME_MAX
-#endif /* defined TZNAME_MAX */
-#ifndef TZNAME_MAX
-# define MY_TZNAME_MAX 255
-#endif /* !defined TZNAME_MAX */
+/* Limit to time zone abbreviation length in POSIX-style TZ strings.
+ This is distinct from TZ_MAX_CHARS, which limits TZif file contents. */
+#ifndef TZNAME_MAXIMUM
+# define TZNAME_MAXIMUM 255
+#endif
struct state {
int leapcnt;
int timecnt;
int typecnt;
int charcnt;
bool goback;
bool goahead;
time_t ats[TZ_MAX_TIMES];
unsigned char types[TZ_MAX_TIMES];
struct ttinfo ttis[TZ_MAX_TYPES];
char chars[max(max(TZ_MAX_CHARS + CHARS_EXTRA, sizeof "UTC"),
- 2 * (MY_TZNAME_MAX + 1))];
+ 2 * (TZNAME_MAXIMUM + 1))];
struct lsinfo lsis[TZ_MAX_LEAPS];
/* The time type to use for early times or if no transitions.
It is always zero for recent tzdb releases.
It might be nonzero for data from tzdb 2018e or earlier. */
int defaulttype;
};
enum r_type {
JULIAN_DAY, /* Jn = Julian day */
DAY_OF_YEAR, /* n = day of year */
MONTH_NTH_DAY_OF_WEEK /* Mm.n.d = month, week, day of week */
};
struct rule {
enum r_type r_type; /* type of rule */
int r_day; /* day number of rule */
int r_week; /* week number of rule */
int r_mon; /* month number of rule */
int_fast32_t r_time; /* transition time of rule */
};
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 normalize_overflow32(int_fast32_t *, int *, int);
static struct tm *timesub(time_t const *, int_fast32_t, struct state const *,
struct tm *);
static bool typesequiv(struct state const *, int, int);
static bool tzparse(char const *, struct state *, struct state *);
#ifdef ALL_STATE
static struct state * lclptr;
static struct state * gmtptr;
#endif /* defined ALL_STATE */
#ifndef ALL_STATE
static struct state lclmem;
static struct state gmtmem;
static struct state *const lclptr = &lclmem;
static struct state *const gmtptr = &gmtmem;
#endif /* State Farm */
#ifndef TZ_STRLEN_MAX
# define TZ_STRLEN_MAX 255
#endif /* !defined TZ_STRLEN_MAX */
static char lcl_TZname[TZ_STRLEN_MAX + 1];
static int lcl_is_set;
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;
/*
** Section 4.12.3 of X3.159-1989 requires that
** Except for the strftime function, these functions [asctime,
** ctime, gmtime, localtime] return values in one of two static
** objects: a broken-down time structure and an array of char.
** Thanks to Paul Eggert for noting this.
+**
+** This requirement was removed in C99, so support it only if requested,
+** as support is more likely to lead to bugs in badly written programs.
*/
+#if SUPPORT_C89
static struct tm tm;
+#endif
#if 2 <= HAVE_TZNAME + TZ_TIME_T
char * tzname[2] = {
(char *) wildabbr,
(char *) wildabbr
};
#endif
#if 2 <= USG_COMPAT + TZ_TIME_T
long timezone;
int daylight;
#endif
#if 2 <= ALTZONE + TZ_TIME_T
long altzone;
#endif
/* Initialize *S to a value based on UTOFF, ISDST, and DESIGIDX. */
static void
init_ttinfo(struct ttinfo *s, int_fast32_t utoff, bool isdst, int desigidx)
{
s->tt_utoff = utoff;
s->tt_isdst = isdst;
s->tt_desigidx = desigidx;
s->tt_ttisstd = false;
s->tt_ttisut = false;
}
/* Return true if SP's time type I does not specify local time. */
static bool
ttunspecified(struct state const *sp, int i)
{
char const *abbr = &sp->chars[sp->ttis[i].tt_desigidx];
/* memcmp is likely faster than strcmp, and is safe due to CHARS_EXTRA. */
return memcmp(abbr, UNSPEC, sizeof UNSPEC) == 0;
}
static int_fast32_t
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;
result = codep[0] & 0x7f;
for (i = 1; i < 4; ++i)
result = (result << 8) | (codep[i] & 0xff);
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;
result += minval;
}
return result;
}
static int_fast64_t
detzcode64(const char *const codep)
{
register int_fast64_t result;
register int i;
int_fast64_t one = 1;
int_fast64_t halfmaxval = one << (64 - 2);
int_fast64_t maxval = halfmaxval - 1 + halfmaxval;
int_fast64_t minval = -TWOS_COMPLEMENT(int_fast64_t) - maxval;
result = codep[0] & 0x7f;
for (i = 1; i < 8; ++i)
result = (result << 8) | (codep[i] & 0xff);
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_fast64_t) && result != 0;
result += minval;
}
return result;
}
static void
update_tzname_etc(struct state const *sp, struct ttinfo const *ttisp)
{
#if HAVE_TZNAME
tzname[ttisp->tt_isdst] = (char *) &sp->chars[ttisp->tt_desigidx];
#endif
#if USG_COMPAT
if (!ttisp->tt_isdst)
timezone = - ttisp->tt_utoff;
#endif
#if ALTZONE
if (ttisp->tt_isdst)
altzone = - ttisp->tt_utoff;
#endif
}
/* If STDDST_MASK indicates that SP's TYPE provides useful info,
update tzname, timezone, and/or altzone and return STDDST_MASK,
diminished by the provided info if it is a specified local time.
Otherwise, return STDDST_MASK. See settzname for STDDST_MASK. */
static int
may_update_tzname_etc(int stddst_mask, struct state *sp, int type)
{
struct ttinfo *ttisp = &sp->ttis[type];
int this_bit = 1 << ttisp->tt_isdst;
if (stddst_mask & this_bit) {
update_tzname_etc(sp, ttisp);
if (!ttunspecified(sp, type))
return stddst_mask & ~this_bit;
}
return stddst_mask;
}
static void
settzname(void)
{
register struct state * const sp = lclptr;
register int i;
/* If STDDST_MASK & 1 we need info about a standard time.
If STDDST_MASK & 2 we need info about a daylight saving time.
When STDDST_MASK becomes zero we can stop looking. */
int stddst_mask = 0;
#if HAVE_TZNAME
tzname[0] = tzname[1] = (char *) (sp ? wildabbr : utc);
stddst_mask = 3;
#endif
#if USG_COMPAT
timezone = 0;
stddst_mask = 3;
#endif
#if ALTZONE
altzone = 0;
stddst_mask |= 2;
#endif
/*
** And to get the latest time zone abbreviations into tzname. . .
*/
if (sp) {
for (i = sp->timecnt - 1; stddst_mask && 0 <= i; i--)
stddst_mask = may_update_tzname_etc(stddst_mask, sp, sp->types[i]);
for (i = sp->typecnt - 1; stddst_mask && 0 <= i; i--)
stddst_mask = may_update_tzname_etc(stddst_mask, sp, i);
}
#if USG_COMPAT
daylight = stddst_mask >> 1 ^ 1;
#endif
}
-static void
+/* Replace bogus characters in time zone abbreviations.
+ Return 0 on success, an errno value if a time zone abbreviation is
+ too long. */
+static int
scrub_abbrs(struct state *sp)
{
int i;
- /*
- ** First, replace bogus characters.
- */
+
+ /* Reject overlong abbreviations. */
+ for (i = 0; i < sp->charcnt - (TZNAME_MAXIMUM + 1); ) {
+ int len = strlen(&sp->chars[i]);
+ if (TZNAME_MAXIMUM < len)
+ return EOVERFLOW;
+ i += len + 1;
+ }
+
+ /* 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;
- /*
- ** Second, truncate long abbreviations.
- */
- for (i = 0; i < sp->typecnt; ++i) {
- register const struct ttinfo * const ttisp = &sp->ttis[i];
- char *cp = &sp->chars[ttisp->tt_desigidx];
- if (strlen(cp) > TZ_ABBR_MAX_LEN &&
- strcmp(cp, GRANDPARENTED) != 0)
- *(cp + TZ_ABBR_MAX_LEN) = '\0';
- }
+ return 0;
}
#ifdef DETECT_TZ_CHANGES
/*
* Determine if there's a change in the timezone since the last time we checked.
* Returns: -1 on error
* 0 if the timezone has not changed
* 1 if the timezone has changed
*/
static int
change_in_tz(const char *name)
{
static char old_name[PATH_MAX];
static struct stat old_sb;
struct stat sb;
int error;
error = stat(name, &sb);
if (error != 0)
return -1;
if (strcmp(name, old_name) != 0) {
strlcpy(old_name, name, sizeof(old_name));
old_sb = sb;
return 1;
}
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;
}
return 0;
}
#else /* !DETECT_TZ_CHANGES */
#define change_in_tz(X) 1
#endif /* !DETECT_TZ_CHANGES */
/* Input buffer for data read from a compiled tz file. */
union input_buffer {
/* The first part of the buffer, interpreted as a header. */
struct tzhead tzhead;
/* The entire buffer. */
char buf[2 * sizeof(struct tzhead) + 2 * sizeof(struct state)
+ 4 * TZ_MAX_TIMES];
};
/* TZDIR with a trailing '/' rather than a trailing '\0'. */
static char const tzdirslash[sizeof TZDIR] = TZDIR "/";
/* Local storage needed for 'tzloadbody'. */
union local_storage {
/* The results of analyzing the file's contents after it is opened. */
struct file_analysis {
/* The input buffer. */
union input_buffer u;
/* A temporary state used for parsing a TZ string in the file. */
struct state st;
} u;
/* The file name to be opened. */
char fullname[max(sizeof(struct file_analysis), sizeof tzdirslash + 1024)];
};
/* Load tz data from the file named NAME into *SP. Read extended
format if DOEXTEND. Use *LSP for temporary storage. Return 0 on
success, an errno value on failure. */
static int
tzloadbody(char const *name, struct state *sp, bool doextend,
union local_storage *lsp)
{
register int i;
register int fid;
register int stored;
register ssize_t nread;
register union input_buffer *up = &lsp->u.u;
register int tzheadsize = sizeof(struct tzhead);
sp->goback = sp->goahead = false;
if (! name) {
name = TZDEFAULT;
if (! name)
return EINVAL;
}
if (name[0] == ':')
++name;
if (name[0] != '/') {
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);
name = lsp->fullname;
}
if (doextend) {
/*
* Detect if the timezone file has changed. Check
* 'doextend' to ignore TZDEFRULES; the change_in_tz()
* function can only keep state for a single file.
*/
int ret = change_in_tz(name);
if (ret <= 0) {
/*
* Returns an errno value if there was an error,
* and 0 if the timezone had not changed.
*/
return errno;
}
}
fid = _open(name, O_RDONLY | O_BINARY);
if (fid < 0)
return errno;
nread = _read(fid, up->buf, sizeof up->buf);
if (nread < tzheadsize) {
int err = nread < 0 ? errno : EINVAL;
_close(fid);
return err;
}
if (_close(fid) < 0)
return errno;
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);
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))
return EINVAL;
datablock_size
= (timecnt * stored /* ats */
+ timecnt /* types */
+ typecnt * 6 /* ttinfos */
+ charcnt /* chars */
+ leapcnt * (stored + 4) /* lsinfos */
+ ttisstdcnt /* ttisstds */
+ ttisutcnt); /* ttisuts */
if (nread < tzheadsize + datablock_size)
return EINVAL;
if (skip_datablock)
p += datablock_size;
else {
if (! ((ttisstdcnt == typecnt || ttisstdcnt == 0)
&& (ttisutcnt == typecnt || ttisutcnt == 0)))
return EINVAL;
sp->leapcnt = leapcnt;
sp->timecnt = timecnt;
sp->typecnt = typecnt;
sp->charcnt = charcnt;
/* Read transitions, discarding those out of time_t range.
But pretend the last transition before TIME_T_MIN
occurred at TIME_T_MIN. */
timecnt = 0;
for (i = 0; i < sp->timecnt; ++i) {
int_fast64_t at
= stored == 4 ? detzcode(p) : detzcode64(p);
sp->types[i] = at <= TIME_T_MAX;
if (sp->types[i]) {
time_t attime
= ((TYPE_SIGNED(time_t) ? at < TIME_T_MIN : at < 0)
? TIME_T_MIN : at);
if (timecnt && attime <= sp->ats[timecnt - 1]) {
if (attime < sp->ats[timecnt - 1])
return EINVAL;
sp->types[i - 1] = 0;
timecnt--;
}
sp->ats[timecnt++] = attime;
}
p += stored;
}
timecnt = 0;
for (i = 0; i < sp->timecnt; ++i) {
unsigned char typ = *p++;
if (sp->typecnt <= typ)
return EINVAL;
if (sp->types[i])
sp->types[timecnt++] = typ;
}
sp->timecnt = timecnt;
for (i = 0; i < sp->typecnt; ++i) {
register struct ttinfo * ttisp;
unsigned char isdst, desigidx;
ttisp = &sp->ttis[i];
ttisp->tt_utoff = detzcode(p);
p += 4;
isdst = *p++;
if (! (isdst < 2))
return EINVAL;
ttisp->tt_isdst = isdst;
desigidx = *p++;
if (! (desigidx < sp->charcnt))
return EINVAL;
ttisp->tt_desigidx = desigidx;
}
for (i = 0; i < sp->charcnt; ++i)
sp->chars[i] = *p++;
/* Ensure '\0'-terminated, and make it safe to call
ttunspecified later. */
memset(&sp->chars[i], 0, CHARS_EXTRA);
/* Read leap seconds, discarding those out of time_t range. */
leapcnt = 0;
for (i = 0; i < sp->leapcnt; ++i) {
int_fast64_t tr = stored == 4 ? detzcode(p) : detzcode64(p);
int_fast32_t corr = detzcode(p + stored);
p += stored + 4;
/* Leap seconds cannot occur before the Epoch,
or out of order. */
if (tr <= prevtr)
return EINVAL;
/* To avoid other botches in this code, each leap second's
correction must differ from the previous one's by 1
second or less, except that the first correction can be
any value; these requirements are more generous than
RFC 8536, to allow future RFC extensions. */
if (! (i == 0
|| (prevcorr < corr
? corr == prevcorr + 1
: (corr == prevcorr
|| corr == prevcorr - 1))))
return EINVAL;
prevtr = tr;
prevcorr = corr;
if (tr <= TIME_T_MAX) {
sp->lsis[leapcnt].ls_trans = tr;
sp->lsis[leapcnt].ls_corr = corr;
leapcnt++;
}
}
sp->leapcnt = leapcnt;
for (i = 0; i < sp->typecnt; ++i) {
register struct ttinfo * ttisp;
ttisp = &sp->ttis[i];
if (ttisstdcnt == 0)
ttisp->tt_ttisstd = false;
else {
if (*p != true && *p != false)
return EINVAL;
ttisp->tt_ttisstd = *p++;
}
}
for (i = 0; i < sp->typecnt; ++i) {
register struct ttinfo * ttisp;
ttisp = &sp->ttis[i];
if (ttisutcnt == 0)
ttisp->tt_ttisut = false;
else {
if (*p != true && *p != false)
return EINVAL;
ttisp->tt_ttisut = *p++;
}
}
}
nread -= p - up->buf;
memmove(up->buf, p, nread);
/* If this is an old file, we're done. */
if (!version)
break;
}
if (doextend && nread > 2 &&
up->buf[0] == '\n' && up->buf[nread - 1] == '\n' &&
sp->typecnt + 2 <= TZ_MAX_TYPES) {
struct state *ts = &lsp->u.st;
up->buf[nread - 1] = '\0';
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
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
stay 40 in this example. */
int gotabbr = 0;
int charcnt = sp->charcnt;
for (i = 0; i < ts->typecnt; i++) {
char *tsabbr = ts->chars + ts->ttis[i].tt_desigidx;
int j;
for (j = 0; j < charcnt; j++)
if (strcmp(sp->chars + j, tsabbr) == 0) {
ts->ttis[i].tt_desigidx = j;
gotabbr++;
break;
}
if (! (j < charcnt)) {
int tsabbrlen = strlen(tsabbr);
if (j + tsabbrlen < TZ_MAX_CHARS) {
strcpy(sp->chars + j, tsabbr);
charcnt = j + tsabbrlen + 1;
ts->ttis[i].tt_desigidx = j;
gotabbr++;
}
}
}
if (gotabbr == ts->typecnt) {
sp->charcnt = charcnt;
/* Ignore any trailing, no-op transitions generated
by zic as they don't help here and can run afoul
of bugs in zic 2016j or earlier. */
while (1 < sp->timecnt
&& (sp->types[sp->timecnt - 1]
== sp->types[sp->timecnt - 2]))
sp->timecnt--;
for (i = 0;
i < ts->timecnt && sp->timecnt < TZ_MAX_TIMES;
i++) {
time_t t = ts->ats[i];
if (increment_overflow_time(&t, leapcorr(sp, t))
|| (0 < sp->timecnt
&& t <= sp->ats[sp->timecnt - 1]))
continue;
sp->ats[sp->timecnt] = t;
sp->types[sp->timecnt] = (sp->typecnt
+ ts->types[i]);
sp->timecnt++;
}
for (i = 0; i < ts->typecnt; i++)
sp->ttis[sp->typecnt++] = ts->ttis[i];
}
}
}
if (sp->typecnt == 0)
return EINVAL;
if (sp->timecnt > 1) {
if (sp->ats[0] <= TIME_T_MAX - SECSPERREPEAT) {
time_t repeatat = sp->ats[0] + SECSPERREPEAT;
int repeattype = sp->types[0];
for (i = 1; i < sp->timecnt; ++i)
if (sp->ats[i] == repeatat
&& typesequiv(sp, sp->types[i], repeattype)) {
sp->goback = true;
break;
}
}
if (TIME_T_MIN + SECSPERREPEAT <= sp->ats[sp->timecnt - 1]) {
time_t repeatat = sp->ats[sp->timecnt - 1] - SECSPERREPEAT;
int repeattype = sp->types[sp->timecnt - 1];
for (i = sp->timecnt - 2; i >= 0; --i)
if (sp->ats[i] == repeatat
&& typesequiv(sp, sp->types[i], repeattype)) {
sp->goahead = true;
break;
}
}
}
/* Infer sp->defaulttype from the data. Although this default
type is always zero for data from recent tzdb releases,
things are trickier for data from tzdb 2018e or earlier.
The first set of heuristics work around bugs in 32-bit data
generated by tzdb 2013c or earlier. The workaround is for
zones like Australia/Macquarie where timestamps before the
first transition have a time type that is not the earliest
standard-time type. See:
https://mm.icann.org/pipermail/tz/2013-May/019368.html */
/*
** If type 0 does not specify local time, or is unused in transitions,
** it's the type to use for early times.
*/
for (i = 0; i < sp->timecnt; ++i)
if (sp->types[i] == 0)
break;
i = i < sp->timecnt && ! ttunspecified(sp, 0) ? -1 : 0;
/*
** Absent the above,
** if there are transition times
** and the first transition is to a daylight time
** find the standard type less than and closest to
** the type of the first transition.
*/
if (i < 0 && sp->timecnt > 0 && sp->ttis[sp->types[0]].tt_isdst) {
i = sp->types[0];
while (--i >= 0)
if (!sp->ttis[i].tt_isdst)
break;
}
/* The next heuristics are for data generated by tzdb 2018e or
earlier, for zones like EST5EDT where the first transition
is to DST. */
/*
** If no result yet, find the first standard type.
** If there is none, punt to type zero.
*/
if (i < 0) {
i = 0;
while (sp->ttis[i].tt_isdst)
if (++i >= sp->typecnt) {
i = 0;
break;
}
}
/* A simple 'sp->defaulttype = 0;' would suffice here if we
didn't have to worry about 2018e-or-earlier data. Even
simpler would be to remove the defaulttype member and just
use 0 in its place. */
sp->defaulttype = i;
return 0;
}
/* Load tz data from the file named NAME into *SP. Read extended
format if DOEXTEND. Return 0 on success, an errno value on failure. */
static int
tzload(char const *name, struct state *sp, bool doextend)
{
#ifdef ALL_STATE
union local_storage *lsp = malloc(sizeof *lsp);
if (!lsp) {
return HAVE_MALLOC_ERRNO ? errno : ENOMEM;
} else {
int err = tzloadbody(name, sp, doextend, lsp);
free(lsp);
return err;
}
#else
union local_storage ls;
return tzloadbody(name, sp, doextend, &ls);
#endif
}
static bool
typesequiv(const struct state *sp, int a, int b)
{
register bool result;
if (sp == NULL ||
a < 0 || a >= sp->typecnt ||
b < 0 || b >= sp->typecnt)
result = false;
else {
/* Compare the relevant members of *AP and *BP.
Ignore tt_ttisstd and tt_ttisut, as they are
irrelevant now and counting them could cause
sp->goahead to mistakenly remain false. */
register const struct ttinfo * ap = &sp->ttis[a];
register const struct ttinfo * bp = &sp->ttis[b];
result = (ap->tt_utoff == bp->tt_utoff
&& ap->tt_isdst == bp->tt_isdst
&& (strcmp(&sp->chars[ap->tt_desigidx],
&sp->chars[bp->tt_desigidx])
== 0));
}
return result;
}
static const int mon_lengths[2][MONSPERYEAR] = {
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};
static const int year_lengths[2] = {
DAYSPERNYEAR, DAYSPERLYEAR
};
/* Is C an ASCII digit? */
static bool
is_digit(char c)
{
return '0' <= c && c <= '9';
}
/*
** Given a pointer into a timezone string, scan until a character that is not
** a valid character in a time zone abbreviation is found.
** Return a pointer to that character.
*/
-static ATTRIBUTE_REPRODUCIBLE const char *
+ATTRIBUTE_REPRODUCIBLE static const char *
getzname(register const char *strp)
{
register char c;
while ((c = *strp) != '\0' && !is_digit(c) && c != ',' && c != '-' &&
c != '+')
++strp;
return strp;
}
/*
** Given a pointer into an extended timezone string, scan until the ending
** delimiter of the time zone abbreviation is located.
** Return a pointer to the delimiter.
**
** As with getzname above, the legal character set is actually quite
** restricted, with other characters producing undefined results.
** We don't do any checking here; checking is done later in common-case code.
*/
-static ATTRIBUTE_REPRODUCIBLE const char *
+ATTRIBUTE_REPRODUCIBLE static const char *
getqzname(register const char *strp, const int delim)
{
register int c;
while ((c = *strp) != '\0' && c != delim)
++strp;
return strp;
}
/*
** Given a pointer into a timezone string, extract a number from that string.
** Check that the number is within a specified range; if it is not, return
** NULL.
** Otherwise, return a pointer to the first character not part of the number.
*/
static const char *
getnum(register const char *strp, int *const nump, const int min, const int max)
{
register char c;
register int num;
if (strp == NULL || !is_digit(c = *strp))
return NULL;
num = 0;
do {
num = num * 10 + (c - '0');
if (num > max)
return NULL; /* illegal value */
c = *++strp;
} while (is_digit(c));
if (num < min)
return NULL; /* illegal value */
*nump = num;
return strp;
}
/*
** Given a pointer into a timezone string, extract a number of seconds,
** in hh[:mm[:ss]] form, from the string.
** If any error occurs, return NULL.
** Otherwise, return a pointer to the first character not part of the number
** of seconds.
*/
static const char *
getsecs(register const char *strp, int_fast32_t *const secsp)
{
int num;
int_fast32_t secsperhour = SECSPERHOUR;
/*
** 'HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like
** "M10.4.6/26", which does not conform to Posix,
** but which specifies the equivalent of
** "02:00 on the first Sunday on or after 23 Oct".
*/
strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1);
if (strp == NULL)
return NULL;
*secsp = num * secsperhour;
if (*strp == ':') {
++strp;
strp = getnum(strp, &num, 0, MINSPERHOUR - 1);
if (strp == NULL)
return NULL;
*secsp += num * SECSPERMIN;
if (*strp == ':') {
++strp;
/* 'SECSPERMIN' allows for leap seconds. */
strp = getnum(strp, &num, 0, SECSPERMIN);
if (strp == NULL)
return NULL;
*secsp += num;
}
}
return strp;
}
/*
** Given a pointer into a timezone string, extract an offset, in
** [+-]hh[:mm[:ss]] form, from the string.
** If any error occurs, return NULL.
** Otherwise, return a pointer to the first character not part of the time.
*/
static const char *
getoffset(register const char *strp, int_fast32_t *const offsetp)
{
register bool neg = false;
if (*strp == '-') {
neg = true;
++strp;
} else if (*strp == '+')
++strp;
strp = getsecs(strp, offsetp);
if (strp == NULL)
return NULL; /* illegal time */
if (neg)
*offsetp = -*offsetp;
return strp;
}
/*
** Given a pointer into a timezone string, extract a rule in the form
** date[/time]. See POSIX section 8 for the format of "date" and "time".
** If a valid rule is not found, return NULL.
** Otherwise, return a pointer to the first character not part of the rule.
*/
static const char *
getrule(const char *strp, register struct rule *const rulep)
{
if (*strp == 'J') {
/*
** Julian day.
*/
rulep->r_type = JULIAN_DAY;
++strp;
strp = getnum(strp, &rulep->r_day, 1, DAYSPERNYEAR);
} else if (*strp == 'M') {
/*
** Month, week, day.
*/
rulep->r_type = MONTH_NTH_DAY_OF_WEEK;
++strp;
strp = getnum(strp, &rulep->r_mon, 1, MONSPERYEAR);
if (strp == NULL)
return NULL;
if (*strp++ != '.')
return NULL;
strp = getnum(strp, &rulep->r_week, 1, 5);
if (strp == NULL)
return NULL;
if (*strp++ != '.')
return NULL;
strp = getnum(strp, &rulep->r_day, 0, DAYSPERWEEK - 1);
} else if (is_digit(*strp)) {
/*
** Day of year.
*/
rulep->r_type = DAY_OF_YEAR;
strp = getnum(strp, &rulep->r_day, 0, DAYSPERLYEAR - 1);
} else return NULL; /* invalid format */
if (strp == NULL)
return NULL;
if (*strp == '/') {
/*
** Time specified.
*/
++strp;
strp = getoffset(strp, &rulep->r_time);
} else rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */
return strp;
}
/*
** Given a year, a rule, and the offset from UT at the time that rule takes
** effect, calculate the year-relative time that rule takes effect.
*/
static int_fast32_t
transtime(const int year, register const struct rule *const rulep,
const int_fast32_t offset)
{
register bool leapyear;
register int_fast32_t value;
register int i;
int d, m1, yy0, yy1, yy2, dow;
leapyear = isleap(year);
switch (rulep->r_type) {
case JULIAN_DAY:
/*
** Jn - Julian day, 1 == January 1, 60 == March 1 even in leap
** years.
** In non-leap years, or if the day number is 59 or less, just
** add SECSPERDAY times the day number-1 to the time of
** January 1, midnight, to get the day.
*/
value = (rulep->r_day - 1) * SECSPERDAY;
if (leapyear && rulep->r_day >= 60)
value += SECSPERDAY;
break;
case DAY_OF_YEAR:
/*
** n - day of year.
** Just add SECSPERDAY times the day number to the time of
** January 1, midnight, to get the day.
*/
value = rulep->r_day * SECSPERDAY;
break;
case MONTH_NTH_DAY_OF_WEEK:
/*
** Mm.n.d - nth "dth day" of month m.
*/
/*
** Use Zeller's Congruence to get day-of-week of first day of
** month.
*/
m1 = (rulep->r_mon + 9) % 12 + 1;
yy0 = (rulep->r_mon <= 2) ? (year - 1) : year;
yy1 = yy0 / 100;
yy2 = yy0 % 100;
dow = ((26 * m1 - 2) / 10 +
1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7;
if (dow < 0)
dow += DAYSPERWEEK;
/*
** "dow" is the day-of-week of the first day of the month. Get
** the day-of-month (zero-origin) of the first "dow" day of the
** month.
*/
d = rulep->r_day - dow;
if (d < 0)
d += DAYSPERWEEK;
for (i = 1; i < rulep->r_week; ++i) {
if (d + DAYSPERWEEK >=
mon_lengths[leapyear][rulep->r_mon - 1])
break;
d += DAYSPERWEEK;
}
/*
** "d" is the day-of-month (zero-origin) of the day we want.
*/
value = d * SECSPERDAY;
for (i = 0; i < rulep->r_mon - 1; ++i)
value += mon_lengths[leapyear][i] * SECSPERDAY;
break;
default: unreachable();
}
/*
** "value" is the year-relative time of 00:00:00 UT on the day in
** question. To get the year-relative time of the specified local
** time on that day, add the transition time and the current offset
** from UT.
*/
return value + rulep->r_time + offset;
}
/*
** Given a POSIX section 8-style TZ string, fill in the rule tables as
** appropriate.
*/
static bool
tzparse(const char *name, struct state *sp, struct state *basep)
{
const char * stdname;
const char * dstname;
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;
stdname = name;
if (*name == '<') {
name++;
stdname = name;
name = getqzname(name, '>');
if (*name != '>')
return false;
stdlen = name - stdname;
name++;
} else {
name = getzname(name);
stdlen = name - stdname;
}
- if (!stdlen)
+ if (! (0 < stdlen && stdlen <= TZNAME_MAXIMUM))
return false;
name = getoffset(name, &stdoffset);
if (name == NULL)
return false;
charcnt = stdlen + 1;
- if (sizeof sp->chars < charcnt)
- return false;
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, false) == 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;
if (*name != '\0') {
if (*name == '<') {
dstname = ++name;
name = getqzname(name, '>');
if (*name != '>')
return false;
dstlen = name - dstname;
name++;
} else {
dstname = name;
name = getzname(name);
dstlen = name - dstname; /* length of DST abbr. */
}
- if (!dstlen)
+ if (! (0 < dstlen && dstlen <= TZNAME_MAXIMUM))
return false;
charcnt += dstlen + 1;
- if (sizeof sp->chars < charcnt)
- return false;
if (*name != '\0' && *name != ',' && *name != ';') {
name = getoffset(name, &dstoffset);
if (name == NULL)
return false;
} else dstoffset = stdoffset - SECSPERHOUR;
if (*name == '\0' && !load_ok)
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);
sp->defaulttype = 0;
timecnt = 0;
janfirst = 0;
yearbeg = EPOCH_YEAR;
do {
int_fast32_t yearsecs
= year_lengths[isleap(yearbeg - 1)] * SECSPERDAY;
yearbeg--;
if (increment_overflow_time(&janfirst, -yearsecs)) {
janoffset = -yearsecs;
break;
}
} 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, YEARSPERREPEAT + 1))
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,
YEARSPERREPEAT + 1))
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 (YEARSPERREPEAT < 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
** POSIX provides for 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;
sp->defaulttype = 0;
}
} else {
dstlen = 0;
sp->typecnt = 1; /* only standard time */
sp->timecnt = 0;
init_ttinfo(&sp->ttis[0], -stdoffset, false, 0);
sp->defaulttype = 0;
}
sp->charcnt = charcnt;
cp = sp->chars;
memcpy(cp, stdname, stdlen);
cp += stdlen;
*cp++ = '\0';
if (dstlen != 0) {
memcpy(cp, dstname, dstlen);
*(cp + dstlen) = '\0';
}
return true;
}
static void
gmtload(struct state *const sp)
{
if (tzload(etc_utc, sp, true) != 0)
tzparse("UTC0", sp, NULL);
}
#ifdef DETECT_TZ_CHANGES
static int
recheck_tzdata()
{
static time_t last_checked;
struct timespec now;
time_t current_time;
int error;
/*
* We want to recheck the timezone file every 61 sec.
*/
error = clock_gettime(CLOCK_MONOTONIC, &now);
if (error < 0) {
/* XXX: Can we somehow report this? */
return 0;
}
current_time = now.tv_sec;
if ((current_time - last_checked > DETECT_TZ_CHANGES_INTERVAL) ||
(last_checked > current_time)) {
last_checked = current_time;
return 1;
}
return 0;
}
#else /* !DETECT_TZ_CHANGES */
#define recheck_tzdata() 0
#endif /* !DETECT_TZ_CHANGES */
/* Initialize *SP to a value appropriate for the TZ setting NAME.
Return 0 on success, an errno value on failure. */
static int
zoneinit(struct state *sp, char const *name)
{
if (name && ! name[0]) {
/*
** User wants it fast rather than right.
*/
sp->leapcnt = 0; /* so, we're off a little */
sp->timecnt = 0;
sp->typecnt = 0;
sp->charcnt = 0;
sp->goback = sp->goahead = false;
init_ttinfo(&sp->ttis[0], 0, false, 0);
strcpy(sp->chars, utc);
sp->defaulttype = 0;
return 0;
} else {
int err = tzload(name, sp, true);
if (err != 0 && name && name[0] != ':' && tzparse(name, sp, NULL))
err = 0;
if (err == 0)
- scrub_abbrs(sp);
+ err = scrub_abbrs(sp);
return err;
}
}
static void
tzset_unlocked_name(char const *name)
{
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)
if (recheck_tzdata() == 0)
return;
#ifdef ALL_STATE
if (! sp)
lclptr = sp = malloc(sizeof *lclptr);
#endif /* defined ALL_STATE */
if (sp) {
if (zoneinit(sp, name) != 0)
zoneinit(sp, "");
if (0 < lcl)
strcpy(lcl_TZname, name);
}
settzname();
lcl_is_set = lcl;
}
static void
tzset_unlocked(void)
{
tzset_unlocked_name(getenv("TZ"));
}
void
tzset(void)
{
if (lock() != 0)
return;
tzset_unlocked();
unlock();
}
void
freebsd13_tzsetwall(void)
{
if (lock() != 0)
return;
tzset_unlocked_name(NULL);
unlock();
}
__sym_compat(tzsetwall, freebsd13_tzsetwall, FBSD_1.0);
__warn_references(tzsetwall,
"warning: tzsetwall() is deprecated, use tzset() instead.");
static void
gmtcheck(void)
{
static bool gmt_is_set;
if (lock() != 0)
return;
if (! gmt_is_set) {
#ifdef ALL_STATE
gmtptr = malloc(sizeof *gmtptr);
#endif
if (gmtptr)
gmtload(gmtptr);
gmt_is_set = true;
}
unlock();
}
#if NETBSD_INSPIRED
timezone_t
tzalloc(char const *name)
{
timezone_t sp = malloc(sizeof *sp);
if (sp) {
int err = zoneinit(sp, name);
if (err != 0) {
free(sp);
errno = err;
return NULL;
}
} else if (!HAVE_MALLOC_ERRNO)
errno = ENOMEM;
return sp;
}
void
tzfree(timezone_t sp)
{
free(sp);
}
/*
** NetBSD 6.1.4 has ctime_rz, but omit it because POSIX says ctime and
** ctime_r are obsolescent and have potential security problems that
** ctime_rz would share. Callers can instead use localtime_rz + strftime.
**
** NetBSD 6.1.4 has tzgetname, but omit it because it doesn't work
** in zones with three or more time zone abbreviations.
** Callers can instead use localtime_rz + strftime.
*/
#endif
/*
** The easy way to behave "as if no library function calls" localtime
** is to not call it, so we drop its guts into "localsub", which can be
** freely called. (And no, the PANS doesn't require the above behavior,
** but it *is* desirable.)
**
** If successful and SETNAME is nonzero,
** set the applicable parts of tzname, timezone and altzone;
** however, it's OK to omit this step if the timezone is POSIX-compatible,
** since in that case tzset should have already done this step correctly.
** SETNAME's type is int_fast32_t for compatibility with gmtsub,
** but it is actually a boolean and its value should be 0 or 1.
*/
/*ARGSUSED*/
static struct tm *
localsub(struct state const *sp, time_t const *timep, int_fast32_t setname,
struct tm *const tmp)
{
register const struct ttinfo * ttisp;
register int i;
register struct tm * result;
const time_t t = *timep;
if (sp == NULL) {
/* Don't bother to set tzname etc.; tzset has already done it. */
return gmtsub(gmtptr, timep, 0, tmp);
}
if ((sp->goback && t < sp->ats[0]) ||
(sp->goahead && t > sp->ats[sp->timecnt - 1])) {
time_t newt;
register time_t seconds;
register time_t years;
if (t < sp->ats[0])
seconds = sp->ats[0] - t;
else seconds = t - sp->ats[sp->timecnt - 1];
--seconds;
/* Beware integer overflow, as SECONDS might
be close to the maximum time_t. */
years = seconds / SECSPERREPEAT * YEARSPERREPEAT;
seconds = years * AVGSECSPERYEAR;
years += YEARSPERREPEAT;
if (t < sp->ats[0])
newt = t + seconds + SECSPERREPEAT;
else
newt = t - seconds - SECSPERREPEAT;
if (newt < sp->ats[0] ||
newt > sp->ats[sp->timecnt - 1])
return NULL; /* "cannot happen" */
result = localsub(sp, &newt, setname, tmp);
if (result) {
#if defined ckd_add && defined ckd_sub
if (t < sp->ats[0]
? ckd_sub(&result->tm_year,
result->tm_year, years)
: ckd_add(&result->tm_year,
result->tm_year, years))
return NULL;
#else
register int_fast64_t newy;
newy = result->tm_year;
if (t < sp->ats[0])
newy -= years;
else newy += years;
if (! (INT_MIN <= newy && newy <= INT_MAX))
return NULL;
result->tm_year = newy;
#endif
}
return result;
}
if (sp->timecnt == 0 || t < sp->ats[0]) {
i = sp->defaulttype;
} else {
register int lo = 1;
register int hi = sp->timecnt;
while (lo < hi) {
register int mid = (lo + hi) >> 1;
if (t < sp->ats[mid])
hi = mid;
else lo = mid + 1;
}
i = sp->types[lo - 1];
}
ttisp = &sp->ttis[i];
/*
** 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);
*/
result = timesub(&t, ttisp->tt_utoff, sp, tmp);
if (result) {
result->tm_isdst = ttisp->tt_isdst;
#ifdef TM_ZONE
result->TM_ZONE = (char *) &sp->chars[ttisp->tt_desigidx];
#endif /* defined TM_ZONE */
if (setname)
update_tzname_etc(sp, ttisp);
}
return result;
}
#if NETBSD_INSPIRED
struct tm *
-localtime_rz(struct state *sp, time_t const *timep, struct tm *tmp)
+localtime_rz(struct state *restrict sp, time_t const *restrict timep,
+ struct tm *restrict tmp)
{
return localsub(sp, timep, 0, tmp);
}
#endif
static struct tm *
localtime_tzset(time_t const *timep, struct tm *tmp, bool setname)
{
int err = lock();
if (err) {
errno = err;
return NULL;
}
#ifndef DETECT_TZ_CHANGES
if (setname || !lcl_is_set)
#endif
tzset_unlocked();
tmp = localsub(lclptr, timep, setname, tmp);
unlock();
return tmp;
}
static void
localtime_key_init(void)
{
localtime_key_error = _pthread_key_create(&localtime_key, free);
}
struct tm *
localtime(const time_t *timep)
{
+#if !SUPPORT_C89
+ static struct tm tm;
+#endif
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);
}
}
}
return localtime_tzset(timep, p_tm, true);
}
struct tm *
-localtime_r(const time_t *timep, struct tm *tmp)
+localtime_r(const time_t *restrict timep, struct tm *restrict tmp)
{
return localtime_tzset(timep, tmp, false);
}
/*
** gmtsub is to gmtime as localsub is to localtime.
*/
static struct tm *
gmtsub(ATTRIBUTE_MAYBE_UNUSED struct state const *sp, time_t const *timep,
int_fast32_t offset, struct tm *tmp)
{
register struct tm * result;
result = timesub(timep, offset, gmtptr, tmp);
#ifdef TM_ZONE
/*
** Could get fancy here and deliver something such as
** "+xx" or "-xx" if offset is non-zero,
** but this is no time for a treasure hunt.
*/
tmp->TM_ZONE = ((char *)
(offset ? wildabbr : gmtptr ? gmtptr->chars : utc));
#endif /* defined TM_ZONE */
return result;
}
/*
* Re-entrant version of gmtime.
*/
struct tm *
-gmtime_r(const time_t *timep, struct tm *tmp)
+gmtime_r(time_t const *restrict timep, struct tm *restrict tmp)
{
_once(&gmt_once, gmtcheck);
return gmtsub(gmtptr, timep, 0, tmp);
}
static void
gmtime_key_init(void)
{
gmtime_key_error = _pthread_key_create(&gmtime_key, free);
}
struct tm *
gmtime(const time_t *timep)
{
+#if !SUPPORT_C89
+ static struct tm tm;
+#endif
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);
}
}
}
return gmtime_r(timep, p_tm);
}
-#ifdef STD_INSPIRED
+#if STD_INSPIRED
+
+struct tm *
+offtime_r(time_t const *restrict timep, long offset, struct tm *restrict tmp)
+{
+ _once(&gmt_once, gmtcheck);
+ return gmtsub(gmtptr, timep, offset, tmp);
+}
+
+static void
+offtime_key_init(void)
+{
+
+ offtime_key_error = _pthread_key_create(&offtime_key, free);
+}
struct tm *
offtime(const time_t *timep, long offset)
{
- _once(&gmt_once, gmtcheck);
- return gmtsub(gmtptr, timep, offset, &tm);
+#if !SUPPORT_C89
+ static struct tm tm;
+#endif
+ 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);
+ }
+ }
+ }
+ return offtime_r(timep, offset, p_tm);
}
-#endif /* defined STD_INSPIRED */
+#endif
/*
** Return the number of leap years through the end of the given year
** where, to make the math easy, the answer for year zero is defined as zero.
*/
static time_t
leaps_thru_end_of_nonneg(time_t y)
{
return y / 4 - y / 100 + y / 400;
}
static time_t
leaps_thru_end_of(time_t y)
{
return (y < 0
? -1 - leaps_thru_end_of_nonneg(-1 - y)
: leaps_thru_end_of_nonneg(y));
}
static struct tm *
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;
register int i;
int_fast32_t idays, rem, dayoff, dayrem;
time_t y;
/* If less than SECSPERMIN, the number of seconds since the
most recent positive leap second; otherwise, do not add 1
to localtime tm_sec because of leap seconds. */
time_t secs_since_posleap = SECSPERMIN;
corr = 0;
i = (sp == NULL) ? 0 : sp->leapcnt;
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;
break;
}
}
/* Calculate the year, avoiding integer overflow even if
time_t is unsigned. */
tdays = *timep / SECSPERDAY;
rem = *timep % SECSPERDAY;
rem += offset % SECSPERDAY - corr % SECSPERDAY + 3 * SECSPERDAY;
dayoff = offset / SECSPERDAY - corr / SECSPERDAY + rem / SECSPERDAY - 3;
rem %= SECSPERDAY;
/* y = (EPOCH_YEAR
+ floor((tdays + dayoff) / DAYSPERREPEAT) * YEARSPERREPEAT),
sans overflow. But calculate against 1570 (EPOCH_YEAR -
YEARSPERREPEAT) instead of against 1970 so that things work
for localtime values before 1970 when time_t is unsigned. */
dayrem = tdays % DAYSPERREPEAT;
dayrem += dayoff % DAYSPERREPEAT;
y = (EPOCH_YEAR - YEARSPERREPEAT
+ ((1 + dayoff / DAYSPERREPEAT + dayrem / DAYSPERREPEAT
- ((dayrem % DAYSPERREPEAT) < 0)
+ tdays / DAYSPERREPEAT)
* YEARSPERREPEAT));
/* idays = (tdays + dayoff) mod DAYSPERREPEAT, sans overflow. */
idays = tdays % DAYSPERREPEAT;
idays += dayoff % DAYSPERREPEAT + 2 * DAYSPERREPEAT;
idays %= DAYSPERREPEAT;
/* Increase Y and decrease IDAYS until IDAYS is in range for Y. */
while (year_lengths[isleap(y)] <= idays) {
int tdelta = idays / DAYSPERLYEAR;
int_fast32_t ydelta = tdelta + !tdelta;
time_t newy = y + ydelta;
register int leapdays;
leapdays = leaps_thru_end_of(newy - 1) -
leaps_thru_end_of(y - 1);
idays -= ydelta * DAYSPERNYEAR;
idays -= leapdays;
y = newy;
}
#ifdef ckd_add
if (ckd_add(&tmp->tm_year, y, -TM_YEAR_BASE)) {
errno = EOVERFLOW;
return NULL;
}
#else
if (!TYPE_SIGNED(time_t) && y < TM_YEAR_BASE) {
int signed_y = y;
tmp->tm_year = signed_y - TM_YEAR_BASE;
} else if ((!TYPE_SIGNED(time_t) || INT_MIN + TM_YEAR_BASE <= y)
&& y - TM_YEAR_BASE <= INT_MAX)
tmp->tm_year = y - TM_YEAR_BASE;
else {
errno = EOVERFLOW;
return NULL;
}
#endif
tmp->tm_yday = idays;
/*
** The "extra" mods below avoid overflow problems.
*/
tmp->tm_wday = (TM_WDAY_BASE
+ ((tmp->tm_year % DAYSPERWEEK)
* (DAYSPERNYEAR % DAYSPERWEEK))
+ leaps_thru_end_of(y - 1)
- leaps_thru_end_of(TM_YEAR_BASE - 1)
+ idays);
tmp->tm_wday %= DAYSPERWEEK;
if (tmp->tm_wday < 0)
tmp->tm_wday += DAYSPERWEEK;
tmp->tm_hour = rem / SECSPERHOUR;
rem %= SECSPERHOUR;
tmp->tm_min = rem / SECSPERMIN;
tmp->tm_sec = rem % SECSPERMIN;
/* Use "... ??:??:60" at the end of the localtime minute containing
the second just before the positive leap second. */
tmp->tm_sec += secs_since_posleap <= tmp->tm_sec;
ip = mon_lengths[isleap(y)];
for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon))
idays -= ip[tmp->tm_mon];
tmp->tm_mday = idays + 1;
tmp->tm_isdst = 0;
#ifdef TM_GMTOFF
tmp->TM_GMTOFF = offset;
#endif /* defined TM_GMTOFF */
return tmp;
}
-char *
-ctime(const time_t *timep)
-{
-/*
-** Section 4.12.3.2 of X3.159-1989 requires that
-** The ctime function converts the calendar time pointed to by timer
-** to local time in the form of a string. It is equivalent to
-** asctime(localtime(timer))
-*/
- struct tm *tmp = localtime(timep);
- return tmp ? asctime(tmp) : NULL;
-}
-
-char *
-ctime_r(const time_t *timep, char *buf)
-{
- struct tm mytm;
- struct tm *tmp = localtime_r(timep, &mytm);
- return tmp ? asctime_r(tmp, buf) : NULL;
-}
-
/*
** Adapted from code provided by Robert Elz, who writes:
** The "best" way to do mktime I think is based on an idea of Bob
** Kridle's (so its said...) from a long time ago.
** It does a binary search of the time_t space. Since time_t's are
** just 32 bits, its a max of 32 iterations (even at 64 bits it
** would still be very reasonable).
*/
#ifndef WRONG
# define WRONG (-1)
#endif /* !defined WRONG */
/*
** Normalize logic courtesy Paul Eggert.
*/
static bool
increment_overflow(int *ip, int j)
{
#ifdef ckd_add
return ckd_add(ip, *ip, j);
#else
register int const i = *ip;
/*
** If i >= 0 there can only be overflow if i + j > INT_MAX
** or if j > INT_MAX - i; given i >= 0, INT_MAX - i cannot overflow.
** If i < 0 there can only be overflow if i + j < INT_MIN
** or if j < INT_MIN - i; given i < 0, INT_MIN - i cannot overflow.
*/
if ((i >= 0) ? (j > INT_MAX - i) : (j < INT_MIN - i))
return true;
*ip += j;
return false;
#endif
}
static bool
increment_overflow32(int_fast32_t *const lp, int const m)
{
#ifdef ckd_add
return ckd_add(lp, *lp, m);
#else
register int_fast32_t const l = *lp;
if ((l >= 0) ? (m > INT_FAST32_MAX - l) : (m < INT_FAST32_MIN - l))
return true;
*lp += m;
return false;
#endif
}
static bool
increment_overflow_time(time_t *tp, int_fast32_t j)
{
#ifdef ckd_add
return ckd_add(tp, *tp, j);
#else
/*
** This is like
** 'if (! (TIME_T_MIN <= *tp + j && *tp + j <= TIME_T_MAX)) ...',
** except that it does the right thing even if *tp + j would overflow.
*/
if (! (j < 0
? (TYPE_SIGNED(time_t) ? TIME_T_MIN - j <= *tp : -1 - j < *tp)
: *tp <= TIME_T_MAX - j))
return true;
*tp += j;
return false;
#endif
}
static bool
normalize_overflow(int *const tensptr, int *const unitsptr, const int base)
{
register int tensdelta;
tensdelta = (*unitsptr >= 0) ?
(*unitsptr / base) :
(-1 - (-1 - *unitsptr) / base);
*unitsptr -= tensdelta * base;
return increment_overflow(tensptr, tensdelta);
}
static bool
normalize_overflow32(int_fast32_t *tensptr, int *unitsptr, int base)
{
register int tensdelta;
tensdelta = (*unitsptr >= 0) ?
(*unitsptr / base) :
(-1 - (-1 - *unitsptr) / base);
*unitsptr -= tensdelta * base;
return increment_overflow32(tensptr, tensdelta);
}
static int
tmcomp(register const struct tm *const atmp,
register const struct tm *const btmp)
{
register int result;
if (atmp->tm_year != btmp->tm_year)
return atmp->tm_year < btmp->tm_year ? -1 : 1;
if ((result = (atmp->tm_mon - btmp->tm_mon)) == 0 &&
(result = (atmp->tm_mday - btmp->tm_mday)) == 0 &&
(result = (atmp->tm_hour - btmp->tm_hour)) == 0 &&
(result = (atmp->tm_min - btmp->tm_min)) == 0)
result = atmp->tm_sec - btmp->tm_sec;
return result;
}
/* Copy to *DEST from *SRC. Copy only the members needed for mktime,
as other members might not be initialized. */
static void
mktmcpy(struct tm *dest, struct tm const *src)
{
dest->tm_sec = src->tm_sec;
dest->tm_min = src->tm_min;
dest->tm_hour = src->tm_hour;
dest->tm_mday = src->tm_mday;
dest->tm_mon = src->tm_mon;
dest->tm_year = src->tm_year;
dest->tm_isdst = src->tm_isdst;
#if defined TM_GMTOFF && ! UNINIT_TRAP
dest->TM_GMTOFF = src->TM_GMTOFF;
#endif
}
static time_t
time2sub(struct tm *const tmp,
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,
bool do_norm_secs)
{
register int dir;
register int i, j;
register int saved_seconds;
register int_fast32_t li;
register time_t lo;
register time_t hi;
int_fast32_t y;
time_t newt;
time_t t;
struct tm yourtm, mytm;
*okayp = false;
mktmcpy(&yourtm, tmp);
if (do_norm_secs) {
if (normalize_overflow(&yourtm.tm_min, &yourtm.tm_sec,
SECSPERMIN))
return WRONG;
}
if (normalize_overflow(&yourtm.tm_hour, &yourtm.tm_min, MINSPERHOUR))
return WRONG;
if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY))
return WRONG;
y = yourtm.tm_year;
if (normalize_overflow32(&y, &yourtm.tm_mon, MONSPERYEAR))
return WRONG;
/*
** Turn y into an actual year number for now.
** It is converted back to an offset from TM_YEAR_BASE later.
*/
if (increment_overflow32(&y, TM_YEAR_BASE))
return WRONG;
while (yourtm.tm_mday <= 0) {
if (increment_overflow32(&y, -1))
return WRONG;
li = y + (1 < yourtm.tm_mon);
yourtm.tm_mday += year_lengths[isleap(li)];
}
while (yourtm.tm_mday > DAYSPERLYEAR) {
li = y + (1 < yourtm.tm_mon);
yourtm.tm_mday -= year_lengths[isleap(li)];
if (increment_overflow32(&y, 1))
return WRONG;
}
for ( ; ; ) {
i = mon_lengths[isleap(y)][yourtm.tm_mon];
if (yourtm.tm_mday <= i)
break;
yourtm.tm_mday -= i;
if (++yourtm.tm_mon >= MONSPERYEAR) {
yourtm.tm_mon = 0;
if (increment_overflow32(&y, 1))
return WRONG;
}
}
#ifdef ckd_add
if (ckd_add(&yourtm.tm_year, y, -TM_YEAR_BASE))
return WRONG;
#else
if (increment_overflow32(&y, -TM_YEAR_BASE))
return WRONG;
if (! (INT_MIN <= y && y <= INT_MAX))
return WRONG;
yourtm.tm_year = y;
#endif
if (yourtm.tm_sec >= 0 && yourtm.tm_sec < SECSPERMIN)
saved_seconds = 0;
else if (yourtm.tm_year < EPOCH_YEAR - TM_YEAR_BASE) {
/*
** We can't set tm_sec to 0, because that might push the
** time below the minimum representable time.
** Set tm_sec to 59 instead.
** This assumes that the minimum representable time is
** not in the same minute that a leap second was deleted from,
** which is a safer assumption than using 58 would be.
*/
if (increment_overflow(&yourtm.tm_sec, 1 - SECSPERMIN))
return WRONG;
saved_seconds = yourtm.tm_sec;
yourtm.tm_sec = SECSPERMIN - 1;
} else {
saved_seconds = yourtm.tm_sec;
yourtm.tm_sec = 0;
}
/*
** Do a binary search (this works whatever time_t's type is).
*/
lo = TIME_T_MIN;
hi = TIME_T_MAX;
for ( ; ; ) {
t = lo / 2 + hi / 2;
if (t < lo)
t = lo;
else if (t > hi)
t = hi;
if (! funcp(sp, &t, offset, &mytm)) {
/*
** Assume that t is too extreme to be represented in
** a struct tm; arrange things so that it is less
** extreme on the next pass.
*/
dir = (t > 0) ? 1 : -1;
} else dir = tmcomp(&mytm, &yourtm);
if (dir != 0) {
if (t == lo) {
if (t == TIME_T_MAX)
return WRONG;
++t;
++lo;
} else if (t == hi) {
if (t == TIME_T_MIN)
return WRONG;
--t;
--hi;
}
if (lo > hi)
return WRONG;
if (dir > 0)
hi = t;
else lo = t;
continue;
}
#if defined TM_GMTOFF && ! UNINIT_TRAP
if (mytm.TM_GMTOFF != yourtm.TM_GMTOFF
&& (yourtm.TM_GMTOFF < 0
? (-SECSPERDAY <= yourtm.TM_GMTOFF
&& (mytm.TM_GMTOFF <=
(min(INT_FAST32_MAX, LONG_MAX)
+ yourtm.TM_GMTOFF)))
: (yourtm.TM_GMTOFF <= SECSPERDAY
&& ((max(INT_FAST32_MIN, LONG_MIN)
+ yourtm.TM_GMTOFF)
<= mytm.TM_GMTOFF)))) {
/* MYTM matches YOURTM except with the wrong UT offset.
YOURTM.TM_GMTOFF is plausible, so try it instead.
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)) {
struct tm alttm;
if (funcp(sp, &altt, offset, &alttm)
&& alttm.tm_isdst == mytm.tm_isdst
&& alttm.TM_GMTOFF == yourtm.TM_GMTOFF
&& tmcomp(&alttm, &yourtm) == 0) {
t = altt;
mytm = alttm;
}
}
}
#endif
if (yourtm.tm_isdst < 0 || mytm.tm_isdst == yourtm.tm_isdst)
break;
/*
** Right time, wrong type.
** Hunt for right time, right type.
** It's okay to guess wrong since the guess
** gets checked.
*/
if (sp == NULL)
return WRONG;
for (i = sp->typecnt - 1; i >= 0; --i) {
if (sp->ttis[i].tt_isdst != yourtm.tm_isdst)
continue;
for (j = sp->typecnt - 1; j >= 0; --j) {
if (sp->ttis[j].tt_isdst == yourtm.tm_isdst)
continue;
if (ttunspecified(sp, j))
continue;
newt = (t + sp->ttis[j].tt_utoff
- sp->ttis[i].tt_utoff);
if (! funcp(sp, &newt, offset, &mytm))
continue;
if (tmcomp(&mytm, &yourtm) != 0)
continue;
if (mytm.tm_isdst != yourtm.tm_isdst)
continue;
/*
** We have a match.
*/
t = newt;
goto label;
}
}
return WRONG;
}
label:
newt = t + saved_seconds;
if ((newt < t) != (saved_seconds < 0))
return WRONG;
t = newt;
if (funcp(sp, &t, offset, tmp))
*okayp = true;
return t;
}
static time_t
time2(struct tm * const tmp,
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)
{
time_t t;
/*
** First try without normalization of seconds
** (in case tm_sec contains a value associated with a leap second).
** If that fails, try with normalization of seconds.
*/
t = time2sub(tmp, funcp, sp, offset, okayp, false);
return *okayp ? t : time2sub(tmp, funcp, sp, offset, okayp, true);
}
static time_t
time1(struct tm *const tmp,
struct tm *(*funcp)(struct state const *, time_t const *,
int_fast32_t, struct tm *),
struct state const *sp,
const int_fast32_t offset)
{
register time_t t;
register int samei, otheri;
register int sameind, otherind;
register int i;
register int nseen;
char seen[TZ_MAX_TYPES];
unsigned char types[TZ_MAX_TYPES];
bool okay;
if (tmp == NULL) {
errno = EINVAL;
return WRONG;
}
if (tmp->tm_isdst > 1)
tmp->tm_isdst = 1;
t = time2(tmp, funcp, sp, offset, &okay);
if (okay)
return t;
if (tmp->tm_isdst < 0)
#ifdef PCTS
/*
** POSIX Conformance Test Suite code courtesy Grant Sullivan.
*/
tmp->tm_isdst = 0; /* reset to std and try again */
#else
return t;
#endif /* !defined PCTS */
/*
** We're supposed to assume that somebody took a time of one type
** and did some math on it that yielded a "struct tm" that's bad.
** We try to divine the type they started from and adjust to the
** type they need.
*/
if (sp == NULL)
return WRONG;
for (i = 0; i < sp->typecnt; ++i)
seen[i] = false;
nseen = 0;
for (i = sp->timecnt - 1; i >= 0; --i)
if (!seen[sp->types[i]] && !ttunspecified(sp, sp->types[i])) {
seen[sp->types[i]] = true;
types[nseen++] = sp->types[i];
}
for (sameind = 0; sameind < nseen; ++sameind) {
samei = types[sameind];
if (sp->ttis[samei].tt_isdst != tmp->tm_isdst)
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;
}
}
return WRONG;
}
static time_t
mktime_tzname(struct state *sp, struct tm *tmp, bool setname)
{
if (sp)
return time1(tmp, localsub, sp, setname);
else {
_once(&gmt_once, gmtcheck);
return time1(tmp, gmtsub, gmtptr, 0);
}
}
#if NETBSD_INSPIRED
time_t
-mktime_z(struct state *sp, struct tm *tmp)
+mktime_z(struct state *restrict sp, struct tm *restrict tmp)
{
return mktime_tzname(sp, tmp, false);
}
#endif
time_t
mktime(struct tm *tmp)
{
time_t t;
int err = lock();
if (err) {
errno = err;
return -1;
}
tzset_unlocked();
t = mktime_tzname(lclptr, tmp, true);
unlock();
return t;
}
-#ifdef STD_INSPIRED
+#if STD_INSPIRED
time_t
timelocal(struct tm *tmp)
{
if (tmp != NULL)
tmp->tm_isdst = -1; /* in case it wasn't initialized */
return mktime(tmp);
}
#else
static
#endif
time_t
timeoff(struct tm *tmp, long offset)
{
if (tmp)
tmp->tm_isdst = 0;
_once(&gmt_once, gmtcheck);
return time1(tmp, gmtsub, gmtptr, offset);
}
time_t
timegm(struct tm *tmp)
{
time_t t;
struct tm tmcpy;
mktmcpy(&tmcpy, tmp);
tmcpy.tm_wday = -1;
t = timeoff(&tmcpy, 0);
if (0 <= tmcpy.tm_wday)
*tmp = tmcpy;
return t;
}
static int_fast32_t
leapcorr(struct state const *sp, time_t t)
{
register struct lsinfo const * lp;
register int i;
i = sp->leapcnt;
while (--i >= 0) {
lp = &sp->lsis[i];
if (t >= lp->ls_trans)
return lp->ls_corr;
}
return 0;
}
/*
** XXX--is the below the right way to conditionalize??
*/
-#ifdef STD_INSPIRED
+#if STD_INSPIRED
/* NETBSD_INSPIRED_EXTERN functions are exported to callers if
NETBSD_INSPIRED is defined, and are private otherwise. */
# if NETBSD_INSPIRED
# define NETBSD_INSPIRED_EXTERN
# else
# define NETBSD_INSPIRED_EXTERN static
# endif
/*
** IEEE Std 1003.1 (POSIX) says that 536457599
** shall correspond to "Wed Dec 31 23:59:59 UTC 1986", which
** is not the case if we are accounting for leap seconds.
** So, we provide the following conversion routines for use
** when exchanging timestamps with POSIX conforming systems.
*/
NETBSD_INSPIRED_EXTERN time_t
time2posix_z(struct state *sp, time_t t)
{
return t - leapcorr(sp, t);
}
time_t
time2posix(time_t t)
{
int err = lock();
if (err) {
errno = err;
return -1;
}
#ifndef DETECT_TZ_CHANGES
if (!lcl_is_set)
#endif
tzset_unlocked();
if (lclptr)
t = time2posix_z(lclptr, t);
unlock();
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;
}
time_t
posix2time(time_t t)
{
int err = lock();
if (err) {
errno = err;
return -1;
}
#ifndef DETECT_TZ_CHANGES
if (!lcl_is_set)
#endif
tzset_unlocked();
if (lclptr)
t = posix2time_z(lclptr, t);
unlock();
return t;
}
-#endif /* defined STD_INSPIRED */
+#endif /* STD_INSPIRED */
#if TZ_TIME_T
# if !USG_COMPAT
# define daylight 0
# define timezone 0
# endif
# if !ALTZONE
# define altzone 0
# endif
/* Convert from the underlying system's time_t to the ersatz time_tz,
which is called 'time_t' in this file. Typically, this merely
converts the time's integer width. On some platforms, the system
time is local time not UT, or uses some epoch other than the POSIX
epoch.
Although this code appears to define a function named 'time' that
returns time_t, the macros in private.h cause this code to actually
define a function named 'tz_time' that returns tz_time_t. The call
to sys_time invokes the underlying system's 'time' function. */
time_t
time(time_t *p)
{
time_t r = sys_time(0);
if (r != (time_t) -1) {
int_fast32_t offset = EPOCH_LOCAL ? (daylight ? timezone : altzone) : 0;
if (increment_overflow32(&offset, -EPOCH_OFFSET)
|| increment_overflow_time(&r, offset)) {
errno = EOVERFLOW;
r = -1;
}
}
if (p)
*p = r;
return r;
}
#endif
diff --git a/contrib/tzcode/newctime.3 b/contrib/tzcode/newctime.3
index e25d841ef537..05bb7deaba51 100644
--- a/contrib/tzcode/newctime.3
+++ b/contrib/tzcode/newctime.3
@@ -1,344 +1,358 @@
.\" This file is in the public domain, so clarified as of
.\" 2009-05-17 by Arthur David Olson.
-.TH NEWCTIME 3
+.TH newctime 3 "" "Time Zone Database"
.SH NAME
asctime, ctime, difftime, gmtime, localtime, mktime \- convert date and time
.SH SYNOPSIS
.nf
.ie \n(.g .ds - \f(CR-\fP
.el .ds - \-
.B #include
.PP
.BR "extern char *tzname[];" " /\(** (optional) \(**/"
.PP
-.B char *ctime(time_t const *clock);
+.B [[deprecated]] char *ctime(time_t const *clock);
.PP
.B char *ctime_r(time_t const *clock, char *buf);
.PP
.B double difftime(time_t time1, time_t time0);
.PP
-.B char *asctime(struct tm const *tm);
+.B [[deprecated]] char *asctime(struct tm const *tm);
.PP
.B "char *asctime_r(struct tm const *restrict tm,"
.B " char *restrict result);"
.PP
.B struct tm *localtime(time_t const *clock);
.PP
.B "struct tm *localtime_r(time_t const *restrict clock,"
.B " struct tm *restrict result);"
.PP
.B "struct tm *localtime_rz(timezone_t restrict zone,"
.B " time_t const *restrict clock,"
.B " struct tm *restrict result);"
.PP
.B struct tm *gmtime(time_t const *clock);
.PP
.B "struct tm *gmtime_r(time_t const *restrict clock,"
.B " struct tm *restrict result);"
.PP
.B time_t mktime(struct tm *tm);
.PP
.B "time_t mktime_z(timezone_t restrict zone,"
.B " struct tm *restrict tm);"
.PP
.B cc ... \*-ltz
.fi
.SH DESCRIPTION
.ie '\(en'' .ds en \-
.el .ds en \(en
.ie '\(lq'' .ds lq \&"\"
.el .ds lq \(lq\"
.ie '\(rq'' .ds rq \&"\"
.el .ds rq \(rq\"
.de q
\\$3\*(lq\\$1\*(rq\\$2
..
The
.B ctime
function
converts a long integer, pointed to by
.IR clock ,
and returns a pointer to a
string of the form
.br
.ce
.eo
Thu Nov 24 18:22:48 1986\n\0
.br
.ec
Years requiring fewer than four characters are padded with leading zeroes.
For years longer than four characters, the string is of the form
.br
.ce
.eo
Thu Nov 24 18:22:48 81986\n\0
.ec
.br
with five spaces before the year.
These unusual formats are designed to make it less likely that older
software that expects exactly 26 bytes of output will mistakenly output
misleading values for out-of-range years.
.PP
The
.BI * clock
timestamp represents the time in seconds since 1970-01-01 00:00:00
Coordinated Universal Time (UTC).
The POSIX standard says that timestamps must be nonnegative
and must ignore leap seconds.
Many implementations extend POSIX by allowing negative timestamps,
and can therefore represent timestamps that predate the
introduction of UTC and are some other flavor of Universal Time (UT).
Some implementations support leap seconds, in contradiction to POSIX.
.PP
The
+.B ctime
+function is deprecated starting in C23.
+Callers can use
+.B localtime_r
+and
+.B strftime
+instead.
+.PP
+The
.B localtime
and
.B gmtime
functions
return pointers to
.q "tm"
structures, described below.
The
.B localtime
function
corrects for the time zone and any time zone adjustments
(such as Daylight Saving Time in the United States).
After filling in the
.q "tm"
structure,
.B localtime
sets the
.BR tm_isdst 'th
element of
.B tzname
to a pointer to a string that's the time zone abbreviation to be used with
.BR localtime 's
return value.
.PP
The
.B gmtime
function
converts to Coordinated Universal Time.
.PP
The
.B asctime
function
converts a time value contained in a
.q "tm"
structure to a string,
as shown in the above example,
and returns a pointer to the string.
+This function is deprecated starting in C23.
+Callers can use
+.B strftime
+instead.
.PP
The
.B mktime
function
converts the broken-down time,
expressed as local time,
in the structure pointed to by
.I tm
into a calendar time value with the same encoding as that of the values
returned by the
.B time
function.
The original values of the
.B tm_wday
and
.B tm_yday
components of the structure are ignored,
and the original values of the other components are not restricted
to their normal ranges.
(A positive or zero value for
.B tm_isdst
causes
.B mktime
to presume initially that daylight saving time
respectively,
is or is not in effect for the specified time.
A negative value for
.B tm_isdst
causes the
.B mktime
function to attempt to divine whether daylight saving time is in effect
for the specified time; in this case it does not use a consistent
rule and may give a different answer when later
presented with the same argument.)
On successful completion, the values of the
.B tm_wday
and
.B tm_yday
components of the structure are set appropriately,
and the other components are set to represent the specified calendar time,
but with their values forced to their normal ranges; the final value of
.B tm_mday
is not set until
.B tm_mon
and
.B tm_year
are determined.
The
.B mktime
function
returns the specified calendar time;
If the calendar time cannot be represented,
it returns \-1.
.PP
The
.B difftime
function
returns the difference between two calendar times,
.RI ( time1
\-
.IR time0 ),
expressed in seconds.
.PP
The
.BR ctime_r ,
.BR localtime_r ,
.BR gmtime_r ,
and
.B asctime_r
functions
are like their unsuffixed counterparts, except that they accept an
additional argument specifying where to store the result if successful.
.PP
The
.B localtime_rz
and
.B mktime_z
functions
are like their unsuffixed counterparts, except that they accept an
extra initial
.B zone
argument specifying the timezone to be used for conversion.
If
.B zone
is null, UT is used; otherwise,
.B zone
should be have been allocated by
.B tzalloc
and should not be freed until after all uses (e.g., by calls to
.BR strftime )
of the filled-in
.B tm_zone
fields.
.PP
Declarations of all the functions and externals, and the
.q "tm"
structure,
are in the
.B
header file.
The structure (of type)
.B struct tm
includes the following fields:
.RS
.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) \(**/
.fi
.RE
.PP
The
.B tm_isdst
field
is non-zero if daylight saving time is in effect.
.PP
The
.B tm_gmtoff
field
is the offset (in seconds) of the time represented
from UT, with positive values indicating east
of the Prime Meridian.
The field's name is derived from Greenwich Mean Time, a precursor of UT.
.PP
In
.B "struct tm"
the
.B tm_zone
and
.B tm_gmtoff
fields exist, and are filled in, only if arrangements to do
so were made when the library containing these functions was
created.
Similarly, the
.B tzname
variable is optional; also, there is no guarantee that
.B tzname
will
continue to exist in this form in future releases of this code.
.SH FILES
.ta \w'/usr/share/zoneinfo/posixrules\0\0'u
/usr/share/zoneinfo timezone information directory
.br
/usr/share/zoneinfo/localtime local timezone file
.br
-/usr/share/zoneinfo/posixrules used with POSIX-style TZ's
+/usr/share/zoneinfo/posixrules default DST rules (obsolete,
+ and can cause bugs if present)
.br
/usr/share/zoneinfo/GMT for UTC leap seconds
.sp
If
.B /usr/share/zoneinfo/GMT
is absent,
UTC leap seconds are loaded from
.BR /usr/share/zoneinfo/posixrules .
.SH SEE ALSO
getenv(3),
newstrftime(3),
newtzset(3),
time(2),
tzfile(5)
.SH NOTES
The return values of
.BR asctime ,
.BR ctime ,
.BR gmtime ,
and
.B localtime
point to static data
overwritten by each call.
The
.B tzname
variable (once set) and the
.B tm_zone
field of a returned
.B "struct tm"
both point to an array of characters that
can be freed or overwritten by later calls to the functions
.BR localtime ,
.BR tzfree ,
and
.BR tzset ,
if these functions affect the timezone information that specifies the
abbreviation in question.
The remaining functions and data are thread-safe.
.PP
The
.BR asctime ,
.BR asctime_r ,
.BR ctime ,
and
.B ctime_r
functions
behave strangely for years before 1000 or after 9999.
The 1989 and 1999 editions of the C Standard say
that years from \-99 through 999 are converted without
extra spaces, but this conflicts with longstanding
tradition and with this implementation.
The 2011 edition says that the behavior
is undefined if the year is before 1000 or after 9999.
Traditional implementations of these two functions are
restricted to years in the range 1900 through 2099.
To avoid this portability mess, new programs should use
.B strftime
instead.
diff --git a/contrib/tzcode/newstrftime.3 b/contrib/tzcode/newstrftime.3
index d5d8ee104d9f..432c3e889344 100644
--- a/contrib/tzcode/newstrftime.3
+++ b/contrib/tzcode/newstrftime.3
@@ -1,290 +1,290 @@
.\" strftime man page
.\"
.\" Based on the UCB file whose corrected copyright information appears below.
.\" Copyright 1989, 1991 The Regents of the University of California.
.\" All rights reserved.
.\"
.\" This code is derived from software contributed to Berkeley by
.\" the American National Standards Committee X3, on Information
.\" Processing Systems.
.\"
.\" Redistribution and use in source and binary forms, with or without
.\" modification, are permitted provided that the following conditions
.\" are met:
.\" 1. Redistributions of source code must retain the above copyright
.\" notice, this list of conditions and the following disclaimer.
.\" 2. Redistributions in binary form must reproduce the above copyright
.\" notice, this list of conditions and the following disclaimer in the
.\" documentation and/or other materials provided with the distribution.
.\" 3. Neither the name of the University nor the names of its contributors
.\" may be used to endorse or promote products derived from this software
.\" without specific prior written permission.
.\"
.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
.\" SUCH DAMAGE.
.\"
.\" from: @(#)strftime.3 5.12 (Berkeley) 6/29/91
.\" $Id: strftime.3,v 1.4 1993/12/15 20:33:00 jtc Exp $
.\"
-.TH NEWSTRFTIME 3
+.TH newstrftime 3 "" "Time Zone Database"
.SH NAME
strftime \- format date and time
.SH SYNOPSIS
.nf
.ie \n(.g .ds - \f(CR-\fP
.el .ds - \-
.B #include
.PP
.B "size_t strftime(char *restrict buf, size_t maxsize,"
.B " char const *restrict format, struct tm const *restrict timeptr);"
.PP
.B cc ... \-ltz
.fi
.SH DESCRIPTION
.ie '\(lq'' .ds lq \&"\"
.el .ds lq \(lq\"
.ie '\(rq'' .ds rq \&"\"
.el .ds rq \(rq\"
.de c
.ie \n(.g \f(CR\\$1\fP\\$2
.el \\$1\\$2
..
.de q
\\$3\*(lq\\$1\*(rq\\$2
..
The
.B strftime
function formats the information from
.I timeptr
into the array pointed to by
.I buf
according to the string pointed to by
.IR format .
.PP
The
.I format
string consists of zero or more conversion specifications and
ordinary characters.
All ordinary characters are copied directly into the array.
A conversion specification consists of a percent sign
.Ql %
and one other character.
.PP
No more than
.I maxsize
bytes are placed into the array.
.PP
Each conversion specification is replaced by the characters as
follows which are then copied into the array.
.TP
%A
is replaced by the locale's full weekday name.
.TP
%a
is replaced by the locale's abbreviated weekday name.
.TP
%B
is replaced by the locale's full month name.
.TP
%b or %h
is replaced by the locale's abbreviated month name.
.TP
%C
is replaced by the century (a year divided by 100 and truncated to an integer)
as a decimal number [00,99].
.TP
%c
is replaced by the locale's appropriate date and time representation.
.TP
%D
is equivalent to
.c %m/%d/%y .
.TP
%d
is replaced by the day of the month as a decimal number [01,31].
.TP
%e
is replaced by the day of month as a decimal number [1,31];
single digits are preceded by a blank.
.TP
%F
is equivalent to
.c %Y-%m-%d
(the ISO 8601 date format).
.TP
%G
is replaced by the ISO 8601 year with century as a decimal number.
See also the
.c %V
conversion specification.
.TP
%g
is replaced by the ISO 8601 year without century as a decimal number [00,99].
This is the year that includes the greater part of the week.
(Monday as the first day of a week).
See also the
.c %V
conversion specification.
.TP
%H
is replaced by the hour (24-hour clock) as a decimal number [00,23].
.TP
%I
is replaced by the hour (12-hour clock) as a decimal number [01,12].
.TP
%j
is replaced by the day of the year as a decimal number [001,366].
.TP
%k
is replaced by the hour (24-hour clock) as a decimal number [0,23];
single digits are preceded by a blank.
.TP
%l
is replaced by the hour (12-hour clock) as a decimal number [1,12];
single digits are preceded by a blank.
.TP
%M
is replaced by the minute as a decimal number [00,59].
.TP
%m
is replaced by the month as a decimal number [01,12].
.TP
%n
is replaced by a newline.
.TP
%p
is replaced by the locale's equivalent of either
.q AM
or
.q PM .
.TP
%R
is replaced by the time in the format
.c %H:%M .
.TP
%r
is replaced by the locale's representation of 12-hour clock time
using AM/PM notation.
.TP
%S
is replaced by the second as a decimal number [00,60].
The range of
seconds is [00,60] instead of [00,59] to allow for the periodic occurrence
of leap seconds.
.TP
%s
is replaced by the number of seconds since the Epoch (see
.BR ctime (3)).
.TP
%T
is replaced by the time in the format
.c %H:%M:%S .
.TP
%t
is replaced by a tab.
.TP
%U
is replaced by the week number of the year (Sunday as the first day of
the week) as a decimal number [00,53].
.TP
%u
is replaced by the weekday (Monday as the first day of the week)
as a decimal number [1,7].
.TP
%V
is replaced by the week number of the year (Monday as the first day of
the week) as a decimal number [01,53]. If the week containing January
1 has four or more days in the new year, then it is week 1; otherwise
it is week 53 of the previous year, and the next week is week 1.
The year is given by the
.c %G
conversion specification.
.TP
%W
is replaced by the week number of the year (Monday as the first day of
the week) as a decimal number [00,53].
.TP
%w
is replaced by the weekday (Sunday as the first day of the week)
as a decimal number [0,6].
.TP
%X
is replaced by the locale's appropriate time representation.
.TP
%x
is replaced by the locale's appropriate date representation.
.TP
%Y
is replaced by the year with century as a decimal number.
.TP
%y
is replaced by the year without century as a decimal number [00,99].
.TP
%Z
is replaced by the time zone abbreviation,
or by the empty string if this is not determinable.
.TP
%z
is replaced by the offset from the Prime Meridian
in the format +HHMM or \*-HHMM (ISO 8601) as appropriate,
with positive values representing locations east of Greenwich,
or by the empty string if this is not determinable.
The numeric time zone abbreviation \*-0000 is used when the time is
Universal Time
but local time is indeterminate; by convention this is used for
locations while uninhabited, and corresponds to a zero offset when the
time zone abbreviation begins with
.q "\*-" .
.TP
%%
is replaced by a single %.
.TP
%+
is replaced by the locale's date and time in
.BR date (1)
format.
.SH "RETURN VALUE"
If the conversion is successful,
.B strftime
returns the number of bytes placed into the array, not counting the
terminating NUL;
.B errno
is unchanged if the returned value is zero.
Otherwise,
.B errno
is set to indicate the error, zero is returned,
and the array contents are unspecified.
.SH ERRORS
This function fails if:
.TP
[ERANGE]
The total number of resulting bytes, including the terminating
NUL character, is more than
.IR maxsize .
.PP
This function may fail if:
.TP
[EOVERFLOW]
The format includes an
.c %s
conversion and the number of seconds since the Epoch cannot be represented
in a
.c time_t .
.SH SEE ALSO
date(1),
getenv(3),
newctime(3),
newtzset(3),
time(2),
tzfile(5)
.SH BUGS
There is no conversion specification for the phase of the moon.
diff --git a/contrib/tzcode/newtzset.3 b/contrib/tzcode/newtzset.3
index 1e75acf0e6e3..78b6b6ce67c4 100644
--- a/contrib/tzcode/newtzset.3
+++ b/contrib/tzcode/newtzset.3
@@ -1,350 +1,351 @@
.\" This file is in the public domain, so clarified as of
.\" 2009-05-17 by Arthur David Olson.
-.TH NEWTZSET 3
+.TH newtzset 3 "" "Time Zone Database"
.SH NAME
tzset \- initialize time conversion information
.SH SYNOPSIS
.nf
.ie \n(.g .ds - \f(CR-\fP
.el .ds - \-
.B #include
.PP
.B timezone_t tzalloc(char const *TZ);
.PP
.B void tzfree(timezone_t tz);
.PP
.B void tzset(void);
.PP
.B cc ... \*-ltz
.fi
.SH DESCRIPTION
.ie '\(en'' .ds en \-
.el .ds en \(en
.ie '\(lq'' .ds lq \&"\"
.el .ds lq \(lq\"
.ie '\(rq'' .ds rq \&"\"
.el .ds rq \(rq\"
.de q
\\$3\*(lq\\$1\*(rq\\$2
..
The
.B tzalloc
function
allocates and returns a timezone object described by
.BR TZ .
If
.B TZ
is not a valid timezone description, or if the object cannot be allocated,
.B tzalloc
returns a null pointer and sets
.BR errno .
.PP
The
.B tzfree
function
frees a timezone object
.BR tz ,
which should have been successfully allocated by
.BR tzalloc .
This invalidates any
.B tm_zone
pointers that
.B tz
was used to set.
.PP
The
.B tzset
function
acts like
.BR tzalloc(getenv("TZ")) ,
except it saves any resulting timezone object into internal
storage that is accessed by
.BR localtime ,
.BR localtime_r ,
and
.BR mktime .
The anonymous shared timezone object is freed by the next call to
.BR tzset .
If the implied call to
.B tzalloc
fails,
.B tzset
falls back on Universal Time (UT).
.PP
If
.B TZ
is null, the best available approximation to local (wall
clock) time, as specified by the
.BR tzfile (5)-format
file
.B localtime
in the system time conversion information directory, is used.
If
.B TZ
is the empty string,
UT is used, with the abbreviation "UTC"
and without leap second correction; please see
.BR newctime (3)
for more about UT, UTC, and leap seconds. If
.B TZ
is nonnull and nonempty:
.IP
if the value begins with a colon, it is used as a pathname of a file
from which to read the time conversion information;
.IP
if the value does not begin with a colon, it is first used as the
pathname of a file from which to read the time conversion information,
and, if that file cannot be read, is used directly as a specification of
the time conversion information.
.PP
When
.B TZ
is used as a pathname, if it begins with a slash,
it is used as an absolute pathname; otherwise,
it is used as a pathname relative to a system time conversion information
directory.
The file must be in the format specified in
.BR tzfile (5).
.PP
When
.B TZ
is used directly as a specification of the time conversion information,
it must have the following syntax (spaces inserted for clarity):
.IP
\fIstd\|offset\fR[\fIdst\fR[\fIoffset\fR][\fB,\fIrule\fR]]
.PP
Where:
.RS
.TP 15
.IR std " and " dst
Three or more bytes that are the designation for the standard
.RI ( std )
or the alternative
.RI ( dst ,
such as daylight saving time)
time zone. Only
.I std
is required; if
.I dst
is missing, then daylight saving time does not apply in this locale.
Upper- and lowercase letters are explicitly allowed. Any characters
except a leading colon
.RB ( : ),
digits, comma
.RB ( , ),
ASCII minus
.RB ( \*- ),
ASCII plus
.RB ( + ),
and NUL bytes are allowed.
Alternatively, a designation can be surrounded by angle brackets
.B <
and
.BR > ;
in this case, the designation can contain any characters other than
.B >
and NUL.
.TP
.I offset
Indicates the value one must add to the local time to arrive at
Coordinated Universal Time. The
.I offset
has the form:
.RS
.IP
\fIhh\fR[\fB:\fImm\fR[\fB:\fIss\fR]]
.RE
.IP
The minutes
.RI ( mm )
and seconds
.RI ( ss )
are optional. The hour
.RI ( hh )
is required and may be a single digit. The
.I offset
following
.I std
is required. If no
.I offset
follows
.IR dst ,
daylight saving time is assumed to be one hour ahead of standard time. One or
more digits may be used; the value is always interpreted as a decimal
number. The hour must be between zero and 24, and the minutes (and
seconds) \*(en if present \*(en between zero and 59. If preceded by a
.q "\*-" ,
the time zone shall be east of the Prime Meridian; otherwise it shall be
west (which may be indicated by an optional preceding
.q "+" .
.TP
.I rule
Indicates when to change to and back from daylight saving time. The
.I rule
has the form:
.RS
.IP
\fIdate\fB/\fItime\fB,\fIdate\fB/\fItime\fR
.RE
.IP
where the first
.I date
describes when the change from standard to daylight saving time occurs and the
second
.I date
describes when the change back happens. Each
.I time
field describes when, in current local time, the change to the other
time is made.
As an extension to POSIX, daylight saving is assumed to be in effect
all year if it begins January 1 at 00:00 and ends December 31 at
24:00 plus the difference between daylight saving and standard time,
leaving no room for standard time in the calendar.
.IP
The format of
.I date
is one of the following:
.RS
.TP 10
.BI J n
The Julian day
.I n
.RI "(1\ \(<=" "\ n\ " "\(<=\ 365).
Leap days are not counted; that is, in all years \*(en including leap
years \*(en February 28 is day 59 and March 1 is day 60. It is
impossible to explicitly refer to the occasional February 29.
.TP
.I n
The zero-based Julian day
.RI "(0\ \(<=" "\ n\ " "\(<=\ 365).
Leap days are counted, and it is possible to refer to February 29.
.TP
.BI M m . n . d
The
.IR d' th
day
.RI "(0\ \(<=" "\ d\ " "\(<=\ 6)
of week
.I n
of month
.I m
of the year
.RI "(1\ \(<=" "\ n\ " "\(<=\ 5,
.RI "1\ \(<=" "\ m\ " "\(<=\ 12,
where week 5 means
.q "the last \fId\fP day in month \fIm\fP"
which may occur in either the fourth or the fifth week). Week 1 is the
first week in which the
.IR d' th
day occurs. Day zero is Sunday.
.RE
.IP "" 15
The
.I time
has the same format as
.I offset
except that POSIX does not allow a leading sign (\c
.q "\*-"
or
.q "+" ).
As an extension to POSIX, the hours part of
.I time
can range from \-167 through 167; this allows for unusual rules such
as
.q "the Saturday before the first Sunday of March" .
The default, if
.I time
is not given, is
.BR 02:00:00 .
.RE
.LP
Here are some examples of
.B TZ
values that directly specify the timezone; they use some of the
extensions to POSIX.
.TP
.B EST5
stands for US Eastern Standard
Time (EST), 5 hours behind UT, without daylight saving.
.TP
.B <+12>\*-12<+13>,M11.1.0,M1.2.1/147
stands for Fiji time, 12 hours ahead
of UT, springing forward on November's first Sunday at 02:00, and
falling back on January's second Monday at 147:00 (i.e., 03:00 on the
first Sunday on or after January 14). The abbreviations for standard
and daylight saving time are
.q "+12"
and
.q "+13".
.TP
.B IST\*-2IDT,M3.4.4/26,M10.5.0
stands for Israel Standard Time (IST) and Israel Daylight Time (IDT),
2 hours ahead of UT, springing forward on March's fourth
Thursday at 26:00 (i.e., 02:00 on the first Friday on or after March
23), and falling back on October's last Sunday at 02:00.
.TP
.B <\*-04>4<\*-03>,J1/0,J365/25
stands for permanent daylight saving time, 3 hours behind UT with
abbreviation
.q "\*-03".
There is a dummy fall-back transition on December 31 at 25:00 daylight
saving time (i.e., 24:00 standard time, equivalent to January 1 at
00:00 standard time), and a simultaneous spring-forward transition on
January 1 at 00:00 standard time, so daylight saving time is in effect
all year and the initial
.B <\*-04>
is a placeholder.
.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
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).
The abbreviations for standard and daylight saving time are
.q "\*-03"
and
.q "\*-02".
.PP
If no
.I rule
is present in
.BR TZ ,
the rules specified
by the
.BR tzfile (5)-format
file
.B posixrules
in the system time conversion information directory are used, with the
standard and daylight saving time offsets from UT replaced by those specified by
the
.I offset
values in
.BR TZ .
.PP
For compatibility with System V Release 3.1, a semicolon
.RB ( ; )
may be used to separate the
.I rule
from the rest of the specification.
.SH FILES
.ta \w'/usr/share/zoneinfo/posixrules\0\0'u
/usr/share/zoneinfo timezone information directory
.br
/usr/share/zoneinfo/localtime local timezone file
.br
-/usr/share/zoneinfo/posixrules used with POSIX-style TZ
+/usr/share/zoneinfo/posixrules default DST rules (obsolete,
+ and can cause bugs if present)
.br
/usr/share/zoneinfo/GMT for UTC leap seconds
.sp
If
.B /usr/share/zoneinfo/GMT
is absent,
UTC leap seconds are loaded from
.BR /usr/share/zoneinfo/posixrules .
.SH SEE ALSO
getenv(3),
newctime(3),
newstrftime(3),
time(2),
tzfile(5)
diff --git a/contrib/tzcode/private.h b/contrib/tzcode/private.h
index 35d2444d4ec1..44a927622987 100644
--- a/contrib/tzcode/private.h
+++ b/contrib/tzcode/private.h
@@ -1,938 +1,1011 @@
/* Private header for tzdb code. */
#ifndef PRIVATE_H
#define PRIVATE_H
/*
** This file is in the public domain, so clarified as of
** 1996-06-05 by Arthur David Olson.
*/
/*
** This header is for use ONLY with the time conversion code.
** There is no guarantee that it will remain unchanged,
** or that it will remain at all.
** Do NOT copy it to any system include directory.
** Thank you!
*/
+/* PORT_TO_C89 means the code should work even if the underlying
+ compiler and library support only C89. SUPPORT_C89 means the
+ tzcode library should support C89 callers in addition to the usual
+ support for C99-and-later callers. These macros are obsolescent,
+ and the plan is to remove them along with any code needed only when
+ they are nonzero. */
+#ifndef PORT_TO_C89
+# define PORT_TO_C89 0
+#endif
+#ifndef SUPPORT_C89
+# define SUPPORT_C89 0
+#endif
+
#ifndef __STDC_VERSION__
# define __STDC_VERSION__ 0
#endif
/* Define true, false and bool if they don't work out of the box. */
-#if __STDC_VERSION__ < 199901
+#if PORT_TO_C89 && __STDC_VERSION__ < 199901
# define true 1
# define false 0
# define bool int
#elif __STDC_VERSION__ < 202311
# include
#endif
+#if __STDC_VERSION__ < 202311
+# define static_assert(cond) extern int static_assert_check[(cond) ? 1 : -1]
+#endif
+
/*
** zdump has been made independent of the rest of the time
** conversion package to increase confidence in the verification it provides.
** You can use zdump to help in verifying other implementations.
** To do this, compile with -DUSE_LTZ=0 and link without the tz library.
*/
#ifndef USE_LTZ
# define USE_LTZ 1
#endif
/* This string was in the Factory zone through version 2016f. */
#define GRANDPARENTED "Local time zone must be set--use tzsetup"
/*
** Defaults for preprocessor symbols.
** You can override these in your C compiler options, e.g. '-DHAVE_GETTEXT=1'.
*/
#ifndef HAVE_DECL_ASCTIME_R
# define HAVE_DECL_ASCTIME_R 1
#endif
-#if !defined HAVE_GENERIC && defined __has_extension
+#if !defined HAVE__GENERIC && defined __has_extension
# if __has_extension(c_generic_selections)
-# define HAVE_GENERIC 1
+# define HAVE__GENERIC 1
# else
-# define HAVE_GENERIC 0
+# define HAVE__GENERIC 0
# endif
#endif
/* _Generic is buggy in pre-4.9 GCC. */
-#if !defined HAVE_GENERIC && defined __GNUC__ && !defined __STRICT_ANSI__
-# define HAVE_GENERIC (4 < __GNUC__ + (9 <= __GNUC_MINOR__))
+#if !defined HAVE__GENERIC && defined __GNUC__ && !defined __STRICT_ANSI__
+# define HAVE__GENERIC (4 < __GNUC__ + (9 <= __GNUC_MINOR__))
#endif
-#ifndef HAVE_GENERIC
-# define HAVE_GENERIC (201112 <= __STDC_VERSION__)
+#ifndef HAVE__GENERIC
+# define HAVE__GENERIC (201112 <= __STDC_VERSION__)
#endif
#if !defined HAVE_GETTEXT && defined __has_include
# if __has_include()
# define HAVE_GETTEXT true
# endif
#endif
#ifndef HAVE_GETTEXT
# define HAVE_GETTEXT false
#endif
#ifndef HAVE_INCOMPATIBLE_CTIME_R
# define HAVE_INCOMPATIBLE_CTIME_R 0
#endif
#ifndef HAVE_LINK
# define HAVE_LINK 1
#endif /* !defined HAVE_LINK */
#ifndef HAVE_MALLOC_ERRNO
# define HAVE_MALLOC_ERRNO 1
#endif
#ifndef HAVE_POSIX_DECLS
# define HAVE_POSIX_DECLS 1
#endif
#ifndef HAVE_SETENV
# define HAVE_SETENV 1
#endif
#ifndef HAVE_STRDUP
# define HAVE_STRDUP 1
#endif
-#ifndef HAVE_STRTOLL
-# define HAVE_STRTOLL 1
-#endif
-
#ifndef HAVE_SYMLINK
# define HAVE_SYMLINK 1
#endif /* !defined HAVE_SYMLINK */
#if !defined HAVE_SYS_STAT_H && defined __has_include
# if !__has_include()
# define HAVE_SYS_STAT_H false
# endif
#endif
#ifndef HAVE_SYS_STAT_H
# define HAVE_SYS_STAT_H true
#endif
#if !defined HAVE_UNISTD_H && defined __has_include
# if !__has_include()
# define HAVE_UNISTD_H false
# endif
#endif
#ifndef HAVE_UNISTD_H
# define HAVE_UNISTD_H true
#endif
#ifndef NETBSD_INSPIRED
# define NETBSD_INSPIRED 1
#endif
#if HAVE_INCOMPATIBLE_CTIME_R
# define asctime_r _incompatible_asctime_r
# define ctime_r _incompatible_ctime_r
#endif /* HAVE_INCOMPATIBLE_CTIME_R */
/* Enable tm_gmtoff, tm_zone, and environ on GNUish systems. */
#define _GNU_SOURCE 1
/* Fix asctime_r on Solaris 11. */
#define _POSIX_PTHREAD_SEMANTICS 1
/* Enable strtoimax on pre-C99 Solaris 11. */
#define __EXTENSIONS__ 1
/* On GNUish systems where time_t might be 32 or 64 bits, use 64.
On these platforms _FILE_OFFSET_BITS must also be 64; otherwise
setting _TIME_BITS to 64 does not work. The code does not
otherwise rely on _FILE_OFFSET_BITS being 64, since it does not
use off_t or functions like 'stat' that depend on off_t. */
#ifndef _FILE_OFFSET_BITS
# define _FILE_OFFSET_BITS 64
#endif
#if !defined _TIME_BITS && _FILE_OFFSET_BITS == 64
# define _TIME_BITS 64
#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
# define timezone sys_timezone
#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
# undef timezone
#endif
#undef timezone_t
#undef tzalloc
#undef tzfree
#include
#include
+#if !PORT_TO_C89
+# include
+#endif
#include /* for CHAR_BIT et al. */
#include
#include
#ifndef EINVAL
# define EINVAL ERANGE
#endif
#ifndef ELOOP
# define ELOOP EINVAL
#endif
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
#ifndef ENOMEM
# define ENOMEM EINVAL
#endif
#ifndef ENOTSUP
# define ENOTSUP EINVAL
#endif
#ifndef EOVERFLOW
# define EOVERFLOW EINVAL
#endif
#if HAVE_GETTEXT
# include
#endif /* HAVE_GETTEXT */
#if HAVE_UNISTD_H
# include /* for R_OK, and other POSIX goodness */
#endif /* HAVE_UNISTD_H */
#ifndef HAVE_STRFTIME_L
# if _POSIX_VERSION < 200809
# define HAVE_STRFTIME_L 0
# else
# define HAVE_STRFTIME_L 1
# endif
#endif
#ifndef USG_COMPAT
# ifndef _XOPEN_VERSION
# define USG_COMPAT 0
# else
# define USG_COMPAT 1
# endif
#endif
#ifndef HAVE_TZNAME
# if _POSIX_VERSION < 198808 && !USG_COMPAT
# define HAVE_TZNAME 0
# else
# define HAVE_TZNAME 1
# endif
#endif
#ifndef ALTZONE
# if defined __sun || defined _M_XENIX
# define ALTZONE 1
# else
# define ALTZONE 0
# endif
#endif
#ifndef R_OK
# define R_OK 4
#endif /* !defined R_OK */
+#if PORT_TO_C89
+
/*
** Define HAVE_STDINT_H's default value here, rather than at the
** start, since __GLIBC__ and INTMAX_MAX's values depend on
-** previously-included files. glibc 2.1 and Solaris 10 and later have
+** 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 true /* 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__
# endif
# ifndef LLONG_MIN
# define LLONG_MIN (-1 - LLONG_MAX)
# endif
# ifndef ULLONG_MAX
# define ULLONG_MAX (LLONG_MAX * 2ull + 1)
# endif
#endif
#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
/* 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
# endif
#endif
#ifndef PRIdFAST64
# if INT_FAST64_MAX == LONG_MAX
# define PRIdFAST64 "ld"
# else
# define PRIdFAST64 "lld"
# endif
#endif
#ifndef SCNdFAST64
# define SCNdFAST64 PRIdFAST64
#endif
#ifndef INT_FAST32_MAX
# if INT_MAX >> 31 == 0
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 INTMAX_MAX
# ifdef LLONG_MAX
typedef long long intmax_t;
+# ifndef HAVE_STRTOLL
+# define HAVE_STRTOLL true
+# 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
# ifndef strtoimax
# define strtoimax strtol
# endif
#endif
#ifndef PRIdMAX
# if INTMAX_MAX == LLONG_MAX
# define PRIdMAX "lld"
# else
# define PRIdMAX "ld"
# endif
#endif
#ifndef PTRDIFF_MAX
# define PTRDIFF_MAX MAXVAL(ptrdiff_t, TYPE_BIT(ptrdiff_t))
#endif
#ifndef UINT_FAST32_MAX
typedef unsigned long uint_fast32_t;
#endif
#ifndef UINT_FAST64_MAX
# if 3 <= ULONG_MAX >> 31 >> 31
typedef unsigned long uint_fast64_t;
# 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
# endif
#endif
#ifndef UINTMAX_MAX
# ifdef ULLONG_MAX
typedef unsigned long long uintmax_t;
+# define UINTMAX_MAX ULLONG_MAX
# else
typedef unsigned long uintmax_t;
+# define UINTMAX_MAX ULONG_MAX
# endif
#endif
#ifndef PRIuMAX
# ifdef ULLONG_MAX
# define PRIuMAX "llu"
# else
# define PRIuMAX "lu"
# endif
#endif
#ifndef SIZE_MAX
# define SIZE_MAX ((size_t) -1)
#endif
+#endif /* PORT_TO_C89 */
+
+/* The maximum size of any created object, as a signed integer.
+ Although the C standard does not outright prohibit larger objects,
+ behavior is undefined if the result of pointer subtraction does not
+ fit into ptrdiff_t, and the code assumes in several places that
+ pointer subtraction works. As a practical matter it's OK to not
+ support objects larger than this. */
+#define INDEX_MAX ((ptrdiff_t) min(PTRDIFF_MAX, SIZE_MAX))
+
/* Support ckd_add, ckd_sub, ckd_mul on C23 or recent-enough GCC-like
hosts, unless compiled with -DHAVE_STDCKDINT_H=0 or with pre-C23 EDG. */
#if !defined HAVE_STDCKDINT_H && defined __has_include
# if __has_include()
# define HAVE_STDCKDINT_H true
# endif
#endif
#ifdef HAVE_STDCKDINT_H
# if HAVE_STDCKDINT_H
# include
# endif
#elif defined __EDG__
/* Do nothing, to work around EDG bug . */
#elif defined __has_builtin
# if __has_builtin(__builtin_add_overflow)
# define ckd_add(r, a, b) __builtin_add_overflow(a, b, r)
# endif
# if __has_builtin(__builtin_sub_overflow)
# define ckd_sub(r, a, b) __builtin_sub_overflow(a, b, r)
# endif
# if __has_builtin(__builtin_mul_overflow)
# define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r)
# endif
#elif 7 <= __GNUC__
# define ckd_add(r, a, b) __builtin_add_overflow(a, b, r)
# define ckd_sub(r, a, b) __builtin_sub_overflow(a, b, r)
# define ckd_mul(r, a, b) __builtin_mul_overflow(a, b, r)
#endif
#if 3 <= __GNUC__
# define ATTRIBUTE_MALLOC __attribute__((malloc))
# define ATTRIBUTE_FORMAT(spec) __attribute__((format spec))
#else
# define ATTRIBUTE_MALLOC /* empty */
# define ATTRIBUTE_FORMAT(spec) /* empty */
#endif
#if (defined __has_c_attribute \
&& (202311 <= __STDC_VERSION__ || !defined __STRICT_ANSI__))
-# define HAVE_HAS_C_ATTRIBUTE true
+# define HAVE___HAS_C_ATTRIBUTE true
#else
-# define HAVE_HAS_C_ATTRIBUTE false
+# define HAVE___HAS_C_ATTRIBUTE false
+#endif
+
+#if HAVE___HAS_C_ATTRIBUTE
+# if __has_c_attribute(deprecated)
+# define ATTRIBUTE_DEPRECATED [[deprecated]]
+# endif
+#endif
+#ifndef ATTRIBUTE_DEPRECATED
+# if 3 < __GNUC__ + (2 <= __GNUC_MINOR__)
+# define ATTRIBUTE_DEPRECATED __attribute__((deprecated))
+# else
+# define ATTRIBUTE_DEPRECATED /* empty */
+# endif
#endif
-#if HAVE_HAS_C_ATTRIBUTE
+#if HAVE___HAS_C_ATTRIBUTE
# if __has_c_attribute(fallthrough)
# define ATTRIBUTE_FALLTHROUGH [[fallthrough]]
# endif
#endif
#ifndef ATTRIBUTE_FALLTHROUGH
# if 7 <= __GNUC__
# define ATTRIBUTE_FALLTHROUGH __attribute__((fallthrough))
# else
# define ATTRIBUTE_FALLTHROUGH ((void) 0)
# endif
#endif
-#if HAVE_HAS_C_ATTRIBUTE
+#if HAVE___HAS_C_ATTRIBUTE
# if __has_c_attribute(maybe_unused)
# define ATTRIBUTE_MAYBE_UNUSED [[maybe_unused]]
# endif
#endif
#ifndef ATTRIBUTE_MAYBE_UNUSED
# if 2 < __GNUC__ + (7 <= __GNUC_MINOR__)
# define ATTRIBUTE_MAYBE_UNUSED __attribute__((unused))
# else
# define ATTRIBUTE_MAYBE_UNUSED /* empty */
# endif
#endif
-#if HAVE_HAS_C_ATTRIBUTE
+#if HAVE___HAS_C_ATTRIBUTE
# if __has_c_attribute(noreturn)
# define ATTRIBUTE_NORETURN [[noreturn]]
# endif
#endif
#ifndef ATTRIBUTE_NORETURN
# if 201112 <= __STDC_VERSION__
# define ATTRIBUTE_NORETURN _Noreturn
# elif 2 < __GNUC__ + (8 <= __GNUC_MINOR__)
# define ATTRIBUTE_NORETURN __attribute__((noreturn))
# else
# define ATTRIBUTE_NORETURN /* empty */
# endif
#endif
-#if HAVE_HAS_C_ATTRIBUTE
+#if HAVE___HAS_C_ATTRIBUTE
# if __has_c_attribute(reproducible)
# define ATTRIBUTE_REPRODUCIBLE [[reproducible]]
# endif
#endif
#ifndef ATTRIBUTE_REPRODUCIBLE
# if 3 <= __GNUC__
# define ATTRIBUTE_REPRODUCIBLE __attribute__((pure))
# else
# define ATTRIBUTE_REPRODUCIBLE /* empty */
# endif
#endif
-#if HAVE_HAS_C_ATTRIBUTE
+#if HAVE___HAS_C_ATTRIBUTE
# if __has_c_attribute(unsequenced)
# define ATTRIBUTE_UNSEQUENCED [[unsequenced]]
# endif
#endif
#ifndef ATTRIBUTE_UNSEQUENCED
# if 3 <= __GNUC__
# define ATTRIBUTE_UNSEQUENCED __attribute__((const))
# else
# define ATTRIBUTE_UNSEQUENCED /* empty */
# endif
#endif
-#if __STDC_VERSION__ < 199901 && !defined restrict
+#if (__STDC_VERSION__ < 199901 && !defined restrict \
+ && (PORT_TO_C89 || defined _MSC_VER))
# define restrict /* empty */
#endif
/*
** Workarounds for compilers/systems.
*/
#ifndef EPOCH_LOCAL
# define EPOCH_LOCAL 0
#endif
#ifndef EPOCH_OFFSET
# define EPOCH_OFFSET 0
#endif
#ifndef RESERVE_STD_EXT_IDS
# define RESERVE_STD_EXT_IDS 0
#endif
/* If standard C identifiers with external linkage (e.g., localtime)
are reserved and are not already being renamed anyway, rename them
as if compiling with '-Dtime_tz=time_t'. */
#if !defined time_tz && RESERVE_STD_EXT_IDS && USE_LTZ
# define time_tz time_t
#endif
/*
** Compile with -Dtime_tz=T to build the tz package with a private
** time_t type equivalent to T rather than the system-supplied time_t.
** This debugging feature can test unusual design decisions
** (e.g., time_t wider than 'long', or unsigned time_t) even on
** typical platforms.
*/
#if defined time_tz || EPOCH_LOCAL || EPOCH_OFFSET != 0
# define TZ_TIME_T 1
#else
# define TZ_TIME_T 0
#endif
#if defined LOCALTIME_IMPLEMENTATION && TZ_TIME_T
static time_t sys_time(time_t *x) { return time(x); }
#endif
#if TZ_TIME_T
typedef time_tz tz_time_t;
# undef asctime
# define asctime tz_asctime
# undef asctime_r
# define asctime_r tz_asctime_r
# undef ctime
# define ctime tz_ctime
# undef ctime_r
# define ctime_r tz_ctime_r
# undef difftime
# define difftime tz_difftime
# undef gmtime
# define gmtime tz_gmtime
# undef gmtime_r
# define gmtime_r tz_gmtime_r
# undef localtime
# define localtime tz_localtime
# undef localtime_r
# define localtime_r tz_localtime_r
# undef localtime_rz
# define localtime_rz tz_localtime_rz
# undef mktime
# define mktime tz_mktime
# undef mktime_z
# define mktime_z tz_mktime_z
# undef offtime
# define offtime tz_offtime
+# undef offtime_r
+# define offtime_r tz_offtime_r
# undef posix2time
# define posix2time tz_posix2time
# undef posix2time_z
# define posix2time_z tz_posix2time_z
# undef strftime
# define strftime tz_strftime
# undef time
# define time tz_time
# undef time2posix
# define time2posix tz_time2posix
# undef time2posix_z
# define time2posix_z tz_time2posix_z
# undef time_t
# define time_t tz_time_t
# undef timegm
# define timegm tz_timegm
# undef timelocal
# define timelocal tz_timelocal
# undef timeoff
# define timeoff tz_timeoff
# undef tzalloc
# define tzalloc tz_tzalloc
# undef tzfree
# define tzfree tz_tzfree
# undef tzset
# define tzset tz_tzset
# if HAVE_STRFTIME_L
# undef strftime_l
# define strftime_l tz_strftime_l
# endif
# if HAVE_TZNAME
# undef tzname
# define tzname tz_tzname
# endif
# if USG_COMPAT
# undef daylight
# define daylight tz_daylight
# undef timezone
# define timezone tz_timezone
# endif
# if ALTZONE
# undef altzone
# define altzone tz_altzone
# endif
-char *asctime(struct tm const *);
+# if __STDC_VERSION__ < 202311
+# define DEPRECATED_IN_C23 /* empty */
+# else
+# define DEPRECATED_IN_C23 ATTRIBUTE_DEPRECATED
+# endif
+DEPRECATED_IN_C23 char *asctime(struct tm const *);
char *asctime_r(struct tm const *restrict, char *restrict);
-char *ctime(time_t const *);
+DEPRECATED_IN_C23 char *ctime(time_t const *);
char *ctime_r(time_t const *, char *);
-double difftime(time_t, time_t) ATTRIBUTE_UNSEQUENCED;
+ATTRIBUTE_UNSEQUENCED double difftime(time_t, time_t);
size_t strftime(char *restrict, size_t, char const *restrict,
struct tm const *restrict);
# if HAVE_STRFTIME_L
size_t strftime_l(char *restrict, size_t, char const *restrict,
struct tm const *restrict, locale_t);
# endif
struct tm *gmtime(time_t const *);
struct tm *gmtime_r(time_t const *restrict, struct tm *restrict);
struct tm *localtime(time_t const *);
struct tm *localtime_r(time_t const *restrict, struct tm *restrict);
time_t mktime(struct tm *);
time_t time(time_t *);
time_t timegm(struct tm *);
void tzset(void);
#endif
#ifndef HAVE_DECL_TIMEGM
# if (202311 <= __STDC_VERSION__ \
|| defined __GLIBC__ || defined __tm_zone /* musl */ \
|| defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
|| (defined __APPLE__ && defined __MACH__))
# define HAVE_DECL_TIMEGM true
# else
# define HAVE_DECL_TIMEGM false
# endif
#endif
#if !HAVE_DECL_TIMEGM && !defined timegm
time_t timegm(struct tm *);
#endif
#if !HAVE_DECL_ASCTIME_R && !defined asctime_r
extern char *asctime_r(struct tm const *restrict, char *restrict);
#endif
#ifndef HAVE_DECL_ENVIRON
# if defined environ || defined __USE_GNU
# define HAVE_DECL_ENVIRON 1
# else
# define HAVE_DECL_ENVIRON 0
# endif
#endif
#if !HAVE_DECL_ENVIRON
extern char **environ;
#endif
#if 2 <= HAVE_TZNAME + (TZ_TIME_T || !HAVE_POSIX_DECLS)
extern char *tzname[];
#endif
#if 2 <= USG_COMPAT + (TZ_TIME_T || !HAVE_POSIX_DECLS)
extern long timezone;
extern int daylight;
#endif
#if 2 <= ALTZONE + (TZ_TIME_T || !HAVE_POSIX_DECLS)
extern long altzone;
#endif
/*
** The STD_INSPIRED functions are similar, but most also need
** declarations if time_tz is defined.
*/
-#ifdef STD_INSPIRED
+#ifndef STD_INSPIRED
+# define STD_INSPIRED 0
+#endif
+#if STD_INSPIRED
# if TZ_TIME_T || !defined offtime
struct tm *offtime(time_t const *, long);
# endif
+# if TZ_TIME_T || !defined offtime_r
+struct tm *offtime_r(time_t const *, long, struct tm *);
+# endif
# if TZ_TIME_T || !defined timelocal
time_t timelocal(struct tm *);
# endif
# if TZ_TIME_T || !defined timeoff
time_t timeoff(struct tm *, long);
# endif
# if TZ_TIME_T || !defined time2posix
time_t time2posix(time_t);
# endif
# if TZ_TIME_T || !defined posix2time
time_t posix2time(time_t);
# endif
#endif
/* Infer TM_ZONE on systems where this information is known, but suppress
guessing if NO_TM_ZONE is defined. Similarly for TM_GMTOFF. */
#if (defined __GLIBC__ \
|| defined __tm_zone /* musl */ \
|| defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ \
|| (defined __APPLE__ && defined __MACH__))
# if !defined TM_GMTOFF && !defined NO_TM_GMTOFF
# define TM_GMTOFF tm_gmtoff
# endif
# if !defined TM_ZONE && !defined NO_TM_ZONE
# define TM_ZONE tm_zone
# endif
#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
** 'struct __state', but this is a private name so it doesn't matter.
*/
#if NETBSD_INSPIRED
typedef struct state *timezone_t;
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);
-# ifdef STD_INSPIRED
+# if STD_INSPIRED
# if TZ_TIME_T || !defined posix2time_z
-time_t posix2time_z(timezone_t, time_t) ATTRIBUTE_REPRODUCIBLE;
+ATTRIBUTE_REPRODUCIBLE time_t posix2time_z(timezone_t, time_t);
# endif
# if TZ_TIME_T || !defined time2posix_z
-time_t time2posix_z(timezone_t, time_t) ATTRIBUTE_REPRODUCIBLE;
+ATTRIBUTE_REPRODUCIBLE time_t time2posix_z(timezone_t, time_t);
# endif
# endif
#endif
/*
** Finally, some convenience items.
*/
-#define TYPE_BIT(type) (sizeof(type) * CHAR_BIT)
+#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)
/* Minimum and maximum of two values. Use lower case to avoid
naming clashes with standard include files. */
#define max(a, b) ((a) > (b) ? (a) : (b))
#define min(a, b) ((a) < (b) ? (a) : (b))
/* Max and min values of the integer type T, of which only the bottom
B bits are used, and where the highest-order used bit is considered
to be a sign bit if T is signed. */
#define MAXVAL(t, b) \
((t) (((t) 1 << ((b) - 1 - TYPE_SIGNED(t))) \
- 1 + ((t) 1 << ((b) - 1 - TYPE_SIGNED(t)))))
#define MINVAL(t, b) \
((t) (TYPE_SIGNED(t) ? - TWOS_COMPLEMENT(t) - MAXVAL(t, b) : 0))
/* The extreme time values, assuming no padding. */
#define TIME_T_MIN_NO_PADDING MINVAL(time_t, TYPE_BIT(time_t))
#define TIME_T_MAX_NO_PADDING MAXVAL(time_t, TYPE_BIT(time_t))
/* The extreme time values. These are macros, not constants, so that
any portability problems occur only when compiling .c files that use
the macros, which is safer for applications that need only zdump and zic.
This implementation assumes no padding if time_t is signed and
either the compiler lacks support for _Generic or time_t is not one
of the standard signed integer types. */
-#if HAVE_GENERIC
+#if HAVE__GENERIC
# define TIME_T_MIN \
_Generic((time_t) 0, \
signed char: SCHAR_MIN, short: SHRT_MIN, \
int: INT_MIN, long: LONG_MIN, long long: LLONG_MIN, \
default: TIME_T_MIN_NO_PADDING)
# define TIME_T_MAX \
(TYPE_SIGNED(time_t) \
? _Generic((time_t) 0, \
signed char: SCHAR_MAX, short: SHRT_MAX, \
int: INT_MAX, long: LONG_MAX, long long: LLONG_MAX, \
default: TIME_T_MAX_NO_PADDING) \
: (time_t) -1)
+enum { SIGNED_PADDING_CHECK_NEEDED
+ = _Generic((time_t) 0,
+ signed char: false, short: false,
+ int: false, long: false, long long: false,
+ default: true) };
#else
# define TIME_T_MIN TIME_T_MIN_NO_PADDING
# define TIME_T_MAX TIME_T_MAX_NO_PADDING
+enum { SIGNED_PADDING_CHECK_NEEDED = true };
#endif
+/* Try to check the padding assumptions. Although TIME_T_MAX and the
+ following check can both have undefined behavior on oddball
+ platforms due to shifts exceeding widths of signed integers, these
+ platforms' compilers are likely to diagnose these issues in integer
+ constant expressions, so it shouldn't hurt to check statically. */
+static_assert(! TYPE_SIGNED(time_t) || ! SIGNED_PADDING_CHECK_NEEDED
+ || TIME_T_MAX >> (TYPE_BIT(time_t) - 2) == 1);
/*
** 302 / 1000 is log10(2.0) rounded up.
** Subtract one for the sign bit if the type is signed;
** add one for integer division truncation;
** add one more for a minus sign if the type is signed.
*/
#define INT_STRLEN_MAXIMUM(type) \
((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \
1 + TYPE_SIGNED(type))
/*
** INITIALIZE(x)
*/
#ifdef GCC_LINT
# define INITIALIZE(x) ((x) = 0)
#else
# define INITIALIZE(x)
#endif
/* Whether memory access must strictly follow the C standard.
If 0, it's OK to read uninitialized storage so long as the value is
not relied upon. Defining it to 0 lets mktime access parts of
struct tm that might be uninitialized, as a heuristic when the
standard doesn't say what to return and when tm_gmtoff can help
mktime likely infer a better value. */
#ifndef UNINIT_TRAP
# define UNINIT_TRAP 0
#endif
#ifdef DEBUG
# undef unreachable
# define unreachable() abort()
#elif !defined unreachable
# ifdef __has_builtin
# if __has_builtin(__builtin_unreachable)
# define unreachable() __builtin_unreachable()
# endif
# elif 4 < __GNUC__ + (5 <= __GNUC_MINOR__)
# define unreachable() __builtin_unreachable()
# endif
# ifndef unreachable
# define unreachable() ((void) 0)
# endif
#endif
/*
** For the benefit of GNU folk...
** '_(MSGID)' uses the current locale's message library string for MSGID.
** The default is to use gettext if available, and use MSGID otherwise.
*/
#if HAVE_GETTEXT
#define _(msgid) gettext(msgid)
#else /* !HAVE_GETTEXT */
#define _(msgid) msgid
#endif /* !HAVE_GETTEXT */
#if !defined TZ_DOMAIN && defined HAVE_GETTEXT
# define TZ_DOMAIN "tz"
#endif
#if HAVE_INCOMPATIBLE_CTIME_R
#undef asctime_r
#undef ctime_r
-char *asctime_r(struct tm const *, char *);
+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. */
enum {
SECSPERMIN = 60,
MINSPERHOUR = 60,
SECSPERHOUR = SECSPERMIN * MINSPERHOUR,
HOURSPERDAY = 24,
DAYSPERWEEK = 7,
DAYSPERNYEAR = 365,
DAYSPERLYEAR = DAYSPERNYEAR + 1,
MONSPERYEAR = 12,
YEARSPERREPEAT = 400 /* years before a Gregorian repeat */
};
#define SECSPERDAY ((int_fast32_t) SECSPERHOUR * HOURSPERDAY)
#define DAYSPERREPEAT ((int_fast32_t) 400 * 365 + 100 - 4 + 1)
#define SECSPERREPEAT ((int_fast64_t) DAYSPERREPEAT * SECSPERDAY)
#define AVGSECSPERYEAR (SECSPERREPEAT / YEARSPERREPEAT)
enum {
TM_SUNDAY,
TM_MONDAY,
TM_TUESDAY,
TM_WEDNESDAY,
TM_THURSDAY,
TM_FRIDAY,
TM_SATURDAY
};
enum {
TM_JANUARY,
TM_FEBRUARY,
TM_MARCH,
TM_APRIL,
TM_MAY,
TM_JUNE,
TM_JULY,
TM_AUGUST,
TM_SEPTEMBER,
TM_OCTOBER,
TM_NOVEMBER,
TM_DECEMBER
};
enum {
TM_YEAR_BASE = 1900,
TM_WDAY_BASE = TM_MONDAY,
EPOCH_YEAR = 1970,
EPOCH_WDAY = TM_THURSDAY
};
#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0))
/*
** Since everything in isleap is modulo 400 (or a factor of 400), we know that
** isleap(y) == isleap(y % 400)
** and so
** isleap(a + b) == isleap((a + b) % 400)
** or
** isleap(a + b) == isleap(a % 400 + b % 400)
** This is true even if % means modulo rather than Fortran remainder
** (which is allowed by C89 but not by C99 or later).
** We use this to avoid addition overflow problems.
*/
#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400)
#endif /* !defined PRIVATE_H */
diff --git a/contrib/tzcode/strftime.c b/contrib/tzcode/strftime.c
index b23b6101b150..df169830f9ef 100644
--- a/contrib/tzcode/strftime.c
+++ b/contrib/tzcode/strftime.c
@@ -1,657 +1,659 @@
/* Convert a broken-down timestamp to a string. */
/* Copyright 1989 The Regents of the University of California.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the University nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE. */
/*
** Based on the UCB version with the copyright notice appearing above.
**
** This is ANSIish only when "multibyte character == plain character".
*/
#include "private.h"
#include
#include
#include
#ifndef DEPRECATE_TWO_DIGIT_YEARS
# define DEPRECATE_TWO_DIGIT_YEARS false
#endif
struct lc_time_T {
const char * mon[MONSPERYEAR];
const char * month[MONSPERYEAR];
const char * wday[DAYSPERWEEK];
const char * weekday[DAYSPERWEEK];
const char * X_fmt;
const char * x_fmt;
const char * c_fmt;
const char * am;
const char * pm;
const char * date_fmt;
};
static const struct lc_time_T C_time_locale = {
{
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
}, {
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
}, {
"Sun", "Mon", "Tue", "Wed",
"Thu", "Fri", "Sat"
}, {
"Sunday", "Monday", "Tuesday", "Wednesday",
"Thursday", "Friday", "Saturday"
},
/* X_fmt */
"%H:%M:%S",
/*
** x_fmt
** C99 and later require this format.
** Using just numbers (as here) makes Quakers happier;
** it's also compatible with SVR4.
*/
"%m/%d/%y",
/*
** c_fmt
** C99 and later require this format.
** Previously this code used "%D %X", but we now conform to C99.
** Note that
** "%a %b %d %H:%M:%S %Y"
** is used by Solaris 2.3.
*/
"%a %b %e %T %Y",
/* am */
"AM",
/* pm */
"PM",
/* date_fmt */
"%a %b %e %H:%M:%S %Z %Y"
};
enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL };
static char * _add(const char *, char *, const char *);
static char * _conv(int, const char *, char *, const char *);
static char * _fmt(const char *, const struct tm *, char *, const char *,
enum warn *);
static char * _yconv(int, int, bool, bool, char *, char const *);
#ifndef YEAR_2000_NAME
# define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS"
#endif /* !defined YEAR_2000_NAME */
#if HAVE_STRFTIME_L
size_t
-strftime_l(char *s, size_t maxsize, char const *format, struct tm const *t,
+strftime_l(char *restrict s, size_t maxsize, char const *restrict format,
+ struct tm const *restrict t,
ATTRIBUTE_MAYBE_UNUSED locale_t locale)
{
/* Just call strftime, as only the C locale is supported. */
return strftime(s, maxsize, format, t);
}
#endif
size_t
-strftime(char *s, size_t maxsize, const char *format, const struct tm *t)
+strftime(char *restrict s, size_t maxsize, char const *restrict format,
+ struct tm const *restrict t)
{
char * p;
int saved_errno = errno;
enum warn warn = IN_NONE;
tzset();
p = _fmt(format, t, s, s + maxsize, &warn);
if (!p) {
errno = EOVERFLOW;
return 0;
}
if (DEPRECATE_TWO_DIGIT_YEARS
&& warn != IN_NONE && getenv(YEAR_2000_NAME)) {
fprintf(stderr, "\n");
fprintf(stderr, "strftime format \"%s\" ", format);
fprintf(stderr, "yields only two digits of years in ");
if (warn == IN_SOME)
fprintf(stderr, "some locales");
else if (warn == IN_THIS)
fprintf(stderr, "the current locale");
else fprintf(stderr, "all locales");
fprintf(stderr, "\n");
}
if (p == s + maxsize) {
errno = ERANGE;
return 0;
}
*p = '\0';
errno = saved_errno;
return p - s;
}
static char *
_fmt(const char *format, const struct tm *t, char *pt,
const char *ptlim, enum warn *warnp)
{
struct lc_time_T const *Locale = &C_time_locale;
for ( ; *format; ++format) {
if (*format == '%') {
label:
switch (*++format) {
case '\0':
--format;
break;
case 'A':
pt = _add((t->tm_wday < 0 ||
t->tm_wday >= DAYSPERWEEK) ?
"?" : Locale->weekday[t->tm_wday],
pt, ptlim);
continue;
case 'a':
pt = _add((t->tm_wday < 0 ||
t->tm_wday >= DAYSPERWEEK) ?
"?" : Locale->wday[t->tm_wday],
pt, ptlim);
continue;
case 'B':
pt = _add((t->tm_mon < 0 ||
t->tm_mon >= MONSPERYEAR) ?
"?" : Locale->month[t->tm_mon],
pt, ptlim);
continue;
case 'b':
case 'h':
pt = _add((t->tm_mon < 0 ||
t->tm_mon >= MONSPERYEAR) ?
"?" : Locale->mon[t->tm_mon],
pt, ptlim);
continue;
case 'C':
/*
** %C used to do a...
** _fmt("%a %b %e %X %Y", t);
** ...whereas now POSIX 1003.2 calls for
** something completely different.
** (ado, 1993-05-24)
*/
pt = _yconv(t->tm_year, TM_YEAR_BASE,
true, false, pt, ptlim);
continue;
case 'c':
{
enum warn warn2 = IN_SOME;
pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2);
if (warn2 == IN_ALL)
warn2 = IN_THIS;
if (warn2 > *warnp)
*warnp = warn2;
}
continue;
case 'D':
pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp);
continue;
case 'd':
pt = _conv(t->tm_mday, "%02d", pt, ptlim);
continue;
case 'E':
case 'O':
/*
** Locale modifiers of C99 and later.
** The sequences
** %Ec %EC %Ex %EX %Ey %EY
** %Od %oe %OH %OI %Om %OM
** %OS %Ou %OU %OV %Ow %OW %Oy
** are supposed to provide alternative
** representations.
*/
goto label;
case 'e':
pt = _conv(t->tm_mday, "%2d", pt, ptlim);
continue;
case 'F':
pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp);
continue;
case 'H':
pt = _conv(t->tm_hour, "%02d", pt, ptlim);
continue;
case 'I':
pt = _conv((t->tm_hour % 12) ?
(t->tm_hour % 12) : 12,
"%02d", pt, ptlim);
continue;
case 'j':
pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim);
continue;
case 'k':
/*
** This used to be...
** _conv(t->tm_hour % 12 ?
** t->tm_hour % 12 : 12, 2, ' ');
** ...and has been changed to the below to
** match SunOS 4.1.1 and Arnold Robbins'
** strftime version 3.0. That is, "%k" and
** "%l" have been swapped.
** (ado, 1993-05-24)
*/
pt = _conv(t->tm_hour, "%2d", pt, ptlim);
continue;
#ifdef KITCHEN_SINK
case 'K':
/*
** After all this time, still unclaimed!
*/
pt = _add("kitchen sink", pt, ptlim);
continue;
#endif /* defined KITCHEN_SINK */
case 'l':
/*
** This used to be...
** _conv(t->tm_hour, 2, ' ');
** ...and has been changed to the below to
** match SunOS 4.1.1 and Arnold Robbin's
** strftime version 3.0. That is, "%k" and
** "%l" have been swapped.
** (ado, 1993-05-24)
*/
pt = _conv((t->tm_hour % 12) ?
(t->tm_hour % 12) : 12,
"%2d", pt, ptlim);
continue;
case 'M':
pt = _conv(t->tm_min, "%02d", pt, ptlim);
continue;
case 'm':
pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim);
continue;
case 'n':
pt = _add("\n", pt, ptlim);
continue;
case 'p':
pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ?
Locale->pm :
Locale->am,
pt, ptlim);
continue;
case 'R':
pt = _fmt("%H:%M", t, pt, ptlim, warnp);
continue;
case 'r':
pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp);
continue;
case 'S':
pt = _conv(t->tm_sec, "%02d", pt, ptlim);
continue;
case 's':
{
struct tm tm;
char buf[INT_STRLEN_MAXIMUM(
time_t) + 1];
time_t mkt;
tm.tm_sec = t->tm_sec;
tm.tm_min = t->tm_min;
tm.tm_hour = t->tm_hour;
tm.tm_mday = t->tm_mday;
tm.tm_mon = t->tm_mon;
tm.tm_year = t->tm_year;
tm.tm_isdst = t->tm_isdst;
#if defined TM_GMTOFF && ! UNINIT_TRAP
tm.TM_GMTOFF = t->TM_GMTOFF;
#endif
mkt = mktime(&tm);
/* If mktime fails, %s expands to the
value of (time_t) -1 as a failure
marker; this is better in practice
than strftime failing. */
if (TYPE_SIGNED(time_t)) {
intmax_t n = mkt;
sprintf(buf, "%"PRIdMAX, n);
} else {
uintmax_t n = mkt;
sprintf(buf, "%"PRIuMAX, n);
}
pt = _add(buf, pt, ptlim);
}
continue;
case 'T':
pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp);
continue;
case 't':
pt = _add("\t", pt, ptlim);
continue;
case 'U':
pt = _conv((t->tm_yday + DAYSPERWEEK -
t->tm_wday) / DAYSPERWEEK,
"%02d", pt, ptlim);
continue;
case 'u':
/*
** From Arnold Robbins' strftime version 3.0:
** "ISO 8601: Weekday as a decimal number
** [1 (Monday) - 7]"
** (ado, 1993-05-24)
*/
pt = _conv((t->tm_wday == 0) ?
DAYSPERWEEK : t->tm_wday,
"%d", pt, ptlim);
continue;
case 'V': /* ISO 8601 week number */
case 'G': /* ISO 8601 year (four digits) */
case 'g': /* ISO 8601 year (two digits) */
/*
** From Arnold Robbins' strftime version 3.0: "the week number of the
** year (the first Monday as the first day of week 1) as a decimal number
** (01-53)."
** (ado, 1993-05-24)
**
** From by Markus Kuhn:
** "Week 01 of a year is per definition the first week which has the
** Thursday in this year, which is equivalent to the week which contains
** the fourth day of January. In other words, the first week of a new year
** is the week which has the majority of its days in the new year. Week 01
** might also contain days from the previous year and the week before week
** 01 of a year is the last week (52 or 53) of the previous year even if
** it contains days from the new year. A week starts with Monday (day 1)
** and ends with Sunday (day 7). For example, the first week of the year
** 1997 lasts from 1996-12-30 to 1997-01-05..."
** (ado, 1996-01-02)
*/
{
int year;
int base;
int yday;
int wday;
int w;
year = t->tm_year;
base = TM_YEAR_BASE;
yday = t->tm_yday;
wday = t->tm_wday;
for ( ; ; ) {
int len;
int bot;
int top;
len = isleap_sum(year, base) ?
DAYSPERLYEAR :
DAYSPERNYEAR;
/*
** What yday (-3 ... 3) does
** the ISO year begin on?
*/
bot = ((yday + 11 - wday) %
DAYSPERWEEK) - 3;
/*
** What yday does the NEXT
** ISO year begin on?
*/
top = bot -
(len % DAYSPERWEEK);
if (top < -3)
top += DAYSPERWEEK;
top += len;
if (yday >= top) {
++base;
w = 1;
break;
}
if (yday >= bot) {
w = 1 + ((yday - bot) /
DAYSPERWEEK);
break;
}
--base;
yday += isleap_sum(year, base) ?
DAYSPERLYEAR :
DAYSPERNYEAR;
}
#ifdef XPG4_1994_04_09
if ((w == 52 &&
t->tm_mon == TM_JANUARY) ||
(w == 1 &&
t->tm_mon == TM_DECEMBER))
w = 53;
#endif /* defined XPG4_1994_04_09 */
if (*format == 'V')
pt = _conv(w, "%02d",
pt, ptlim);
else if (*format == 'g') {
*warnp = IN_ALL;
pt = _yconv(year, base,
false, true,
pt, ptlim);
} else pt = _yconv(year, base,
true, true,
pt, ptlim);
}
continue;
case 'v':
/*
** From Arnold Robbins' strftime version 3.0:
** "date as dd-bbb-YYYY"
** (ado, 1993-05-24)
*/
pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp);
continue;
case 'W':
pt = _conv((t->tm_yday + DAYSPERWEEK -
(t->tm_wday ?
(t->tm_wday - 1) :
(DAYSPERWEEK - 1))) / DAYSPERWEEK,
"%02d", pt, ptlim);
continue;
case 'w':
pt = _conv(t->tm_wday, "%d", pt, ptlim);
continue;
case 'X':
pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp);
continue;
case 'x':
{
enum warn warn2 = IN_SOME;
pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2);
if (warn2 == IN_ALL)
warn2 = IN_THIS;
if (warn2 > *warnp)
*warnp = warn2;
}
continue;
case 'y':
*warnp = IN_ALL;
pt = _yconv(t->tm_year, TM_YEAR_BASE,
false, true,
pt, ptlim);
continue;
case 'Y':
pt = _yconv(t->tm_year, TM_YEAR_BASE,
true, true,
pt, ptlim);
continue;
case 'Z':
#ifdef TM_ZONE
pt = _add(t->TM_ZONE, pt, ptlim);
#elif HAVE_TZNAME
if (t->tm_isdst >= 0)
pt = _add(tzname[t->tm_isdst != 0],
pt, ptlim);
#endif
/*
** C99 and later say that %Z must be
** replaced by the empty string if the
** time zone abbreviation is not
** determinable.
*/
continue;
case 'z':
#if defined TM_GMTOFF || USG_COMPAT || ALTZONE
{
long diff;
char const * sign;
bool negative;
# ifdef TM_GMTOFF
diff = t->TM_GMTOFF;
# else
/*
** C99 and later say that the UT offset must
** be computed by looking only at
** tm_isdst. This requirement is
** incorrect, since it means the code
** must rely on magic (in this case
** altzone and timezone), and the
** magic might not have the correct
** offset. Doing things correctly is
** tricky and requires disobeying the standard;
** see GNU C strftime for details.
** For now, punt and conform to the
** standard, even though it's incorrect.
**
** C99 and later say that %z must be replaced by
** the empty string if the time zone is not
** determinable, so output nothing if the
** appropriate variables are not available.
*/
if (t->tm_isdst < 0)
continue;
if (t->tm_isdst == 0)
# if USG_COMPAT
diff = -timezone;
# else
continue;
# endif
else
# if ALTZONE
diff = -altzone;
# else
continue;
# endif
# endif
negative = diff < 0;
if (diff == 0) {
# ifdef TM_ZONE
negative = t->TM_ZONE[0] == '-';
# else
negative = t->tm_isdst < 0;
# if HAVE_TZNAME
if (tzname[t->tm_isdst != 0][0] == '-')
negative = true;
# endif
# endif
}
if (negative) {
sign = "-";
diff = -diff;
} else sign = "+";
pt = _add(sign, pt, ptlim);
diff /= SECSPERMIN;
diff = (diff / MINSPERHOUR) * 100 +
(diff % MINSPERHOUR);
pt = _conv(diff, "%04d", pt, ptlim);
}
#endif
continue;
case '+':
pt = _fmt(Locale->date_fmt, t, pt, ptlim,
warnp);
continue;
case '%':
/*
** X311J/88-090 (4.12.3.5): if conversion char is
** undefined, behavior is undefined. Print out the
** character itself as printf(3) also does.
*/
default:
break;
}
}
if (pt == ptlim)
break;
*pt++ = *format;
}
return pt;
}
static char *
_conv(int n, const char *format, char *pt, const char *ptlim)
{
char buf[INT_STRLEN_MAXIMUM(int) + 1];
sprintf(buf, format, n);
return _add(buf, pt, ptlim);
}
static char *
_add(const char *str, char *pt, const char *ptlim)
{
while (pt < ptlim && (*pt = *str++) != '\0')
++pt;
return pt;
}
/*
** POSIX and the C Standard are unclear or inconsistent about
** what %C and %y do if the year is negative or exceeds 9999.
** Use the convention that %C concatenated with %y yields the
** same output as %Y, and that %Y contains at least 4 bytes,
** with more only if necessary.
*/
static char *
_yconv(int a, int b, bool convert_top, bool convert_yy,
char *pt, const char *ptlim)
{
register int lead;
register int trail;
int DIVISOR = 100;
trail = a % DIVISOR + b % DIVISOR;
lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR;
trail %= DIVISOR;
if (trail < 0 && lead > 0) {
trail += DIVISOR;
--lead;
} else if (lead < 0 && trail > 0) {
trail -= DIVISOR;
++lead;
}
if (convert_top) {
if (lead == 0 && trail < 0)
pt = _add("-0", pt, ptlim);
else pt = _conv(lead, "%02d", pt, ptlim);
}
if (convert_yy)
pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim);
return pt;
}
diff --git a/contrib/tzcode/theory.html b/contrib/tzcode/theory.html
index 75e347f0f9d1..369c75433ff2 100644
--- a/contrib/tzcode/theory.html
+++ b/contrib/tzcode/theory.html
@@ -1,1479 +1,1501 @@
Theory and pragmatics of the tz code and data
Theory and pragmatics of the tz code and data
Outline
Scope of the tz database
The tz
database attempts to record the history and predicted future of
civil time scales.
It organizes time zone and daylight saving time
data by partitioning the world into timezones
whose clocks all agree about timestamps that occur after the POSIX Epoch
(1970-01-01 00:00:00 UTC).
Although 1970 is a somewhat-arbitrary cutoff, there are significant
challenges to moving the cutoff earlier even by a decade or two, due
to the wide variety of local practices before computer timekeeping
became prevalent.
Most timezones correspond to a notable location and the database
records all known clock transitions for that location;
some timezones correspond instead to a fixed UTC offset.
Each timezone typically corresponds to a geographical region that is
smaller than a traditional time zone, because clocks in a timezone
all agree after 1970 whereas a traditional time zone merely
specifies current standard time. For example, applications that deal
with current and future timestamps in the traditional North
American mountain time zone can choose from the timezones
America/Denver which observes US-style daylight saving
time (DST),
and America/Phoenix which does not observe DST.
Applications that also deal with past timestamps in the mountain time
zone can choose from over a dozen timezones, such as
America/Boise, America/Edmonton, and
America/Hermosillo, each of which currently uses mountain
time but differs from other timezones for some timestamps after 1970.
Clock transitions before 1970 are recorded for location-based timezones,
because most systems support timestamps before 1970 and could
misbehave if data entries were omitted for pre-1970 transitions.
However, the database is not designed for and does not suffice for
applications requiring accurate handling of all past times everywhere,
as it would take far too much effort and guesswork to record all
details of pre-1970 civil timekeeping.
Although some information outside the scope of the database is
collected in a file backzone that is distributed along
with the database proper, this file is less reliable and does not
necessarily follow database guidelines.
As described below, reference source code for using the
tz database is also available.
The tz code is upwards compatible with POSIX, an international
standard for UNIX-like systems.
As of this writing, the current edition of POSIX is: The Open
Group Base Specifications Issue 7, IEEE Std 1003.1-2017, 2018
Edition.
Because the database's scope encompasses real-world changes to civil
timekeeping, its model for describing time is more complex than the
standard and daylight saving times supported by POSIX.
A tz timezone corresponds to a ruleset that can
have more than two changes per year, these changes need not merely
flip back and forth between two alternatives, and the rules themselves
can change at times.
Whether and when a timezone changes its clock,
and even the timezone's notional base offset from UTC,
are variable.
It does not always make sense to talk about a timezone's
"base offset", which is not necessarily a single number.
Timezone identifiers
Each timezone has a name that uniquely identifies the timezone.
Inexperienced users are not expected to select these names unaided.
Distributors should provide documentation and/or a simple selection
interface that explains each name via a map or via descriptive text like
"Czech Republic" instead of the timezone name "Europe/Prague".
If geolocation information is available, a selection interface can
locate the user on a timezone map or prioritize names that are
geographically close. For an example selection interface, see the
tzselect program in the tz code.
The Unicode Common Locale Data
Repository contains data that may be useful for other selection
interfaces; it maps timezone names like Europe/Prague to
locale-dependent strings like "Prague", "Praha", "Прага", and "布拉格".
The naming conventions attempt to strike a balance
among the following goals:
Uniquely identify every timezone where clocks have agreed since 1970.
This is essential for the intended use: static clocks keeping local
civil time.
Indicate to experts where the timezone's clocks typically are.
Be robust in the presence of political changes.
For example, names are typically not tied to countries, to avoid
incompatibilities when countries change their name (e.g.,
Swaziland→Eswatini) or when locations change countries (e.g., Hong
Kong from UK colony to China).
There is no requirement that every country or national
capital must have a timezone name.
Be portable to a wide variety of implementations.
Use a consistent naming conventions over the entire world.
Names normally have the form
AREA/LOCATION, where
AREA is a continent or ocean, and
LOCATION is a specific location within the area.
North and South America share the same area, 'America'.
Typical names are 'Africa/Cairo',
'America/New_York', and 'Pacific/Honolulu'.
Some names are further qualified to help avoid confusion; for example,
'America/Indiana/Petersburg' distinguishes Petersburg,
Indiana from other Petersburgs in America.
Here are the general guidelines used for
choosing timezone names,
in decreasing order of importance:
Use only valid POSIX file name components (i.e., the parts of
names other than '/').
Do not use the file name components '.' and
'..'.
Within a file name component, use only ASCII letters,
'.', '-' and '_'.
Do not use digits, as that might create an ambiguity with POSIX
TZ strings.
A file name component must not exceed 14 characters or start with
'-'.
E.g., prefer America/Noronha to
America/Fernando_de_Noronha.
Exceptions: see the discussion of legacy names below.
A name must not be empty, or contain '//', or
start or end with '/'.
Do not use names that differ only in case.
Although the reference implementation is case-sensitive, some
other implementations are not, and they would mishandle names
differing only in case.
If one name A is an initial prefix of another
name AB (ignoring case), then B must not
start with '/', as a regular file cannot have the
same name as a directory in POSIX.
For example, America/New_York precludes
America/New_York/Bronx.
Uninhabited regions like the North Pole and Bouvet Island
do not need locations, since local time is not defined there.
If all the clocks in a timezone have agreed since 1970,
do not bother to include more than one timezone
even if some of the clocks disagreed before 1970.
Otherwise these tables would become annoyingly large.
If boundaries between regions are fluid, such as during a war or
insurrection, do not bother to create a new timezone merely
because of yet another boundary change. This helps prevent table
bloat and simplifies maintenance.
If a name is ambiguous, use a less ambiguous alternative;
e.g., many cities are named San José and Georgetown, so
prefer America/Costa_Rica to
America/San_Jose and America/Guyana
to America/Georgetown.
Keep locations compact.
Use cities or small islands, not countries or regions, so that any
future changes do not split individual locations into different
timezones.
E.g., prefer Europe/Paris to Europe/France,
since
France
has had multiple time zones.
Use mainstream English spelling, e.g., prefer
Europe/Rome to Europa/Roma, and
prefer Europe/Athens to the Greek
Ευρώπη/Αθήνα or the Romanized
Evrópi/Athína.
The POSIX file name restrictions encourage this guideline.
Use the most populous among locations in a region,
e.g., prefer Asia/Shanghai to
Asia/Beijing.
Among locations with similar populations, pick the best-known
location, e.g., prefer Europe/Rome to
Europe/Milan.
Use the singular form, e.g., prefer Atlantic/Canary to
Atlantic/Canaries.
Omit common suffixes like '_Islands' and
'_City', unless that would lead to ambiguity.
E.g., prefer America/Cayman to
America/Cayman_Islands and
America/Guatemala to
America/Guatemala_City, but prefer
America/Mexico_City to
America/Mexico
because the
country of Mexico has several time zones.
Use '_' to represent a space.
Omit '.' from abbreviations in names.
E.g., prefer Atlantic/St_Helena to
Atlantic/St._Helena.
Do not change established names if they only marginally violate
the above guidelines.
For example, do not change the existing name Europe/Rome to
Europe/Milan merely because Milan's population has grown
to be somewhat greater than Rome's.
If a name is changed, put its old spelling in the
'backward' file as a link to the new spelling.
This means old spellings will continue to work.
Ordinarily a name change should occur only in the rare case when
a location's consensus English-language spelling changes; for example,
in 2008 Asia/Calcutta was renamed to Asia/Kolkata
due to long-time widespread use of the new city name instead of the old.
Guidelines have evolved with time, and names following old versions of
these guidelines might not follow the current version. When guidelines
have changed, old names continue to be supported. Guideline changes
have included the following:
Older versions of this package used a different naming scheme.
See the file 'backward' for most of these older names
(e.g., 'US/Eastern' instead of 'America/New_York').
The other old-fashioned names still supported are
'WET', 'CET', 'MET', and
'EET' (see the file 'europe').
Older versions of this package defined legacy names that are
incompatible with the first guideline of location names, but which are
still supported.
These legacy names are mostly defined in the file
'etcetera'.
Also, the file 'backward' defines the legacy names
'Etc/GMT0', 'Etc/GMT-0', 'Etc/GMT+0',
'GMT0', 'GMT-0' and 'GMT+0',
and the file 'northamerica' defines the legacy names
'EST5EDT', 'CST6CDT',
'MST7MDT', and 'PST8PDT'.
Older versions of these guidelines said that
there should typically be at least one name for each ISO
3166-1 officially assigned two-letter code for an inhabited
country or territory.
This old guideline has been dropped, as it was not needed to handle
timestamps correctly and it increased maintenance burden.
The file zone1970.tab lists geographical locations used
to name timezones.
It is intended to be an exhaustive list of names for geographic
regions as described above; this is a subset of the timezones in the data.
Although a zone1970.tab location's
longitude
corresponds to
its local mean
time (LMT) offset with one hour for every 15°
east longitude, this relationship is not exact.
The backward-compatibility file zone.tab is similar
but conforms to the older-version guidelines related to ISO 3166-1;
it lists only one country code per entry and unlike zone1970.tab
it can list names defined in backward.
The database defines each timezone name to be a zone, or a link to a zone.
The source file backward defines links for backward
compatibility; it does not define zones.
Although backward was originally designed to be optional,
nowadays distributions typically use it
and no great weight should be attached to whether a link
is defined in backward or in some other file.
The source file etcetera defines names that may be useful
on platforms that do not support POSIX-style TZ strings;
no other source file other than backward
contains links to its zones.
One of etcetera's names is Etc/UTC,
used by functions like gmtime to obtain leap
second information on platforms that support leap seconds.
Another etcetera name, GMT,
is used by older code releases.
Time zone abbreviations
When this package is installed, it generates time zone abbreviations
like 'EST' to be compatible with human tradition and POSIX.
Here are the general guidelines used for choosing time zone abbreviations,
in decreasing order of importance:
Use three to six characters that are ASCII alphanumerics or
'+' or '-'.
Previous editions of this database also used characters like
space and '?', but these characters have a
special meaning to the
UNIX shell
and cause commands like
'set
`date`'
to have unexpected effects.
Previous editions of this guideline required upper-case letters, but the
Congressman who introduced
Chamorro
Standard Time preferred "ChST", so lower-case letters are now
allowed.
Also, POSIX from 2001 on relaxed the rule to allow '-',
'+', and alphanumeric characters from the portable
character set in the current locale.
In practice ASCII alphanumerics and '+' and
'-' are safe in all locales.
In other words, in the C locale the POSIX extended regular
expression [-+[:alnum:]]{3,6} should match the
abbreviation.
This guarantees that all abbreviations could have been specified by a
POSIX TZ string.
Use abbreviations that are in common use among English-speakers,
e.g., 'EST' for Eastern Standard Time in North America.
We assume that applications translate them to other languages
as part of the normal localization process; for example,
a French application might translate 'EST' to 'HNE'.
These abbreviations (for standard/daylight/etc. time) are:
ACST/ACDT Australian Central,
AST/ADT/APT/AWT/ADDT Atlantic,
AEST/AEDT Australian Eastern,
AHST/AHDT Alaska-Hawaii,
AKST/AKDT Alaska,
AWST/AWDT Australian Western,
BST/BDT Bering,
CAT/CAST Central Africa,
CET/CEST/CEMT Central European,
ChST Chamorro,
- CST/CDT/CWT/CPT/CDDT Central [North America],
+ CST/CDT/CWT/CPT Central [North America],
CST/CDT China,
GMT/BST/IST/BDST Greenwich,
EAT East Africa,
- EST/EDT/EWT/EPT/EDDT Eastern [North America],
+ EST/EDT/EWT/EPT Eastern [North America],
EET/EEST Eastern European,
GST/GDT Guam,
HST/HDT/HWT/HPT Hawaii,
HKT/HKST/HKWT Hong Kong,
IST India,
IST/GMT Irish,
IST/IDT/IDDT Israel,
JST/JDT Japan,
KST/KDT Korea,
MET/MEST Middle European (a backward-compatibility alias for
Central European),
MSK/MSD Moscow,
- MST/MDT/MWT/MPT/MDDT Mountain,
+ MST/MDT/MWT/MPT Mountain,
NST/NDT/NWT/NPT/NDDT Newfoundland,
NST/NDT/NWT/NPT Nome,
NZMT/NZST New Zealand through 1945,
NZST/NZDT New Zealand 1946–present,
PKT/PKST Pakistan,
- PST/PDT/PWT/PPT/PDDT Pacific,
+ PST/PDT/PWT/PPT Pacific,
PST/PDT Philippine,
SAST South Africa,
SST Samoa,
UTC Universal,
WAT/WAST West Africa,
WET/WEST/WEMT Western European,
WIB Waktu Indonesia Barat,
WIT Waktu Indonesia Timur,
WITA Waktu Indonesia Tengah,
YST/YDT/YWT/YPT/YDDT Yukon.
For times taken from a city's longitude, use the
traditional xMT notation.
The only abbreviation like this in current use is 'GMT'.
The others are for timestamps before 1960,
except that Monrovia Mean Time persisted until 1972.
Typically, numeric abbreviations (e.g., '-004430' for
MMT) would cause trouble here, as the numeric strings would exceed
the POSIX length limit.
A few abbreviations also follow the pattern that
GMT/BST established for time in the UK.
They are:
BMT/BST for Bermuda 1890–1930,
CMT/BST for Calamarca Mean Time and Bolivian Summer Time
1890–1932,
DMT/IST for Dublin/Dunsink Mean Time and Irish Summer Time
1880–1916,
MMT/MST/MDST for Moscow 1880–1919, and
RMT/LST for Riga Mean Time and Latvian Summer time 1880–1926.
Use 'LMT' for local mean time of locations before the
introduction of standard time; see "Scope of the
tz database".
If there is no common English abbreviation, use numeric offsets like
-05 and +0530 that are generated
by zic's %z notation.
Use current abbreviations for older timestamps to avoid confusion.
For example, in 1910 a common English abbreviation for time
in central Europe was 'MEZ' (short for both "Middle European
Zone" and for "Mitteleuropäische Zeit" in German).
Nowadays 'CET' ("Central European Time") is more common in
English, and the database uses 'CET' even for circa-1910
timestamps as this is less confusing for modern users and avoids
the need for determining when 'CET' supplanted 'MEZ' in common
usage.
Use a consistent style in a timezone's history.
For example, if a history tends to use numeric
abbreviations and a particular entry could go either way, use a
numeric abbreviation.
Use
Universal Time
(UT) (with time zone abbreviation '-00') for
locations while uninhabited.
The leading '-' is a flag that the UT offset is in
some sense undefined; this notation is derived
from Internet
RFC 3339.
Application writers should note that these abbreviations are ambiguous
in practice: e.g., 'CST' means one thing in China and something else
in North America, and 'IST' can refer to time in India, Ireland or
Israel.
To avoid ambiguity, use numeric UT offsets like
'-0600' instead of time zone abbreviations like 'CST'.
Accuracy of the tz database
The tz database is not authoritative, and it
surely has errors.
Corrections are welcome and encouraged; see the file CONTRIBUTING.
Users requiring authoritative data should consult national standards
bodies and the references cited in the database's comments.
Errors in the tz database arise from many sources:
The tz database predicts future
timestamps, and current predictions
will be incorrect after future governments change the rules.
For example, if today someone schedules a meeting for 13:00 next
October 1, Casablanca time, and tomorrow Morocco changes its
daylight saving rules, software can mess up after the rule change
if it blithely relies on conversions made before the change.
The pre-1970 entries in this database cover only a tiny sliver of how
clocks actually behaved; the vast majority of the necessary
information was lost or never recorded.
Thousands more timezones would be needed if
the tz database's scope were extended to
cover even just the known or guessed history of standard time; for
example, the current single entry for France would need to split
into dozens of entries, perhaps hundreds.
And in most of the world even this approach would be misleading
due to widespread disagreement or indifference about what times
should be observed.
In her 2015 book
The
Global Transformation of Time, 1870–1950,
Vanessa Ogle writes
"Outside of Europe and North America there was no system of time
zones at all, often not even a stable landscape of mean times,
prior to the middle decades of the twentieth century".
See: Timothy Shenk, Booked:
A Global History of Time. Dissent 2015-12-17.
Most of the pre-1970 data entries come from unreliable sources, often
astrology books that lack citations and whose compilers evidently
invented entries when the true facts were unknown, without
reporting which entries were known and which were invented.
These books often contradict each other or give implausible entries,
and on the rare occasions when they are checked they are
typically found to be incorrect.
For the UK the tz database relies on
years of first-class work done by
Joseph Myers and others; see
"History of
legal time in Britain".
Other countries are not done nearly as well.
Sometimes, different people in the same city maintain clocks
that differ significantly.
Historically, railway time was used by railroad companies (which
did not always
agree with each other), church-clock time was used for birth
certificates, etc.
More recently, competing political groups might disagree about
clock settings. Often this is merely common practice, but
sometimes it is set by law.
For example, from 1891 to 1911 the UT offset in France
was legally UT +00:09:21 outside train stations and
UT +00:04:21 inside. Other examples include
Chillicothe in 1920, Palm Springs in 1946/7, and Jerusalem and
Ürümqi to this day.
Although a named location in the tz
database stands for the containing region, its pre-1970 data
entries are often accurate for only a small subset of that region.
For example, Europe/London stands for the United
Kingdom, but its pre-1847 times are valid only for locations that
have London's exact meridian, and its 1847 transition
to GMT is known to be valid only for the L&NW and
the Caledonian railways.
The tz database does not record the
earliest time for which a timezone's
data entries are thereafter valid for every location in the region.
For example, Europe/London is valid for all locations
in its region after GMT was made the standard time,
but the date of standardization (1880-08-02) is not in the
tz database, other than in commentary.
For many timezones the earliest time of
validity is unknown.
The tz database does not record a
region's boundaries, and in many cases the boundaries are not known.
For example, the timezone
America/Kentucky/Louisville represents a region
around the city of Louisville, the boundaries of which are
unclear.
Changes that are modeled as instantaneous transitions in the
tz
database were often spread out over hours, days, or even decades.
Even if the time is specified by law, locations sometimes
deliberately flout the law.
Early timekeeping practices, even assuming perfect clocks, were
often not specified to the accuracy that the
tz database requires.
The tz database cannot represent stopped clocks.
However, on 1911-03-11 at 00:00, some public-facing French clocks
were changed by stopping them for a few minutes to effect a transition.
The tz database models this via a
backward transition; the relevant French legislation does not
specify exactly how the transition was to occur.
Sometimes historical timekeeping was specified more precisely
than what the tz code can handle.
For example, from 1880 to 1916 clocks in Ireland observed Dublin Mean
Time (estimated to be UT
−00:25:21.1); although the tz
source data can represent the .1 second, TZif files and the code cannot.
In practice these old specifications were rarely if ever
implemented to subsecond precision.
Even when all the timestamp transitions recorded by the
tz database are correct, the
tz rules that generate them may not
faithfully reflect the historical rules.
For example, from 1922 until World War II the UK moved clocks
forward the day following the third Saturday in April unless that
was Easter, in which case it moved clocks forward the previous
Sunday.
Because the tz database has no
way to specify Easter, these exceptional years are entered as
separate tz Rule lines, even though the
legal rules did not change.
When transitions are known but the historical rules behind them are not,
the database contains Zone and Rule
entries that are intended to represent only the generated
transitions, not any underlying historical rules; however, this
intent is recorded at best only in commentary.
The tz database models time
using the proleptic
Gregorian calendar with days containing 24 equal-length hours
numbered 00 through 23, except when clock transitions occur.
Pre-standard time is modeled as local mean time.
However, historically many people used other calendars and other timescales.
For example, the Roman Empire used
the Julian
calendar,
and Roman
timekeeping had twelve varying-length daytime hours with a
non-hour-based system at night.
And even today, some local practices diverge from the Gregorian
calendar with 24-hour days. These divergences range from
relatively minor, such as Japanese bars giving times like "24:30" for the
wee hours of the morning, to more-significant differences such as the
east African practice of starting the day at dawn, renumbering
the Western 06:00 to be 12:00. These practices are largely outside
the scope of the tz code and data, which
provide only limited support for date and time localization
such as that required by POSIX.
If DST is not used a different time zone
can often do the trick; for example, in Kenya a TZ setting
like <-03>3 or America/Cayenne starts
the day six hours later than Africa/Nairobi does.
Early clocks were less reliable, and data entries do not represent
clock error.
The tz database assumes Universal Time
(UT) as an origin, even though UT is not
standardized for older timestamps.
In the tz database commentary,
UT denotes a family of time standards that includes
Coordinated Universal Time (UTC) along with other
variants such as UT1 and GMT,
with days starting at midnight.
Although UT equals UTC for modern
timestamps, UTC was not defined until 1960, so
- commentary uses the more-general abbreviation UT for
+ commentary uses the more general abbreviation UT for
timestamps that might predate 1960.
Since UT, UT1, etc. disagree slightly,
and since pre-1972 UTC seconds varied in length,
interpretation of older timestamps can be problematic when
subsecond accuracy is needed.
The relationship between POSIX time (that is, UTC but
ignoring leap
- seconds) and UTC is not agreed upon after 1972.
+ seconds) and UTC is not agreed upon.
+ This affects time stamps during the leap second era (1972–2035).
Although the POSIX
clock officially stops during an inserted leap second, at least one
proposed standard has it jumping back a second instead; and in
practice POSIX clocks more typically either progress glacially during
a leap second, or are slightly slowed while near a leap second.
The tz database does not represent how
uncertain its information is.
Ideally it would contain information about when data entries are
incomplete or dicey.
Partial temporal knowledge is a field of active research, though,
and it is not clear how to apply it here.
In short, many, perhaps most, of the tz
database's pre-1970 and future timestamps are either wrong or
misleading.
Any attempt to pass the
tz database off as the definition of time
should be unacceptable to anybody who cares about the facts.
In particular, the tz database's
LMT offsets should not be considered meaningful, and
should not prompt creation of timezones
merely because two locations
differ in LMT or transitioned to standard time at
different dates.
Time and date functions
The tz code contains time and date functions
that are upwards compatible with those of POSIX.
Code compatible with this package is already
part of many platforms, where the
primary use of this package is to update obsolete time-related files.
To do this, you may need to compile the time zone compiler
'zic' supplied with this package instead of using the
system 'zic', since the format of zic's
input is occasionally extended, and a platform may still be shipping
an older zic.
POSIX properties and limitations
In POSIX, time display in a process is controlled by the
environment variable TZ.
Unfortunately, the POSIX
TZ string takes a form that is hard to describe and
is error-prone in practice.
Also, POSIX TZ strings cannot deal with daylight
saving time rules not based on the Gregorian calendar (as in
- Iran), or with situations where more than two time zone
+ Morocco), or with situations where more than two time zone
abbreviations or UT offsets are used in an area.
The POSIX TZ string takes the following form:
stdoffset[dst[offset][,date[/time],date[/time]]]
where:
std and dst
are 3 or more characters specifying the standard
and daylight saving time (DST) zone abbreviations.
Starting with POSIX.1-2001, std and dst
may also be in a quoted form like '<+09>';
this allows "+" and "-" in the names.
offset
is of the form
'[±]hh:[mm[:ss]]'
and specifies the offset west of UT.
'hh' may be a single digit;
0≤hh≤24.
The default DST offset is one hour ahead of
standard time.
date[/time],date[/time]
specifies the beginning and end of DST.
If this is absent, the system supplies its own ruleset
- for DST, and its rules can differ from year to year;
- typically US DST rules are used.
+ for DST, typically current US
+ DST rules.
time
takes the form
'hh:[mm[:ss]]'
and defaults to 02:00.
This is the same format as the offset, except that a
leading '+' or '-' is not allowed.
date
takes one of the following forms:
Jn (1≤n≤365)
origin-1 day number not counting February 29
n (0≤n≤365)
origin-0 day number counting February 29 if present
Mm.n.d
(0[Sunday]≤d≤6[Saturday], 1≤n≤5,
1≤m≤12)
for the dth day of week n of
month m of the year, where week 1 is the first
week in which day d appears, and
'5' stands for the last week in which
day d appears (which may be either the 4th or
5th week).
Typically, this is the only useful form; the n
and Jn forms are rarely used.
Here is an example POSIX TZ string for New
Zealand after 2007.
It says that standard time (NZST) is 12 hours ahead
of UT, and that daylight saving time
(NZDT) is observed from September's last Sunday at
02:00 until April's first Sunday at 03:00:
TZ='NZST-12NZDT,M9.5.0,M4.1.0/3'
This POSIX TZ string is hard to remember, and
mishandles some timestamps before 2008.
With this package you can use this instead:
TZ='Pacific/Auckland'
POSIX does not define the DST transitions
for TZ values like
"EST5EDT".
Traditionally the current US DST rules
were used to interpret such values, but this meant that the
US DST rules were compiled into each
- program that did time conversion. This meant that when
+ time conversion package, and when
US time conversion rules changed (as in the United
- States in 1987), all programs that did time conversion had to be
- recompiled to ensure proper results.
+ States in 1987 and again in 2007), all packages that
+ interpreted TZ values had to be updated
+ to ensure proper results.
The TZ environment variable is process-global, which
makes it hard to write efficient, thread-safe applications that
need access to multiple timezones.
In POSIX, there is no tamper-proof way for a process to learn the
system's best idea of local (wall clock) time.
This is important for applications that an administrator wants
used only at certain times – without regard to whether the
user has fiddled the
TZ environment variable.
While an administrator can "do everything in UT" to
get around the problem, doing so is inconvenient and precludes
handling daylight saving time shifts – as might be required to
limit phone calls to off-peak hours.
POSIX provides no convenient and efficient way to determine
the UT offset and time zone abbreviation of arbitrary
timestamps, particularly for timezones
that do not fit into the POSIX model.
POSIX requires that time_t clock counts exclude leap
seconds.
The tz code attempts to support all the
time_t implementations allowed by POSIX.
The time_t type represents a nonnegative count of seconds
since 1970-01-01 00:00:00 UTC, ignoring leap seconds.
In practice, time_t is usually a signed 64- or 32-bit
integer; 32-bit signed time_t values stop working after
2038-01-19 03:14:07 UTC, so new implementations these
days typically use a signed 64-bit integer.
Unsigned 32-bit integers are used on one or two platforms, and 36-bit
and 40-bit integers are also used occasionally.
Although earlier POSIX versions allowed time_t to be a
floating-point type, this was not supported by any practical system,
and POSIX.1-2013 and the tz code both
require time_t to be an integer type.
Extensions to POSIX in the
tz code
The TZ environment variable is used in generating
the name of a file from which time-related information is read
(or is interpreted à la POSIX); TZ is no longer
constrained to be a string containing abbreviations
and numeric data as described above.
The file's format is TZif,
a timezone information format that contains binary data; see
Internet
RFC 8536.
The daylight saving time rules to be used for a
particular timezone are encoded in the
TZif file; the format of the file allows US,
Australian, and other rules to be encoded, and
allows for situations where more than two time zone
abbreviations are used.
It was recognized that allowing the TZ environment
variable to take on values such as 'America/New_York'
might cause "old" programs (that expect TZ to have a
certain form) to operate incorrectly; consideration was given to using
some other environment variable (for example, TIMEZONE)
to hold the string used to generate the TZif file's name.
In the end, however, it was decided to continue using
TZ: it is widely used for time zone purposes;
separately maintaining both TZ
and TIMEZONE seemed a nuisance; and systems where
"new" forms of TZ might cause problems can simply
use legacy TZ values such as "EST5EDT" which
can be used by "new" programs as well as by "old" programs that
assume pre-POSIX TZ values.
The code supports platforms with a UT offset member
in struct tm, e.g., tm_gmtoff,
or with a time zone abbreviation member in
struct tm, e.g., tm_zone. As noted
in Austin
Group defect 1533, a future version of POSIX is planned to
require tm_gmtoff and tm_zone.
Functions tzalloc, tzfree,
localtime_rz, and mktime_z for
more-efficient thread-safe applications that need to use multiple
timezones.
The tzalloc and tzfree functions
allocate and free objects of type timezone_t,
and localtime_rz and mktime_z are
like localtime_r and mktime with an
extra timezone_t argument.
The functions were inspired by NetBSD.
Negative time_t values are supported, on systems
where time_t is signed.
These functions can account for leap seconds;
see Leap seconds below.
POSIX features no longer needed
POSIX and ISO C
define some APIs that are vestigial:
they are not needed, and are relics of a too-simple model that does
not suffice to handle many real-world timestamps.
Although the tz code supports these
vestigial APIs for backwards compatibility, they should
be avoided in portable applications.
The vestigial APIs are:
The POSIX tzname variable does not suffice and is no
longer needed.
To get a timestamp's time zone abbreviation, consult
the tm_zone member if available; otherwise,
use strftime's "%Z" conversion
specification.
The POSIX daylight and timezone
variables do not suffice and are no longer needed.
To get a timestamp's UT offset, consult
the tm_gmtoff member if available; otherwise,
subtract values returned by localtime
and gmtime using the rules of the Gregorian calendar,
or use strftime's "%z" conversion
specification if a string like "+0900" suffices.
The tm_isdst member is almost never needed and most of
its uses should be discouraged in favor of the abovementioned
APIs.
Although it can still be used in arguments to
mktime to disambiguate timestamps near
a DST transition when the clock jumps back on
platforms lacking tm_gmtoff, this
disambiguation does not work when standard time itself jumps back,
which can occur when a location changes to a time zone with a
lesser UT offset.
Other portability notes
The 7th Edition
UNIXtimezone function is not present in this
package; it is impossible to reliably map timezone's
arguments (a "minutes west of GMT" value and a
"daylight saving time in effect" flag) to a time zone
abbreviation, and we refuse to guess.
Programs that in the past used the timezone function
may now examine localtime(&clock)->tm_zone
(if TM_ZONE is defined) or
tzname[localtime(&clock)->tm_isdst]
(if HAVE_TZNAME is nonzero) to learn the correct time
zone abbreviation to use.
The 4.2BSDgettimeofday function is not
used in this package.
This formerly let users obtain the current UTC offset
and DST flag, but this functionality was removed in
later versions of BSD.
In SVR2, time conversion fails for near-minimum or
near-maximum time_t values when doing conversions
for places that do not use UT.
This package takes care to do these conversions correctly.
A comment in the source code tells how to get compatibly wrong
results.
The functions that are conditionally compiled
- if STD_INSPIRED is defined should, at this point, be
+ if STD_INSPIRED is nonzero should, at this point, be
looked on primarily as food for thought.
They are not in any sense "standard compatible" – some are
not, in fact, specified in any standard.
They do, however, represent responses of various authors to
standardization proposals.
Other time conversion proposals, in particular those supported by the
Time Zone
Database Parser, offer a wider selection of functions
that provide capabilities beyond those provided here.
The absence of such functions from this package is not meant to
discourage the development, standardization, or use of such
functions.
Rather, their absence reflects the decision to make this package
contain valid extensions to POSIX, to ensure its broad
acceptability.
If more powerful time conversion functions can be standardized, so
much the better.
Interface stability
The tz code and data supply the following interfaces:
The programs tzselect, zdump,
and zic, documented in their man pages.
The format of zic input files, documented in
the zic man page.
The format of zic output files, documented in
the tzfile man page.
The format of zone table files, documented in zone1970.tab.
The format of the country code file, documented in iso3166.tab.
The version number of the code and data, as the first line of
the text file 'version' in each release.
Interface changes in a release attempt to preserve compatibility with
recent releases.
For example, tz data files typically do not
-rely on recently-added zic features, so that users can
+rely on recently added zic features, so that users can
run older zic versions to process newer data files.
Downloading
the tz database describes how releases
are tagged and distributed.
Interfaces not listed above are less stable.
For example, users should not rely on particular UT
offsets or abbreviations for timestamps, as data entries are often
based on guesswork and these guesses may be corrected or improved.
Timezone boundaries are not part of the stable interface.
For example, even though the Asia/Bangkok timezone
currently includes Chang Mai, Hanoi, and Phnom Penh, this is not part
of the stable interface and the timezone can split at any time.
If a calendar application records a future event in some location other
than Bangkok by putting "Asia/Bangkok" in the event's record,
the application should be robust in the presence of timezone splits
between now and the future time.
Leap seconds
+
+Leap seconds were introduced in 1972 to accommodate the
+difference between atomic time and the less regular rotation of the earth.
+Unfortunately they caused so many problems with civil
+timekeeping that they
+are planned
+to be discontinued by 2035, with some as-yet-undetermined
+mechanism replacing them, perhaps after the year 2135.
+Despite their impending obsolescence, a record of leap seconds is still
+needed to resolve timestamps from 1972 through 2035.
+
+
The tz code and data can account for leap seconds,
thanks to code contributed by Bradley White.
However, the leap second support of this package is rarely used directly
because POSIX requires leap seconds to be excluded and many
software packages would mishandle leap seconds if they were present.
Instead, leap seconds are more commonly handled by occasionally adjusting
the operating system kernel clock as described in
Precision timekeeping,
and this package by default installs a leapseconds file
commonly used by
NTP
software that adjusts the kernel clock.
However, kernel-clock twiddling approximates UTC only roughly,
-and systems needing more-precise UTC can use this package's leap
+and systems needing more precise UTC can use this package's leap
second support directly.
-The directly-supported mechanism assumes that time_t
+The directly supported mechanism assumes that time_t
counts of seconds since the POSIX epoch normally include leap seconds,
as opposed to POSIX time_t counts which exclude leap seconds.
This modified timescale is converted to UTC
at the same point that time zone and DST
adjustments are applied –
namely, at calls to localtime and analogous functions –
and the process is driven by leap second information
stored in alternate versions of the TZif files.
Because a leap second adjustment may be needed even
if no time zone correction is desired,
calls to gmtime-like functions
also need to consult a TZif file,
conventionally named Etc/UTC
(GMT in previous versions),
to see whether leap second corrections are needed.
To convert an application's time_t timestamps to or from
POSIX time_t timestamps (for use when, say,
embedding or interpreting timestamps in portable
tar
files),
the application can call the utility functions
time2posix and posix2time
included with this package.
If the POSIX-compatible TZif file set is installed
in a directory whose basename is zoneinfo, the
leap-second-aware file set is by default installed in a separate
directory zoneinfo-leaps.
Although each process can have its own time zone by setting
its TZ environment variable, there is no support for some
processes being leap-second aware while other processes are
POSIX-compatible; the leap-second choice is system-wide.
So if you configure your kernel to count leap seconds, you should also
discard zoneinfo and rename zoneinfo-leaps
to zoneinfo.
Alternatively, you can install just one set of TZif files
in the first place; see the REDO variable in this package's
makefile.
Calendrical issues
Calendrical issues are a bit out of scope for a time zone database,
but they indicate the sort of problems that we would run into if we
extended the time zone database further into the past.
An excellent resource in this area is Edward M. Reingold
and Nachum Dershowitz, Calendrical
Calculations: The Ultimate Edition, Cambridge University Press (2018).
Other information and sources are given in the file 'calendars'
in the tz distribution.
They sometimes disagree.
-
Time and time zones on other planets
+
Time and time zones off Earth
+
+The European Space Agency is considering
+the establishment of a reference timescale for the Moon, which has
+days roughly equivalent to 29.5 Earth days, and where relativistic
+effects cause clocks to tick slightly faster than on Earth.
+
+
Some people's work schedules have used
Mars time.
Jet Propulsion Laboratory (JPL) coordinators kept Mars time on
and off during the
Mars
Pathfinder mission (1997).
Some of their family members also adapted to Mars time.
Dozens of special Mars watches were built for JPL workers who kept
Mars time during the
Mars
Exploration Rovers (MER) mission (2004–2018).
These timepieces looked like normal Seikos and Citizens but were adjusted
to use Mars seconds rather than terrestrial seconds, although
unfortunately the adjusted watches were unreliable and appear to have
had only limited use.
A Mars solar day is called a "sol" and has a mean period equal to
about 24 hours 39 minutes 35.244 seconds in terrestrial time.
It is divided into a conventional 24-hour clock, so each Mars second
equals about 1.02749125 terrestrial seconds.
(One MER worker noted, "If I am working Mars hours, and Mars hours are
2.5% more than Earth hours, shouldn't I get an extra 2.5% pay raise?")
The prime
meridian of Mars goes through the center of the crater
Airy-0, named in
honor of the British astronomer who built the Greenwich telescope that
defines Earth's prime meridian.
Mean solar time on the Mars prime meridian is
called Mars Coordinated Time (MTC).
Each landed mission on Mars has adopted a different reference for
solar timekeeping, so there is no real standard for Mars time zones.
For example, the MER mission defined two time zones "Local
Solar Time A" and "Local Solar Time B" for its two missions, each zone
designed so that its time equals local true solar time at
approximately the middle of the nominal mission.
The A and B zones differ enough so that an MER worker assigned to
the A zone might suffer "Mars lag" when switching to work in the B zone.
Such a "time zone" is not particularly suited for any application
other than the mission itself.
Many calendars have been proposed for Mars, but none have achieved
wide acceptance.
Astronomers often use Mars Sol Date (MSD) which is a
sequential count of Mars solar days elapsed since about 1873-12-29
12:00 GMT.
In our solar system, Mars is the planet with time and calendar most
like Earth's.
On other planets, Sun-based time and calendars would work quite
differently.
For example, although Mercury's
sidereal
rotation period is 58.646 Earth days, Mercury revolves around the
Sun so rapidly that an observer on Mercury's equator would see a
sunrise only every 175.97 Earth days, i.e., a Mercury year is 0.5 of a
Mercury day.
Venus is more complicated, partly because its rotation is slightly
retrograde:
its year is 1.92 of its days.
Gas giants like Jupiter are trickier still, as their polar and
equatorial regions rotate at different rates, so that the length of a
day depends on latitude.
This effect is most pronounced on Neptune, where the day is about 12
hours at the poles and 18 hours at the equator.
Although the tz database does not support
time on other planets, it is documented here in the hopes that support
will be added eventually.
diff --git a/contrib/tzcode/time2posix.3 b/contrib/tzcode/time2posix.3
index 6959c890a65b..f4e4ffdc6590 100644
--- a/contrib/tzcode/time2posix.3
+++ b/contrib/tzcode/time2posix.3
@@ -1,149 +1,146 @@
.\" This file is in the public domain, so clarified as of
.\" 1996-06-05 by Arthur David Olson.
-.\"
-.\" $FreeBSD$
-.\"
.Dd December 15, 2022
.Dt TIME2POSIX 3
.Os
.Sh NAME
.Nm time2posix ,
.Nm posix2time
.Nd convert seconds since the Epoch
.Sh LIBRARY
.Lb libc
.Sh SYNOPSIS
.In time.h
.Ft time_t
.Fn time2posix "time_t t"
.Ft time_t
.Fn posix2time "time_t t"
.Sh DESCRIPTION
.St -p1003.1-88
requires the time_t value 536457599 to stand for 1986-12-31 23:59:59 UTC.
This effectively implies that POSIX
.Vt time_t
values cannot include leap
seconds and,
therefore,
that the system time must be adjusted as each leap occurs.
.Pp
If the time package is configured with leap-second support
enabled,
however,
no such adjustment is needed and
.Vt time_t
values continue to increase over leap events
(as a true
.Dq "seconds since..."
value).
This means that these values will differ from those required by POSIX
by the net number of leap seconds inserted since the Epoch.
.Pp
Typically this is not a problem as the type
.Vt time_t
is intended
to be
(mostly)
opaque \(em
.Vt time_t
values should only be obtained-from and
passed-to functions such as
.Xr time 3 ,
.Xr localtime 3 ,
.Xr mktime 3
and
.Xr difftime 3 .
However,
.St -p1003.1-88
gives an arithmetic
expression for directly computing a
.Vt time_t
value from a given date/time,
and the same relationship is assumed by some
(usually older)
applications.
Any programs creating/dissecting
.Vt time_t
values
using such a relationship will typically not handle intervals
over leap seconds correctly.
.Pp
The
.Fn time2posix
and
.Fn posix2time
functions are provided to address this
.Vt time_t
mismatch by converting
between local
.Vt time_t
values and their POSIX equivalents.
This is done by accounting for the number of time-base changes that
would have taken place on a POSIX system as leap seconds were inserted
or deleted.
These converted values can then be used in lieu of correcting the older
applications,
or when communicating with POSIX-compliant systems.
.Pp
The
.Fn time2posix
function
is single-valued.
That is,
every local
.Vt time_t
corresponds to a single POSIX
.Vt time_t .
The
.Fn posix2time
function
is less well-behaved:
for a positive leap second hit the result is not unique,
and for a negative leap second hit the corresponding
POSIX
.Vt time_t
does not exist so an adjacent value is returned.
Both of these are good indicators of the inferiority of the
POSIX representation.
.Pp
The following table summarizes the relationship between a time
T and its conversion to,
and back from,
the POSIX representation over the leap second inserted at the end of June,
1993.
.Bl -column "93/06/30" "23:59:59" "A+0" "X=time2posix(T)"
.It Sy "DATE TIME T X=time2posix(T) posix2time(X)"
.It "93/06/30 23:59:59 A+0 B+0 A+0"
.It "93/06/30 23:59:60 A+1 B+1 A+1 or A+2"
.It "93/07/01 00:00:00 A+2 B+1 A+1 or A+2"
.It "93/07/01 00:00:01 A+3 B+2 A+3"
.El
.Pp
A leap second deletion would look like...
.Bl -column "??/06/30" "23:59:58" "A+0" "X=time2posix(T)"
.It Sy "DATE TIME T X=time2posix(T) posix2time(X)"
.It "??/06/30 23:59:58 A+0 B+0 A+0"
.It "??/07/01 00:00:00 A+1 B+2 A+1"
.It "??/07/01 00:00:01 A+2 B+3 A+2"
.El
.Pp
.D1 No "[Note: posix2time(B+1) => A+0 or A+1]"
.Pp
If leap-second support is not enabled,
local
.Vt time_t
and
POSIX
.Vt time_t
values are equivalent,
and both
.Fn time2posix
and
.Fn posix2time
degenerate to the identity function.
.Sh "SEE ALSO"
.Xr difftime 3 ,
.Xr localtime 3 ,
.Xr mktime 3 ,
.Xr time 3
diff --git a/contrib/tzcode/tz-art.html b/contrib/tzcode/tz-art.html
index 567953415f63..c86c186c09f9 100644
--- a/contrib/tzcode/tz-art.html
+++ b/contrib/tzcode/tz-art.html
@@ -1,636 +1,649 @@
Time and the Arts
Time and the Arts
Documentaries
"Daylight
Saving Time Explained" (2011; 6:39) lightly covers daylight saving
time's theory, history, pros and cons. Among other things, it explains
Arizona's daylight-saving enclaves quite well.
"About Time" (1962; 59 minutes) is part of the
Bell Science extravaganza, with Frank Baxter, Richard Deacon, and Les Tremayne.
Its advisor was Richard Feynman, and it was voiced by Mel Blanc.
(IMDb entry.)
Movies
In the 1946 movie A Matter of Life and Death
(U.S. title Stairway to Heaven)
there is a reference to British Double Summer Time.
The time does not play a large part in the plot;
it's just a passing reference to the time when one of the
characters was supposed to have died (but didn't).
(IMDb entry.)
(Dave Cantor)
The 1953 railway comedy movie The Titfield Thunderbolt includes a
play on words on British Double Summer Time. Valentine's wife wants
him to leave the pub and asks him, "Do you know what time it is?"
And he, happy where he is, replies: "Yes, my love. Summer double time."
(IMDb entry.)
(Mark Brader, 2009-10-02)
The premise of the 1999 caper movie Entrapment involves computers
in an international banking network being shut down briefly at
midnight in each time zone to avoid any problems at the transition
from the year 1999 to 2000 in that zone. (Hmmmm.) If this shutdown
is extended by 10 seconds, it will create a one-time opportunity for
a gigantic computerized theft. To achieve this, at one location the
crooks interfere with the microwave system supplying time signals to
the computer, advancing the time by 0.1 second each minute over the
last hour of 1999. (So this movie teaches us that 0.1 × 60 = 10.)
(IMDb entry.)
(Mark Brader, 2009-10-02)
One mustn't forget the
trailer
(2014; 2:23) for the movie Daylight Saving.
TV episodes
An episode of The Adventures of Superman entitled "The Mysterious
Cube," first aired 1958-02-24, had Superman convincing the controllers
of the Arlington Time Signal to broadcast ahead of actual time;
doing so got a crook trying to be declared dead to
emerge a bit too early from the titular enclosure.
(IMDb entry.)
"The Chimes
of Big Ben", The Prisoner, episode 2, ITC, 1967-10-06.
Our protagonist tumbles to
the fraudulent nature of a Poland-to-England escape upon hearing "Big
Ben" chiming on Polish local time.
(IMDb entry.)
"The Susie", Seinfeld, season 8, episode 15, NBC, 1997-02-13.
Kramer decides that daylight saving time
isn't coming fast enough, so he sets his watch ahead an hour.
"20 Hours in America", The West Wing, season 4, episodes 1–2,
2002-09-25, contained a scene that
saw White House staffers stranded in Indiana; they thought they had time to
catch Air Force One but were done in by intra-Indiana local time changes.
"In what time zone would you find New York City?" was a $200 question on
the 1999-11-13 United States airing of Who Wants to Be a Millionaire?,
and "In 1883, what industry led the movement to divide the U.S. into four time
zones?" was a $32,000 question on the 2001-05-23 United States airing of
the same show. At this rate, the million-dollar time-zone
question should have been asked 2002-06-04.
A private jet's mid-flight change of time zones distorts Alison Dubois'
premonition in the "We Had a Dream" episode of Medium
(originally aired 2007-02-28).
A criminal's failure to account for the start of daylight saving is pivotal
in "Mr. Monk
and the Rapper" (first aired 2007-07-20).
In the 30 Rock episode "Anna Howard Shaw Day"
(first broadcast 2010-02-11),
Jack Donaghy's date realizes that a Geneva-to-New-York business phone call
received in the evening must be fake given the difference in local times.
In the "Run by the Monkeys" episode of Da Vinci's Inquest
(first broadcast 2002-11-17),
a witness in a five-year-old fire case realizes they may not have set
their clock back when daylight saving ended on the day of the fire,
introducing the possibility of an hour when arson might have occurred.
In "The Todd Couple" episode of Outsourced (first aired 2011-02-10),
Manmeet sets up Valentine's Day teledates for 6:00 and 9:00pm;
since one is with a New Yorker and the other with a San Franciscan,
hilarity ensues.
(Never mind that this should be 7:30am in Mumbai, yet for some reason the show
proceeds as though it's also mid-evening there.)
In the "14 Days to Go"/"T Minus..." episode of
You, Me and the Apocalypse
(first aired 2015-11-11 in the UK, 2016-03-10 in the US),
the success of a mission to deal with a comet
hinges on whether or not Russia observes daylight saving time.
(In the US,
the episode first aired in the week before the switch to DST.)
"The Lost Hour", Eerie, Indiana, episode 10, NBC, 1991-12-01.
Despite Indiana's then-lack of DST,
Marshall changes his clock with unusual consequences.
See "Eerie,
Indiana was a few dimensions ahead of its time".
"Time Tunnel", The Adventures of Pete & Pete, season 2, episode 5,
Nickelodeon, 1994-10-23.
The two Petes travel back in time an hour
on the day that DST ends.
"King-Size Homer", The Simpsons, episode 135, Fox, 1995-11-05.
Homer, working from home, remarks "8:58, first
time I've ever been early for work. Except for all those daylight
savings days. Lousy farmers."
"Tracks", The Good Wife, season 7, episode 12,
CBS, 2016-01-17.
The applicability of a contract hinges on the
time zone associated with a video timestamp.
"Justice", Veep, season 6, episode 4, HBO, 2017-05-07.
Jonah's inability to understand DST ends up impressing a wealthy
backer who sets him up for a 2020 presidential run.
Books, plays, and magazines
Jules Verne, Around the World in Eighty Days
(Le tour du monde en quatre-vingts jours), 1873.
Wall-clock time plays a central role in the plot.
European readers of the 1870s clearly held the U.S. press in
deep contempt; the protagonists cross the U.S. without once
reading a paper.
Available versions include
an English
translation, and
the original French
"with illustrations from the original 1873 French-language edition".
Nick Enright, Daylight Saving, 1989.
A fast-paced comedy about love and loneliness as the clocks turn back.
Umberto Eco,
The
Island of the Day Before
(L'isola del giorno prima), 1994.
"...the story of a 17th century Italian nobleman trapped near an island
on the International Date Line. Time and time zones play an integral
part in the novel." (Paul Eggert, 2006-04-22)
Surrealist artist Guy Billout's work "Date Line" appeared on page 103
of the 1999-11 Atlantic Monthly.
"Gloom, Gloom, Go Away" by Walter Kirn appeared on page 106 of Time
magazine's 2002-11-11 issue; among other things, it proposed
year-round DST as a way of lessening wintertime despair.
Music
Data on recordings of "Save That Time," Russ Long, Serrob Publishing, BMI:
Milt Hinton, bass;
Doc Cheatham, Dizzy Gillespie, Clark Terry, trumpet;
Al Grey, trombone;
Eddie Barefield, Joe Camel (Flip Phillips), Buddy Tate,
clarinet and saxophone;
John Bunch, Red Richards, Norman Simmons, Derek Smith,
Ralph Sutton, piano;
Danny Barker, Al Casey, guitar;
Gus Johnson, Gerryck King, Bob Rosengarden, Jackie Williams,
drums;
Lionel Hampton, vibraphone;
Cab Calloway, Joe Williams, vocal;
Buck Clayton, arrangements
Notes
tunes include Old Man Time, Time After Time,
Sometimes I'm Happy,
A Hot Time in the Old Town Tonight,
Four or Five Times, Now's the Time,
Time on My Hands, This Time It's Us,
and Good Time Charlie.
Album info
is available.
Anthony Braxton, sopranino and alto saxophones,
contrebasse clarinet, miscellaneous instruments;
Leo Smith, trumpet and miscellaneous instruments;
Leroy Jenkins, violin and miscellaneous instruments;
Richard Teitelbaum, modular moog and micromoog synthesizer
Won the 2004 Record of the Year honor at the
Grammy Awards. Co-written and performed by Chris Martin,
great-great-grandson of DST inventor William Willett.
The song's first line is "Lights go out and I can't be saved".
The song protested "Sixto Hour" in Ecuador
(1992–3). Its lyrics include "Amanecía en mitad de la noche, los
guaguas iban a clase sin sol" ("It was dawning in the middle of the
night, the buses went to class without sun").
Artist
Irving Kahal and Harry Richman
Song
There Ought to be a Moonlight Saving Time
Copyright Date
1931
Notes
This musical standard was a No. 1 hit for Guy Lombardo
in 1931, and was also performed by Maurice Chevalier, Blossom Dearie
and many others. The phrase "Moonlight saving time" also appears in
the 1995 country song "Not Enough Hours in the Night" written by Aaron
Barker, Kim Williams and Rob Harbin and performed by Doug
Supernaw.
Peppermint Patty: "What if the world comes to an end tonight, Marcie?"
Marcie: "I promise there'll be a tomorrow, sir ... in fact,
it's already tomorrow in Australia!"
(Charles M. Schulz, Peanuts, 1980-06-13)
Jokes
The idea behind daylight saving time was first proposed as a joke by
Benjamin Franklin. To enforce it, he suggested, "Every
morning, as soon as the sun rises, let all the bells in every church
be set ringing; and if that is not sufficient, let cannon be fired in
every street, to wake the sluggards effectually, and make them open
their eyes to see their true interest. All the difficulty will be in
the first two or three days: after which the reformation will be as
natural and easy as the present irregularity; for, ce n'est que le
premier pas qui coûte."
Franklin's
joke was first published on 1784-04-26 by the
Journal de Paris as an
anonymous letter translated into French.
"We've been using the five-cent nickel in this country since 1492.
Now that's pretty near 100 years, daylight saving."
(Groucho Marx as Captain Spaulding in Animal Crackers, 1930,
as noted by Will Fitzgerald)
BRADY. ...[Bishop Usher] determined that the Lord began the Creation
on the 23rd of October in the Year 4,004 B.C. at – uh, 9 A.M.!
DRUMMOND. That Eastern Standard Time? (Laughter.) Or Rocky Mountain
Time? (More laughter.) It wasn't daylight-saving time, was it? Because
the Lord didn't make the sun until the fourth day!
(From the play Inherit the Wind by Jerome Lawrence and Robert E. Lee,
filmed in 1960 with Spencer Tracy as Drummond and Fredric March as
Brady, and several other times. Thanks to Mark Brader.)
"Good news."
"What did they do? Extend Daylight Saving Time year round?"
(Professional tanner George Hamilton, in dialog from a
May, 1999 episode of the syndicated television series Baywatch)
"A fundamental belief held by Americans is that if you are on land, you
cannot be killed by a fish...So most Americans remain on land, believing
they're safe. Unfortunately, this belief – like so many myths, such as that
there's a reason for 'Daylight Saving Time' – is false."
(Dave Barry column, 2000-07-02)
"I once had sex for an hour and five minutes, but that was on the day
when you turn the clocks ahead."
(Garry Shandling, 52nd Annual Emmys, 2000-09-10)
"Would it impress you if I told you I invented Daylight Savings Time?"
("Sahjhan" to "Lilah" in dialog from the "Loyalty" episode of Angel,
originally aired 2002-02-25)
"I thought you said Tulsa was a three-hour flight."
"Well, you're forgetting about the time difference."
("Joey" and "Chandler" in dialog from the episode of Friends
entitled "The One With Rachel's Phone Number," originally aired 2002-12-05)
"Is that a pertinent fact,
or are you just trying to dazzle me with your command of time zones?"
(Kelsey Grammer as "Frasier Crane" to "Roz" from the episode of Frasier
entitled "The Kid," originally aired 1997-11-04)
"I put myself and my staff through this crazy, huge ordeal, all because
I refused to go on at midnight, okay? And so I work, you know, and
then I get this job at eleven, supposed to be a big deal. Then
yesterday daylight [saving] time ended. Right now it's basically
midnight." (Conan O'Brien on the 2010-11-08 premiere of Conan.)
"The best method, I told folks, was to hang a large clock high on a
barn wall where all the cows could see it. If you have Holsteins, you
will need to use an analog clock." (Jerry Nelson, How
to adjust dairy cows to daylight saving time", Successful Farming,
2017-10-09.)
"And now, driving to California, I find that I must enter a password
in order to change the time zone on my laptop clock. Evidently,
someone is out to mess up my schedule and my clock must be secured."
(Garrison Keillor,
"We've
never been here before", 2017-08-22)
"Well, in my time zone that's all the time I have,
but maybe in your time zone I haven't finished yet. So stay tuned!"
(Goldie Hawn, Rowan & Martin's Laugh-In No. 65, 1970-03-09)
This web page is in the public domain, so clarified as of
2009-05-17 by Arthur David Olson.
Please send corrections to this web page to the
time zone mailing list.
diff --git a/contrib/tzcode/tz-how-to.html b/contrib/tzcode/tz-how-to.html
index e1e28f2e257d..9e438f93092a 100644
--- a/contrib/tzcode/tz-how-to.html
+++ b/contrib/tzcode/tz-how-to.html
@@ -1,719 +1,719 @@
How to Read the tz Database
This guide uses the America/Chicago and
Pacific/Honolulu zones as examples of how to infer
times of day from the tz database
source files. It might be helpful, but not absolutely necessary,
for the reader to have already downloaded the
latest release of the database and become familiar with the basic layout
of the data files. The format is explained in the “man
page” for the zic compiler, zic.8.txt, in
the code subdirectory.
Although this guide covers many of the common cases, it is not a
complete summary of what zic accepts; the man page is the
authoritative reference.
We’ll begin by talking about the rules for changing between standard
and daylight saving time since we’ll need that information when we talk
about the zones.
First, let’s consider the special daylight saving time rules
for Chicago (from the northamerica file in
the data subdirectory):
From the Source File
#Rule NAME FROM TO - IN ON AT SAVE LETTER
Rule Chicago 1920 only - Jun 13 2:00 1:00 D
Rule Chicago 1920 1921 - Oct lastSun 2:00 0 S
Rule Chicago 1921 only - Mar lastSun 2:00 1:00 D
Rule Chicago 1922 1966 - Apr lastSun 2:00 1:00 D
Rule Chicago 1922 1954 - Sep lastSun 2:00 0 S
Rule Chicago 1955 1966 - Oct lastSun 2:00 0 S
Reformatted a Bit
From
To
On
At
Action
1920 only
June 13th
02:00 local
go to daylight saving time
1920
1921
last Sunday
in October
return to standard time
1921 only
in March
go to daylight saving time
1922
1966
in April
1954
in September
return to standard time
1955
1966
in October
The FROM and TO columns, respectively, specify the
first and last calendar years defining a contiguous range over which a specific
Rule line is to apply. The keyword only can be used in the
TO field to repeat the value of the FROM field in the
event that a rule should only apply to a single year. Often, the keyword
max is used to extend a rule’s application into the
indefinite future; it is a platform-agnostic stand-in for the largest
representable year.
The next column, -, is reserved; for compatibility with earlier
releases, it always contains a hyphen, which acts as a kind of null value.
Prior to the 2020b release, it was called the TYPE field, though
it had not been used in the main data since the 2000e release.
An obsolescent supplementary file used the
field as a proof-of-concept to allow zic to apply a given Rule
line only to certain “types” of years within the specified range as
dictated by the output of a separate script, such as: only years which would
have a US presidential election, or only years which wouldn’t.
The SAVE column contains the local (wall clock) offset from
local standard time.
This is usually either zero for standard time or one hour for daylight
saving time; but there’s no reason, in principle, why it can’t
take on other values.
The LETTER (sometimes called LETTER/S)
column can contain a variable
part of the usual abbreviation of the time zone’s name, or it can just
be a hyphen if there’s no variable part. For example, the abbreviation
used in the central time zone will be either “CST” or
“CDT”. The variable part is ‘S’ or ‘D’;
and, sure enough, that’s just what we find in
the LETTER column
in the Chicago rules. More about this when we talk about
“Zone” lines.
One important thing to notice is that “Rule” lines
want at once to be both transitions and steady states:
On the one hand, they represent transitions between standard and
daylight saving time; and any number of Rule lines can be in effect
during a given period (which will always be a non-empty set of
contiguous calendar years).
On the other hand, the SAVE and LETTER
columns contain state that exists between transitions. More about this
when we talk about the US rules.
In the example above, the transition to daylight saving time
happened on the 13th of June in 1920, and on
the last Sunday in March in 1921; but the return to standard time
happened on the last Sunday in October in both of those
years. Similarly, the rule for changing to daylight saving time was
the same from 1922 to 1966; but the rule for returning to standard
time changed in 1955. Got it?
OK, now for the somewhat more interesting “US” rules:
From the Source File
#Rule NAME FROM TO - IN ON AT SAVE LETTER/S
Rule US 1918 1919 - Mar lastSun 2:00 1:00 D
Rule US 1918 1919 - Oct lastSun 2:00 0 S
Rule US 1942 only - Feb 9 2:00 1:00 W # War
Rule US 1945 only - Aug 14 23:00u 1:00 P # Peace
Rule US 1945 only - Sep 30 2:00 0 S
Rule US 1967 2006 - Oct lastSun 2:00 0 S
Rule US 1967 1973 - Apr lastSun 2:00 1:00 D
Rule US 1974 only - Jan 6 2:00 1:00 D
Rule US 1975 only - Feb 23 2:00 1:00 D
Rule US 1976 1986 - Apr lastSun 2:00 1:00 D
Rule US 1987 2006 - Apr Sun>=1 2:00 1:00 D
Rule US 2007 max - Mar Sun>=8 2:00 1:00 D
Rule US 2007 max - Nov Sun>=1 2:00 0 S
rename “war time” to “peace time;”
clocks don’t change
September 30th
02:00 local
return to standard time
1967
2006
last Sunday
in October
1973
in April
go to daylight saving time
1974 only
January 6th
1975 only
February 23rd
1976
1986
last Sunday
in April
1987
2006
first Sunday
2007
present
second Sunday in March
first Sunday in November
return to standard time
There are two interesting things to note here.
First, the time that something happens (in the AT
column) is not necessarily the local (wall clock) time. The time can be
suffixed with ‘s’ (for “standard”) to mean
local standard time, different from local (wall clock) time when observing
daylight saving time; or it can be suffixed with ‘g’,
‘u’, or ‘z’, all three of which mean the
standard time at the
prime meridian.
‘g’ stands for “GMT”;
‘u’ stands for “UT” or “UTC”
(whichever was official at the time); ‘z’ stands for the
nautical time zone
Z (a.k.a. “Zulu” which, in turn, stands for ‘Z’).
The time can also be suffixed with ‘w’ meaning local (wall
clock) time; but it usually isn’t because that’s the
default.
Second, the day in the ON column, in addition to
“lastSun” or a particular day of the month,
can have the form, “Sun>=x” or
“Sun<=x,” where x is a day
of the month. For example, “Sun>=8” means
“the first Sunday on or after the eighth of the month,” in
other words, the second Sunday of the month. Furthermore, although
there are no examples above, the weekday needn’t be
“Sun” in either form, but can be the usual
three-character English abbreviation for any day of the week.
And the US rules give us more examples of a couple of things
already mentioned:
The rules for changing to and from daylight saving time are
actually different sets of rules; and the two sets can change
independently. Consider, for example, that the rule for the return to
standard time stayed the same from 1967 to 2006; but the rule for the
transition to daylight saving time changed several times in the same
period. There can also be periods, 1946 to 1966 for example, when no
rule from this group is in effect, and so either no transition
happened in those years, or some other rule is in effect (perhaps a
state or other more local rule).
The SAVE and LETTER columns
contain steady state, not transitions. Consider, for example,
the transition from “war time” to “peace time”
that happened on August 14, 1945. The “1:00” in
the SAVE column is not an instruction to advance
the clock an hour. It means that clocks should be one hour
ahead of standard time, which they already are because of the previous
rule, so there should be no change.
OK, now let’s look at a Zone record:
From the Source File
#Zone NAME STDOFF RULES FORMAT [UNTIL]
Zone America/Chicago -5:50:36 - LMT 1883 Nov 18 12:09:24
-6:00 US C%sT 1920
-6:00 Chicago C%sT 1936 Mar 1 2:00
-5:00 - EST 1936 Nov 15 2:00
-6:00 Chicago C%sT 1942
-6:00 US C%sT 1946
-6:00 Chicago C%sT 1967
-6:00 US C%sT
There are a couple of interesting differences between Zones and Rules.
First, and somewhat trivially, whereas Rules are considered to
contain one or more records, a Zone is considered to be a single
record with zero or more continuation lines. Thus, the keyword,
“Zone,” and the zone name are not
repeated. The last line is the one without anything in
the [UNTIL] column.
Second, and more fundamentally, each line of a Zone represents a
steady state, not a transition between states. The state exists from
the date and time in the previous line’s [UNTIL]
column up to the date and time in the current
line’s [UNTIL] column. In other words, the date and
time in the [UNTIL] column is the instant that separates
this state from the next. Where that would be ambiguous because
we’re setting our clocks back, the [UNTIL] column
specifies the first occurrence of the instant. The state specified by
the last line, the one without anything in the [UNTIL]
column, continues to the present.
The first line typically specifies the mean solar time observed
before the introduction of standard time. Since there’s no line before
that, it has no beginning. 8-) For some places near the International
Date Line, the first two lines will show solar times
differing by 24 hours; this corresponds to a movement of the Date
Line. For example:
#Zone NAME STDOFF RULES FORMAT [UNTIL]
Zone America/Juneau 15:02:19 - LMT 1867 Oct 18
-8:57:41 - LMT ...
When Alaska was purchased from Russia in 1867, the Date Line moved
from the Alaska/Canada border to the Bering Strait; and the time in
Alaska was then 24 hours earlier than it had
been. <aside>(6 October in the Julian calendar,
which Russia was still using then for religious reasons, was followed
by a second instance of the same day with a different name, 18
October in the Gregorian calendar. Isn’t civil time
wonderful? 8-))</aside>
The abbreviation, “LMT” stands for “local mean
time”, which is an invention of
the tz
database and was probably never actually used during the
period. Furthermore, the value is almost certainly wrong except in the
archetypal place after which the zone is named. (The tz database
usually doesn’t provide a separate Zone record for places where
nothing significant happened after 1970.)
The RULES column tells us whether daylight saving time is being observed:
A hyphen, a kind of null value, means that we have not set our
clocks ahead of standard time.
An amount of time (usually but not necessarily “1:00”
meaning one hour) means that we have set our clocks ahead by that
amount.
Some alphabetic string means that we might have set our
clocks ahead; and we need to check the rule the name of which is the
given alphabetic string.
An example of a specific amount of time is:
#Zone NAME STDOFF RULES FORMAT [UNTIL]
Zone Pacific/Honolulu ... 1933 Apr 30 2:00
-10:30 1:00 HDT 1933 May 21 12:00
...
Hawaii tried daylight saving time for three weeks in 1933 and
decided they didn’t like it. 8-) Note that
the STDOFF column always contains the standard time
offset, so the local (wall clock) time during this period was GMT −
10:30 + 1:00 = GMT − 9:30.
The FORMAT column specifies the usual abbreviation of
the time zone name. It should have one of four forms:
a time zone abbreviation that is a string of three or more
characters that are either ASCII alphanumerics,
“+”, or “-”
the string “%z”, in which case the
“%z” will be replaced by a numeric time zone
abbreviation
a pair of time zone abbreviations separated by a slash
(‘/’), in which case the first string is the
abbreviation for the standard time name and the second string is the
abbreviation for the daylight saving time name
a string containing “%s”, in which case
the “%s” will be replaced by the text in the
appropriate Rule’s LETTER column, and the resulting
string should be a time zone abbreviation
The last two make sense only if there’s a named rule in effect.
An example of a slash is:
#Zone NAME STDOFF RULES FORMAT [UNTIL]
Zone Europe/London ... 1996
0:00 EU GMT/BST
The current time in the UK is called either Greenwich mean time or
British summer time.
One wrinkle, not fully explained in zic.8.txt, is what
happens when switching to a named rule. To what values should
the SAVE and LETTER data be initialized?
If at least one transition has happened, use
the SAVE and LETTER data from the most
recent.
If switching to a named rule before any transition has happened,
assume standard time (SAVE zero), and use
the LETTER data from the earliest transition with
a SAVE of zero.
And three last things about the FORMAT column:
The tz
-database gives abbreviations for time zones in popular
-usage, which is not necessarily “correct” by law. For
+database gives abbreviations for time zones
+in popular English-language usage. For
example, the last line in
ZonePacific/Honolulu (shown below) gives
“HST” for “Hawaii standard time” even though the
legal
name for that time zone is “Hawaii-Aleutian standard time.”
This author has read that there are also some places in Australia where
popular time zone names differ from the legal ones.
No attempt is made to localize
the abbreviations. They are intended to be the values returned through the
"%Z" format specifier to
C’s
strftime
function in the
“C” locale.
-
If there is no generally-accepted abbreviation for a time zone,
+
If there is no generally accepted abbreviation for a time zone,
a numeric offset is used instead, e.g., +07 for 7 hours
ahead of Greenwich. By convention, -00 is used in a
zone while uninhabited, where the offset is zero but in some sense
the true offset is undefined.
As a final example, here’s the complete history for Hawaii:
Relevant Excerpts from the US Rules
#Rule NAME FROM TO - IN ON AT SAVE LETTER/S
Rule US 1918 1919 - Oct lastSun 2:00 0 S
Rule US 1942 only - Feb 9 2:00 1:00 W # War
Rule US 1945 only - Aug 14 23:00u 1:00 P # Peace
Rule US 1945 only - Sep lastSun 2:00 0 S
The Zone Record
#Zone NAME STDOFF RULES FORMAT [UNTIL]
Zone Pacific/Honolulu -10:31:26 - LMT 1896 Jan 13 12:00
-10:30 - HST 1933 Apr 30 2:00
-10:30 1:00 HDT 1933 May 21 2:00
-10:30 US H%sT 1947 Jun 8 2:00
-10:00 - HST
What We Infer
Wall-Clock Offset from Prime Meridian
Adjust Clocks
Time Zone
Ending at Local Time
Abbrv.
Name
Date
Time
−10:31:26
—
LMT
local mean time
1896-01-13
12:00
−10:30
+0:01:26
HST
Hawaii standard time
1933-04-30
02:00
−9:30
+1:00
HDT
Hawaii daylight time
1933-05-21
12:00
−10:30¹
−1:00¹
HST¹
Hawaii standard time
1942-02-09
02:00
−9:30
+1:00
HWT
Hawaii war time
1945-08-14
13:30²
0
HPT
Hawaii peace time
1945-09-30
02:00
−10:30
−1:00
HST
Hawaii standard time
1947-06-08
−10:00³
+0:30³
—
¹Switching to US rules…most recent transition (in 1919) was to standard time
This web page is in the public domain, so clarified as of
2015-10-20 by Bill Seymour.
All suggestions and corrections will be welcome; all flames will be amusing.
Mail to was at pobox dot com.
diff --git a/contrib/tzcode/tz-link.html b/contrib/tzcode/tz-link.html
index d3b37664c71c..43190dd7efcd 100644
--- a/contrib/tzcode/tz-link.html
+++ b/contrib/tzcode/tz-link.html
@@ -1,1175 +1,1216 @@
Time zone and daylight saving time data
Time zone and daylight saving time data
Time zone and
daylight-saving
rules are controlled by individual
governments. They are sometimes changed with little notice, and their
histories and planned futures are often recorded only fitfully. Here
is a summary of attempts to organize and record relevant data in this
area.
Each main entry in the database represents a timezone
for a set of civil-time clocks that have all agreed since 1970.
Timezones are typically identified by continent or ocean and then by the
name of the largest city within the region containing the clocks.
For example, America/New_York
represents most of the US eastern time zone;
America/Phoenix represents most of Arizona, which
uses mountain time without daylight saving time (DST);
America/Detroit represents most of Michigan, which uses
eastern time but with different DST rules in 1975;
and other entries represent smaller regions like Starke County,
Indiana, which switched from central to eastern time in 1991
and switched back in 2006.
To use the database on an extended POSIX
implementation set the TZ
environment variable to the location's full name,
e.g., TZ="America/New_York".
Associated with each timezone is a history of offsets from
Universal
Time (UT), which is Greenwich Mean
Time (GMT) with days beginning at midnight;
for timestamps after 1960 this is more precisely Coordinated
Universal Time (UTC).
The database also records when daylight saving time was in use,
along with some time zone abbreviations such as EST
for Eastern Standard Time in the US.
Downloading the tz database
The following shell commands download
the latest release's two
tarballs
to a GNU/Linux or similar host.
mkdir tzdb
cd tzdb
wget https://www.iana.org/time-zones/repository/tzcode-latest.tar.gz
wget https://www.iana.org/time-zones/repository/tzdata-latest.tar.gz
gzip -dc tzcode-latest.tar.gz | tar -xf -
gzip -dc tzdata-latest.tar.gz | tar -xf -
Alternatively, the following shell commands download the same
release in a single-tarball format containing extra data
useful for regression testing:
These commands use convenience links to the latest release
of the tz database hosted by the
Time Zone Database website
of the Internet Assigned Numbers
Authority (IANA).
Older releases are in files named
tzcodeV.tar.gz,
tzdataV.tar.gz, and
tzdb-V.tar.lz,
where V is the version.
Since 1996, each version has been a four-digit year followed by
lower-case letter (a through z,
then za through zz, then zza
through zzz, and so on).
Since version 2022a, each release has been distributed in
POSIX
ustar interchange format, compressed as described above;
-older releases use a nearly-compatible format.
+older releases use a nearly compatible format.
Since version 2016h, each release has contained a text file named
"version" whose first (and currently only) line is the version.
Older releases are archived,
and are also available in an
FTP directory via a
-less-secure protocol.
+less secure protocol.
Alternatively, a development repository of code and data can be
retrieved from GitHub via the shell
command:
Since version 2012e, each release has been tagged in development repositories.
Untagged commits are less well tested and probably contain
more errors.
After obtaining the code and data files, see the
README file for what to do next.
The code lets you compile the tz source files into
machine-readable binary files, one for each location. The binary files
are in a special timezone information format (TZif)
specified by Internet
RFC 8536.
The code also lets
you read a TZif file and interpret timestamps for that
location.
-If your government plans to change its time zone boundaries or
-daylight saving rules, inform tz@iana.org well in
-advance, as this will coordinate updates to many cell phones,
-computers, and other devices around the world.
-The change should be officially announced at least a year before it affects
-how clocks operate; otherwise, there is a good chance that some
-clocks will operate incorrectly after the change, due
-to delays in propagating updates to software and data. The shorter
-the notice, the more likely clock problems will arise; see "On
-the Timing of Time Zone Changes" for examples.
-The tz data can represent planned changes
-far into the future, and a long-planned change can easily be reverted
-or otherwise altered with a year's notice before the change would have
-affected clocks.
-
-
Changes to the tz code and data are often
propagated to clients via operating system updates, so
client tz data can often be corrected by
applying these updates. With GNU/Linux and similar systems, if your
maintenance provider has not yet adopted the
latest tz data, you can often short-circuit
the process by tailoring the generic instructions in
the tz README file and installing the latest
data yourself. System-specific instructions for installing the
latest tz data have also been published
for AIX,
Android,
ICU,
IBM
JDK,
Joda-Time, MySQL,
Noda Time, and OpenJDK/Oracle JDK.
+As discussed in
+"How
+Time Zones Are Coordinated", the time zone database relies on
+collaboration among governments, the time zone database volunteer
+community, and data distributors downstream.
+
+If your government plans to change its time zone boundaries or
+daylight saving rules, please send email to tz@iana.org well in advance,
+as this will lessen confusion and will coordinate updates to many cell phones,
+computers, and other devices around the world.
+In your email, please cite the legislation or regulation that specifies
+the change, so that it can be checked for details such as the exact times
+when clock transitions occur.
+It is OK if a rule change is planned to affect clocks
+far into the future, as a long-planned change can easily be reverted
+or otherwise altered with a year's notice before the change would have
+affected clocks.
+
+There is no fixed schedule for tzdb releases.
+However, typically a release occurs every few months.
+Many downstream timezone data distributors wait for
+a tzdb release before they produce an update
+to time zone behavior in consumer devices and software products.
+After a release, various parties must integrate, test,
+and roll out an update before end users see changes.
+These updates can be expensive, for both the quality
+assurance process and the overall cost of shipping and installing
+updates to each device's copy of tzdb.
+Updates may be batched with other updates and may take substantial
+time to reach end users after a release.
+Older devices may no longer be supported and thus may never be updated,
+which means they will continue to use out-of-date rules.
+
+For these reasons any rule change should be promulgated at least a
+year before it affects how clocks operate; otherwise, there is a good
+chance that many clocks will be wrong due to delays in propagating updates,
+and that residents will be confused or even actively resist the change.
+The shorter the notice, the more likely clock problems will arise; see "On
+the Timing of Time Zone Changes" for examples.
+
+
+
Commentary on the tz database
The article
tz database is
an encyclopedic summary.
Although some of these do not fully support
tz data, in recent tzdb
distributions you can generally work around compatibility problems by
running the command make rearguard_tarballs and compiling
from the resulting tarballs instead.
Vzic is a C
+href="https://en.wikipedia.org/wiki/C_(programming_language)">C
program that compiles
tz source into iCalendar-compatible VTIMEZONE files.
Vzic is freely
available under the GNU
General Public License (GPL).
DateTime::TimeZone
contains a script parse_olson that compiles
tz source into Perl
modules. It is part of the Perl DateTime Project,
which is freely
available under both the GPL and the Perl Artistic
License. DateTime::TimeZone also contains a script
tests_from_zdump that generates test cases for each clock
transition in the tz database.
The Time Zone
Database Parser is a
-C++ parser and
+C++ parser and
runtime library with API
adopted by
C++20,
the current iteration of the C++ standard.
It is freely available under the
MIT license.
International Components for
Unicode (ICU) contains C/C++ and Java
+href="https://en.wikipedia.org/wiki/Java_(programming_language)">Java
libraries for internationalization that
has a compiler from tz source
and from CLDR data
(mentioned below)
into an ICU-specific format.
ICU is freely available under a
BSD-style license.
The Tzdata package for
the Elixir language downloads
and compiles tz source and exposes APIs for use. It is
freely available under the MIT license.
Joda-Time – Java date
and time API contains a class
org.joda.time.tz.ZoneInfoCompiler that compiles
tz source into a binary format. It inspired
Java 8 java.time, which its users should migrate to once
they can assume Java 8 or later. It is available under the Apache License.
IANA Updater and ZIUpdater
are alternatives to TZUpdater. IANA Updater's license is unclear;
ZIUpdater is licensed under the GPL.
ICU (mentioned above) contains compilers and
Java-based libraries.
Noda Time – Date and
time API for .NET
is like Joda-Time and Time4J, but for the .NET framework instead of Java.
It is freely available under the Apache License.
Many modern
JavaScript
runtimes support tz natively via the
timeZone option of Intl.DateTimeFormat.
This can be used as-is or with most of the following libraries,
many of which also support runtimes lacking the timeZone option.
The date-fns
library manipulates timezone-aware timestamps in browsers and
in Node.js.
It is freely available under the MIT license.
Day.js is a
minimalist replacement for the date and time API of
the now-legacy Moment.js date
manipulation library.
It is freely available under the MIT license.
Luxon improves
timezone support for the Intl API.
It is freely available under the MIT license.
Moment Timezone is a
Moment.js plugin.
It is freely available under the MIT license.
Timezone is a
JavaScript library that supports date arithmetic that is time zone
aware. It is freely available under the MIT license.
@tubular/time
supports live tzdb updates,
astronomical and atomic time, a command-line interface,
and full TypeScript.
Its companion @tubular/time-tzdb
can generate TZif and other files, and a companion website
Timezone Database Explorer lets you
convert timestamps, view transition histories, and download code and data.
It is freely available under the MIT license.
The Chronos Date/Time
Library is
a Smalltalk class
library that compiles tz source into a time
zone repository whose format
is either proprietary or an XML-encoded
representation.
Tcl
contains a developer-oriented parser that compiles tz
source into text files, along with a runtime that can read those
files. Tcl is freely available under a BSD-style
license.
Other TZif readers
The GNU C
Library
has an independent, thread-safe implementation of
a TZif file reader.
This library is freely available under the LGPL
and is widely used in GNU/Linux systems.
GNOME's
GLib has
a TZif file reader written in C that
creates a GTimeZone object representing sets
of UT offsets.
It is freely available under the LGPL.
The
BDE Standard Library's
baltzo::TimeZoneUtil component contains a C++
implementation of a TZif file reader. It is freely available under
the Apache License.
CCTZ is a simple C++
library that translates between UT and civil time and
can read TZif files. It is freely available under the Apache
License.
Timelib is a C
library that reads TZif files and converts
timestamps from one time zone or format to another.
It is used by PHP,
HHVM,
and MongoDB.
It is freely available under the MIT license.
Tcl, mentioned above, also contains a
TZif file reader.
DateTime::TimeZone::Tzfile
is a TZif file reader written in Perl.
It is freely available under the same terms as Perl
(dual GPL and Artistic license).
Python has a zoneinfo.ZoneInfo
class that reads TZif data and creates objects
that represent tzdb timezones.
Python is freely available under the
Python Software Foundation
License.
A companion PyPI module
tzdata
supplies TZif data if the underlying system data cannot be found;
it is freely available under the Apache License.
The
public-domain tz.js
library contains a Python tool that
converts TZif data into
JSON-format data suitable for use
in its JavaScript library for time zone conversion. Dates before 1970
are not supported.
The timezone-olson
package contains Haskell code that
parses and uses TZif data. It is freely
available under a BSD-style license.
Oracle
Java contains a copy of a subset of a recent
tz database in a
Java-specific format.
Other time zone databases
Time-zone Atlas
is Astrodienst's Web version of Shanks and Pottenger's out-of-print
time zone history atlases
for the US and
for the world.
Although these extensive atlases
were
sources for much of the older tz data,
they are unreliable as Shanks appears to have
guessed many UT offsets and transitions. The atlases cite no
sources and do not indicate which entries are guesswork.
The Standard
Schedules Information Manual of the
International Air Transport Association
gives current time zone rules for airports served by commercial aviation.
World Time Zone Map
with current time
has several fancy time zone maps; it covers Russia particularly well.
The maps' pictorial quality is not quite as good as the
CIA's
but the maps are more up to date.
How
much is time wrong around the world? maps the difference between
mean solar and standard time, highlighting areas such as western China
where the two differ greatly. It's a bit out of date, unfortunately.
Time zone boundaries
Geographical boundaries between timezones are available
from several Internet
geolocation
services and other sources.
A ship within the territorial
waters of any nation uses that nation's time. In international
waters, time zone boundaries are meridians 15° apart, except that
UT−12 and UT+12 are each 7.5°
wide and are separated by
the 180° meridian (not by the International Date Line, which is
for land and territorial waters only). A captain can change ship's
clocks any time after entering a new time zone; midnight changes are
common.
The Oceanography, Hydrography, and Meteorology Service of the Uruguayan
Navy (SOHMA) publishes an annual almanac
(in Spanish).
Costs and benefits of time shifts
Various sources argue for and against daylight saving time and time
zone shifts, and many scientific studies have been conducted. This
section summarizes reviews and position statements based on
scientific literature in the area.
Havranek T, Herman D, Irsova D.
Does
daylight saving save electricity? A meta-analysis.Energy J. 2018;39(2):35–61.
doi:10.5547/01956574.39.2.thav.
This analyzes research literature and concludes, "Electricity savings
are larger for countries farther away from the equator, while
subtropical regions consume more electricity because of DST."
Roenneberg T, Wirz-Justice A, Skene DJ et al.
Why
should we abolish Daylight Saving Time?J Biol Rhythms. 2019;34(3):227–230.
doi:10.1177/0748730419854197.
This position paper of the Society for Research on Biological Rhythms
opposes DST changes and permanent DST, and advocates that governments adopt
"permanent Standard Time for the health and safety of their citizens".
Precision timekeeping
The
Science of Timekeeping is a thorough introduction
to the theory and practice of precision timekeeping.
The Huygens
family of software algorithms can achieve accuracy to a few tens of
nanoseconds in scalable server farms without special hardware.
The Precision
Time Protocol (IEEE 1588)
can achieve submicrosecond clock accuracy on a local area network
with special-purpose hardware.
Timezone
Options for DHCP
(Internet RFC 4833)
specifies a DHCP
option for a server to configure
a client's time zone and daylight saving settings automatically.
Time
Scales describes astronomical time scales like
TDT,
TCG, and
TDB.
The IAU's SOFA
collection contains C and Fortran
code for converting among time scales like
TAI,
TDB, TDT and
UTC. It is freely available under the
SOFA license.
Mars24 Sunclock
– Time on Mars describes Airy Mean Time (AMT) and the
diverse local time
scales used by each landed mission on Mars.
LeapSecond.com is
dedicated not only to leap seconds but to precise time and frequency
in general. It covers the state of the art in amateur timekeeping, and
how the art has progressed over the past few decades.
The rules for leap seconds are specified in Annex 1 (Time scales) of Standard-frequency
and time-signal emissions, International Telecommunication Union –
Radiocommunication Sector (ITU-R) Recommendation TF.460-6 (02/2002).
IERS
Bulletins contains official publications of the International
Earth Rotation and Reference Systems Service, which decides when leap
seconds occur. The tz code and data support leap seconds
via an optional "right" configuration where a computer's internal
time_t integer clock counts every TAI second,
as opposed to the default "posix" configuration
where the internal clock ignores leap seconds.
The two configurations agree for timestamps starting with 1972-01-01 00:00:00
UTC (time_t 63 072 000) and diverge for
timestamps starting with time_t 78 796 800,
which corresponds to the first leap second
1972-06-30 23:59:60 UTC in the "right" configuration,
and to
1972-07-01 00:00:00 UTC in the "posix" configuration.
In practice the two configurations also agree for timestamps before
1972 even though the historical situation is messy, partly because
neither UTC nor TAI
-is well-defined for sufficiently-old timestamps.
+is well-defined for sufficiently old timestamps.
Leap Smear
discusses how to gradually adjust POSIX clocks near a
leap second so that they disagree with UTC by at most a
half second, even though every POSIX minute has exactly
sixty seconds. This approach works with the default tz
"posix" configuration, is supported by
the NTP reference implementation, supports conversion between
UTC and smeared POSIX timestamps, and is used by major
cloud service providers. However, according to
§3.7.1 of
Network Time Protocol Best Current Practices
(Internet RFC 8633), leap smearing is not suitable for
applications requiring accurate UTC or civil time,
and is intended for use only in single, well-controlled environments.
+contentious issue.
+The General Conference on Weights and Measures
+voted in 2022
+to discontinue the use of leap seconds by 2035, replacing them with an
+as-yet-undetermined scheme some time after the year 2135.
+
Time notation
The Unicode Common Locale Data
Repository (CLDR) Project has localizations for time
zone names, abbreviations, identifiers, and formats. For example, it
contains French translations for "Eastern European Summer Time",
"EEST", and
"Bucharest". Its
by-type
charts show these values for many locales. Data values are available in
both LDML
(an XML format) and JSON.
Alphabetic time zone abbreviations should not be used as unique
identifiers for UT offsets as they are ambiguous in
practice. For example, in English-speaking North America
"CST" denotes 6 hours behind UT,
but in China it denotes 8 hours ahead of UT,
and French-speaking North Americans prefer
"HNC" to
"CST". The tz
database contains English abbreviations for many timestamps;
unfortunately some of these abbreviations were merely the database maintainers'
inventions, and these have been removed when possible.
Numeric time zone abbreviations typically count hours east of
UT, e.g., +09 for Japan and
−10 for Hawaii. However, the POSIX
TZ environment variable uses the opposite convention.
For example, one might use TZ="JST-9" and
TZ="HST10"
for Japan and Hawaii, respectively. If the
tz database is available, it is usually better to use
settings like TZ="Asia/Tokyo" and
TZ="Pacific/Honolulu" instead, as this should avoid
confusion, handle old timestamps better, and insulate you better from
any future changes to the rules. One should never set
POSIX TZ to a value like
"GMT-9", though, since this would incorrectly imply that
local time is nine hours ahead of UT and the time zone
is called "GMT".
diff --git a/contrib/tzcode/tzfile.5 b/contrib/tzcode/tzfile.5
index b53abaab18d6..d0ca730f2c96 100644
--- a/contrib/tzcode/tzfile.5
+++ b/contrib/tzcode/tzfile.5
@@ -1,492 +1,489 @@
.\" This file is in the public domain, so clarified as of
.\" 1996-06-05 by Arthur David Olson.
-.\"
-.\" $FreeBSD$
-.\"
.Dd December 15, 2022
.Dt TZFILE 5
.Os
.Sh NAME
.Nm tzfile
.Nd timezone information
.Sh DESCRIPTION
The timezone information files used by
.Xr tzset 3
are found under
.Pa /usr/share/zoneinfo .
These files use the format described in Internet RFC 8536.
Each file is a sequence of 8-bit bytes.
In a file, a binary integer is represented by a sequence of one or
more bytes in network order (bigendian, or high-order byte first),
with all bits significant,
a signed binary integer is represented using two's complement,
and a boolean is represented by a one-byte binary integer that is
either 0 (false) or 1 (true).
The format begins with a 44-byte header containing the following fields:
.Pp
.Bl -bullet
.It
The magic four-byte ASCII sequence
.Dq "TZif"
identifies the file as a timezone information file.
.It
A byte identifying the version of the file's format
(as of 2021, either an ASCII NUL,
.Dq "2" ,
.Dq "3" ,
or
.Dq "4" ) .
.It
Fifteen bytes containing zeros reserved for future use.
.It
Six four-byte integer values, in the following order:
.Pp
.Bl -tag -compat -width tzh_ttisstdcnt
.It Va tzh_ttisutcnt
The number of UT/local indicators stored in the file.
(UT is Universal Time.)
.It Va tzh_ttisstdcnt
The number of standard/wall indicators stored in the file.
.It Va tzh_leapcnt
The number of leap seconds for which data entries are stored in the file.
.It Va tzh_timecnt
The number of transition times for which data entries are stored
in the file.
.It Va tzh_typecnt
The number of local time types for which data entries are stored
in the file (must not be zero).
.It Va tzh_charcnt
The number of bytes of time zone abbreviation strings
stored in the file.
.El
.El
.Pp
The above header is followed by the following fields, whose lengths
depend on the contents of the header:
.Bl -tag -compat -width tzh_timecnt
.It Va tzh_timecnt
four-byte signed integer values sorted in ascending order.
These values are written in network byte order.
Each is used as a transition time (as returned by
.Xt time 2 )
at which the rules for computing local time change.
.It Va tzh_timecnt
one-byte unsigned integer values;
each one but the last tells which of the different types of local time types
described in the file is associated with the time period
starting with the same-indexed transition time
and continuing up to but not including the next transition time.
(The last time type is present only for consistency checking with the
POSIX-style TZ string described below.)
These values serve as indices into the next field.
.It Va tzh_typecnt
.Vt ttinfo
entries, each defined as follows:
.Pp
.Bd -literal -offset indent
struct ttinfo {
int32_t tt_utoff;
unsigned char tt_isdst;
unsigned char tt_desigidx;
};
.Ed
.Pp
Each structure is written as a four-byte signed integer value for
.Va tt_utoff ,
in network byte order, followed by a one-byte boolean for
.Va tt_isdst
and a one-byte value for
.Va tt_desigidx .
In each structure,
.Va tt_utoff
gives the number of seconds to be added to UT,
.Va tt_isdst
tells whether
.Va tm_isdst
should be set by
.Xr localtime 3
and
.Va tt_desigidx
serves as an index into the array of time zone abbreviation bytes
that follow the
.Vt ttinfo
entries in the file; if the designated string is "\*-00", the
.Vt ttinfo
entry is a placeholder indicating that local time is unspecified.
The
.Va tt_utoff
value is never equal to \-2**31, to let 32-bit clients negate it without
overflow.
Also, in realistic applications
.Va tt_utoff
is in the range [\-89999, 93599] (i.e., more than \-25 hours and less
than 26 hours); this allows easy support by implementations that
already support the POSIX-required range [\-24:59:59, 25:59:59].
.It Va tzh_charcnt
bytes that represent time zone designations,
which are null-terminated byte strings, each indexed by the
.Va tt_desigidx
values mentioned above.
The byte strings can overlap if one is a suffix of the other.
The encoding of these strings is not specified.
.It Va tzh_leapcnt
pairs of four-byte values, written in network byte order;
the first value of each pair gives the nonnegative time
(as returned by
.Xr time 3 )
at which a leap second occurs or at which the leap second table expires;
the second is a signed integer specifying the correction, which is the
.Em total
number of leap seconds to be applied during the time period
starting at the given time.
The pairs of values are sorted in strictly ascending order by time.
Each pair denotes one leap second, either positive or negative,
except that if the last pair has the same correction as the previous one,
the last pair denotes the leap second table's expiration time.
Each leap second is at the end of a UTC calendar month.
The first leap second has a nonnegative occurrence time,
and is a positive leap second if and only if its correction is positive;
the correction for each leap second after the first differs
from the previous leap second by either 1 for a positive leap second,
or \-1 for a negative leap second.
If the leap second table is empty, the leap-second correction is zero
for all timestamps;
otherwise, for timestamps before the first occurrence time,
the leap-second correction is zero if the first pair's correction is 1 or \-1,
and is unspecified otherwise (which can happen only in files
truncated at the start).
.It Va tzh_ttisstdcnt
standard/wall indicators, each stored as a one-byte boolean;
they tell whether the transition times associated with local time types
were specified as standard time or local (wall clock) time.
.It Va tzh_ttisutcnt
UT/local indicators, each stored as a one-byte boolean;
they tell whether the transition times associated with local time types
were specified as UT or local time.
If a UT/local indicator is set, the corresponding standard/wall indicator
must also be set.
.El
.Pp
The standard/wall and UT/local indicators were designed for
transforming a TZif file's transition times into transitions appropriate
for another time zone specified via a POSIX-style TZ string that lacks rules.
For example, when TZ="EET\*-2EEST" and there is no TZif file "EET\*-2EEST",
the idea was to adapt the transition times from a TZif file with the
well-known name "posixrules" that is present only for this purpose and
is a copy of the file "Europe/Brussels", a file with a different UT offset.
POSIX does not specify this obsolete transformational behavior,
the default rules are installation-dependent, and no implementation
is known to support this feature for timestamps past 2037,
so users desiring (say) Greek time should instead specify
TZ="Europe/Athens" for better historical coverage, falling back on
TZ="EET\*-2EEST,M3.5.0/3,M10.5.0/4" if POSIX conformance is required
and older timestamps need not be handled accurately.
.Pp
The
.Xr localtime 3
function
normally uses the first
.Vt ttinfo
structure in the file
if either
.Va tzh_timecnt
is zero or the time argument is less than the first transition time recorded
in the file.
.Ss Version 2 format
For version-2-format timezone files,
the above header and data are followed by a second header and data,
identical in format except that
eight bytes are used for each transition time or leap second time.
(Leap second counts remain four bytes.)
After the second header and data comes a newline-enclosed,
POSIX-TZ-environment-variable-style string for use in handling instants
after the last transition time stored in the file
or for all instants if the file has no transitions.
The POSIX-style TZ string is empty (i.e., nothing between the newlines)
if there is no POSIX-style representation for such instants.
If nonempty, the POSIX-style TZ string must agree with the local time
type after the last transition time if present in the eight-byte data;
for example, given the string
-.Dq "WET0WEST,M3.5.0,M10.5.0/3"
+.Dq "WET0WEST,M3.5.0/1,M10.5.0"
then if a last transition time is in July, the transition's local time
type must specify a daylight-saving time abbreviated
.Dq "WEST"
that is one hour east of UT.
Also, if there is at least one transition, time type 0 is associated
with the time period from the indefinite past up to but not including
the earliest transition time.
.Ss Version 3 format
For version-3-format timezone files, the POSIX-TZ-style string may
use two minor extensions to the POSIX TZ format, as described in
.Xr newtzset 3 .
First, the hours part of its transition times may be signed and range from
\-167 through 167 instead of the POSIX-required unsigned values
from 0 through 24.
Second, DST is in effect all year if it starts
January 1 at 00:00 and ends December 31 at 24:00 plus the difference
between daylight saving and standard time.
.Ss Version 4 format
For version-4-format TZif files,
the first leap second record can have a correction that is neither
+1 nor \-1, to represent truncation of the TZif file at the start.
Also, if two or more leap second transitions are present and the last
entry's correction equals the previous one, the last entry
denotes the expiration of the leap second table instead of a leap second;
timestamps after this expiration are unreliable in that future
releases will likely add leap second entries after the expiration, and
the added leap seconds will change how post-expiration timestamps are treated.
.Ss Interoperability considerations
Future changes to the format may append more data.
.Pp
Version 1 files are considered a legacy format and
should not be generated, as they do not support transition
times after the year 2038.
Readers that understand only Version 1 must ignore
any data that extends beyond the calculated end of the version
1 data block.
.Pp
Other than version 1, writers should generate
the lowest version number needed by a file's data.
For example, a writer should generate a version 4 file
only if its leap second table either expires or is truncated at the start.
Likewise, a writer not generating a version 4 file
should generate a version 3 file only if
TZ string extensions are necessary to accurately
model transition times.
.Pp
The sequence of time changes defined by the version 1
header and data block should be a contiguous sub-sequence
of the time changes defined by the version 2+ header and data
block, and by the footer.
This guideline helps obsolescent version 1 readers
agree with current readers about timestamps within the
contiguous sub-sequence.
It also lets writers not
supporting obsolescent readers use a
.Va tzh_timecnt
of zero
in the version 1 data block to save space.
.Pp
When a TZif file contains a leap second table expiration
time, TZif readers should either refuse to process
post-expiration timestamps, or process them as if the expiration
time did not exist (possibly with an error indication).
.Pp
Time zone designations should consist of at least three (3)
and no more than six (6) ASCII characters from the set of
alphanumerics,
.Dq "\*-" ,
and
.Dq "+" .
This is for compatibility with POSIX requirements for
time zone abbreviations.
.Pp
When reading a version 2 or higher file, readers
should ignore the version 1 header and data block except for
the purpose of skipping over them.
.Pp
Readers should calculate the total lengths of the
headers and data blocks and check that they all fit within
the actual file size, as part of a validity check for the file.
.Pp
When a positive leap second occurs, readers should append an extra
second to the local minute containing the second just before the leap
second.
If this occurs when the UTC offset is not a multiple of 60
seconds, the leap second occurs earlier than the last second of the
local minute and the minute's remaining local seconds are numbered
through 60 instead of the usual 59; the UTC offset is unaffected.
.Ss Common interoperability issues
This section documents common problems in reading or writing TZif files.
Most of these are problems in generating TZif files for use by
older readers.
The goals of this section are:
.Bl -bullet
.It
to help TZif writers output files that avoid common
pitfalls in older or buggy TZif readers,
.It
to help TZif readers avoid common pitfalls when reading
files generated by future TZif writers, and
.It
to help any future specification authors see what sort of
problems arise when the TZif format is changed.
.El
.Pp
When new versions of the TZif format have been defined, a
design goal has been that a reader can successfully use a TZif
file even if the file is of a later TZif version than what the
reader was designed for.
When complete compatibility was not achieved, an attempt was
made to limit glitches to rarely used timestamps and allow
simple partial workarounds in writers designed to generate
new-version data useful even for older-version readers.
This section attempts to document these compatibility issues and
workarounds, as well as to document other common bugs in
readers.
.Pp
Interoperability problems with TZif include the following:
.Bl -bullet
.It
Some readers examine only version 1 data.
As a partial workaround, a writer can output as much version 1
data as possible.
However, a reader should ignore version 1 data, and should use
version 2+ data even if the reader's native timestamps have only
32 bits.
.It
Some readers designed for version 2 might mishandle
timestamps after a version 3 or higher file's last transition, because
they cannot parse extensions to POSIX in the TZ-like string.
As a partial workaround, a writer can output more transitions
than necessary, so that only far-future timestamps are
mishandled by version 2 readers.
.It
Some readers designed for version 2 do not support
permanent daylight saving time with transitions after 24:00
\(en e.g., a TZ string
.Dq "EST5EDT,0/0,J365/25"
denoting permanent Eastern Daylight Time
(\-04).
As a workaround, a writer can substitute standard time
for two time zones east, e.g.,
.Dq "XXX3EDT4,0/0,J365/23"
for a time zone with a never-used standard time (XXX, \-03)
and negative daylight saving time (EDT, \-04) all year.
Alternatively,
as a partial workaround a writer can substitute standard time
for the next time zone east \(en e.g.,
.Dq "AST4"
for permanent
Atlantic Standard Time (\-04).
.It
Some readers designed for version 2 or 3, and that require strict
conformance to RFC 8536, reject version 4 files whose leap second
tables are truncated at the start or that end in expiration times.
.It
Some readers ignore the footer, and instead predict future
timestamps from the time type of the last transition.
As a partial workaround, a writer can output more transitions
than necessary.
.It
Some readers do not use time type 0 for timestamps before
the first transition, in that they infer a time type using a
heuristic that does not always select time type 0.
As a partial workaround, a writer can output a dummy (no-op)
first transition at an early time.
.It
Some readers mishandle timestamps before the first
transition that has a timestamp not less than \-2**31.
Readers that support only 32-bit timestamps are likely to be
more prone to this problem, for example, when they process
64-bit transitions only some of which are representable in 32
bits.
As a partial workaround, a writer can output a dummy
transition at timestamp \-2**31.
.It
Some readers mishandle a transition if its timestamp has
the minimum possible signed 64-bit value.
Timestamps less than \-2**59 are not recommended.
.It
Some readers mishandle POSIX-style TZ strings that
contain
.Dq "<"
or
.Dq ">".
As a partial workaround, a writer can avoid using
.Dq "<"
or
.Dq ">"
for time zone abbreviations containing only alphabetic
characters.
.It
Many readers mishandle time zone abbreviations that contain
non-ASCII characters.
These characters are not recommended.
.It
Some readers may mishandle time zone abbreviations that
contain fewer than 3 or more than 6 characters, or that
contain ASCII characters other than alphanumerics,
.Dq "\*-",
and
.Dq "+".
These abbreviations are not recommended.
.It
Some readers mishandle TZif files that specify
daylight-saving time UT offsets that are less than the UT
offsets for the corresponding standard time.
These readers do not support locations like Ireland, which
uses the equivalent of the POSIX TZ string
.Dq "IST\*-1GMT0,M10.5.0,M3.5.0/1" ,
observing standard time
(IST, +01) in summer and daylight saving time (GMT, +00) in winter.
As a partial workaround, a writer can output data for the
equivalent of the POSIX TZ string
.Dq "GMT0IST,M3.5.0/1,M10.5.0" ,
thus swapping standard and daylight saving time.
Although this workaround misidentifies which part of the year
uses daylight saving time, it records UT offsets and time zone
abbreviations correctly.
.It
Some readers generate ambiguous timestamps for positive leap seconds
that occur when the UTC offset is not a multiple of 60 seconds.
For example, in a timezone with UTC offset +01:23:45 and with
a positive leap second 78796801 (1972-06-30 23:59:60 UTC), some readers will
map both 78796800 and 78796801 to 01:23:45 local time the next day
instead of mapping the latter to 01:23:46, and they will map 78796815 to
01:23:59 instead of to 01:23:60.
This has not yet been a practical problem, since no civil authority
has observed such UTC offsets since leap seconds were
introduced in 1972.
.El
.Pp
Some interoperability problems are reader bugs that
are listed here mostly as warnings to developers of readers.
.Bl -bullet
.It
Some readers do not support negative timestamps.
Developers of distributed applications should keep this
in mind if they need to deal with pre-1970 data.
.It
Some readers mishandle timestamps before the first
transition that has a nonnegative timestamp.
Readers that do not support negative timestamps are likely to
be more prone to this problem.
.It
Some readers mishandle time zone abbreviations like
.Dq "\*-08"
that contain
.Dq "+" ,
.Dq "\*-" ,
or digits.
.It
Some readers mishandle UT offsets that are out of the
traditional range of \-12 through +12 hours, and so do not
support locations like Kiritimati that are outside this
range.
.It
Some readers mishandle UT offsets in the range [\-3599, \-1]
seconds from UT, because they integer-divide the offset by
3600 to get 0 and then display the hour part as
.Dq "+00" .
.It
Some readers mishandle UT offsets that are not a multiple
of one hour, or of 15 minutes, or of 1 minute.
.El
.Sh SEE ALSO
.Xr time 3 ,
.Xr localtime 3 ,
.Xr tzset 3 ,
.Xr tzsetup 8 ,
.Xr zic 8 ,
.Xr zdump 8
.Rs
.%A A. Olson
.%A P. Eggert
.%A K. Murchison
.%T "The Time Zone Information Format (TZif)"
.%R RFC 8536
.%D February 2019
.%U https://datatracker.ietf.org/doc/html/rfc8536
.%U https://doi.org/10.17487/RFC8536
.Re
diff --git a/contrib/tzcode/tzfile.h b/contrib/tzcode/tzfile.h
index e59d2ea4bb47..3764f36d4d92 100644
--- a/contrib/tzcode/tzfile.h
+++ b/contrib/tzcode/tzfile.h
@@ -1,125 +1,129 @@
/* Layout and location of TZif files. */
#ifndef TZFILE_H
#define TZFILE_H
/*
** This file is in the public domain, so clarified as of
** 1996-06-05 by Arthur David Olson.
**
** $FreeBSD$
*/
/*
** This header is for use ONLY with the time conversion code.
** There is no guarantee that it will remain unchanged,
** or that it will remain at all.
** Do NOT copy it to any system include directory.
** Thank you!
*/
/*
** Information about time zone files.
*/
#ifndef TZDIR
# define TZDIR "/usr/share/zoneinfo" /* Time zone object file directory */
#endif /* !defined TZDIR */
#ifndef TZDEFAULT
# define TZDEFAULT "/etc/localtime"
#endif /* !defined TZDEFAULT */
#ifndef TZDEFRULES
# define TZDEFRULES "posixrules"
#endif /* !defined TZDEFRULES */
/* See Internet RFC 8536 for more details about the following format. */
/*
** Each file begins with. . .
*/
#define TZ_MAGIC "TZif"
struct tzhead {
char tzh_magic[4]; /* TZ_MAGIC */
char tzh_version[1]; /* '\0' or '2'-'4' as of 2021 */
char tzh_reserved[15]; /* reserved; must be zero */
char tzh_ttisutcnt[4]; /* coded number of trans. time flags */
char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */
char tzh_leapcnt[4]; /* coded number of leap seconds */
char tzh_timecnt[4]; /* coded number of transition times */
char tzh_typecnt[4]; /* coded number of local time types */
char tzh_charcnt[4]; /* coded number of abbr. chars */
};
/*
** . . .followed by. . .
**
** tzh_timecnt (char [4])s coded transition times a la time(2)
** tzh_timecnt (unsigned char)s types of local time starting at above
** tzh_typecnt repetitions of
** one (char [4]) coded UT offset in seconds
** one (unsigned char) used to set tm_isdst
** one (unsigned char) that's an abbreviation list index
** tzh_charcnt (char)s '\0'-terminated zone abbreviations
** tzh_leapcnt repetitions of
** one (char [4]) coded leap second transition times
** one (char [4]) total correction after above
** tzh_ttisstdcnt (char)s indexed by type; if 1, transition
** time is standard time, if 0,
** transition time is local (wall clock)
** time; if absent, transition times are
** assumed to be local time
** tzh_ttisutcnt (char)s indexed by type; if 1, transition
** time is UT, if 0, transition time is
** local time; if absent, transition
** times are assumed to be local time.
** When this is 1, the corresponding
** std/wall indicator must also be 1.
*/
/*
** If tzh_version is '2' or greater, the above is followed by a second instance
** of tzhead and a second instance of the data in which each coded transition
** time uses 8 rather than 4 chars,
** then a POSIX-TZ-environment-variable-style string for use in handling
** instants after the last transition time stored in the file
** (with nothing between the newlines if there is no POSIX representation for
** such instants).
**
** If tz_version is '3' or greater, the above is extended as follows.
** First, the POSIX TZ string's hour offset may range from -167
** through 167 as compared to the POSIX-required 0 through 24.
** Second, its DST start time may be January 1 at 00:00 and its stop
** time December 31 at 24:00 plus the difference between DST and
** standard time, indicating DST all year.
*/
/*
** In the current implementation, "tzset()" refuses to deal with files that
** exceed any of the limits below.
*/
#ifndef TZ_MAX_TIMES
+/* This must be at least 242 for Europe/London with 'zic -b fat'. */
# define TZ_MAX_TIMES 2000
#endif /* !defined TZ_MAX_TIMES */
#ifndef TZ_MAX_TYPES
-/* This must be at least 17 for Europe/Samara and Europe/Vilnius. */
+/* This must be at least 18 for Europe/Vilnius with 'zic -b fat'. */
# define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */
#endif /* !defined TZ_MAX_TYPES */
#ifndef TZ_MAX_CHARS
+/* This must be at least 40 for America/Anchorage. */
# define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */
/* (limited by what unsigned chars can hold) */
#endif /* !defined TZ_MAX_CHARS */
#ifndef TZ_MAX_LEAPS
+/* This must be at least 27 for leap seconds from 1972 through mid-2023.
+ There's a plan to discontinue leap seconds by 2035. */
# define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */
#endif /* !defined TZ_MAX_LEAPS */
#endif /* !defined TZFILE_H */
diff --git a/contrib/tzcode/tzselect.8 b/contrib/tzcode/tzselect.8
index 846b867be1c0..4578090f9ea7 100644
--- a/contrib/tzcode/tzselect.8
+++ b/contrib/tzcode/tzselect.8
@@ -1,125 +1,125 @@
.\" This file is in the public domain, so clarified as of
.\" 2009-05-17 by Arthur David Olson.
-.TH TZSELECT 8
+.TH tzselect 8 "" "Time Zone Database"
.SH NAME
tzselect \- select a timezone
.SH SYNOPSIS
.ie \n(.g .ds - \f(CR-\fP
.el .ds - \-
.ds d " degrees
.ds m " minutes
.ds s " seconds
.ds _ " \&
.if t \{\
. if \n(.g .if c \(de .if c \(fm .if c \(sd \{\
. ds d \(de
. ds m \(fm
. ds s \(sd
. ds _ \|
. \}
.\}
.B tzselect
[
.B \*-c
.I coord
] [
.B \*-n
.I limit
] [
.B \*-\*-help
] [
.B \*-\*-version
]
.SH DESCRIPTION
The
.B tzselect
program asks the user for information about the current location,
and outputs the resulting timezone to standard output.
The output is suitable as a value for the TZ environment variable.
.PP
All interaction with the user is done via standard input and standard error.
.SH OPTIONS
.TP
.BI "\*-c " coord
Instead of asking for continent and then country and then city,
ask for selection from time zones whose largest cities
are closest to the location with geographical coordinates
.I coord.
Use ISO 6709 notation for
.I coord,
that is, a latitude immediately followed by a longitude. The latitude
and longitude should be signed integers followed by an optional
decimal point and fraction: positive numbers represent north and east,
negative south and west. Latitudes with two and longitudes with three
integer digits are treated as degrees; latitudes with four or six and
longitudes with five or seven integer digits are treated as
.I "DDMM, DDDMM, DDMMSS,"
or
.I DDDMMSS
representing
.I DD
or
.I DDD
degrees,
.I MM
minutes,
and zero or
.I SS
seconds, with any trailing fractions represent fractional minutes or
(if
.I SS
is present) seconds. The decimal point is that of the current locale.
For example, in the (default) C locale,
.B "\*-c\ +40.689\*-074.045"
specifies 40.689\*d\*_N, 74.045\*d\*_W,
.B "\*-c\ +4041.4\*-07402.7"
specifies 40\*d\*_41.4\*m\*_N, 74\*d\*_2.7\*m\*_W, and
.B "\*-c\ +404121\*-0740240"
specifies 40\*d\*_41\*m\*_21\*s\*_N, 74\*d\*_2\*m\*_40\*s\*_W.
If
.I coord
is not one of the documented forms, the resulting behavior is unspecified.
.TP
.BI "\*-n " limit
When
.B \*-c
is used, display the closest
.I limit
locations (default 10).
.TP
.B "\*-\*-help"
Output help information and exit.
.TP
.B "\*-\*-version"
Output version information and exit.
.SH "ENVIRONMENT VARIABLES"
.TP
\f3AWK\fP
Name of a Posix-compliant
.B awk
program (default:
.BR awk ).
.TP
\f3TZDIR\fP
Name of the directory containing timezone data files (default:
.BR /usr/share/zoneinfo ).
.SH FILES
.TP
\f2TZDIR\fP\f3/iso3166.tab\fP
Table of ISO 3166 2-letter country codes and country names.
.TP
\f2TZDIR\fP\f3/zone1970.tab\fP
Table of country codes, latitude and longitude, timezones, and
descriptive comments.
.TP
\f2TZDIR\fP\f3/\fP\f2TZ\fP
Timezone data file for timezone \f2TZ\fP.
.SH "EXIT STATUS"
The exit status is zero if a timezone was successfully obtained from the user,
nonzero otherwise.
.SH "SEE ALSO"
newctime(3), tzfile(5), zdump(8), zic(8)
.SH NOTES
Applications should not assume that
.BR tzselect 's
output matches the user's political preferences.
diff --git a/contrib/tzcode/tzselect.ksh b/contrib/tzcode/tzselect.ksh
index 28c32a2430f8..9a91acfc7412 100644
--- a/contrib/tzcode/tzselect.ksh
+++ b/contrib/tzcode/tzselect.ksh
@@ -1,586 +1,693 @@
#!/bin/bash
# Ask the user about the time zone, and output the resulting TZ value to stdout.
# Interact with the user via stderr and stdin.
PKGVERSION='(tzcode) '
TZVERSION=see_Makefile
REPORT_BUGS_TO=tz@iana.org
# Contributed by Paul Eggert. This file is in the public domain.
# Porting notes:
#
# This script requires a Posix-like shell and prefers the extension of a
# 'select' statement. The 'select' statement was introduced in the
# Korn shell and is available in Bash and other shell implementations.
# If your host lacks both Bash and the Korn shell, you can get their
# source from one of these locations:
#
# Bash
# Korn Shell
# MirBSD Korn Shell
#
# For portability to Solaris 10 /bin/sh (supported by Oracle through
# January 2024) this script avoids some POSIX features and common
# extensions, such as $(...) (which works sometimes but not others),
# $((...)), ! CMD, ${#ID}, ${ID##PAT}, ${ID%%PAT}, and $10.
#
# This script also uses several features of modern awk programs.
# If your host lacks awk, or has an old awk that does not conform to Posix,
# you can use either of the following free programs instead:
#
# Gawk (GNU awk)
# mawk
# nawk
# Specify default values for environment variables if they are unset.
: ${AWK=awk}
: ${TZDIR=`pwd`}
-# Output one argument as-is to standard output.
+# Output one argument as-is to standard output, with trailing newline.
# Safer than 'echo', which can mishandle '\' or leading '-'.
say() {
printf '%s\n' "$1"
}
# Check for awk Posix compliance.
($AWK -v x=y 'BEGIN { exit 123 }') /dev/null 2>&1
[ $? = 123 ] || {
say >&2 "$0: Sorry, your '$AWK' program is not Posix compatible."
exit 1
}
coord=
location_limit=10
zonetabtype=zone1970
usage="Usage: tzselect [--version] [--help] [-c COORD] [-n LIMIT]
Select a timezone interactively.
Options:
-c COORD
Instead of asking for continent and then country and then city,
ask for selection from time zones whose largest cities
are closest to the location with geographical coordinates COORD.
COORD should use ISO 6709 notation, for example, '-c +4852+00220'
for Paris (in degrees and minutes, North and East), or
'-c -35-058' for Buenos Aires (in degrees, South and West).
-n LIMIT
Display at most LIMIT locations when -c is used (default $location_limit).
--version
Output version information.
--help
Output this help.
Report bugs to $REPORT_BUGS_TO."
# Ask the user to select from the function's arguments,
# and assign the selected argument to the variable 'select_result'.
-# Exit on EOF or I/O error. Use the shell's 'select' builtin if available,
-# falling back on a less-nice but portable substitute otherwise.
+# Exit on EOF or I/O error. Use the shell's nicer 'select' builtin if
+# available, falling back on a portable substitute otherwise.
if
case $BASH_VERSION in
?*) : ;;
'')
# '; exit' should be redundant, but Dash doesn't properly fail without it.
(eval 'set --; select x; do break; done; exit') /dev/null
esac
then
# Do this inside 'eval', as otherwise the shell might exit when parsing it
# even though it is never executed.
eval '
doselect() {
select select_result
do
case $select_result in
"") echo >&2 "Please enter a number in range." ;;
?*) break
esac
done || exit
}
'
else
doselect() {
# Field width of the prompt numbers.
select_width=`expr $# : '.*'`
select_i=
while :
do
case $select_i in
'')
select_i=0
for select_word
do
select_i=`expr $select_i + 1`
printf >&2 "%${select_width}d) %s\\n" $select_i "$select_word"
done ;;
*[!0-9]*)
echo >&2 'Please enter a number in range.' ;;
*)
if test 1 -le $select_i && test $select_i -le $#; then
shift `expr $select_i - 1`
select_result=$1
break
fi
echo >&2 'Please enter a number in range.'
esac
# Prompt and read input.
printf >&2 %s "${PS3-#? }"
read select_i || exit
done
}
fi
while getopts c:n:t:-: opt
do
case $opt$OPTARG in
c*)
coord=$OPTARG ;;
n*)
location_limit=$OPTARG ;;
t*) # Undocumented option, used for developer testing.
zonetabtype=$OPTARG ;;
-help)
exec echo "$usage" ;;
-version)
exec echo "tzselect $PKGVERSION$TZVERSION" ;;
-*)
say >&2 "$0: -$opt$OPTARG: unknown option; try '$0 --help'"; exit 1 ;;
*)
say >&2 "$0: try '$0 --help'"; exit 1 ;;
esac
done
shift `expr $OPTIND - 1`
case $# in
0) ;;
*) say >&2 "$0: $1: unknown argument"; exit 1 ;;
esac
# Make sure the tables are readable.
TZ_COUNTRY_TABLE=$TZDIR/iso3166.tab
TZ_ZONE_TABLE=$TZDIR/$zonetabtype.tab
for f in $TZ_COUNTRY_TABLE $TZ_ZONE_TABLE
do
<"$f" || {
say >&2 "$0: time zone files are not set up correctly"
exit 1
}
done
# If the current locale does not support UTF-8, convert data to current
# locale's format if possible, as the shell aligns columns better that way.
# Check the UTF-8 of U+12345 CUNEIFORM SIGN URU TIMES KI.
$AWK 'BEGIN { u12345 = "\360\222\215\205"; exit length(u12345) != 1 }' || {
{ tmp=`(mktemp -d) 2>/dev/null` || {
tmp=${TMPDIR-/tmp}/tzselect.$$ &&
(umask 77 && mkdir -- "$tmp")
};} &&
trap 'status=$?; rm -fr -- "$tmp"; exit $status' 0 HUP INT PIPE TERM &&
(iconv -f UTF-8 -t //TRANSLIT <"$TZ_COUNTRY_TABLE" >$tmp/iso3166.tab) \
2>/dev/null &&
TZ_COUNTRY_TABLE=$tmp/iso3166.tab &&
iconv -f UTF-8 -t //TRANSLIT <"$TZ_ZONE_TABLE" >$tmp/$zonetabtype.tab &&
TZ_ZONE_TABLE=$tmp/$zonetabtype.tab
}
newline='
'
IFS=$newline
+# Awk script to output a country list.
+output_country_list='
+ BEGIN { FS = "\t" }
+ /^#$/ { next }
+ /^#[^@]/ { next }
+ {
+ commentary = $0 ~ /^#@/
+ if (commentary) {
+ col1ccs = substr($1, 3)
+ conts = $2
+ } else {
+ col1ccs = $1
+ conts = $3
+ }
+ ncc = split(col1ccs, cc, /,/)
+ ncont = split(conts, cont, /,/)
+ for (i = 1; i <= ncc; i++) {
+ elsewhere = commentary
+ for (ci = 1; ci <= ncont; ci++) {
+ if (cont[ci] ~ continent_re) {
+ if (!cc_seen[cc[i]]++) cc_list[++ccs] = cc[i]
+ elsewhere = 0
+ }
+ }
+ if (elsewhere) {
+ for (i = 1; i <= ncc; i++) {
+ cc_elsewhere[cc[i]] = 1
+ }
+ }
+ }
+ }
+ END {
+ while (getline &2 'Please identify a location' \
'so that time zone rules can be set correctly.'
continent=
country=
region=
case $coord in
?*)
continent=coord;;
'')
# Ask the user for continent or ocean.
- echo >&2 'Please select a continent, ocean, "coord", or "TZ".'
+ echo >&2 'Please select a continent, ocean, "coord", "TZ", or "time".'
quoted_continents=`
$AWK '
function handle_entry(entry) {
entry = substr(entry, 1, index(entry, "/") - 1)
if (entry == "America")
entry = entry "s"
if (entry ~ /^(Arctic|Atlantic|Indian|Pacific)$/)
entry = entry " Ocean"
printf "'\''%s'\''\n", entry
}
BEGIN { FS = "\t" }
/^[^#]/ {
handle_entry($3)
}
/^#@/ {
ncont = split($2, cont, /,/)
for (ci = 1; ci <= ncont; ci++) {
handle_entry(cont[ci])
}
}
' <"$TZ_ZONE_TABLE" |
sort -u |
tr '\n' ' '
echo ''
`
eval '
doselect '"$quoted_continents"' \
"coord - I want to use geographical coordinates." \
- "TZ - I want to specify the timezone using the Posix TZ format."
+ "TZ - I want to specify the timezone using the Posix TZ format." \
+ "time - I know local time already."
continent=$select_result
case $continent in
Americas) continent=America;;
*" "*) continent=`expr "$continent" : '\''\([^ ]*\)'\''`
esac
'
esac
case $continent in
TZ)
# Ask the user for a Posix TZ string. Check that it conforms.
while
echo >&2 'Please enter the desired value' \
'of the TZ environment variable.'
echo >&2 'For example, AEST-10 is abbreviated' \
'AEST and is 10 hours'
echo >&2 'ahead (east) of Greenwich,' \
'with no daylight saving time.'
read TZ
$AWK -v TZ="$TZ" 'BEGIN {
tzname = "(<[[:alnum:]+-]{3,}>|[[:alpha:]]{3,})"
time = "(2[0-4]|[0-1]?[0-9])" \
"(:[0-5][0-9](:[0-5][0-9])?)?"
offset = "[-+]?" time
mdate = "M([1-9]|1[0-2])\\.[1-5]\\.[0-6]"
jdate = "((J[1-9]|[0-9]|J?[1-9][0-9]" \
"|J?[1-2][0-9][0-9])|J?3[0-5][0-9]|J?36[0-5])"
datetime = ",(" mdate "|" jdate ")(/" time ")?"
tzpattern = "^(:.*|" tzname offset "(" tzname \
"(" offset ")?(" datetime datetime ")?)?)$"
if (TZ ~ tzpattern) exit 1
exit 0
}'
do
say >&2 "'$TZ' is not a conforming Posix timezone string."
done
TZ_for_date=$TZ;;
*)
case $continent in
coord)
case $coord in
'')
echo >&2 'Please enter coordinates' \
'in ISO 6709 notation.'
echo >&2 'For example, +4042-07403 stands for'
echo >&2 '40 degrees 42 minutes north,' \
'74 degrees 3 minutes west.'
read coord;;
esac
distance_table=`$AWK \
-v coord="$coord" \
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
- "$output_distances" <"$TZ_ZONE_TABLE" |
+ "$output_distances_or_times" <"$TZ_ZONE_TABLE" |
sort -n |
sed "${location_limit}q"
`
- regions=`say "$distance_table" | $AWK '
- BEGIN { FS = "\t" }
- { print $NF }
+ regions=`$AWK \
+ -v distance_table="$distance_table" '
+ BEGIN {
+ nlines = split(distance_table, line, /\n/)
+ for (nr = 1; nr <= nlines; nr++) {
+ nf = split(line[nr], f, /\t/)
+ print f[nf]
+ }
+ }
'`
- echo >&2 'Please select one of the following timezones,' \
+ echo >&2 'Please select one of the following timezones,'
echo >&2 'listed roughly in increasing order' \
"of distance from $coord".
doselect $regions
region=$select_result
- TZ=`say "$distance_table" | $AWK -v region="$region" '
- BEGIN { FS="\t" }
- $NF == region { print $4 }
+ TZ=`$AWK \
+ -v distance_table="$distance_table" \
+ -v region="$region" '
+ BEGIN {
+ nlines = split(distance_table, line, /\n/)
+ for (nr = 1; nr <= nlines; nr++) {
+ nf = split(line[nr], f, /\t/)
+ if (f[nf] == region) {
+ print f[4]
+ }
+ }
+ }
'`
;;
*)
- # Get list of names of countries in the continent or ocean.
- countries=`$AWK \
+ case $continent in
+ time)
+ minute_format='%a %b %d %H:%M'
+ old_minute=`TZ=UTC0 date +"$minute_format"`
+ for i in 1 2 3
+ do
+ time_table_command=`
+ $AWK -v output_times=1 \
+ "$output_distances_or_times" <"$TZ_ZONE_TABLE"
+ `
+ time_table=`eval "$time_table_command"`
+ new_minute=`TZ=UTC0 date +"$minute_format"`
+ case $old_minute in
+ "$new_minute") break;;
+ esac
+ old_minute=$new_minute
+ done
+ echo >&2 "The system says Universal Time is $new_minute."
+ echo >&2 "Assuming that's correct, what is the local time?"
+ eval doselect `
+ say "$time_table" |
+ sort -k2n -k2,5 -k1n |
+ $AWK '{
+ line = $6 " " $7 " " $4 " " $5
+ if (line == oldline) next
+ oldline = line
+ gsub(/'\''/, "&\\\\&&", line)
+ printf "'\''%s'\''\n", line
+ }'
+ `
+ time=$select_result
+ zone_table=`
+ say "$time_table" |
+ $AWK -v time="$time" '{
+ if ($6 " " $7 " " $4 " " $5 == time) {
+ sub(/[^\t]*\t/, "")
+ print
+ }
+ }'
+ `
+ countries=`
+ say "$zone_table" |
+ $AWK \
+ -v continent_re='' \
+ -v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
+ "$output_country_list" |
+ sort -f
+ `
+ ;;
+ *)
+ zone_table=file
+ # Get list of names of countries in the continent or ocean.
+ countries=`$AWK \
-v continent_re="^$continent/" \
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
- '
- BEGIN { FS = "\t" }
- /^#$/ { next }
- /^#[^@]/ { next }
- {
- commentary = $0 ~ /^#@/
- if (commentary) {
- col1ccs = substr($1, 3)
- conts = $2
- } else {
- col1ccs = $1
- conts = $3
- }
- ncc = split(col1ccs, cc, /,/)
- ncont = split(conts, cont, /,/)
- for (i = 1; i <= ncc; i++) {
- elsewhere = commentary
- for (ci = 1; ci <= ncont; ci++) {
- if (cont[ci] ~ continent_re) {
- if (!cc_seen[cc[i]]++) cc_list[++ccs] = cc[i]
- elsewhere = 0
- }
- }
- if (elsewhere) {
- for (i = 1; i <= ncc; i++) {
- cc_elsewhere[cc[i]] = 1
- }
- }
- }
- }
- END {
- while (getline &2 'Please select a country' \
'whose clocks agree with yours.'
doselect $countries
+ country_result=$select_result
country=$select_result;;
*)
country=$countries
esac
# Get list of timezones in the country.
- regions=`$AWK \
+ regions=`
+ case $zone_table in
+ file) cat -- "$TZ_ZONE_TABLE";;
+ *) say "$zone_table";;
+ esac |
+ $AWK \
-v country="$country" \
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
- '
+ '
BEGIN {
FS = "\t"
cc = country
while (getline &2 'Please select one of the following timezones.'
doselect $regions
- region=$select_result;;
- *)
- region=$regions
+ region=$select_result
esac
# Determine TZ from country and region.
- TZ=`$AWK \
+ TZ=`
+ case $zone_table in
+ file) cat -- "$TZ_ZONE_TABLE";;
+ *) say "$zone_table";;
+ esac |
+ $AWK \
-v country="$country" \
-v region="$region" \
-v TZ_COUNTRY_TABLE="$TZ_COUNTRY_TABLE" \
- '
+ '
BEGIN {
FS = "\t"
cc = country
while (getline &2 "$0: time zone files are not set up correctly"
exit 1
}
esac
# Use the proposed TZ to output the current date relative to UTC.
# Loop until they agree in seconds.
# Give up after 8 unsuccessful tries.
extra_info=
for i in 1 2 3 4 5 6 7 8
do
TZdate=`LANG=C TZ="$TZ_for_date" date`
UTdate=`LANG=C TZ=UTC0 date`
TZsec=`expr "$TZdate" : '.*:\([0-5][0-9]\)'`
UTsec=`expr "$UTdate" : '.*:\([0-5][0-9]\)'`
case $TZsec in
$UTsec)
extra_info="
Selected time is now: $TZdate.
Universal Time is now: $UTdate."
break
esac
done
# Output TZ info and ask the user to confirm.
echo >&2 ""
- echo >&2 "The following information has been given:"
+ echo >&2 "Based on the following information:"
echo >&2 ""
- case $country%$region%$coord in
- ?*%?*%) say >&2 " $country$newline $region";;
- ?*%%) say >&2 " $country";;
- %?*%?*) say >&2 " coord $coord$newline $region";;
- %%?*) say >&2 " coord $coord";;
+ case $time%$country_result%$region%$coord in
+ ?*%?*%?*%)
+ say >&2 " $time$newline $country_result$newline $region";;
+ ?*%?*%%|?*%%?*%) say >&2 " $time$newline $country_result$region";;
+ ?*%%%) say >&2 " $time";;
+ %?*%?*%) say >&2 " $country_result$newline $region";;
+ %?*%%) say >&2 " $country_result";;
+ %%?*%?*) say >&2 " coord $coord$newline $region";;
+ %%%?*) say >&2 " coord $coord";;
*) say >&2 " TZ='$TZ'"
esac
say >&2 ""
- say >&2 "Therefore TZ='$TZ' will be used.$extra_info"
+ say >&2 "TZ='$TZ' will be used.$extra_info"
say >&2 "Is the above information OK?"
doselect Yes No
ok=$select_result
case $ok in
Yes) break
esac
do coord=
done
case $SHELL in
*csh) file=.login line="setenv TZ '$TZ'";;
*) file=.profile line="TZ='$TZ'; export TZ"
esac
test -t 1 && say >&2 "
You can make this change permanent for yourself by appending the line
$line
to the file '$file' in your home directory; then log out and log in again.
Here is that TZ value again, this time on standard output so that you
can use the $0 command in shell scripts:"
say "$TZ"
diff --git a/contrib/tzcode/version b/contrib/tzcode/version
index b74fa117a223..7daa77e00d99 100644
--- a/contrib/tzcode/version
+++ b/contrib/tzcode/version
@@ -1 +1 @@
-2022g
+2023c
diff --git a/contrib/tzcode/zdump.8 b/contrib/tzcode/zdump.8
index 0b9e956526cc..7a78f6b9c040 100644
--- a/contrib/tzcode/zdump.8
+++ b/contrib/tzcode/zdump.8
@@ -1,231 +1,228 @@
.\" This file is in the public domain, so clarified as of
.\" 2009-05-17 by Arthur David Olson.
-.\"
-.\" $FreeBSD$
-.\"
.Dd December 15, 2022
.Dt ZDUMP 8
.Os
.Sh NAME
.Nm zdump
.Nd timezone dumper
.Sh SYNOPSIS
.Nm
.Op Fl -help
.Op Fl -version
.Op Fl ivV
.Oo
.Fl c
.Op Ar loyear , Ns
.Ar hiyear
.Oc
.Oo
.Fl t
.Op Ar lotime , Ns
.Ar hitime
.Oc
.Op Ar timezone ...
.Sh DESCRIPTION
The
.Nm
program prints the current time in each
.Ar timezone
named on the command line.
.Pp
The following options are available:
.Bl -tag -width indent
.It Fl -version
Output version information and exit.
.It Fl -help
Output short usage message and exit.
.It Fl i
Output a description of time intervals.
For each
.Ar timezone
on the command line, output an interval-format description of the
timezone.
See
.Sx "INTERVAL FORMAT"
below.
.It Fl v
Output a verbose description of time intervals.
For each
.Ar timezone
on the command line,
print the times at the two extreme time values,
the times (if present) at and just beyond the boundaries of years that
.Xr localtime 3
and
.Xr gmtime 3
can represent, and
the times both one second before and exactly at
each detected time discontinuity.
Each line is followed by
.Cm isdst= Ns Ar D
where
.Ar D
is positive, zero, or negative depending on whether
the given time is daylight saving time, standard time,
or an unknown time type, respectively.
Each line is also followed by
.Cm gmtoff= Ns Ar N
if the given local time is known to be
.Ar N
seconds east of Greenwich.
.It Fl V
Like
.Fl v ,
except omit output concerning extreme time and year values.
This generates output that is easier to compare to that of
implementations with different time representations.
.It Fl c Oo Ar loyear , Oc Ns Ar hiyear
Cut off interval output at the given year(s).
Cutoff times are computed using the proleptic Gregorian calendar with year 0
and with Universal Time (UT) ignoring leap seconds.
Cutoffs are at the start of each year, where the lower-bound
timestamp is inclusive and the upper is exclusive; for example,
.Ql "-c 1970,2070"
selects transitions on or after 1970-01-01 00:00:00 UTC
and before 2070-01-01 00:00:00 UTC.
The default cutoff is
.Ql -500,2500 .
.It Fl t Oo Ar lotime , Oc Ns Ar hitime
Cut off interval output at the given time(s),
given in decimal seconds since 1970-01-01 00:00:00
Coordinated Universal Time (UTC).
The
.Ar timezone
determines whether the count includes leap seconds.
As with
.Fl c ,
the cutoff's lower bound is inclusive and its upper bound is exclusive.
.El
.Sh "INTERVAL FORMAT"
The interval format is a compact text representation that is intended
to be both human- and machine-readable.
It consists of an empty line,
then a line
.Dq "TZ=\fIstring\fP"
where
.Ar string
is a double-quoted string giving the timezone, a second line
.Dq "\*- \*- \fIinterval\fP"
describing the time interval before the first transition if any, and
zero or more following lines
.Dq "\fIdate time interval\fP",
one line for each transition time and following interval.
Fields are
separated by single tabs.
.Pp
Dates are in
.Ql "yyyy - mm - dd"
format and times are in 24-hour
.Ql "hh : mm : ss"
format where
.Ql "hh <24" .
Times are in local time immediately after the transition.
A
time interval description consists of a UT offset in signed
.Ql "\(+- hhmmss"
format, a time zone abbreviation, and an isdst flag.
An abbreviation
that equals the UT offset is omitted; other abbreviations are
double-quoted strings unless they consist of one or more alphabetic
characters.
An isdst flag is omitted for standard time, and otherwise
is a decimal integer that is unsigned and positive (typically 1) for
daylight saving time and negative for unknown.
.Pp
In times and in UT offsets with absolute value less than 100 hours,
the seconds are omitted if they are zero, and
the minutes are also omitted if they are also zero.
Positive UT
offsets are east of Greenwich.
The UT offset \*-00 denotes a UT
placeholder in areas where the actual offset is unspecified; by
convention, this occurs when the UT offset is zero and the time zone
abbreviation begins with
.Dq "-"
or is
.Dq "zzz".
.Pp
In double-quoted strings, escape sequences represent unusual
characters.
The escape sequences are \es for space, and \e", \e\e,
\ef, \en, \er, \et, and \ev with their usual meaning in the C
programming language.
E.g., the double-quoted string
\*(lq"CET\es\e"\e\e"\*(rq represents the character sequence \*(lqCET
"\e\*(rq.\""
.Pp
Here is an example of the output, with the leading empty line omitted.
(This example is shown with tab stops set far enough apart so that the
tabbed columns line up.)
.Bd -literal -offset indent
TZ="Pacific/Honolulu"
- - -103126 LMT
1896-01-13 12:01:26 -1030 HST
1933-04-30 03 -0930 HDT 1
1933-05-21 11 -1030 HST
1942-02-09 03 -0930 HWT 1
1945-08-14 13:30 -0930 HPT 1
1945-09-30 01 -1030 HST
1947-06-08 02:30 -10 HST
.Ed
.Pp
Here, local time begins 10 hours, 31 minutes and 26 seconds west of
UT, and is a standard time abbreviated LMT.
Immediately after the
first transition, the date is 1896-01-13 and the time is 12:01:26, and
the following time interval is 10.5 hours west of UT, a standard time
abbreviated HST.
Immediately after the second transition, the date is
1933-04-30 and the time is 03:00:00 and the following time interval is
9.5 hours west of UT, is abbreviated HDT, and is daylight saving time.
Immediately after the last transition the date is 1947-06-08 and the
time is 02:30:00, and the following time interval is 10 hours west of
UT, a standard time abbreviated HST.
.Pp
Here are excerpts from another example:
.Bd -literal -offset indent
TZ="Europe/Astrakhan"
- - +031212 LMT
1924-04-30 23:47:48 +03
1930-06-21 01 +04
1981-04-01 01 +05 1
1981-09-30 23 +04
\&...
2014-10-26 01 +03
2016-03-27 03 +04
.Ed
.Pp
This time zone is east of UT, so its UT offsets are positive.
Also,
many of its time zone abbreviations are omitted since they duplicate
the text of the UT offset.
.Sh LIMITATIONS
Time discontinuities are found by sampling the results returned by
.Xr localtime 3
at twelve-hour intervals.
This works in all real-world cases;
one can construct artificial time zones for which this fails.
.Pp
In the
.Fl v
and
.Fl V
output,
.Dq "UT"
denotes the value returned by
.Xr gmtime 3 ,
which uses UTC for modern timestamps and some other UT flavor for
timestamps that predate the introduction of UTC.
No attempt is currently made to have the output use
.Dq "UTC"
for newer and
.Dq "UT"
for older timestamps, partly because the exact date of the
introduction of UTC is problematic.
.Sh SEE ALSO
.Xr tzfile 5 ,
.Xr zic 8
diff --git a/contrib/tzcode/zdump.c b/contrib/tzcode/zdump.c
index e4ac81cc220c..74ba0639633d 100644
--- a/contrib/tzcode/zdump.c
+++ b/contrib/tzcode/zdump.c
@@ -1,1256 +1,1269 @@
/* Dump time zone data in a textual format. */
/*
** This file is in the public domain, so clarified as of
** 2009-05-17 by Arthur David Olson.
*/
#include "version.h"
#ifndef NETBSD_INSPIRED
# define NETBSD_INSPIRED 1
#endif
#include "private.h"
#include
#ifndef HAVE_SNPRINTF
-# define HAVE_SNPRINTF (199901 <= __STDC_VERSION__)
+# define HAVE_SNPRINTF (!PORT_TO_C89 || 199901 <= __STDC_VERSION__)
#endif
#ifndef HAVE_LOCALTIME_R
# define HAVE_LOCALTIME_R 1
#endif
#ifndef HAVE_LOCALTIME_RZ
# ifdef TM_ZONE
# define HAVE_LOCALTIME_RZ (NETBSD_INSPIRED && USE_LTZ)
# else
# define HAVE_LOCALTIME_RZ 0
# endif
#endif
#ifndef HAVE_TZSET
# define HAVE_TZSET 1
#endif
#ifndef ZDUMP_LO_YEAR
# define ZDUMP_LO_YEAR (-500)
#endif /* !defined ZDUMP_LO_YEAR */
#ifndef ZDUMP_HI_YEAR
# define ZDUMP_HI_YEAR 2500
#endif /* !defined ZDUMP_HI_YEAR */
#define SECSPERNYEAR (SECSPERDAY * DAYSPERNYEAR)
#define SECSPERLYEAR (SECSPERNYEAR + SECSPERDAY)
#define SECSPER400YEARS (SECSPERNYEAR * (intmax_t) (300 + 3) \
+ SECSPERLYEAR * (intmax_t) (100 - 3))
/*
** True if SECSPER400YEARS is known to be representable as an
** intmax_t. It's OK that SECSPER400YEARS_FITS can in theory be false
** even if SECSPER400YEARS is representable, because when that happens
** the code merely runs a bit more slowly, and this slowness doesn't
** occur on any practical platform.
*/
enum { SECSPER400YEARS_FITS = SECSPERLYEAR <= INTMAX_MAX / 400 };
#if HAVE_GETTEXT
# include /* for setlocale */
#endif /* HAVE_GETTEXT */
#if ! HAVE_LOCALTIME_RZ
# undef timezone_t
# define timezone_t char **
#endif
#if !HAVE_POSIX_DECLS
extern int getopt(int argc, char * const argv[],
const char * options);
extern char * optarg;
extern int optind;
#endif
/* The minimum and maximum finite time values. */
enum { atime_shift = CHAR_BIT * sizeof(time_t) - 2 };
static time_t const absolute_min_time =
((time_t) -1 < 0
? (- ((time_t) ~ (time_t) 0 < 0)
- (((time_t) 1 << atime_shift) - 1 + ((time_t) 1 << atime_shift)))
: 0);
static time_t const absolute_max_time =
((time_t) -1 < 0
? (((time_t) 1 << atime_shift) - 1 + ((time_t) 1 << atime_shift))
: -1);
static size_t longest;
static char const *progname;
static bool warned;
static bool errout;
static char const *abbr(struct tm const *);
-static intmax_t delta(struct tm *, struct tm *) ATTRIBUTE_REPRODUCIBLE;
+ATTRIBUTE_REPRODUCIBLE static intmax_t delta(struct tm *, struct tm *);
static void dumptime(struct tm const *);
static time_t hunt(timezone_t, time_t, time_t, bool);
static void show(timezone_t, char *, time_t, bool);
static void showextrema(timezone_t, char *, time_t, struct tm *, time_t);
static void showtrans(char const *, struct tm const *, time_t, char const *,
char const *);
static const char *tformat(void);
-static time_t yeartot(intmax_t) ATTRIBUTE_REPRODUCIBLE;
+ATTRIBUTE_REPRODUCIBLE static time_t yeartot(intmax_t);
/* Is C an ASCII digit? */
static bool
is_digit(char c)
{
return '0' <= c && c <= '9';
}
/* Is A an alphabetic character in the C locale? */
static bool
is_alpha(char a)
{
switch (a) {
default:
return false;
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':
return true;
}
}
-static ATTRIBUTE_NORETURN void
+ATTRIBUTE_NORETURN static void
size_overflow(void)
{
fprintf(stderr, _("%s: size overflow\n"), progname);
exit(EXIT_FAILURE);
}
/* Return A + B, exiting if the result would overflow either ptrdiff_t
- or size_t. */
-static ATTRIBUTE_REPRODUCIBLE size_t
-sumsize(size_t a, size_t b)
+ or size_t. A and B are both nonnegative. */
+ATTRIBUTE_REPRODUCIBLE static ptrdiff_t
+sumsize(ptrdiff_t a, ptrdiff_t b)
{
#ifdef ckd_add
- size_t sum;
- if (!ckd_add(&sum, a, b))
+ ptrdiff_t sum;
+ if (!ckd_add(&sum, a, b) && sum <= INDEX_MAX)
return sum;
#else
- if (a <= SIZE_MAX && b <= SIZE_MAX - a)
+ if (a <= INDEX_MAX && b <= INDEX_MAX - a)
return a + b;
#endif
size_overflow();
}
+/* Return the size of of the string STR, including its trailing NUL.
+ Report an error and exit if this would exceed INDEX_MAX which means
+ pointer subtraction wouldn't work. */
+static ptrdiff_t
+xstrsize(char const *str)
+{
+ size_t len = strlen(str);
+ if (len < INDEX_MAX)
+ return len + 1;
+ size_overflow();
+}
+
/* Return a pointer to a newly allocated buffer of size SIZE, exiting
- on failure. SIZE should be nonzero. */
-static void * ATTRIBUTE_MALLOC
-xmalloc(size_t size)
+ on failure. SIZE should be positive. */
+ATTRIBUTE_MALLOC static void *
+xmalloc(ptrdiff_t size)
{
void *p = malloc(size);
if (!p) {
fprintf(stderr, _("%s: Memory exhausted\n"), progname);
exit(EXIT_FAILURE);
}
return p;
}
#if ! HAVE_TZSET
# undef tzset
# define tzset zdump_tzset
static void tzset(void) { }
#endif
/* Assume gmtime_r works if localtime_r does.
A replacement localtime_r is defined below if needed. */
#if ! HAVE_LOCALTIME_R
# undef gmtime_r
# define gmtime_r zdump_gmtime_r
static struct tm *
gmtime_r(time_t *tp, struct tm *tmp)
{
struct tm *r = gmtime(tp);
if (r) {
*tmp = *r;
r = tmp;
}
return r;
}
#endif
/* Platforms with TM_ZONE don't need tzname, so they can use the
faster localtime_rz or localtime_r if available. */
#if defined TM_ZONE && HAVE_LOCALTIME_RZ
# define USE_LOCALTIME_RZ true
#else
# define USE_LOCALTIME_RZ false
#endif
#if ! USE_LOCALTIME_RZ
# if !defined TM_ZONE || ! HAVE_LOCALTIME_R || ! HAVE_TZSET
# undef localtime_r
# define localtime_r zdump_localtime_r
static struct tm *
localtime_r(time_t *tp, struct tm *tmp)
{
struct tm *r = localtime(tp);
if (r) {
*tmp = *r;
r = tmp;
}
return r;
}
# endif
# undef localtime_rz
# define localtime_rz zdump_localtime_rz
static struct tm *
-localtime_rz(timezone_t rz __unused, time_t *tp, struct tm *tmp)
+localtime_rz(ATTRIBUTE_MAYBE_UNUSED timezone_t rz, time_t *tp, struct tm *tmp)
{
return localtime_r(tp, tmp);
}
# ifdef TYPECHECK
# undef mktime_z
# define mktime_z zdump_mktime_z
static time_t
mktime_z(timezone_t tz, struct tm *tmp)
{
return mktime(tmp);
}
# endif
# undef tzalloc
# undef tzfree
# define tzalloc zdump_tzalloc
# define tzfree zdump_tzfree
static timezone_t
tzalloc(char const *val)
{
# if HAVE_SETENV
if (setenv("TZ", val, 1) != 0) {
- perror("setenv");
+ char const *e = strerror(errno);
+ fprintf(stderr, _("%s: setenv: %s\n"), progname, e);
exit(EXIT_FAILURE);
}
tzset();
return &optarg; /* Any valid non-null char ** will do. */
# else
enum { TZeqlen = 3 };
static char const TZeq[TZeqlen] = "TZ=";
static char **fakeenv;
static ptrdiff_t fakeenv0size;
void *freeable = NULL;
char **env = fakeenv, **initial_environ;
- size_t valsize = strlen(val) + 1;
+ ptrdiff_t valsize = xstrsize(val);
if (fakeenv0size < valsize) {
char **e = environ, **to;
ptrdiff_t initial_nenvptrs = 1; /* Counting the trailing NULL pointer. */
while (*e++) {
# ifdef ckd_add
- if (ckd_add(&initial_nenvptrs, initial_envptrs, 1)
- || SIZE_MAX < initial_envptrs)
+ if (ckd_add(&initial_nenvptrs, initial_nenvptrs, 1)
+ || INDEX_MAX < initial_nenvptrs)
size_overflow();
# else
- if (initial_nenvptrs == min(PTRDIFF_MAX, SIZE_MAX) / sizeof *environ)
+ if (initial_nenvptrs == INDEX_MAX / sizeof *environ)
size_overflow();
initial_nenvptrs++;
# endif
}
fakeenv0size = sumsize(valsize, valsize);
fakeenv0size = max(fakeenv0size, 64);
freeable = env;
fakeenv = env =
xmalloc(sumsize(sumsize(sizeof *environ,
initial_nenvptrs * sizeof *environ),
sumsize(TZeqlen, fakeenv0size)));
to = env + 1;
for (e = environ; (*to = *e); e++)
to += strncmp(*e, TZeq, TZeqlen) != 0;
env[0] = memcpy(to + 1, TZeq, TZeqlen);
}
memcpy(env[0] + TZeqlen, val, valsize);
initial_environ = environ;
environ = env;
tzset();
free(freeable);
return initial_environ;
# endif
}
static void
-tzfree(timezone_t initial_environ)
+tzfree(ATTRIBUTE_MAYBE_UNUSED timezone_t initial_environ)
{
# if !HAVE_SETENV
environ = initial_environ;
tzset();
# else
(void)initial_environ;
# endif
}
#endif /* ! USE_LOCALTIME_RZ */
/* A UT time zone, and its initializer. */
static timezone_t gmtz;
static void
gmtzinit(void)
{
if (USE_LOCALTIME_RZ) {
/* Try "GMT" first to find out whether this is one of the rare
platforms where time_t counts leap seconds; this works due to
the "Zone GMT 0 - GMT" line in the "etcetera" file. If "GMT"
fails, fall back on "GMT0" which might be similar due to the
"Link GMT GMT0" line in the "backward" file, and which
should work on all POSIX platforms. The rest of zdump does not
use the "GMT" abbreviation that comes from this setting, so it
- is OK to use "GMT" here rather than the more-modern "UTC" which
+ is OK to use "GMT" here rather than the modern "UTC" which
would not work on platforms that omit the "backward" file. */
gmtz = tzalloc("GMT");
if (!gmtz) {
static char const gmt0[] = "GMT0";
gmtz = tzalloc(gmt0);
if (!gmtz) {
- perror(gmt0);
+ char const *e = strerror(errno);
+ fprintf(stderr, _("%s: unknown timezone '%s': %s\n"),
+ progname, gmt0, e);
exit(EXIT_FAILURE);
}
}
}
}
/* Convert *TP to UT, storing the broken-down time into *TMP.
Return TMP if successful, NULL otherwise. This is like gmtime_r(TP, TMP),
except typically faster if USE_LOCALTIME_RZ. */
static struct tm *
my_gmtime_r(time_t *tp, struct tm *tmp)
{
return USE_LOCALTIME_RZ ? localtime_rz(gmtz, tp, tmp) : gmtime_r(tp, tmp);
}
#ifndef TYPECHECK
# define my_localtime_rz localtime_rz
#else /* !defined TYPECHECK */
static struct tm *
my_localtime_rz(timezone_t tz, time_t *tp, struct tm *tmp)
{
tmp = localtime_rz(tz, tp, tmp);
if (tmp) {
struct tm tm;
register time_t t;
tm = *tmp;
t = mktime_z(tz, &tm);
if (t != *tp) {
fflush(stdout);
fprintf(stderr, "\n%s: ", progname);
fprintf(stderr, tformat(), *tp);
fprintf(stderr, " ->");
fprintf(stderr, " year=%d", tmp->tm_year);
fprintf(stderr, " mon=%d", tmp->tm_mon);
fprintf(stderr, " mday=%d", tmp->tm_mday);
fprintf(stderr, " hour=%d", tmp->tm_hour);
fprintf(stderr, " min=%d", tmp->tm_min);
fprintf(stderr, " sec=%d", tmp->tm_sec);
fprintf(stderr, " isdst=%d", tmp->tm_isdst);
fprintf(stderr, " -> ");
fprintf(stderr, tformat(), t);
fprintf(stderr, "\n");
errout = true;
}
}
return tmp;
}
#endif /* !defined TYPECHECK */
static void
abbrok(const char *const abbrp, const char *const zone)
{
register const char * cp;
register const char * wp;
if (warned)
return;
cp = abbrp;
while (is_alpha(*cp) || is_digit(*cp) || *cp == '-' || *cp == '+')
++cp;
if (*cp)
wp = _("has characters other than ASCII alphanumerics, '-' or '+'");
else if (cp - abbrp < 3)
wp = _("has fewer than 3 characters");
else if (cp - abbrp > 6)
wp = _("has more than 6 characters");
else
return;
fflush(stdout);
fprintf(stderr,
_("%s: warning: zone \"%s\" abbreviation \"%s\" %s\n"),
progname, zone, abbrp, wp);
warned = errout = true;
}
/* Return a time zone abbreviation. If the abbreviation needs to be
saved, use *BUF (of size *BUFALLOC) to save it, and return the
- abbreviation in the possibly-reallocated *BUF. Otherwise, just
+ abbreviation in the possibly reallocated *BUF. Otherwise, just
return the abbreviation. Get the abbreviation from TMP.
Exit on memory allocation failure. */
static char const *
saveabbr(char **buf, ptrdiff_t *bufalloc, struct tm const *tmp)
{
char const *ab = abbr(tmp);
if (HAVE_LOCALTIME_RZ)
return ab;
else {
- size_t ablen = strlen(ab);
- if ((size_t)*bufalloc <= ablen) {
+ ptrdiff_t absize = xstrsize(ab);
+ if (*bufalloc < absize) {
free(*buf);
/* Make the new buffer at least twice as long as the old,
to avoid O(N**2) behavior on repeated calls. */
- *bufalloc = sumsize(*bufalloc, ablen + 1);
+ *bufalloc = sumsize(*bufalloc, absize);
*buf = xmalloc(*bufalloc);
}
return strcpy(*buf, ab);
}
}
static void
close_file(FILE *stream)
{
char const *e = (ferror(stream) ? _("I/O error")
: fclose(stream) != 0 ? strerror(errno) : NULL);
if (e) {
fprintf(stderr, "%s: %s\n", progname, e);
exit(EXIT_FAILURE);
}
}
static void
usage(FILE * const stream, const int status)
{
fprintf(stream,
_("%s: usage: %s OPTIONS TIMEZONE ...\n"
"Options include:\n"
" -c [L,]U Start at year L (default -500), end before year U (default 2500)\n"
" -t [L,]U Start at time L, end before time U (in seconds since 1970)\n"
" -i List transitions briefly (format is experimental)\n" \
" -v List transitions verbosely\n"
" -V List transitions a bit less verbosely\n"
" --help Output this help\n"
" --version Output version info\n"
"\n"
"Report bugs to %s.\n"),
progname, progname, REPORT_BUGS_TO);
if (status == EXIT_SUCCESS)
close_file(stream);
exit(status);
}
int
main(int argc, char *argv[])
{
/* These are static so that they're initially zero. */
static char * abbrev;
static ptrdiff_t abbrevsize;
register int i;
register bool vflag;
register bool Vflag;
register char * cutarg;
register char * cuttimes;
register time_t cutlotime;
register time_t cuthitime;
time_t now;
bool iflag = false;
cutlotime = absolute_min_time;
cuthitime = absolute_max_time;
#if HAVE_GETTEXT
setlocale(LC_ALL, "");
# ifdef TZ_DOMAINDIR
bindtextdomain(TZ_DOMAIN, TZ_DOMAINDIR);
# endif /* defined TEXTDOMAINDIR */
textdomain(TZ_DOMAIN);
#endif /* HAVE_GETTEXT */
progname = argv[0] ? argv[0] : "zdump";
for (i = 1; i < argc; ++i)
if (strcmp(argv[i], "--version") == 0) {
printf("zdump %s%s\n", PKGVERSION, TZVERSION);
return EXIT_SUCCESS;
} else if (strcmp(argv[i], "--help") == 0) {
usage(stdout, EXIT_SUCCESS);
}
vflag = Vflag = false;
cutarg = cuttimes = NULL;
for (;;)
switch (getopt(argc, argv, "c:it:vV")) {
case 'c': cutarg = optarg; break;
case 't': cuttimes = optarg; break;
case 'i': iflag = true; break;
case 'v': vflag = true; break;
case 'V': Vflag = true; break;
case -1:
if (! (optind == argc - 1 && strcmp(argv[optind], "=") == 0))
goto arg_processing_done;
ATTRIBUTE_FALLTHROUGH;
default:
usage(stderr, EXIT_FAILURE);
}
arg_processing_done:;
if (iflag | vflag | Vflag) {
intmax_t lo;
intmax_t hi;
char *loend, *hiend;
register intmax_t cutloyear = ZDUMP_LO_YEAR;
register intmax_t cuthiyear = ZDUMP_HI_YEAR;
if (cutarg != NULL) {
lo = strtoimax(cutarg, &loend, 10);
if (cutarg != loend && !*loend) {
hi = lo;
cuthiyear = hi;
} else if (cutarg != loend && *loend == ','
&& (hi = strtoimax(loend + 1, &hiend, 10),
loend + 1 != hiend && !*hiend)) {
cutloyear = lo;
cuthiyear = hi;
} else {
fprintf(stderr, _("%s: wild -c argument %s\n"),
progname, cutarg);
return EXIT_FAILURE;
}
}
if (cutarg != NULL || cuttimes == NULL) {
cutlotime = yeartot(cutloyear);
cuthitime = yeartot(cuthiyear);
}
if (cuttimes != NULL) {
lo = strtoimax(cuttimes, &loend, 10);
if (cuttimes != loend && !*loend) {
hi = lo;
if (hi < cuthitime) {
if (hi < absolute_min_time + 1)
hi = absolute_min_time + 1;
cuthitime = hi;
}
} else if (cuttimes != loend && *loend == ','
&& (hi = strtoimax(loend + 1, &hiend, 10),
loend + 1 != hiend && !*hiend)) {
if (cutlotime < lo) {
if (absolute_max_time < lo)
lo = absolute_max_time;
cutlotime = lo;
}
if (hi < cuthitime) {
if (hi < absolute_min_time + 1)
hi = absolute_min_time + 1;
cuthitime = hi;
}
} else {
fprintf(stderr,
_("%s: wild -t argument %s\n"),
progname, cuttimes);
return EXIT_FAILURE;
}
}
}
gmtzinit();
if (iflag | vflag | Vflag)
now = 0;
else {
now = time(NULL);
now |= !now;
}
longest = 0;
for (i = optind; i < argc; i++) {
size_t arglen = strlen(argv[i]);
if (longest < arglen)
longest = min(arglen, INT_MAX);
}
for (i = optind; i < argc; ++i) {
timezone_t tz = tzalloc(argv[i]);
char const *ab;
time_t t;
struct tm tm, newtm;
bool tm_ok;
if (!tz) {
- perror(argv[i]);
+ char const *e = strerror(errno);
+ fprintf(stderr, _("%s: unknown timezone '%s': %s\n"),
+ progname, argv[1], e);
return EXIT_FAILURE;
}
if (now) {
show(tz, argv[i], now, false);
tzfree(tz);
continue;
}
warned = false;
t = absolute_min_time;
if (! (iflag | Vflag)) {
show(tz, argv[i], t, true);
if (my_localtime_rz(tz, &t, &tm) == NULL
&& t < cutlotime) {
time_t newt = cutlotime;
if (my_localtime_rz(tz, &newt, &newtm) != NULL)
showextrema(tz, argv[i], t, NULL, newt);
}
}
if (t + 1 < cutlotime)
t = cutlotime - 1;
tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;
if (tm_ok) {
ab = saveabbr(&abbrev, &abbrevsize, &tm);
if (iflag) {
showtrans("\nTZ=%f", &tm, t, ab, argv[i]);
showtrans("-\t-\t%Q", &tm, t, ab, argv[i]);
}
} else
ab = NULL;
while (t < cuthitime - 1) {
time_t newt = ((t < absolute_max_time - SECSPERDAY / 2
&& t + SECSPERDAY / 2 < cuthitime - 1)
? t + SECSPERDAY / 2
: cuthitime - 1);
struct tm *newtmp = localtime_rz(tz, &newt, &newtm);
bool newtm_ok = newtmp != NULL;
if (tm_ok != newtm_ok
|| (ab && (delta(&newtm, &tm) != newt - t
|| newtm.tm_isdst != tm.tm_isdst
|| strcmp(abbr(&newtm), ab) != 0))) {
newt = hunt(tz, t, newt, false);
newtmp = localtime_rz(tz, &newt, &newtm);
newtm_ok = newtmp != NULL;
if (iflag)
showtrans("%Y-%m-%d\t%L\t%Q", newtmp, newt,
newtm_ok ? abbr(&newtm) : NULL, argv[i]);
else {
show(tz, argv[i], newt - 1, true);
show(tz, argv[i], newt, true);
}
}
t = newt;
tm_ok = newtm_ok;
if (newtm_ok) {
ab = saveabbr(&abbrev, &abbrevsize, &newtm);
tm = newtm;
}
}
if (! (iflag | Vflag)) {
time_t newt = absolute_max_time;
t = cuthitime;
if (t < newt) {
struct tm *tmp = my_localtime_rz(tz, &t, &tm);
if (tmp != NULL
&& my_localtime_rz(tz, &newt, &newtm) == NULL)
showextrema(tz, argv[i], t, tmp, newt);
}
show(tz, argv[i], absolute_max_time, true);
}
tzfree(tz);
}
close_file(stdout);
if (errout && (ferror(stderr) || fclose(stderr) != 0))
return EXIT_FAILURE;
return EXIT_SUCCESS;
}
static time_t
yeartot(intmax_t y)
{
register intmax_t myy, seconds, years;
register time_t t;
myy = EPOCH_YEAR;
t = 0;
while (myy < y) {
if (SECSPER400YEARS_FITS && 400 <= y - myy) {
intmax_t diff400 = (y - myy) / 400;
if (INTMAX_MAX / SECSPER400YEARS < diff400)
return absolute_max_time;
seconds = diff400 * SECSPER400YEARS;
years = diff400 * 400;
} else {
seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR;
years = 1;
}
myy += years;
if (t > absolute_max_time - seconds)
return absolute_max_time;
t += seconds;
}
while (y < myy) {
if (SECSPER400YEARS_FITS && y + 400 <= myy && myy < 0) {
intmax_t diff400 = (myy - y) / 400;
if (INTMAX_MAX / SECSPER400YEARS < diff400)
return absolute_min_time;
seconds = diff400 * SECSPER400YEARS;
years = diff400 * 400;
} else {
seconds = isleap(myy - 1) ? SECSPERLYEAR : SECSPERNYEAR;
years = 1;
}
myy -= years;
if (t < absolute_min_time + seconds)
return absolute_min_time;
t -= seconds;
}
return t;
}
/* Search for a discontinuity in timezone TZ, in the
timestamps ranging from LOT through HIT. LOT and HIT disagree
about some aspect of timezone. If ONLY_OK, search only for
definedness changes, i.e., localtime succeeds on one side of the
transition but fails on the other side. Return the timestamp just
before the transition from LOT's settings. */
static time_t
hunt(timezone_t tz, time_t lot, time_t hit, bool only_ok)
{
static char * loab;
static ptrdiff_t loabsize;
struct tm lotm;
struct tm tm;
/* Convert LOT into a broken-down time here, even though our
caller already did that. On platforms without TM_ZONE,
tzname may have been altered since our caller broke down
LOT, and tzname needs to be changed back. */
bool lotm_ok = my_localtime_rz(tz, &lot, &lotm) != NULL;
bool tm_ok;
char const *ab = lotm_ok ? saveabbr(&loab, &loabsize, &lotm) : NULL;
for ( ; ; ) {
/* T = average of LOT and HIT, rounding down.
- Avoid overflow, even on oddball C89 platforms
- where / rounds down and TIME_T_MIN == -TIME_T_MAX
- so lot / 2 + hit / 2 might overflow. */
- time_t t = (lot / 2
- - ((lot % 2 + hit % 2) < 0)
- + ((lot % 2 + hit % 2) == 2)
- + hit / 2);
+ Avoid overflow. */
+ int rem_sum = lot % 2 + hit % 2;
+ time_t t = (rem_sum == 2) - (rem_sum < 0) + lot / 2 + hit / 2;
if (t == lot)
break;
tm_ok = my_localtime_rz(tz, &t, &tm) != NULL;
if (lotm_ok == tm_ok
&& (only_ok
|| (ab && tm.tm_isdst == lotm.tm_isdst
&& delta(&tm, &lotm) == t - lot
&& strcmp(abbr(&tm), ab) == 0))) {
lot = t;
if (tm_ok)
lotm = tm;
} else hit = t;
}
return hit;
}
/*
** Thanks to Paul Eggert for logic used in delta_nonneg.
*/
static intmax_t
delta_nonneg(struct tm *newp, struct tm *oldp)
{
intmax_t oldy = oldp->tm_year;
int cycles = (newp->tm_year - oldy) / YEARSPERREPEAT;
intmax_t sec = SECSPERREPEAT, result = cycles * sec;
int tmy = oldp->tm_year + cycles * YEARSPERREPEAT;
for ( ; tmy < newp->tm_year; ++tmy)
result += DAYSPERNYEAR + isleap_sum(tmy, TM_YEAR_BASE);
result += newp->tm_yday - oldp->tm_yday;
result *= HOURSPERDAY;
result += newp->tm_hour - oldp->tm_hour;
result *= MINSPERHOUR;
result += newp->tm_min - oldp->tm_min;
result *= SECSPERMIN;
result += newp->tm_sec - oldp->tm_sec;
return result;
}
static intmax_t
delta(struct tm *newp, struct tm *oldp)
{
return (newp->tm_year < oldp->tm_year
? -delta_nonneg(oldp, newp)
: delta_nonneg(newp, oldp));
}
#ifndef TM_GMTOFF
/* Return A->tm_yday, adjusted to compare it fairly to B->tm_yday.
Assume A and B differ by at most one year. */
static int
adjusted_yday(struct tm const *a, struct tm const *b)
{
int yday = a->tm_yday;
if (b->tm_year < a->tm_year)
yday += 365 + isleap_sum(b->tm_year, TM_YEAR_BASE);
return yday;
}
#endif
/* If A is the broken-down local time and B the broken-down UT for
the same instant, return A's UT offset in seconds, where positive
offsets are east of Greenwich. On failure, return LONG_MIN.
If T is nonnull, *T is the timestamp that corresponds to A; call
my_gmtime_r and use its result instead of B. Otherwise, B is the
possibly nonnull result of an earlier call to my_gmtime_r. */
static long
gmtoff(struct tm const *a, ATTRIBUTE_MAYBE_UNUSED time_t *t,
ATTRIBUTE_MAYBE_UNUSED struct tm const *b)
{
#ifdef TM_GMTOFF
return a->TM_GMTOFF;
#else
struct tm tm;
if (t)
b = my_gmtime_r(t, &tm);
if (! b)
return LONG_MIN;
else {
int ayday = adjusted_yday(a, b);
int byday = adjusted_yday(b, a);
int days = ayday - byday;
long hours = a->tm_hour - b->tm_hour + 24 * days;
long minutes = a->tm_min - b->tm_min + 60 * hours;
long seconds = a->tm_sec - b->tm_sec + 60 * minutes;
return seconds;
}
#endif
}
static void
show(timezone_t tz, char *zone, time_t t, bool v)
{
register struct tm * tmp;
register struct tm * gmtmp;
struct tm tm, gmtm;
printf("%-*s ", (int)longest, zone);
if (v) {
gmtmp = my_gmtime_r(&t, &gmtm);
if (gmtmp == NULL) {
printf(tformat(), t);
printf(_(" (gmtime failed)"));
} else {
dumptime(gmtmp);
printf(" UT");
}
printf(" = ");
}
tmp = my_localtime_rz(tz, &t, &tm);
if (tmp == NULL) {
printf(tformat(), t);
printf(_(" (localtime failed)"));
} else {
dumptime(tmp);
if (*abbr(tmp) != '\0')
printf(" %s", abbr(tmp));
if (v) {
long off = gmtoff(tmp, NULL, gmtmp);
printf(" isdst=%d", tmp->tm_isdst);
if (off != LONG_MIN)
printf(" gmtoff=%ld", off);
}
}
printf("\n");
if (tmp != NULL && *abbr(tmp) != '\0')
abbrok(abbr(tmp), zone);
}
/* Show timestamps just before and just after a transition between
defined and undefined (or vice versa) in either localtime or
gmtime. These transitions are for timezone TZ with name ZONE, in
the range from LO (with broken-down time LOTMP if that is nonnull)
through HI. LO and HI disagree on definedness. */
static void
showextrema(timezone_t tz, char *zone, time_t lo, struct tm *lotmp, time_t hi)
{
struct tm localtm[2], gmtm[2];
time_t t, boundary = hunt(tz, lo, hi, true);
bool old = false;
hi = (SECSPERDAY < hi - boundary
? boundary + SECSPERDAY
: hi + (hi < TIME_T_MAX));
if (SECSPERDAY < boundary - lo) {
lo = boundary - SECSPERDAY;
lotmp = my_localtime_rz(tz, &lo, &localtm[old]);
}
if (lotmp)
localtm[old] = *lotmp;
else
localtm[old].tm_sec = -1;
if (! my_gmtime_r(&lo, &gmtm[old]))
gmtm[old].tm_sec = -1;
/* Search sequentially for definedness transitions. Although this
could be sped up by refining 'hunt' to search for either
localtime or gmtime definedness transitions, it hardly seems
worth the trouble. */
for (t = lo + 1; t < hi; t++) {
bool new = !old;
if (! my_localtime_rz(tz, &t, &localtm[new]))
localtm[new].tm_sec = -1;
if (! my_gmtime_r(&t, &gmtm[new]))
gmtm[new].tm_sec = -1;
if (((localtm[old].tm_sec < 0) != (localtm[new].tm_sec < 0))
| ((gmtm[old].tm_sec < 0) != (gmtm[new].tm_sec < 0))) {
show(tz, zone, t - 1, true);
show(tz, zone, t, true);
}
old = new;
}
}
#if HAVE_SNPRINTF
# define my_snprintf snprintf
#else
# include
/* A substitute for snprintf that is good enough for zdump. */
-static int ATTRIBUTE_FORMAT((printf, 3, 4))
+ATTRIBUTE_FORMAT((printf, 3, 4)) static int
my_snprintf(char *s, size_t size, char const *format, ...)
{
int n;
va_list args;
char const *arg;
size_t arglen, slen;
char buf[1024];
va_start(args, format);
if (strcmp(format, "%s") == 0) {
arg = va_arg(args, char const *);
arglen = strlen(arg);
} else {
n = vsprintf(buf, format, args);
if (n < 0) {
va_end(args);
return n;
}
arg = buf;
arglen = n;
}
slen = arglen < size ? arglen : size - 1;
memcpy(s, arg, slen);
s[slen] = '\0';
n = arglen <= INT_MAX ? arglen : -1;
va_end(args);
return n;
}
#endif
/* Store into BUF, of size SIZE, a formatted local time taken from *TM.
Use ISO 8601 format +HH:MM:SS. Omit :SS if SS is zero, and omit
:MM too if MM is also zero.
Return the length of the resulting string. If the string does not
fit, return the length that the string would have been if it had
fit; do not overrun the output buffer. */
static int
format_local_time(char *buf, ptrdiff_t size, struct tm const *tm)
{
int ss = tm->tm_sec, mm = tm->tm_min, hh = tm->tm_hour;
return (ss
? my_snprintf(buf, size, "%02d:%02d:%02d", hh, mm, ss)
: mm
? my_snprintf(buf, size, "%02d:%02d", hh, mm)
: my_snprintf(buf, size, "%02d", hh));
}
/* Store into BUF, of size SIZE, a formatted UT offset for the
localtime *TM corresponding to time T. Use ISO 8601 format
+HHMMSS, or -HHMMSS for timestamps west of Greenwich; use the
format -00 for unknown UT offsets. If the hour needs more than
two digits to represent, extend the length of HH as needed.
Otherwise, omit SS if SS is zero, and omit MM too if MM is also
zero.
Return the length of the resulting string, or -1 if the result is
not representable as a string. If the string does not fit, return
the length that the string would have been if it had fit; do not
overrun the output buffer. */
static int
format_utc_offset(char *buf, ptrdiff_t size, struct tm const *tm, time_t t)
{
long off = gmtoff(tm, &t, NULL);
char sign = ((off < 0
|| (off == 0
&& (*abbr(tm) == '-' || strcmp(abbr(tm), "zzz") == 0)))
? '-' : '+');
long hh;
int mm, ss;
if (off < 0)
{
if (off == LONG_MIN)
return -1;
off = -off;
}
ss = off % 60;
mm = off / 60 % 60;
hh = off / 60 / 60;
return (ss || 100 <= hh
? my_snprintf(buf, size, "%c%02ld%02d%02d", sign, hh, mm, ss)
: mm
? my_snprintf(buf, size, "%c%02ld%02d", sign, hh, mm)
: my_snprintf(buf, size, "%c%02ld", sign, hh));
}
/* Store into BUF (of size SIZE) a quoted string representation of P.
If the representation's length is less than SIZE, return the
length; the representation is not null terminated. Otherwise
return SIZE, to indicate that BUF is too small. */
static ptrdiff_t
format_quoted_string(char *buf, ptrdiff_t size, char const *p)
{
char *b = buf;
ptrdiff_t s = size;
if (!s)
return size;
*b++ = '"', s--;
for (;;) {
char c = *p++;
if (s <= 1)
return size;
switch (c) {
default: *b++ = c, s--; continue;
case '\0': *b++ = '"', s--; return size - s;
case '"': case '\\': break;
case ' ': c = 's'; break;
case '\f': c = 'f'; break;
case '\n': c = 'n'; break;
case '\r': c = 'r'; break;
case '\t': c = 't'; break;
case '\v': c = 'v'; break;
}
*b++ = '\\', *b++ = c, s -= 2;
}
}
/* Store into BUF (of size SIZE) a timestamp formatted by TIME_FMT.
TM is the broken-down time, T the seconds count, AB the time zone
abbreviation, and ZONE_NAME the zone name. Return true if
successful, false if the output would require more than SIZE bytes.
TIME_FMT uses the same format that strftime uses, with these
additions:
%f zone name
%L local time as per format_local_time
%Q like "U\t%Z\tD" where U is the UT offset as for format_utc_offset
and D is the isdst flag; except omit D if it is zero, omit %Z if
it equals U, quote and escape %Z if it contains nonalphabetics,
and omit any trailing tabs. */
static bool
istrftime(char *buf, ptrdiff_t size, char const *time_fmt,
struct tm const *tm, time_t t, char const *ab, char const *zone_name)
{
char *b = buf;
ptrdiff_t s = size;
char const *f = time_fmt, *p;
for (p = f; ; p++)
if (*p == '%' && p[1] == '%')
p++;
else if (!*p
|| (*p == '%'
&& (p[1] == 'f' || p[1] == 'L' || p[1] == 'Q'))) {
ptrdiff_t formatted_len;
ptrdiff_t f_prefix_len = p - f;
ptrdiff_t f_prefix_copy_size = sumsize(f_prefix_len, 2);
char fbuf[100];
bool oversized = sizeof fbuf <= (size_t)f_prefix_copy_size;
char *f_prefix_copy = oversized ? xmalloc(f_prefix_copy_size) : fbuf;
memcpy(f_prefix_copy, f, f_prefix_len);
strcpy(f_prefix_copy + f_prefix_len, "X");
formatted_len = strftime(b, s, f_prefix_copy, tm);
if (oversized)
free(f_prefix_copy);
if (formatted_len == 0)
return false;
formatted_len--;
b += formatted_len, s -= formatted_len;
if (!*p++)
break;
switch (*p) {
case 'f':
formatted_len = format_quoted_string(b, s, zone_name);
break;
case 'L':
formatted_len = format_local_time(b, s, tm);
break;
case 'Q':
{
bool show_abbr;
int offlen = format_utc_offset(b, s, tm, t);
if (! (0 <= offlen && offlen < s))
return false;
show_abbr = strcmp(b, ab) != 0;
b += offlen, s -= offlen;
if (show_abbr) {
char const *abp;
ptrdiff_t len;
if (s <= 1)
return false;
*b++ = '\t', s--;
for (abp = ab; is_alpha(*abp); abp++)
continue;
len = (!*abp && *ab
? my_snprintf(b, s, "%s", ab)
: format_quoted_string(b, s, ab));
if (s <= len)
return false;
b += len, s -= len;
}
formatted_len
= (tm->tm_isdst
? my_snprintf(b, s, &"\t\t%d"[show_abbr], tm->tm_isdst)
: 0);
}
break;
}
if (s <= formatted_len)
return false;
b += formatted_len, s -= formatted_len;
f = p + 1;
}
*b = '\0';
return true;
}
/* Show a time transition. */
static void
showtrans(char const *time_fmt, struct tm const *tm, time_t t, char const *ab,
char const *zone_name)
{
if (!tm) {
printf(tformat(), t);
putchar('\n');
} else {
char stackbuf[1000];
ptrdiff_t size = sizeof stackbuf;
char *buf = stackbuf;
char *bufalloc = NULL;
while (! istrftime(buf, size, time_fmt, tm, t, ab, zone_name)) {
size = sumsize(size, size);
free(bufalloc);
buf = bufalloc = xmalloc(size);
}
puts(buf);
free(bufalloc);
}
}
static char const *
abbr(struct tm const *tmp)
{
#ifdef TM_ZONE
return tmp->TM_ZONE;
#else
# if HAVE_TZNAME
if (0 <= tmp->tm_isdst && tzname[0 < tmp->tm_isdst])
return tzname[0 < tmp->tm_isdst];
# endif
return "";
#endif
}
/*
** The code below can fail on certain theoretical systems;
** it works on all known real-world systems as of 2022-01-25.
*/
static const char *
tformat(void)
{
-#if HAVE_GENERIC
+#if HAVE__GENERIC
/* C11-style _Generic is more likely to return the correct
format when distinct types have the same size. */
char const *fmt =
_Generic(+ (time_t) 0,
int: "%d", long: "%ld", long long: "%lld",
unsigned: "%u", unsigned long: "%lu",
unsigned long long: "%llu",
default: NULL);
if (fmt)
return fmt;
fmt = _Generic((time_t) 0,
intmax_t: "%"PRIdMAX, uintmax_t: "%"PRIuMAX,
default: NULL);
if (fmt)
return fmt;
#endif
if (0 > (time_t) -1) { /* signed */
if (sizeof(time_t) == sizeof(intmax_t))
return "%"PRIdMAX;
if (sizeof(time_t) > sizeof(long))
return "%lld";
if (sizeof(time_t) > sizeof(int))
return "%ld";
return "%d";
}
#ifdef PRIuMAX
if (sizeof(time_t) == sizeof(uintmax_t))
return "%"PRIuMAX;
#endif
if (sizeof(time_t) > sizeof(unsigned long))
return "%llu";
if (sizeof(time_t) > sizeof(unsigned int))
return "%lu";
return "%u";
}
static void
dumptime(register const struct tm *timeptr)
{
static const char wday_name[][4] = {
"Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
};
static const char mon_name[][4] = {
"Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
};
register int lead;
register int trail;
int DIVISOR = 10;
/*
** The packaged localtime_rz and gmtime_r never put out-of-range
** values in tm_wday or tm_mon, but since this code might be compiled
** with other (perhaps experimental) versions, paranoia is in order.
*/
printf("%s %s%3d %.2d:%.2d:%.2d ",
((0 <= timeptr->tm_wday
&& timeptr->tm_wday < (int)(sizeof wday_name / sizeof wday_name[0]))
? wday_name[timeptr->tm_wday] : "???"),
((0 <= timeptr->tm_mon
&& timeptr->tm_mon < (int)(sizeof mon_name / sizeof mon_name[0]))
? mon_name[timeptr->tm_mon] : "???"),
timeptr->tm_mday, timeptr->tm_hour,
timeptr->tm_min, timeptr->tm_sec);
trail = timeptr->tm_year % DIVISOR + TM_YEAR_BASE % DIVISOR;
lead = timeptr->tm_year / DIVISOR + TM_YEAR_BASE / DIVISOR +
trail / DIVISOR;
trail %= DIVISOR;
if (trail < 0 && lead > 0) {
trail += DIVISOR;
--lead;
} else if (lead < 0 && trail > 0) {
trail -= DIVISOR;
++lead;
}
if (lead == 0)
printf("%d", trail);
else printf("%d%d", lead, ((trail < 0) ? -trail : trail));
}
diff --git a/contrib/tzcode/zic.8 b/contrib/tzcode/zic.8
index 37d9a3abd418..d6ded3563ee1 100644
--- a/contrib/tzcode/zic.8
+++ b/contrib/tzcode/zic.8
@@ -1,858 +1,859 @@
.\" This file is in the public domain, so clarified as of
.\" 2009-05-17 by Arthur David Olson.
-.\"
-.\" $FreeBSD$
-.\"
.Dd January 21, 2023
.Dt ZIC 8
.Os
.Sh NAME
.Nm zic
.Nd timezone compiler
.Sh SYNOPSIS
.Nm
.Op Fl -help
.Op Fl -version
.Op Fl Dsv
.Op Fl b Ar slim | fat
.Op Fl d Ar directory
.Op Fl g Ar gid
.Op Fl l Ar localtime
.Op Fl L Ar leapseconds
.Op Fl m Ar mode
.Op Fl p Ar posixrules
.Oo
.Fl r
.Op @ Ns Ar lo Ns
.Op /@ Ns Ar hi
.Oc
.Op Fl R @ Ns Ar hi
.Op Fl t Ar localtime-link
.Op Fl u Ar uid
.Op Ar filename ...
.Sh DESCRIPTION
The
.Nm
program reads text from the file(s) named on the command line
and creates the timezone information format (TZif) files
specified in this input.
If a
.Ar filename
is
.Dq "-" ,
standard input is read.
.Pp
The following options are available:
.Bl -tag -width indent
.It Fl -version
Output version information and exit.
.It Fl -help
Output short usage message and exit.
.It Fl b Ar bloat
Output backward-compatibility data as specified by
.Ar bloat .
If
.Ar bloat
is
.Cm fat ,
generate additional data entries that work around potential bugs or
incompatibilities in older software, such as software that mishandles
the 64-bit generated data.
If
.Ar bloat
is
.Cm slim ,
keep the output files small; this can help check for the bugs
and incompatibilities.
The default is
.Cm slim ,
as software that mishandles 64-bit data typically
mishandles timestamps after the year 2038 anyway.
Also see the
.Fl r
option for another way to alter output size.
.It Fl D
Do not create directories.
.It Fl d Ar directory
Create time conversion information files in the named directory rather than
in the standard directory named below.
.It Fl l Ar timezone
Use
.Ar timezone
as local time.
The
.Nm
utility
will act as if the input contained a link line of the form
.Bd -literal -offset indent
Link timezone localtime
.Ed
.Pp
If
.Ar timezone
is
.Ql - ,
any already-existing link is removed.
.It Fl L Ar filename
Read leap second information from the file with the given name.
If this option is not used,
no leap second information appears in output files.
.It Fl p Ar timezone
Use
.Ar timezone 's
rules when handling nonstandard
TZ strings like
.Dq "EET\-2EEST"
that lack transition rules.
The
.Nm
utility
will act as if the input contained a link line of the form
.Bd -literal -offset indent
Link \fItimezone\fP posixrules
.Ed
.Pp
-This feature is obsolete and poorly supported.
+Unless
+.Ar timezone
+is
+.Dq "\*-" ,
+this option is obsolete and poorly supported.
Among other things it should not be used for timestamps after the year 2037,
and it should not be combined with
.Fl b Cm slim
if
.Ar timezone 's
transitions are at standard time or Universal Time (UT) instead of local time.
.Pp
If
.Ar timezone
is
.Ql - ,
any already-existing link is removed.
.It Fl r Oo @ Ns Ar lo Oc Ns Oo /@ Ns Ar hi Oc
Limit the applicability of output files
to timestamps in the range from
.Ar lo
(inclusive) to
.Ar hi
(exclusive), where
.Ar lo
and
.Ar hi
-are possibly-signed decimal counts of seconds since the Epoch
+are possibly signed decimal counts of seconds since the Epoch
(1970-01-01 00:00:00 UTC).
Omitted counts default to extreme values.
The output files use UT offset 0 and abbreviation
.Dq "\-00"
in place of the omitted timestamp data.
For example,
.Fl r @0
omits data intended for negative timestamps (i.e., before the Epoch), and
.Fl r @0/@2147483648
outputs data intended only for nonnegative timestamps that fit into
31-bit signed integers.
Although this option typically reduces the output file's size,
the size can increase due to the need to represent the timestamp range
boundaries, particularly if
.Ar hi
causes a TZif file to contain explicit entries for
.Em pre-
.Ar hi
transitions rather than concisely representing them
with an extended POSIX TZ string.
Also see the
.Fl b Cm slim
option for another way to shrink output size.
.It Fl R @ Ns Ar hi
Generate redundant trailing explicit transitions for timestamps
that occur less than
.Ar hi
seconds since the Epoch, even though the transitions could be
more concisely represented via the extended POSIX TZ string.
This option does not affect the represented timestamps.
Although it accommodates nonstandard TZif readers
that ignore the extended POSIX TZ string,
it increases the size of the altered output files.
.It Fl t Ar file
When creating local time information, put the configuration link in
the named file rather than in the standard location.
.It Fl v
Be more verbose, and complain about the following situations:
.Bl -bullet
.It
The input specifies a link to a link,
something not supported by some older parsers, including
.Nm
itself through release 2022e.
.It
A year that appears in a data file is outside the range
of representable years.
.It
A time of 24:00 or more appears in the input.
Pre-1998 versions of
.Nm
prohibit 24:00, and pre-2007 versions prohibit times greater than 24:00.
.It
A rule goes past the start or end of the month.
Pre-2004 versions of
.Nm
prohibit this.
.It
A time zone abbreviation uses a
.Ql %z
format.
Pre-2015 versions of
.Nm
do not support this.
.It
A timestamp contains fractional seconds.
Pre-2018 versions of
.Nm
do not support this.
.It
The input contains abbreviations that are mishandled by pre-2018 versions of
.Nm
due to a longstanding coding bug.
These abbreviations include
.Dq L
for
.Dq Link ,
.Dq mi
for
.Dq min ,
.Dq Sa
for
.Dq Sat ,
and
.Dq Su
for
.Dq Sun .
.It
The output file does not contain all the information about the
long-term future of a timezone, because the future cannot be summarized as
an extended POSIX TZ string.
-For example, as of 2019 this problem
-occurs for Iran's daylight-saving rules for the predicted future, as
-these rules are based on the Iranian calendar, which cannot be
-represented.
+For example, as of 2023 this problem
+occurs for Morocco's daylight-saving rules, as these rules are based
+on predictions for when Ramadan will be observed, something that
+an extended POSIX TZ string cannot represent.
.It
The output contains data that may not be handled properly by client
code designed for older
.Nm
output formats.
These compatibility issues affect only timestamps
before 1970 or after the start of 2038.
.It
The output contains a truncated leap second table,
which can cause some older TZif readers to misbehave.
This can occur if the
.Fl L
option is used, and either an Expires line is present or
the
.Fl r
option is also used.
.It
The output file contains more than 1200 transitions,
which may be mishandled by some clients.
The current reference client supports at most 2000 transitions;
pre-2014 versions of the reference client support at most 1200
transitions.
.It
A time zone abbreviation has fewer than 3 or more than 6 characters.
POSIX requires at least 3, and requires implementations to support
at least 6.
.It
An output file name contains a byte that is not an ASCII letter,
.Dq "\-" ,
.Dq "/" ,
or
.Dq "_" ;
or it contains a file name component that contains more than 14 bytes
or that starts with
.Dq "\-" .
.El
.El
.RE
.Sh FILES
Input files use the format described in this section; output files use
.Xr tzfile 5
format.
.Pp
Input files should be text files, that is, they should be a series of
zero or more lines, each ending in a newline byte and containing at
most 2048 bytes counting the newline, and without any NUL bytes.
The input text's encoding
is typically UTF-8 or ASCII; it should have a unibyte representation
for the POSIX Portable Character Set (PPCS)
\*
and the encoding's non-unibyte characters should consist entirely of
non-PPCS bytes.
Non-PPCS characters typically occur only in comments:
although output file names and time zone abbreviations can contain
nearly any character, other software will work better if these are
limited to the restricted syntax described under the
.Fl v
option.
.Pp
Input lines are made up of fields.
Fields are separated from one another by one or more white space characters.
The white space characters are space, form feed, carriage return, newline,
tab, and vertical tab.
Leading and trailing white space on input lines is ignored.
An unquoted sharp character (\(sh) in the input introduces a comment which extends
to the end of the line the sharp character appears on.
White space characters and sharp characters may be enclosed in double quotes
(\(dq) if they're to be used as part of a field.
Any line that is blank (after comment stripping) is ignored.
Nonblank lines are expected to be of one of three types:
rule lines, zone lines, and link lines.
.Pp
Names must be in English and are case insensitive.
They appear in several contexts, and include month and weekday names
and keywords such as
.Dq "maximum" ,
.Dq "only" ,
.Dq "Rolling" ,
and
.Dq "Zone" .
A name can be abbreviated by omitting all but an initial prefix; any
abbreviation must be unambiguous in context.
.Pp
A rule line has the form
.Bd -literal -offset indent
Rule NAME FROM TO \- IN ON AT SAVE LETTER/S
.Ed
.Pp
For example:
.Bd -literal -offset indent
Rule US 1967 1973 \- Apr lastSun 2:00w 1:00d D
.Ed
.Pp
The fields that make up a rule line are:
.Bl -tag -width "LETTER/S"
.It NAME
Gives the name of the rule set that contains this line.
The name must start with a character that is neither
an ASCII digit nor
.Dq \-
nor
.Dq + .
To allow for future extensions,
an unquoted name should not contain characters from the set
.Dq Ql "!$%&'()*,/:;<=>?@[\]^`{|}~" .
.It FROM
Gives the first year in which the rule applies.
Any signed integer year can be supplied; the proleptic Gregorian calendar
is assumed, with year 0 preceding year 1.
The word
.Cm minimum
(or an abbreviation) means the indefinite past.
The word
.Cm maximum
(or an abbreviation) means the indefinite future.
Rules can describe times that are not representable as time values,
with the unrepresentable times ignored; this allows rules to be portable
among hosts with differing time value types.
.It TO
Gives the final year in which the rule applies.
In addition to
.Cm minimum
and
.Cm maximum
(as above),
the word
.Cm only
(or an abbreviation)
may be used to repeat the value of the
.Ar FROM
field.
.It \-
Is a reserved field and should always contain
.Ql \-
for compatibility with older versions of
.Nm .
It was previously known as the
.Ar TYPE
field, which could contain values to allow a
separate script to further restrict in which
.Dq types
of years the rule would apply.
.It IN
Names the month in which the rule takes effect.
Month names may be abbreviated.
.It ON
Gives the day on which the rule takes effect.
Recognized forms include:
.Bl -tag -compact -width "Sun<=25"
.It 5
the fifth of the month
.It lastSun
the last Sunday in the month
.It lastMon
the last Monday in the month
.It Sun>=8
first Sunday on or after the eighth
.It Sun<=25
last Sunday on or before the 25th
.El
.Pp
A weekday name (e.g.,
.Ql "Sunday" )
or a weekday name preceded by
.Dq "last"
(e.g.,
.Ql "lastSunday" )
may be abbreviated or spelled out in full.
There must be no white space characters within the
.Ar ON
field.
The
.Dq <=
and
.Dq >=
constructs can result in a day in the neighboring month;
for example, the IN-ON combination
.Dq "Oct Sun>=31"
stands for the first Sunday on or after October 31,
even if that Sunday occurs in November.
.It AT
Gives the time of day at which the rule takes effect,
relative to 00:00, the start of a calendar day.
Recognized forms include:
.Bl -tag -compact -width "00:19:32.13"
.It 2
time in hours
.It 2:00
time in hours and minutes
.It 01:28:14
time in hours, minutes, and seconds
.It 00:19:32.13
time with fractional seconds
.It 12:00
midday, 12 hours after 00:00
.It 15:00
3 PM, 15 hours after 00:00
.It 24:00
end of day, 24 hours after 00:00
.It 260:00
260 hours after 00:00
.It \-2:30
2.5 hours before 00:00
.It \-
equivalent to 0
.El
.Pp
Although
.Nm
rounds times to the nearest integer second
(breaking ties to the even integer), the fractions may be useful
to other applications requiring greater precision.
The source format does not specify any maximum precision.
Any of these forms may be followed by the letter
.Ql w
if the given time is local or
.Dq "wall clock"
time,
.Ql s
if the given time is standard time without any adjustment for daylight saving,
or
.Ql u
(or
.Ql g
or
.Ql z )
if the given time is universal time;
in the absence of an indicator,
local (wall clock) time is assumed.
These forms ignore leap seconds; for example,
if a leap second occurs at 00:59:60 local time,
.Ql "1:00"
stands for 3601 seconds after local midnight instead of the usual 3600 seconds.
The intent is that a rule line describes the instants when a
clock/calendar set to the type of time specified in the
.Ar AT
field would show the specified date and time of day.
.It SAVE
Gives the amount of time to be added to local standard time when the rule is in
effect, and whether the resulting time is standard or daylight saving.
This field has the same format as the
.Ar AT
field
except with a different set of suffix letters:
.Ql s
for standard time and
.Ql d
for daylight saving time.
The suffix letter is typically omitted, and defaults to
.Ql s
if the offset is zero and to
.Ql d
otherwise.
Negative offsets are allowed; in Ireland, for example, daylight saving
time is observed in winter and has a negative offset relative to
Irish Standard Time.
The offset is merely added to standard time; for example,
.Nm
does not distinguish a 10:30 standard time plus an 0:30
.Ar SAVE
from a 10:00 standard time plus a 1:00
.Ar SAVE .
.It LETTER/S
Gives the
.Dq "variable part"
(for example, the
.Dq "S"
or
.Dq "D"
in
.Dq "EST"
or
.Dq "EDT" )
of time zone abbreviations to be used when this rule is in effect.
If this field is
.Ql \- ,
the variable part is null.
.El
.Pp
A zone line has the form
.Bd -literal -offset indent
Zone NAME STDOFF RULES FORMAT [UNTIL]
.Ed
.Pp
For example:
.Bd -literal -offset indent
Zone Asia/Amman 2:00 Jordan EE%sT 2017 Oct 27 01:00
.Ed
.Pp
The fields that make up a zone line are:
.Bl -tag -width "STDOFF"
.It NAME
The name of the timezone.
This is the name used in creating the time conversion information file for the
timezone.
It should not contain a file name component
.Dq ".\&"
or
.Dq ".." ;
a file name component is a maximal substring that does not contain
.Dq "/" .
.It STDOFF
The amount of time to add to UT to get standard time,
without any adjustment for daylight saving.
This field has the same format as the
.Ar AT
and
.Ar SAVE
fields of rule lines, except without suffix letters;
begin the field with a minus sign if time must be subtracted from UT.
.It RULES
The name of the rules that apply in the timezone or,
alternatively, a field in the same format as a rule-line SAVE column,
giving the amount of time to be added to local standard time
and whether the resulting time is standard or daylight saving.
If this field is
.Ql \-
then standard time always applies.
When an amount of time is given, only the sum of standard time and
this amount matters.
.It FORMAT
The format for time zone abbreviations.
The pair of characters
.Ql %s
is used to show where the
.Dq "variable part"
of the time zone abbreviation goes.
Alternatively, a format can use the pair of characters
.Ql %z
to stand for the UT offset in the form
.Ar \(+- hh ,
.Ar \(+- hhmm ,
or
.Ar \(+- hhmmss ,
using the shortest form that does not lose information, where
.Ar hh ,
.Ar mm ,
and
.Ar ss
are the hours, minutes, and seconds east (+) or west (\-) of UT.
Alternatively,
a slash (/)
separates standard and daylight abbreviations.
To conform to POSIX, a time zone abbreviation should contain only
alphanumeric ASCII characters,
.Ql "+"
and
.Ql "\-".
By convention, the time zone abbreviation
.Ql "\-00"
is a placeholder that means local time is unspecified.
.It UNTIL
The time at which the UT offset or the rule(s) change for a location.
It takes the form of one to four fields
.Ar YEAR Op Ar MONTH Op Ar DAY Op Ar TIME .
If this is specified,
the time zone information is generated from the given UT offset
and rule change until the time specified, which is interpreted using
the rules in effect just before the transition.
The month, day, and time of day have the same format as the
.Ar IN ,
.Ar ON ,
and
.Ar AT
fields of a rule; trailing fields can be omitted, and default to the
earliest possible value for the missing fields.
.IP
The next line must be a
.Dq "continuation"
line; this has the same form as a zone line except that the
string
.Dq "Zone"
and the name are omitted, as the continuation line will
place information starting at the time specified as the
.Dq "until"
information in the previous line in the file used by the previous line.
Continuation lines may contain
.Dq "until"
information, just as zone lines do, indicating that the next line is a further
continuation.
.El
.Pp
If a zone changes at the same instant that a rule would otherwise take
effect in the earlier zone or continuation line, the rule is ignored.
A zone or continuation line
.Ar L
with a named rule set starts with standard time by default:
that is, any of
.Ar L 's
timestamps preceding
.Ar L 's
earliest rule use the rule in effect after
.Ar L 's
first transition into standard time.
In a single zone it is an error if two rules take effect at the same
instant, or if two zone changes take effect at the same instant.
.Pp
If a continuation line subtracts
.Ar N
seconds from the UT offset after a transition that would be
interpreted to be later if using the continuation line's UT offset and
rules, the
.Dq "until"
time of the previous zone or continuation line is interpreted
according to the continuation line's UT offset and rules, and any rule
that would otherwise take effect in the next
.Ar N
seconds is instead assumed to take effect simultaneously.
For example:
.Bd -literal -offset indent
# Rule NAME FROM TO \*- IN ON AT SAVE LETTER/S
Rule US 1967 2006 - Oct lastSun 2:00 0 S
Rule US 1967 1973 - Apr lastSun 2:00 1:00 D
# Zone\0\0NAME STDOFF RULES FORMAT [UNTIL]
Zone\0\0America/Menominee \*-5:00 \*- EST 1973 Apr 29 2:00
\*-6:00 US C%sT
.Ed
Here, an incorrect reading would be there were two clock changes on 1973-04-29,
the first from 02:00 EST (\-05) to 01:00 CST (\-06),
and the second an hour later from 02:00 CST (\-06) to 03:00 CDT (\-05).
However,
.Nm
interprets this more sensibly as a single transition from 02:00 CST (\-05) to
02:00 CDT (\-05).
.Pp
A link line has the form
.Bd -literal -offset indent
Link TARGET LINK-NAME
.Ed
.Pp
For example:
.Bd -literal -offset indent
Link Europe/Istanbul Asia/Istanbul
.Ed
.Pp
The
.Ar TARGET
field should appear as the
.Ar NAME
field in some zone line or as the
.Ar LINK-NAME
field in some link line.
The
.Ar LINK-NAME
field is used as an alternative name for that zone;
it has the same syntax as a zone line's
.Ar NAME
field.
Links can chain together, although the behavior is unspecified if a
chain of one or more links does not terminate in a Zone name.
A link line can appear before the line that defines the link target.
For example:
.Bd -literal -offset indent
Link Greenwich G_M_T
Link Etc/GMT Greenwich
Zone Etc/GMT\0\00\0\0\-\0\0GMT
.Ed
.Pp
The two links are chained together, and G_M_T, Greenwich, and Etc/GMT
all name the same zone.
.Pp
Except for continuation lines,
lines may appear in any order in the input.
However, the behavior is unspecified if multiple zone or link lines
define the same name.
.Pp
The file that describes leap seconds can have leap lines and an
expiration line.
Leap lines have the following form:
.Bd -literal -offset indent
Leap YEAR MONTH DAY HH:MM:SS CORR R/S
.Ed
.Pp
For example:
.Bd -literal -offset indent
Leap 2016 Dec 31 23:59:60 + S
.Ed
.Pp
The
.Ar YEAR ,
.Ar MONTH ,
.Ar DAY ,
and
.Ar HH:MM:SS
fields tell when the leap second happened.
The
.Ar CORR
field
should be
.Ql "+"
if a second was added
or
.Ql "\-"
if a second was skipped.
The
.Ar R/S
field
should be (an abbreviation of)
.Dq "Stationary"
if the leap second time given by the other fields should be interpreted as UTC
or
(an abbreviation of)
.Dq "Rolling"
if the leap second time given by the other fields should be interpreted as
local (wall clock) time.
.Pp
Rolling leap seconds were implemented back when it was not
clear whether common practice was rolling or stationary,
with concerns that one would see
Times Square ball drops where there'd be a
.Dq "3... 2... 1... leap... Happy New Year"
countdown, placing the leap second at
midnight New York time rather than midnight UTC.
However, this countdown style does not seem to have caught on,
which means rolling leap seconds are not used in practice;
also, they are not supported if the
.Fl r
option is used.
.Pp
The expiration line, if present, has the form:
.Bd -literal -offset indent
Expires YEAR MONTH DAY HH:MM:SS
.Ed
.Pp
For example:
.Bd -literal -offset indent
Expires 2020 Dec 28 00:00:00
.Ed
.Pp
The
.Ar YEAR ,
.Ar MONTH ,
.Ar DAY ,
and
.Ar HH:MM:SS
fields give the expiration timestamp in UTC for the leap second table.
.Sh "EXTENDED EXAMPLE"
Here is an extended example of
.Nm
input, intended to illustrate many of its features.
.Bd -literal -offset indent
# Rule NAME FROM TO \- IN ON AT SAVE LETTER/S
Rule Swiss 1941 1942 \- May Mon>=1 1:00 1:00 S
Rule Swiss 1941 1942 \- Oct Mon>=1 2:00 0 \-
Rule EU 1977 1980 \- Apr Sun>=1 1:00u 1:00 S
Rule EU 1977 only \- Sep lastSun 1:00u 0 \-
Rule EU 1978 only \- Oct 1 1:00u 0 \-
Rule EU 1979 1995 \- Sep lastSun 1:00u 0 \-
Rule EU 1981 max \- Mar lastSun 1:00u 1:00 S
Rule EU 1996 max \- Oct lastSun 1:00u 0 \-
# Zone NAME STDOFF RULES FORMAT [UNTIL]
Zone Europe/Zurich 0:34:08 \- LMT 1853 Jul 16
0:29:45.50 \- BMT 1894 Jun
1:00 Swiss CE%sT 1981
1:00 EU CE%sT
Link Europe/Zurich Europe/Vaduz
.Ed
.Pp
In this example, the EU rules are for the European Union
and for its predecessor organization, the European Communities.
The timezone is named Europe/Zurich and it has the alias Europe/Vaduz.
This example says that Zurich was 34 minutes and 8
seconds east of UT until 1853-07-16 at 00:00, when the legal offset
was changed to
7\(de26\(fm22.50\(sd,
which works out to 0:29:45.50;
.Nm
treats this by rounding it to 0:29:46.
After 1894-06-01 at 00:00 the UT offset became one hour
and Swiss daylight saving rules (defined with lines beginning with
.Dq "Rule Swiss")
apply.
From 1981 to the present, EU daylight saving rules have
applied, and the UTC offset has remained at one hour.
.Pp
In 1941 and 1942, daylight saving time applied from the first Monday
in May at 01:00 to the first Monday in October at 02:00.
The pre-1981 EU daylight-saving rules have no effect
here, but are included for completeness.
Since 1981, daylight
saving has begun on the last Sunday in March at 01:00 UTC.
Until 1995 it ended the last Sunday in September at 01:00 UTC,
but this changed to the last Sunday in October starting in 1996.
.Pp
For purposes of display,
.Dq "LMT"
and
.Dq "BMT"
were initially used, respectively.
Since
Swiss rules and later EU rules were applied, the time zone abbreviation
has been CET for standard time and CEST for daylight saving
time.
.Sh FILES
.Bl -tag -width "/usr/share/zoneinfo"
.It Pa /etc/localtime
Default local timezone file.
.It Pa /usr/share/zoneinfo
Default timezone information directory.
.El
.Sh NOTES
For areas with more than two types of local time,
you may need to use local standard time in the
.Ar AT
field of the earliest transition time's rule to ensure that
the earliest transition time recorded in the compiled file is correct.
.Pp
If,
for a particular timezone,
a clock advance caused by the start of daylight saving
coincides with and is equal to
a clock retreat caused by a change in UT offset,
.Nm
produces a single transition to daylight saving at the new UT offset
without any change in local (wall clock) time.
To get separate transitions
use multiple zone continuation lines
specifying transition instants using universal time.
.Sh SEE ALSO
.Xr tzfile 5 ,
.Xr zdump 8
diff --git a/contrib/tzcode/zic.c b/contrib/tzcode/zic.c
index c65282d46d27..55ce60315ffd 100644
--- a/contrib/tzcode/zic.c
+++ b/contrib/tzcode/zic.c
@@ -1,4049 +1,4050 @@
/* Compile .zi time zone data into TZif binary files. */
/*
** This file is in the public domain, so clarified as of
** 2006-07-17 by Arthur David Olson.
*/
/* Use the system 'time' function, instead of any private replacement.
This avoids creating an unnecessary dependency on localtime.c. */
#undef EPOCH_LOCAL
#undef EPOCH_OFFSET
#undef RESERVE_STD_EXT_IDS
#undef time_tz
#include "version.h"
#include "private.h"
#include "tzfile.h"
#include
#include
#include
#include
#include
typedef int_fast64_t zic_t;
static zic_t const
ZIC_MIN = INT_FAST64_MIN,
ZIC_MAX = INT_FAST64_MAX,
ZIC32_MIN = -1 - (zic_t) 0x7fffffff,
ZIC32_MAX = 0x7fffffff;
#define SCNdZIC SCNdFAST64
#ifndef ZIC_MAX_ABBR_LEN_WO_WARN
# define ZIC_MAX_ABBR_LEN_WO_WARN 6
#endif /* !defined ZIC_MAX_ABBR_LEN_WO_WARN */
/* An upper bound on how much a format might grow due to concatenation. */
enum { FORMAT_LEN_GROWTH_BOUND = 5 };
#ifdef HAVE_DIRECT_H
# include
# include
# undef mkdir
# define mkdir(name, mode) _mkdir(name)
#endif
#ifndef HAVE_GETRANDOM
# ifdef __has_include
# if __has_include()
# include
# endif
# elif 2 < __GLIBC__ + (25 <= __GLIBC_MINOR__)
# include
# endif
# define HAVE_GETRANDOM GRND_RANDOM
#elif HAVE_GETRANDOM
# include
#endif
#if HAVE_SYS_STAT_H
# include
#endif
#ifdef S_IRUSR
# define MKDIR_UMASK (S_IRUSR|S_IWUSR|S_IXUSR|S_IRGRP|S_IXGRP|S_IROTH|S_IXOTH)
#else
# define MKDIR_UMASK 0755
#endif
-/* The minimum alignment of a type, for pre-C23 platforms. */
-#if __STDC_VERSION__ < 201112
+/* The minimum alignment of a type, for pre-C23 platforms.
+ The __SUNPRO_C test is because Oracle Developer Studio 12.6 lacks
+ even though __STDC_VERSION__ == 201112. */
+#if __STDC_VERSION__ < 201112 || defined __SUNPRO_C
# define alignof(type) offsetof(struct { char a; type b; }, b)
#elif __STDC_VERSION__ < 202311
# include
#endif
/* The maximum length of a text line, including the trailing newline. */
#ifndef _POSIX2_LINE_MAX
# define _POSIX2_LINE_MAX 2048
#endif
/* The type for line numbers. Use PRIdMAX to format them; formerly
there was also "#define PRIdLINENO PRIdMAX" and formats used
PRIdLINENO, but xgettext cannot grok that. */
typedef intmax_t lineno;
struct rule {
int r_filenum;
lineno r_linenum;
const char * r_name;
zic_t r_loyear; /* for example, 1986 */
zic_t r_hiyear; /* for example, 1986 */
bool r_lowasnum;
bool r_hiwasnum;
int r_month; /* 0..11 */
int r_dycode; /* see below */
int r_dayofmonth;
int r_wday;
zic_t r_tod; /* time from midnight */
bool r_todisstd; /* is r_tod standard time? */
bool r_todisut; /* is r_tod UT? */
bool r_isdst; /* is this daylight saving time? */
zic_t r_save; /* offset from standard time */
const char * r_abbrvar; /* variable part of abbreviation */
bool r_todo; /* a rule to do (used in outzone) */
zic_t r_temp; /* used in outzone */
};
/*
** r_dycode r_dayofmonth r_wday
*/
enum {
DC_DOM, /* 1..31 */ /* unused */
DC_DOWGEQ, /* 1..31 */ /* 0..6 (Sun..Sat) */
DC_DOWLEQ /* 1..31 */ /* 0..6 (Sun..Sat) */
};
struct zone {
int z_filenum;
lineno z_linenum;
const char * z_name;
zic_t z_stdoff;
char * z_rule;
const char * z_format;
char z_format_specifier;
bool z_isdst;
zic_t z_save;
struct rule * z_rules;
ptrdiff_t z_nrules;
struct rule z_untilrule;
zic_t z_untiltime;
};
#if !HAVE_POSIX_DECLS
extern int getopt(int argc, char * const argv[],
const char * options);
extern int link(const char * target, const char * linkname);
extern char * optarg;
extern int optind;
#endif
#if ! HAVE_SYMLINK
static ssize_t
readlink(char const *restrict file, char *restrict buf, size_t size)
{
errno = ENOTSUP;
return -1;
}
static int
symlink(char const *target, char const *linkname)
{
errno = ENOTSUP;
return -1;
}
#endif
#ifndef AT_SYMLINK_FOLLOW
# if HAVE_LINK
# define linkat(targetdir, target, linknamedir, linkname, flag) \
(itssymlink(target) ? (errno = ENOTSUP, -1) : link(target, linkname))
# else
# define linkat(targetdir, target, linknamedir, linkname, flag) \
(errno = ENOTSUP, -1)
# endif
#endif
static void addtt(zic_t starttime, int type);
static int addtype(zic_t, char const *, bool, bool, bool);
static void leapadd(zic_t, int, int);
static void adjleap(void);
static void associate(void);
static void dolink(const char *, const char *, bool);
static int getfields(char *, char **, int);
static zic_t gethms(const char * string, const char * errstring);
static zic_t getsave(char *, bool *);
static void inexpires(char **, int);
static void infile(int, char const *);
static void inleap(char ** fields, int nfields);
static void inlink(char ** fields, int nfields);
static void inrule(char ** fields, int nfields);
static bool inzcont(char ** fields, int nfields);
static bool inzone(char ** fields, int nfields);
static bool inzsub(char **, int, bool);
static bool itssymlink(char const *);
static bool is_alpha(char a);
static char lowerit(char);
static void mkdirs(char const *, bool);
static void newabbr(const char * abbr);
static zic_t oadd(zic_t t1, zic_t t2);
static void outzone(const struct zone * zp, ptrdiff_t ntzones);
static zic_t rpytime(const struct rule * rp, zic_t wantedy);
static bool rulesub(struct rule * rp,
const char * loyearp, const char * hiyearp,
const char * typep, const char * monthp,
const char * dayp, const char * timep);
static void setgroup(gid_t *flag, const char *name);
static void setuser(uid_t *flag, const char *name);
static zic_t tadd(zic_t t1, zic_t t2);
/* Bound on length of what %z can expand to. */
enum { PERCENT_Z_LEN_BOUND = sizeof "+995959" - 1 };
static int charcnt;
static bool errors;
static bool warnings;
static int filenum;
static int leapcnt;
static bool leapseen;
static zic_t leapminyear;
static zic_t leapmaxyear;
static lineno linenum;
static size_t max_abbrvar_len = PERCENT_Z_LEN_BOUND;
static int max_format_len;
static zic_t max_year;
static zic_t min_year;
static bool noise;
static int rfilenum;
static lineno rlinenum;
static const char * progname;
static char const * leapsec;
static char *const * main_argv;
static ptrdiff_t timecnt;
static ptrdiff_t timecnt_alloc;
static int typecnt;
static int unspecifiedtype;
/*
** Line codes.
*/
enum {
LC_RULE,
LC_ZONE,
LC_LINK,
LC_LEAP,
LC_EXPIRES
};
/*
** Which fields are which on a Zone line.
*/
enum {
ZF_NAME = 1,
ZF_STDOFF,
ZF_RULE,
ZF_FORMAT,
ZF_TILYEAR,
ZF_TILMONTH,
ZF_TILDAY,
ZF_TILTIME,
ZONE_MAXFIELDS,
ZONE_MINFIELDS = ZF_TILYEAR
};
/*
** Which fields are which on a Zone continuation line.
*/
enum {
ZFC_STDOFF,
ZFC_RULE,
ZFC_FORMAT,
ZFC_TILYEAR,
ZFC_TILMONTH,
ZFC_TILDAY,
ZFC_TILTIME,
ZONEC_MAXFIELDS,
ZONEC_MINFIELDS = ZFC_TILYEAR
};
/*
** Which files are which on a Rule line.
*/
enum {
RF_NAME = 1,
RF_LOYEAR,
RF_HIYEAR,
RF_COMMAND,
RF_MONTH,
RF_DAY,
RF_TOD,
RF_SAVE,
RF_ABBRVAR,
RULE_FIELDS
};
/*
** Which fields are which on a Link line.
*/
enum {
LF_TARGET = 1,
LF_LINKNAME,
LINK_FIELDS
};
/*
** Which fields are which on a Leap line.
*/
enum {
LP_YEAR = 1,
LP_MONTH,
LP_DAY,
LP_TIME,
LP_CORR,
LP_ROLL,
LEAP_FIELDS,
/* Expires lines are like Leap lines, except without CORR and ROLL fields. */
EXPIRES_FIELDS = LP_TIME + 1
};
/* The maximum number of fields on any of the above lines.
(The "+"s pacify gcc -Wenum-compare.) */
enum {
MAX_FIELDS = max(max(+RULE_FIELDS, +LINK_FIELDS),
max(+LEAP_FIELDS, +EXPIRES_FIELDS))
};
/*
** Year synonyms.
*/
enum {
YR_MINIMUM,
YR_MAXIMUM,
YR_ONLY
};
static struct rule * rules;
static ptrdiff_t nrules; /* number of rules */
static ptrdiff_t nrules_alloc;
static struct zone * zones;
static ptrdiff_t nzones; /* number of zones */
static ptrdiff_t nzones_alloc;
struct link {
int l_filenum;
lineno l_linenum;
const char * l_target;
const char * l_linkname;
};
static struct link * links;
static ptrdiff_t nlinks;
static ptrdiff_t nlinks_alloc;
struct lookup {
const char * l_word;
const int l_value;
};
static struct lookup const * byword(const char * string,
const struct lookup * lp);
static struct lookup const zi_line_codes[] = {
{ "Rule", LC_RULE },
{ "Zone", LC_ZONE },
{ "Link", LC_LINK },
{ NULL, 0 }
};
static struct lookup const leap_line_codes[] = {
{ "Leap", LC_LEAP },
{ "Expires", LC_EXPIRES },
{ NULL, 0}
};
static struct lookup const mon_names[] = {
{ "January", TM_JANUARY },
{ "February", TM_FEBRUARY },
{ "March", TM_MARCH },
{ "April", TM_APRIL },
{ "May", TM_MAY },
{ "June", TM_JUNE },
{ "July", TM_JULY },
{ "August", TM_AUGUST },
{ "September", TM_SEPTEMBER },
{ "October", TM_OCTOBER },
{ "November", TM_NOVEMBER },
{ "December", TM_DECEMBER },
{ NULL, 0 }
};
static struct lookup const wday_names[] = {
{ "Sunday", TM_SUNDAY },
{ "Monday", TM_MONDAY },
{ "Tuesday", TM_TUESDAY },
{ "Wednesday", TM_WEDNESDAY },
{ "Thursday", TM_THURSDAY },
{ "Friday", TM_FRIDAY },
{ "Saturday", TM_SATURDAY },
{ NULL, 0 }
};
static struct lookup const lasts[] = {
{ "last-Sunday", TM_SUNDAY },
{ "last-Monday", TM_MONDAY },
{ "last-Tuesday", TM_TUESDAY },
{ "last-Wednesday", TM_WEDNESDAY },
{ "last-Thursday", TM_THURSDAY },
{ "last-Friday", TM_FRIDAY },
{ "last-Saturday", TM_SATURDAY },
{ NULL, 0 }
};
static struct lookup const begin_years[] = {
{ "minimum", YR_MINIMUM },
{ "maximum", YR_MAXIMUM },
{ NULL, 0 }
};
static struct lookup const end_years[] = {
{ "minimum", YR_MINIMUM },
{ "maximum", YR_MAXIMUM },
{ "only", YR_ONLY },
{ NULL, 0 }
};
static struct lookup const leap_types[] = {
{ "Rolling", true },
{ "Stationary", false },
{ NULL, 0 }
};
static const int len_months[2][MONSPERYEAR] = {
{ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 },
{ 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }
};
static const int len_years[2] = {
DAYSPERNYEAR, DAYSPERLYEAR
};
static struct attype {
zic_t at;
bool dontmerge;
unsigned char type;
} * attypes;
static zic_t utoffs[TZ_MAX_TYPES];
static char isdsts[TZ_MAX_TYPES];
static unsigned char desigidx[TZ_MAX_TYPES];
static bool ttisstds[TZ_MAX_TYPES];
static bool ttisuts[TZ_MAX_TYPES];
static char chars[TZ_MAX_CHARS];
static zic_t trans[TZ_MAX_LEAPS];
static zic_t corr[TZ_MAX_LEAPS];
static char roll[TZ_MAX_LEAPS];
/*
** Memory allocation.
*/
-static ATTRIBUTE_NORETURN void
+ATTRIBUTE_NORETURN static void
memory_exhausted(const char *msg)
{
fprintf(stderr, _("%s: Memory exhausted: %s\n"), progname, msg);
exit(EXIT_FAILURE);
}
-static ATTRIBUTE_NORETURN void
+ATTRIBUTE_NORETURN static void
size_overflow(void)
{
memory_exhausted(_("size overflow"));
}
-static ATTRIBUTE_REPRODUCIBLE size_t
+ATTRIBUTE_REPRODUCIBLE static ptrdiff_t
size_sum(size_t a, size_t b)
{
#ifdef ckd_add
- size_t sum;
- if (!ckd_add(&sum, a, b))
+ ptrdiff_t sum;
+ if (!ckd_add(&sum, a, b) && sum <= INDEX_MAX)
return sum;
#else
- if (b <= SIZE_MAX - a)
+ if (a <= INDEX_MAX && b <= INDEX_MAX - a)
return a + b;
#endif
size_overflow();
}
-static ATTRIBUTE_REPRODUCIBLE size_t
-size_product(size_t nitems, size_t itemsize)
+ATTRIBUTE_REPRODUCIBLE static ptrdiff_t
+size_product(ptrdiff_t nitems, ptrdiff_t itemsize)
{
#ifdef ckd_mul
- size_t product;
- if (!ckd_mul(&product, nitems, itemsize))
+ ptrdiff_t product;
+ if (!ckd_mul(&product, nitems, itemsize) && product <= INDEX_MAX)
return product;
#else
- if (nitems <= SIZE_MAX / itemsize)
+ ptrdiff_t nitems_max = INDEX_MAX / itemsize;
+ if (nitems <= nitems_max)
return nitems * itemsize;
#endif
size_overflow();
}
-static ATTRIBUTE_REPRODUCIBLE size_t
-align_to(size_t size, size_t alignment)
+ATTRIBUTE_REPRODUCIBLE static ptrdiff_t
+align_to(ptrdiff_t size, ptrdiff_t alignment)
{
size_t lo_bits = alignment - 1, sum = size_sum(size, lo_bits);
return sum & ~lo_bits;
}
#if !HAVE_STRDUP
static char *
strdup(char const *str)
{
char *result = malloc(strlen(str) + 1);
return result ? strcpy(result, str) : result;
}
#endif
static void *
memcheck(void *ptr)
{
if (ptr == NULL)
memory_exhausted(strerror(HAVE_MALLOC_ERRNO ? errno : ENOMEM));
return ptr;
}
-static void * ATTRIBUTE_MALLOC
+ATTRIBUTE_MALLOC static void *
emalloc(size_t size)
{
return memcheck(malloc(size));
}
static void *
erealloc(void *ptr, size_t size)
{
return memcheck(realloc(ptr, size));
}
-static char * ATTRIBUTE_MALLOC
+ATTRIBUTE_MALLOC static char *
estrdup(char const *str)
{
return memcheck(strdup(str));
}
static ptrdiff_t
grow_nitems_alloc(ptrdiff_t *nitems_alloc, ptrdiff_t itemsize)
{
ptrdiff_t addend = (*nitems_alloc >> 1) + 1;
#if defined ckd_add && defined ckd_mul
ptrdiff_t product;
if (!ckd_add(nitems_alloc, *nitems_alloc, addend)
- && !ckd_mul(&product, *nitems_alloc, itemsize) /* && product <= SIZE_MAX */)
+ && !ckd_mul(&product, *nitems_alloc, itemsize) && product <= INDEX_MAX)
return product;
#else
- ptrdiff_t amax = min(PTRDIFF_MAX, SIZE_MAX);
- if (*nitems_alloc <= ((amax - 1) / 3 * 2) / itemsize) {
+ if (*nitems_alloc <= ((INDEX_MAX - 1) / 3 * 2) / itemsize) {
*nitems_alloc += addend;
return *nitems_alloc * itemsize;
}
#endif
memory_exhausted(_("integer overflow"));
}
static void *
growalloc(void *ptr, ptrdiff_t itemsize, ptrdiff_t nitems,
ptrdiff_t *nitems_alloc)
{
return (nitems < *nitems_alloc
? ptr
: erealloc(ptr, grow_nitems_alloc(nitems_alloc, itemsize)));
}
/*
** Error handling.
*/
/* In most of the code, an input file name is represented by its index
into the main argument vector, except that LEAPSEC_FILENUM stands
for leapsec and COMMAND_LINE_FILENUM stands for the command line. */
enum { LEAPSEC_FILENUM = -2, COMMAND_LINE_FILENUM = -1 };
/* Return the name of the Ith input file, for diagnostics. */
static char const *
filename(int i)
{
if (i == COMMAND_LINE_FILENUM)
return _("command line");
else {
char const *fname = i == LEAPSEC_FILENUM ? leapsec : main_argv[i];
return strcmp(fname, "-") == 0 ? _("standard input") : fname;
}
}
static void
eats(int fnum, lineno num, int rfnum, lineno rnum)
{
filenum = fnum;
linenum = num;
rfilenum = rfnum;
rlinenum = rnum;
}
static void
eat(int fnum, lineno num)
{
eats(fnum, num, 0, -1);
}
-static void ATTRIBUTE_FORMAT((printf, 1, 0))
+ATTRIBUTE_FORMAT((printf, 1, 0)) static void
verror(const char *const string, va_list args)
{
/*
** Match the format of "cc" to allow sh users to
** zic ... 2>&1 | error -t "*" -v
** on BSD systems.
*/
if (filenum)
fprintf(stderr, _("\"%s\", line %"PRIdMAX": "),
filename(filenum), linenum);
vfprintf(stderr, string, args);
if (rfilenum)
fprintf(stderr, _(" (rule from \"%s\", line %"PRIdMAX")"),
filename(rfilenum), rlinenum);
fprintf(stderr, "\n");
}
-static void ATTRIBUTE_FORMAT((printf, 1, 2))
+ATTRIBUTE_FORMAT((printf, 1, 2)) static void
error(const char *const string, ...)
{
va_list args;
va_start(args, string);
verror(string, args);
va_end(args);
errors = true;
}
-static void ATTRIBUTE_FORMAT((printf, 1, 2))
+ATTRIBUTE_FORMAT((printf, 1, 2)) static void
warning(const char *const string, ...)
{
va_list args;
fprintf(stderr, _("warning: "));
va_start(args, string);
verror(string, args);
va_end(args);
warnings = true;
}
/* Close STREAM. If it had an I/O error, report it against DIR/NAME,
remove TEMPNAME if nonnull, and then exit. */
static void
close_file(FILE *stream, char const *dir, char const *name,
char const *tempname)
{
char const *e = (ferror(stream) ? _("I/O error")
: fclose(stream) != 0 ? strerror(errno) : NULL);
if (e) {
fprintf(stderr, "%s: %s%s%s%s%s\n", progname,
dir ? dir : "", dir ? "/" : "",
name ? name : "", name ? ": " : "",
e);
if (tempname)
(void)remove(tempname);
exit(EXIT_FAILURE);
}
}
-static ATTRIBUTE_NORETURN void
+ATTRIBUTE_NORETURN static void
usage(FILE *stream, int status)
{
fprintf(stream,
_("%s: usage is %s [ --version ] [ --help ] [ -v ] \\\n"
"\t[ -b {slim|fat} ] [ -d directory ] [ -l localtime ]"
" [ -L leapseconds ] \\\n"
"\t[ -p posixrules ] [ -r '[@lo][/@hi]' ] [ -R '@hi' ] \\\n"
"\t[ -t localtime-link ] [ -D ] [ -g gid ] [ -u uid ] \\\n"
"\t[ filename ... ]\n\n"
"Report bugs to %s.\n"),
progname, progname, REPORT_BUGS_TO);
if (status == EXIT_SUCCESS)
close_file(stream, NULL, NULL, NULL);
exit(status);
}
/* Change the working directory to DIR, possibly creating DIR and its
ancestors. After this is done, all files are accessed with names
relative to DIR. */
static void
change_directory(char const *dir)
{
if (chdir(dir) != 0) {
int chdir_errno = errno;
if (chdir_errno == ENOENT) {
mkdirs(dir, false);
chdir_errno = chdir(dir) == 0 ? 0 : errno;
}
if (chdir_errno != 0) {
fprintf(stderr, _("%s: Can't chdir to %s: %s\n"),
progname, dir, strerror(chdir_errno));
exit(EXIT_FAILURE);
}
}
}
/* Compare the two links A and B, for a stable sort by link name. */
static int
qsort_linkcmp(void const *a, void const *b)
{
struct link const *l = a;
struct link const *m = b;
int cmp = strcmp(l->l_linkname, m->l_linkname);
if (cmp)
return cmp;
/* The link names are the same. Make the sort stable by comparing
file numbers (where subtraction cannot overflow) and possibly
line numbers (where it can). */
cmp = l->l_filenum - m->l_filenum;
if (cmp)
return cmp;
return (l->l_linenum > m->l_linenum) - (l->l_linenum < m->l_linenum);
}
/* Compare the string KEY to the link B, for bsearch. */
static int
bsearch_linkcmp(void const *key, void const *b)
{
struct link const *m = b;
return strcmp(key, m->l_linkname);
}
/* Make the links specified by the Link lines. */
static void
make_links(void)
{
ptrdiff_t i, j, nalinks, pass_size;
if (1 < nlinks)
qsort(links, nlinks, sizeof *links, qsort_linkcmp);
/* Ignore each link superseded by a later link with the same name. */
j = 0;
for (i = 0; i < nlinks; i++) {
while (i + 1 < nlinks
&& strcmp(links[i].l_linkname, links[i + 1].l_linkname) == 0)
i++;
links[j++] = links[i];
}
nlinks = pass_size = j;
/* Walk through the link array making links. However,
if a link's target has not been made yet, append a copy to the
end of the array. The end of the array will gradually fill
up with a small sorted subsequence of not-yet-made links.
nalinks counts all the links in the array, including copies.
When we reach the copied subsequence, it may still contain
a link to a not-yet-made link, so the process repeats.
At any given point in time, the link array consists of the
following subregions, where 0 <= i <= j <= nalinks and
0 <= nlinks <= nalinks:
0 .. (i - 1):
links that either have been made, or have been copied to a
later point point in the array (this later point can be in
any of the three subregions)
i .. (j - 1):
not-yet-made links for this pass
j .. (nalinks - 1):
not-yet-made links that this pass has skipped because
they were links to not-yet-made links
The first subregion might not be sorted if nlinks < i;
the other two subregions are sorted. This algorithm does
not alter entries 0 .. (nlinks - 1), which remain sorted.
If there are L links, this algorithm is O(C*L*log(L)) where
C is the length of the longest link chain. Usually C is
short (e.g., 3) though its worst-case value is L. */
j = nalinks = nlinks;
for (i = 0; i < nalinks; i++) {
struct link *l;
eat(links[i].l_filenum, links[i].l_linenum);
/* If this pass examined all its links, start the next pass. */
if (i == j) {
if (nalinks - i == pass_size) {
error(_("\"Link %s %s\" is part of a link cycle"),
links[i].l_target, links[i].l_linkname);
break;
}
j = nalinks;
pass_size = nalinks - i;
}
/* Diagnose self links, which the cycle detection algorithm would not
otherwise catch. */
if (strcmp(links[i].l_target, links[i].l_linkname) == 0) {
error(_("link %s targets itself"), links[i].l_target);
continue;
}
/* Make this link unless its target has not been made yet. */
l = bsearch(links[i].l_target, &links[i + 1], j - (i + 1),
sizeof *links, bsearch_linkcmp);
if (!l)
l = bsearch(links[i].l_target, &links[j], nalinks - j,
sizeof *links, bsearch_linkcmp);
if (!l)
dolink(links[i].l_target, links[i].l_linkname, false);
else {
/* The link target has not been made yet; copy the link to the end. */
links = growalloc(links, sizeof *links, nalinks, &nlinks_alloc);
links[nalinks++] = links[i];
}
if (noise && i < nlinks) {
if (l)
warning(_("link %s targeting link %s mishandled by pre-2023 zic"),
links[i].l_linkname, links[i].l_target);
else if (bsearch(links[i].l_target, links, nlinks, sizeof *links,
bsearch_linkcmp))
warning(_("link %s targeting link %s"),
links[i].l_linkname, links[i].l_target);
}
}
}
/* Simple signal handling: just set a flag that is checked
periodically outside critical sections. To set up the handler,
prefer sigaction if available to close a signal race. */
static sig_atomic_t got_signal;
static void
signal_handler(int sig)
{
#ifndef SA_SIGINFO
signal(sig, signal_handler);
#endif
got_signal = sig;
}
/* Arrange for SIGINT etc. to be caught by the handler. */
static void
catch_signals(void)
{
static int const signals[] = {
#ifdef SIGHUP
SIGHUP,
#endif
SIGINT,
#ifdef SIGPIPE
SIGPIPE,
#endif
SIGTERM
};
size_t i;
for (i = 0; i < sizeof signals / sizeof signals[0]; i++) {
#ifdef SA_SIGINFO
struct sigaction act0, act;
act.sa_handler = signal_handler;
sigemptyset(&act.sa_mask);
act.sa_flags = 0;
if (sigaction(signals[i], &act, &act0) == 0
&& ! (act0.sa_flags & SA_SIGINFO) && act0.sa_handler == SIG_IGN) {
sigaction(signals[i], &act0, NULL);
got_signal = 0;
}
#else
if (signal(signals[i], signal_handler) == SIG_IGN) {
signal(signals[i], SIG_IGN);
got_signal = 0;
}
#endif
}
}
/* If a signal has arrived, terminate zic with appropriate status. */
static void
check_for_signal(void)
{
int sig = got_signal;
if (sig) {
signal(sig, SIG_DFL);
raise(sig);
abort(); /* A bug in 'raise'. */
}
}
enum { TIME_T_BITS_IN_FILE = 64 };
/* The minimum and maximum values representable in a TZif file. */
static zic_t const min_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE);
static zic_t const max_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE);
/* The minimum, and one less than the maximum, values specified by
the -r option. These default to MIN_TIME and MAX_TIME. */
static zic_t lo_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE);
static zic_t hi_time = MAXVAL(zic_t, TIME_T_BITS_IN_FILE);
/* The time specified by the -R option, defaulting to MIN_TIME. */
static zic_t redundant_time = MINVAL(zic_t, TIME_T_BITS_IN_FILE);
/* The time specified by an Expires line, or negative if no such line. */
static zic_t leapexpires = -1;
/* Set the time range of the output to TIMERANGE.
Return true if successful. */
static bool
timerange_option(char *timerange)
{
intmax_t lo = min_time, hi = max_time;
char *lo_end = timerange, *hi_end;
if (*timerange == '@') {
errno = 0;
lo = strtoimax(timerange + 1, &lo_end, 10);
if (lo_end == timerange + 1 || (lo == INTMAX_MAX && errno == ERANGE))
return false;
}
hi_end = lo_end;
if (lo_end[0] == '/' && lo_end[1] == '@') {
errno = 0;
hi = strtoimax(lo_end + 2, &hi_end, 10);
if (hi_end == lo_end + 2 || hi == INTMAX_MIN)
return false;
hi -= ! (hi == INTMAX_MAX && errno == ERANGE);
}
if (*hi_end || hi < lo || max_time < lo || hi < min_time)
return false;
lo_time = max(lo, min_time);
hi_time = min(hi, max_time);
return true;
}
/* Generate redundant time stamps up to OPT. Return true if successful. */
static bool
redundant_time_option(char *opt)
{
if (*opt == '@') {
intmax_t redundant;
char *opt_end;
redundant = strtoimax(opt + 1, &opt_end, 10);
if (opt_end != opt + 1 && !*opt_end) {
redundant_time = max(redundant_time, redundant);
return true;
}
}
return false;
}
static const char * psxrules;
static const char * lcltime;
static const char * directory;
static const char * leapsec;
static int Dflag;
static uid_t uflag = (uid_t)-1;
static gid_t gflag = (gid_t)-1;
static mode_t mflag = (S_IRUSR | S_IRGRP | S_IROTH
| S_IWUSR);
static const char * tzdefault;
/* -1 if the TZif output file should be slim, 0 if default, 1 if the
output should be fat for backward compatibility. ZIC_BLOAT_DEFAULT
determines the default. */
static int bloat;
static bool
want_bloat(void)
{
return 0 <= bloat;
}
#ifndef ZIC_BLOAT_DEFAULT
# define ZIC_BLOAT_DEFAULT "slim"
#endif
int
main(int argc, char **argv)
{
register int c, k;
register ptrdiff_t i, j;
bool timerange_given = false;
#ifdef S_IWGRP
umask(umask(S_IWGRP | S_IWOTH) | (S_IWGRP | S_IWOTH));
#endif
#if HAVE_GETTEXT
setlocale(LC_ALL, "");
# ifdef TZ_DOMAINDIR
bindtextdomain(TZ_DOMAIN, TZ_DOMAINDIR);
# endif /* defined TEXTDOMAINDIR */
textdomain(TZ_DOMAIN);
#endif /* HAVE_GETTEXT */
main_argv = argv;
progname = /* argv[0] ? argv[0] : */ "zic";
if (TYPE_BIT(zic_t) < 64) {
fprintf(stderr, "%s: %s\n", progname,
_("wild compilation-time specification of zic_t"));
return EXIT_FAILURE;
}
for (k = 1; k < argc; k++)
if (strcmp(argv[k], "--version") == 0) {
printf("zic %s%s\n", PKGVERSION, TZVERSION);
close_file(stdout, NULL, NULL, NULL);
return EXIT_SUCCESS;
} else if (strcmp(argv[k], "--help") == 0) {
usage(stdout, EXIT_SUCCESS);
}
while ((c = getopt(argc, argv, "Db:d:g:l:L:m:p:r:R:st:u:vy:")) != EOF
&& c != -1)
switch (c) {
default:
usage(stderr, EXIT_FAILURE);
case 'D':
Dflag = 1;
break;
case 'b':
if (strcmp(optarg, "slim") == 0) {
if (0 < bloat)
error(_("incompatible -b options"));
bloat = -1;
} else if (strcmp(optarg, "fat") == 0) {
if (bloat < 0)
error(_("incompatible -b options"));
bloat = 1;
} else
error(_("invalid option: -b '%s'"), optarg);
break;
case 'd':
if (directory == NULL)
directory = optarg;
else {
fprintf(stderr,
_("%s: More than one -d option specified\n"),
progname);
return EXIT_FAILURE;
}
break;
case 'g':
setgroup(&gflag, optarg);
break;
case 'l':
if (lcltime == NULL)
lcltime = optarg;
else {
fprintf(stderr,
_("%s: More than one -l option specified\n"),
progname);
return EXIT_FAILURE;
}
break;
case 'm':
{
void *set = setmode(optarg);
if (set == NULL) {
fprintf(stderr,
_("invalid file mode"));
return EXIT_FAILURE;
}
mflag = getmode(set, mflag);
free(set);
break;
}
case 'p':
if (psxrules == NULL)
psxrules = optarg;
else {
fprintf(stderr,
_("%s: More than one -p option specified\n"),
progname);
return EXIT_FAILURE;
}
break;
case 't':
if (tzdefault != NULL) {
fprintf(stderr,
_("%s: More than one -t option"
" specified\n"),
progname);
return EXIT_FAILURE;
}
tzdefault = optarg;
break;
case 'u':
setuser(&uflag, optarg);
break;
case 'y':
warning(_("-y ignored"));
break;
case 'L':
if (leapsec == NULL)
leapsec = optarg;
else {
fprintf(stderr,
_("%s: More than one -L option specified\n"),
progname);
return EXIT_FAILURE;
}
break;
case 'v':
noise = true;
break;
case 'r':
if (timerange_given) {
fprintf(stderr,
_("%s: More than one -r option specified\n"),
progname);
return EXIT_FAILURE;
}
if (! timerange_option(optarg)) {
fprintf(stderr,
_("%s: invalid time range: %s\n"),
progname, optarg);
return EXIT_FAILURE;
}
timerange_given = true;
break;
case 'R':
if (! redundant_time_option(optarg)) {
fprintf(stderr, _("%s: invalid time: %s\n"),
progname, optarg);
return EXIT_FAILURE;
}
break;
case 's':
warning(_("-s ignored"));
break;
}
if (optind == argc - 1 && strcmp(argv[optind], "=") == 0)
usage(stderr, EXIT_FAILURE); /* usage message by request */
if (hi_time + (hi_time < ZIC_MAX) < redundant_time) {
fprintf(stderr, _("%s: -R time exceeds -r cutoff\n"), progname);
return EXIT_FAILURE;
}
if (bloat == 0) {
static char const bloat_default[] = ZIC_BLOAT_DEFAULT;
if (strcmp(bloat_default, "slim") == 0)
bloat = -1;
else if (strcmp(bloat_default, "fat") == 0)
bloat = 1;
else
abort(); /* Configuration error. */
}
if (directory == NULL)
directory = TZDIR;
if (tzdefault == NULL)
tzdefault = TZDEFAULT;
if (optind < argc && leapsec != NULL) {
infile(LEAPSEC_FILENUM, leapsec);
adjleap();
}
for (k = optind; k < argc; k++)
infile(k, argv[k]);
if (errors)
return EXIT_FAILURE;
associate();
change_directory(directory);
catch_signals();
for (i = 0; i < nzones; i = j) {
/*
** Find the next non-continuation zone entry.
*/
for (j = i + 1; j < nzones && zones[j].z_name == NULL; ++j)
continue;
outzone(&zones[i], j - i);
}
make_links();
if (lcltime != NULL) {
eat(COMMAND_LINE_FILENUM, 1);
dolink(lcltime, tzdefault, true);
}
if (psxrules != NULL) {
eat(COMMAND_LINE_FILENUM, 1);
dolink(psxrules, TZDEFRULES, true);
}
if (warnings && (ferror(stderr) || fclose(stderr) != 0))
return EXIT_FAILURE;
return errors ? EXIT_FAILURE : EXIT_SUCCESS;
}
static bool
componentcheck(char const *name, char const *component,
char const *component_end)
{
enum { component_len_max = 14 };
ptrdiff_t component_len = component_end - component;
if (component_len == 0) {
if (!*name)
error(_("empty file name"));
else
error(_(component == name
? "file name '%s' begins with '/'"
: *component_end
? "file name '%s' contains '//'"
: "file name '%s' ends with '/'"),
name);
return false;
}
if (0 < component_len && component_len <= 2
&& component[0] == '.' && component_end[-1] == '.') {
int len = component_len;
error(_("file name '%s' contains '%.*s' component"),
name, len, component);
return false;
}
if (noise) {
if (0 < component_len && component[0] == '-')
warning(_("file name '%s' component contains leading '-'"),
name);
if (component_len_max < component_len)
warning(_("file name '%s' contains overlength component"
" '%.*s...'"),
name, component_len_max, component);
}
return true;
}
static bool
namecheck(const char *name)
{
register char const *cp;
/* Benign characters in a portable file name. */
static char const benign[] =
"-/_"
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
/* Non-control chars in the POSIX portable character set,
excluding the benign characters. */
static char const printable_and_not_benign[] =
" !\"#$%&'()*+,.0123456789:;<=>?@[\\]^`{|}~";
register char const *component = name;
for (cp = name; *cp; cp++) {
unsigned char c = *cp;
if (noise && !strchr(benign, c)) {
warning((strchr(printable_and_not_benign, c)
? _("file name '%s' contains byte '%c'")
: _("file name '%s' contains byte '\\%o'")),
name, c);
}
if (c == '/') {
if (!componentcheck(name, component, cp))
return false;
component = cp + 1;
}
}
return componentcheck(name, component, cp);
}
/* Return a random uint_fast64_t. */
static uint_fast64_t
get_rand_u64(void)
{
#if HAVE_GETRANDOM
static uint_fast64_t entropy_buffer[max(1, 256 / sizeof(uint_fast64_t))];
static int nwords;
if (!nwords) {
ssize_t s;
do
s = getrandom(entropy_buffer, sizeof entropy_buffer, 0);
while (s < 0 && errno == EINTR);
if (s < 0)
nwords = -1;
else
nwords = s / sizeof *entropy_buffer;
}
if (0 < nwords)
return entropy_buffer[--nwords];
#endif
/* getrandom didn't work, so fall back on portable code that is
not the best because the seed isn't cryptographically random and
'rand' might not be cryptographically secure. */
{
static bool initialized;
if (!initialized) {
srand(time(NULL));
initialized = true;
}
}
/* Return a random number if rand() yields a random number and in
the typical case where RAND_MAX is one less than a power of two.
In other cases this code yields a sort-of-random number. */
{
uint_fast64_t rand_max = RAND_MAX,
nrand = rand_max < UINT_FAST64_MAX ? rand_max + 1 : 0,
rmod = INT_MAX < UINT_FAST64_MAX ? 0 : UINT_FAST64_MAX / nrand + 1,
r = 0, rmax = 0;
do {
uint_fast64_t rmax1 = rmax;
if (rmod) {
/* Avoid signed integer overflow on theoretical platforms
where uint_fast64_t promotes to int. */
rmax1 %= rmod;
r %= rmod;
}
rmax1 = nrand * rmax1 + rand_max;
r = nrand * r + rand();
rmax = rmax < rmax1 ? rmax1 : UINT_FAST64_MAX;
} while (rmax < UINT_FAST64_MAX);
return r;
}
}
/* Generate a randomish name in the same directory as *NAME. If
*NAMEALLOC, put the name into *NAMEALLOC which is assumed to be
that returned by a previous call and is thus already almost set up
and equal to *NAME; otherwise, allocate a new name and put its
address into both *NAMEALLOC and *NAME. */
static void
random_dirent(char const **name, char **namealloc)
{
char const *src = *name;
char *dst = *namealloc;
static char const prefix[] = ".zic";
static char const alphabet[] =
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"0123456789";
enum { prefixlen = sizeof prefix - 1, alphabetlen = sizeof alphabet - 1 };
int suffixlen = 6;
char const *lastslash = strrchr(src, '/');
ptrdiff_t dirlen = lastslash ? lastslash + 1 - src : 0;
int i;
uint_fast64_t r;
uint_fast64_t base = alphabetlen;
/* BASE**6 */
uint_fast64_t base__6 = base * base * base * base * base * base;
/* The largest uintmax_t that is a multiple of BASE**6. Any random
uintmax_t value that is this value or greater, yields a biased
remainder when divided by BASE**6. UNFAIR_MIN equals the
mathematical value of ((UINTMAX_MAX + 1) - (UINTMAX_MAX + 1) % BASE**6)
computed without overflow. */
uint_fast64_t unfair_min = - ((UINTMAX_MAX % base__6 + 1) % base__6);
if (!dst) {
dst = emalloc(size_sum(dirlen, prefixlen + suffixlen + 1));
memcpy(dst, src, dirlen);
memcpy(dst + dirlen, prefix, prefixlen);
dst[dirlen + prefixlen + suffixlen] = '\0';
*name = *namealloc = dst;
}
do
r = get_rand_u64();
while (unfair_min <= r);
for (i = 0; i < suffixlen; i++) {
dst[dirlen + prefixlen + i] = alphabet[r % alphabetlen];
r /= alphabetlen;
}
}
/* Prepare to write to the file *OUTNAME, using *TEMPNAME to store the
name of the temporary file that will eventually be renamed to
*OUTNAME. Assign the temporary file's name to both *OUTNAME and
*TEMPNAME. If *TEMPNAME is null, allocate the name of any such
temporary file; otherwise, reuse *TEMPNAME's storage, which is
already set up and only needs its trailing suffix updated. */
static FILE *
open_outfile(char const **outname, char **tempname)
{
#if __STDC_VERSION__ < 201112
static char const fopen_mode[] = "wb";
#else
static char const fopen_mode[] = "wbx";
#endif
FILE *fp;
bool dirs_made = false;
if (!*tempname)
random_dirent(outname, tempname);
/*
* Remove old file, if any, to snap links.
*/
if (remove(*outname) != 0 && errno != ENOENT && errno != EISDIR) {
fprintf(stderr, _("can't remove %s"), *outname);
exit(EXIT_FAILURE);
}
while (! (fp = fopen(*outname, fopen_mode))) {
int fopen_errno = errno;
if (fopen_errno == ENOENT && !dirs_made) {
mkdirs(*outname, true);
dirs_made = true;
} else if (fopen_errno == EEXIST)
random_dirent(outname, tempname);
else {
fprintf(stderr, _("%s: Can't create %s/%s: %s\n"),
progname, directory, *outname, strerror(fopen_errno));
exit(EXIT_FAILURE);
}
}
return fp;
}
/* If TEMPNAME, the result is in the temporary file TEMPNAME even
though the user wanted it in NAME, so rename TEMPNAME to NAME.
Report an error and exit if there is trouble. Also, free TEMPNAME. */
static void
rename_dest(char *tempname, char const *name)
{
if (tempname) {
if (rename(tempname, name) != 0) {
int rename_errno = errno;
(void)remove(tempname);
fprintf(stderr, _("%s: rename to %s/%s: %s\n"),
progname, directory, name, strerror(rename_errno));
exit(EXIT_FAILURE);
}
free(tempname);
}
}
/* Create symlink contents suitable for symlinking FROM to TO, as a
freshly allocated string. FROM should be a relative file name, and
is relative to the global variable DIRECTORY. TO can be either
relative or absolute. */
static char *
relname(char const *target, char const *linkname)
{
size_t i, taillen, dir_len = 0, dotdots = 0;
- ptrdiff_t dotdotetcsize, linksize = min(PTRDIFF_MAX, SIZE_MAX);
+ ptrdiff_t dotdotetcsize, linksize = INDEX_MAX;
char const *f = target;
char *result = NULL;
if (*linkname == '/') {
/* Make F absolute too. */
size_t len = strlen(directory);
size_t lenslash = len + (len && directory[len - 1] != '/');
size_t targetsize = strlen(target) + 1;
linksize = size_sum(lenslash, targetsize);
f = result = emalloc(linksize);
memcpy(result, directory, len);
result[len] = '/';
memcpy(result + lenslash, target, targetsize);
}
for (i = 0; f[i] && f[i] == linkname[i]; i++)
if (f[i] == '/')
dir_len = i + 1;
for (; linkname[i]; i++)
dotdots += linkname[i] == '/' && linkname[i - 1] != '/';
taillen = strlen(f + dir_len);
dotdotetcsize = size_sum(size_product(dotdots, 3), taillen + 1);
if (dotdotetcsize <= linksize) {
if (!result)
result = emalloc(dotdotetcsize);
for (i = 0; i < dotdots; i++)
memcpy(result + 3 * i, "../", 3);
memmove(result + 3 * dotdots, f + dir_len, taillen + 1);
}
return result;
}
static void
dolink(char const *target, char const *linkname, bool staysymlink)
{
bool linkdirs_made = false;
int link_errno;
char *tempname = NULL;
char const *outname = linkname;
check_for_signal();
if (strcmp(target, "-") == 0) {
if (remove(linkname) == 0 || errno == ENOENT || errno == ENOTDIR)
return;
else {
char const *e = strerror(errno);
fprintf(stderr, _("%s: Can't remove %s/%s: %s\n"),
progname, directory, linkname, e);
exit(EXIT_FAILURE);
}
}
while (true) {
if (linkat(AT_FDCWD, target, AT_FDCWD, outname, AT_SYMLINK_FOLLOW)
== 0) {
link_errno = 0;
break;
}
link_errno = errno;
if (link_errno == EXDEV || link_errno == ENOTSUP)
break;
if (link_errno == EEXIST) {
staysymlink &= !tempname;
random_dirent(&outname, &tempname);
if (staysymlink && itssymlink(linkname))
break;
} else if (link_errno == ENOENT && !linkdirs_made) {
mkdirs(linkname, true);
linkdirs_made = true;
} else {
fprintf(stderr, _("%s: Can't link %s/%s to %s/%s: %s\n"),
progname, directory, target, directory, outname,
strerror(link_errno));
exit(EXIT_FAILURE);
}
}
if (link_errno != 0) {
bool absolute = *target == '/';
char *linkalloc = absolute ? NULL : relname(target, linkname);
char const *contents = absolute ? target : linkalloc;
int symlink_errno;
while (true) {
if (symlink(contents, outname) == 0) {
symlink_errno = 0;
break;
}
symlink_errno = errno;
if (symlink_errno == EEXIST)
random_dirent(&outname, &tempname);
else if (symlink_errno == ENOENT && !linkdirs_made) {
mkdirs(linkname, true);
linkdirs_made = true;
} else
break;
}
free(linkalloc);
if (symlink_errno == 0) {
if (link_errno != ENOTSUP && link_errno != EEXIST)
warning(_("symbolic link used because hard link failed: %s"),
strerror(link_errno));
} else {
FILE *fp, *tp;
int c;
fp = fopen(target, "rb");
if (!fp) {
char const *e = strerror(errno);
fprintf(stderr, _("%s: Can't read %s/%s: %s\n"),
progname, directory, target, e);
exit(EXIT_FAILURE);
}
tp = open_outfile(&outname, &tempname);
while ((c = getc(fp)) != EOF)
putc(c, tp);
close_file(tp, directory, linkname, tempname);
close_file(fp, directory, target, NULL);
if (link_errno != ENOTSUP)
warning(_("copy used because hard link failed: %s"),
strerror(link_errno));
else if (symlink_errno != ENOTSUP)
warning(_("copy used because symbolic link failed: %s"),
strerror(symlink_errno));
}
}
rename_dest(tempname, linkname);
}
/* Return true if NAME is a symbolic link. */
static bool
itssymlink(char const *name)
{
char c;
return 0 <= readlink(name, &c, 1);
}
/*
** Associate sets of rules with zones.
*/
/*
** Sort by rule name.
*/
static int
rcomp(const void *cp1, const void *cp2)
{
struct rule const *r1 = cp1, *r2 = cp2;
return strcmp(r1->r_name, r2->r_name);
}
static void
associate(void)
{
register struct zone * zp;
register struct rule * rp;
register ptrdiff_t i, j, base, out;
if (1 < nrules) {
qsort(rules, nrules, sizeof *rules, rcomp);
for (i = 0; i < nrules - 1; ++i) {
if (strcmp(rules[i].r_name,
rules[i + 1].r_name) != 0)
continue;
if (rules[i].r_filenum == rules[i + 1].r_filenum)
continue;
eat(rules[i].r_filenum, rules[i].r_linenum);
warning(_("same rule name in multiple files"));
eat(rules[i + 1].r_filenum, rules[i + 1].r_linenum);
warning(_("same rule name in multiple files"));
for (j = i + 2; j < nrules; ++j) {
if (strcmp(rules[i].r_name,
rules[j].r_name) != 0)
break;
if (rules[i].r_filenum == rules[j].r_filenum)
continue;
if (rules[i + 1].r_filenum
== rules[j].r_filenum)
continue;
break;
}
i = j - 1;
}
}
for (i = 0; i < nzones; ++i) {
zp = &zones[i];
zp->z_rules = NULL;
zp->z_nrules = 0;
}
for (base = 0; base < nrules; base = out) {
rp = &rules[base];
for (out = base + 1; out < nrules; ++out)
if (strcmp(rp->r_name, rules[out].r_name) != 0)
break;
for (i = 0; i < nzones; ++i) {
zp = &zones[i];
if (strcmp(zp->z_rule, rp->r_name) != 0)
continue;
zp->z_rules = rp;
zp->z_nrules = out - base;
}
}
for (i = 0; i < nzones; ++i) {
zp = &zones[i];
if (zp->z_nrules == 0) {
/*
** Maybe we have a local standard time offset.
*/
eat(zp->z_filenum, zp->z_linenum);
zp->z_save = getsave(zp->z_rule, &zp->z_isdst);
/*
** Note, though, that if there's no rule,
** a '%s' in the format is a bad thing.
*/
if (zp->z_format_specifier == 's')
error("%s", _("%s in ruleless zone"));
}
}
if (errors)
exit(EXIT_FAILURE);
}
/* Read a text line from FP into BUF, which is of size BUFSIZE.
Terminate it with a NUL byte instead of a newline.
Return true if successful, false if EOF.
On error, report the error and exit. */
static bool
inputline(FILE *fp, char *buf, ptrdiff_t bufsize)
{
ptrdiff_t linelen = 0, ch;
while ((ch = getc(fp)) != '\n') {
if (ch < 0) {
if (ferror(fp)) {
error(_("input error"));
exit(EXIT_FAILURE);
}
if (linelen == 0)
return false;
error(_("unterminated line"));
exit(EXIT_FAILURE);
}
if (!ch) {
error(_("NUL input byte"));
exit(EXIT_FAILURE);
}
buf[linelen++] = ch;
if (linelen == bufsize) {
error(_("line too long"));
exit(EXIT_FAILURE);
}
}
buf[linelen] = '\0';
return true;
}
static void
infile(int fnum, char const *name)
{
register FILE * fp;
register const struct lookup * lp;
register bool wantcont;
register lineno num;
if (strcmp(name, "-") == 0) {
fp = stdin;
} else if ((fp = fopen(name, "r")) == NULL) {
const char *e = strerror(errno);
fprintf(stderr, _("%s: Can't open %s: %s\n"),
progname, name, e);
exit(EXIT_FAILURE);
}
wantcont = false;
for (num = 1; ; ++num) {
enum { bufsize_bound
- = (min(INT_MAX, min(PTRDIFF_MAX, SIZE_MAX))
- / FORMAT_LEN_GROWTH_BOUND) };
+ = (min(INT_MAX, INDEX_MAX) / FORMAT_LEN_GROWTH_BOUND) };
char buf[min(_POSIX2_LINE_MAX, bufsize_bound)];
int nfields;
char *fields[MAX_FIELDS];
eat(fnum, num);
if (!inputline(fp, buf, sizeof buf))
break;
nfields = getfields(buf, fields,
sizeof fields / sizeof *fields);
if (nfields == 0) {
/* nothing to do */
} else if (wantcont) {
wantcont = inzcont(fields, nfields);
} else {
struct lookup const *line_codes
= fnum < 0 ? leap_line_codes : zi_line_codes;
lp = byword(fields[0], line_codes);
if (lp == NULL)
error(_("input line of unknown type"));
else switch (lp->l_value) {
case LC_RULE:
inrule(fields, nfields);
wantcont = false;
break;
case LC_ZONE:
wantcont = inzone(fields, nfields);
break;
case LC_LINK:
inlink(fields, nfields);
wantcont = false;
break;
case LC_LEAP:
inleap(fields, nfields);
wantcont = false;
break;
case LC_EXPIRES:
inexpires(fields, nfields);
wantcont = false;
break;
default: unreachable();
}
}
}
close_file(fp, NULL, filename(fnum), NULL);
if (wantcont)
error(_("expected continuation line not found"));
}
/*
** Convert a string of one of the forms
** h -h hh:mm -hh:mm hh:mm:ss -hh:mm:ss
** into a number of seconds.
** A null string maps to zero.
** Call error with errstring and return zero on errors.
*/
static zic_t
gethms(char const *string, char const *errstring)
{
zic_t hh;
int sign, mm = 0, ss = 0;
char hhx, mmx, ssx, xr = '0', xs;
int tenths = 0;
bool ok = true;
if (string == NULL || *string == '\0')
return 0;
if (*string == '-') {
sign = -1;
++string;
} else sign = 1;
switch (sscanf(string,
"%"SCNdZIC"%c%d%c%d%c%1d%*[0]%c%*[0123456789]%c",
&hh, &hhx, &mm, &mmx, &ss, &ssx, &tenths, &xr, &xs)) {
default: ok = false; break;
case 8:
ok = '0' <= xr && xr <= '9';
ATTRIBUTE_FALLTHROUGH;
case 7:
ok &= ssx == '.';
if (ok && noise)
warning(_("fractional seconds rejected by"
" pre-2018 versions of zic"));
ATTRIBUTE_FALLTHROUGH;
case 5: ok &= mmx == ':'; ATTRIBUTE_FALLTHROUGH;
case 3: ok &= hhx == ':'; ATTRIBUTE_FALLTHROUGH;
case 1: break;
}
if (!ok) {
error("%s", errstring);
return 0;
}
if (hh < 0 ||
mm < 0 || mm >= MINSPERHOUR ||
ss < 0 || ss > SECSPERMIN) {
error("%s", errstring);
return 0;
}
if (ZIC_MAX / SECSPERHOUR < hh) {
error(_("time overflow"));
return 0;
}
ss += 5 + ((ss ^ 1) & (xr == '0')) <= tenths; /* Round to even. */
if (noise && (hh > HOURSPERDAY ||
(hh == HOURSPERDAY && (mm != 0 || ss != 0))))
warning(_("values over 24 hours not handled by pre-2007 versions of zic"));
return oadd(sign * hh * SECSPERHOUR,
sign * (mm * SECSPERMIN + ss));
}
static zic_t
getsave(char *field, bool *isdst)
{
int dst = -1;
zic_t save;
ptrdiff_t fieldlen = strlen(field);
if (fieldlen != 0) {
char *ep = field + fieldlen - 1;
switch (*ep) {
case 'd': dst = 1; *ep = '\0'; break;
case 's': dst = 0; *ep = '\0'; break;
}
}
save = gethms(field, _("invalid saved time"));
*isdst = dst < 0 ? save != 0 : dst;
return save;
}
static void
inrule(char **fields, int nfields)
{
struct rule r = { 0 };
if (nfields != RULE_FIELDS) {
error(_("wrong number of fields on Rule line"));
return;
}
switch (*fields[RF_NAME]) {
case '\0':
case ' ': case '\f': case '\n': case '\r': case '\t': case '\v':
case '+': case '-':
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
error(_("Invalid rule name \"%s\""), fields[RF_NAME]);
return;
}
r.r_filenum = filenum;
r.r_linenum = linenum;
r.r_save = getsave(fields[RF_SAVE], &r.r_isdst);
if (!rulesub(&r, fields[RF_LOYEAR], fields[RF_HIYEAR],
fields[RF_COMMAND], fields[RF_MONTH], fields[RF_DAY],
fields[RF_TOD]))
return;
r.r_name = estrdup(fields[RF_NAME]);
r.r_abbrvar = estrdup(fields[RF_ABBRVAR]);
if (max_abbrvar_len < strlen(r.r_abbrvar))
max_abbrvar_len = strlen(r.r_abbrvar);
rules = growalloc(rules, sizeof *rules, nrules, &nrules_alloc);
rules[nrules++] = r;
}
static bool
inzone(char **fields, int nfields)
{
register ptrdiff_t i;
if (nfields < ZONE_MINFIELDS || nfields > ZONE_MAXFIELDS) {
error(_("wrong number of fields on Zone line"));
return false;
}
if (lcltime != NULL && strcmp(fields[ZF_NAME], tzdefault) == 0) {
error(
_("\"Zone %s\" line and -l option are mutually exclusive"),
tzdefault);
return false;
}
if (strcmp(fields[ZF_NAME], TZDEFRULES) == 0 && psxrules != NULL) {
error(
_("\"Zone %s\" line and -p option are mutually exclusive"),
TZDEFRULES);
return false;
}
for (i = 0; i < nzones; ++i)
if (zones[i].z_name != NULL &&
strcmp(zones[i].z_name, fields[ZF_NAME]) == 0) {
error(_("duplicate zone name %s"
" (file \"%s\", line %"PRIdMAX")"),
fields[ZF_NAME],
filename(zones[i].z_filenum),
zones[i].z_linenum);
return false;
}
return inzsub(fields, nfields, false);
}
static bool
inzcont(char **fields, int nfields)
{
if (nfields < ZONEC_MINFIELDS || nfields > ZONEC_MAXFIELDS) {
error(_("wrong number of fields on Zone continuation line"));
return false;
}
return inzsub(fields, nfields, true);
}
static bool
inzsub(char **fields, int nfields, bool iscont)
{
register char * cp;
char * cp1;
struct zone z = { 0 };
int format_len;
register int i_stdoff, i_rule, i_format;
register int i_untilyear, i_untilmonth;
register int i_untilday, i_untiltime;
register bool hasuntil;
if (iscont) {
i_stdoff = ZFC_STDOFF;
i_rule = ZFC_RULE;
i_format = ZFC_FORMAT;
i_untilyear = ZFC_TILYEAR;
i_untilmonth = ZFC_TILMONTH;
i_untilday = ZFC_TILDAY;
i_untiltime = ZFC_TILTIME;
} else if (!namecheck(fields[ZF_NAME]))
return false;
else {
i_stdoff = ZF_STDOFF;
i_rule = ZF_RULE;
i_format = ZF_FORMAT;
i_untilyear = ZF_TILYEAR;
i_untilmonth = ZF_TILMONTH;
i_untilday = ZF_TILDAY;
i_untiltime = ZF_TILTIME;
}
z.z_filenum = filenum;
z.z_linenum = linenum;
z.z_stdoff = gethms(fields[i_stdoff], _("invalid UT offset"));
if ((cp = strchr(fields[i_format], '%')) != 0) {
if ((*++cp != 's' && *cp != 'z') || strchr(cp, '%')
|| strchr(fields[i_format], '/')) {
error(_("invalid abbreviation format"));
return false;
}
}
z.z_format_specifier = cp ? *cp : '\0';
format_len = strlen(fields[i_format]);
if (max_format_len < format_len)
max_format_len = format_len;
hasuntil = nfields > i_untilyear;
if (hasuntil) {
z.z_untilrule.r_filenum = filenum;
z.z_untilrule.r_linenum = linenum;
if (!rulesub(
&z.z_untilrule,
fields[i_untilyear],
"only",
"",
(nfields > i_untilmonth) ?
fields[i_untilmonth] : "Jan",
(nfields > i_untilday) ? fields[i_untilday] : "1",
(nfields > i_untiltime) ? fields[i_untiltime] : "0"))
return false;
z.z_untiltime = rpytime(&z.z_untilrule,
z.z_untilrule.r_loyear);
if (iscont && nzones > 0 &&
z.z_untiltime > min_time &&
z.z_untiltime < max_time &&
zones[nzones - 1].z_untiltime > min_time &&
zones[nzones - 1].z_untiltime < max_time &&
zones[nzones - 1].z_untiltime >= z.z_untiltime) {
error(_(
"Zone continuation line end time is not after end time of previous line"
));
return false;
}
}
z.z_name = iscont ? NULL : estrdup(fields[ZF_NAME]);
z.z_rule = estrdup(fields[i_rule]);
z.z_format = cp1 = estrdup(fields[i_format]);
if (z.z_format_specifier == 'z') {
cp1[cp - fields[i_format]] = 's';
if (noise)
warning(_("format '%s' not handled by pre-2015 versions of zic"),
fields[i_format]);
}
zones = growalloc(zones, sizeof *zones, nzones, &nzones_alloc);
zones[nzones++] = z;
/*
** If there was an UNTIL field on this line,
** there's more information about the zone on the next line.
*/
return hasuntil;
}
static zic_t
getleapdatetime(char **fields, bool expire_line)
{
register const char * cp;
register const struct lookup * lp;
register zic_t i, j;
zic_t year;
int month, day;
zic_t dayoff, tod;
zic_t t;
char xs;
dayoff = 0;
cp = fields[LP_YEAR];
if (sscanf(cp, "%"SCNdZIC"%c", &year, &xs) != 1) {
/*
** Leapin' Lizards!
*/
error(_("invalid leaping year"));
return -1;
}
if (!expire_line) {
if (!leapseen || leapmaxyear < year)
leapmaxyear = year;
if (!leapseen || leapminyear > year)
leapminyear = year;
leapseen = true;
}
j = EPOCH_YEAR;
while (j != year) {
if (year > j) {
i = len_years[isleap(j)];
++j;
} else {
--j;
i = -len_years[isleap(j)];
}
dayoff = oadd(dayoff, i);
}
if ((lp = byword(fields[LP_MONTH], mon_names)) == NULL) {
error(_("invalid month name"));
return -1;
}
month = lp->l_value;
j = TM_JANUARY;
while (j != month) {
i = len_months[isleap(year)][j];
dayoff = oadd(dayoff, i);
++j;
}
cp = fields[LP_DAY];
if (sscanf(cp, "%d%c", &day, &xs) != 1 ||
day <= 0 || day > len_months[isleap(year)][month]) {
error(_("invalid day of month"));
return -1;
}
dayoff = oadd(dayoff, day - 1);
if (dayoff < min_time / SECSPERDAY) {
error(_("time too small"));
return -1;
}
if (dayoff > max_time / SECSPERDAY) {
error(_("time too large"));
return -1;
}
t = dayoff * SECSPERDAY;
tod = gethms(fields[LP_TIME], _("invalid time of day"));
t = tadd(t, tod);
if (t < 0)
error(_("leap second precedes Epoch"));
return t;
}
static void
inleap(char **fields, int nfields)
{
if (nfields != LEAP_FIELDS)
error(_("wrong number of fields on Leap line"));
else {
zic_t t = getleapdatetime(fields, false);
if (0 <= t) {
struct lookup const *lp = byword(fields[LP_ROLL], leap_types);
if (!lp)
error(_("invalid Rolling/Stationary field on Leap line"));
else {
int correction = 0;
if (!fields[LP_CORR][0]) /* infile() turns "-" into "". */
correction = -1;
else if (strcmp(fields[LP_CORR], "+") == 0)
correction = 1;
else
error(_("invalid CORRECTION field on Leap line"));
if (correction)
leapadd(t, correction, lp->l_value);
}
}
}
}
static void
inexpires(char **fields, int nfields)
{
if (nfields != EXPIRES_FIELDS)
error(_("wrong number of fields on Expires line"));
else if (0 <= leapexpires)
error(_("multiple Expires lines"));
else
leapexpires = getleapdatetime(fields, true);
}
static void
inlink(char **fields, int nfields)
{
struct link l;
if (nfields != LINK_FIELDS) {
error(_("wrong number of fields on Link line"));
return;
}
if (*fields[LF_TARGET] == '\0') {
error(_("blank TARGET field on Link line"));
return;
}
if (! namecheck(fields[LF_LINKNAME]))
return;
l.l_filenum = filenum;
l.l_linenum = linenum;
l.l_target = estrdup(fields[LF_TARGET]);
l.l_linkname = estrdup(fields[LF_LINKNAME]);
links = growalloc(links, sizeof *links, nlinks, &nlinks_alloc);
links[nlinks++] = l;
}
static bool
rulesub(struct rule *rp, const char *loyearp, const char *hiyearp,
const char *typep, const char *monthp, const char *dayp,
const char *timep)
{
register const struct lookup * lp;
register const char * cp;
register char * dp;
register char * ep;
char xs;
if ((lp = byword(monthp, mon_names)) == NULL) {
error(_("invalid month name"));
return false;
}
rp->r_month = lp->l_value;
rp->r_todisstd = false;
rp->r_todisut = false;
dp = estrdup(timep);
if (*dp != '\0') {
ep = dp + strlen(dp) - 1;
switch (lowerit(*ep)) {
case 's': /* Standard */
rp->r_todisstd = true;
rp->r_todisut = false;
*ep = '\0';
break;
case 'w': /* Wall */
rp->r_todisstd = false;
rp->r_todisut = false;
*ep = '\0';
break;
case 'g': /* Greenwich */
case 'u': /* Universal */
case 'z': /* Zulu */
rp->r_todisstd = true;
rp->r_todisut = true;
*ep = '\0';
break;
}
}
rp->r_tod = gethms(dp, _("invalid time of day"));
free(dp);
/*
** Year work.
*/
cp = loyearp;
lp = byword(cp, begin_years);
rp->r_lowasnum = lp == NULL;
if (!rp->r_lowasnum) switch (lp->l_value) {
case YR_MINIMUM:
rp->r_loyear = ZIC_MIN;
break;
case YR_MAXIMUM:
rp->r_loyear = ZIC_MAX;
break;
default: unreachable();
} else if (sscanf(cp, "%"SCNdZIC"%c", &rp->r_loyear, &xs) != 1) {
error(_("invalid starting year"));
return false;
}
cp = hiyearp;
lp = byword(cp, end_years);
rp->r_hiwasnum = lp == NULL;
if (!rp->r_hiwasnum) switch (lp->l_value) {
case YR_MINIMUM:
rp->r_hiyear = ZIC_MIN;
break;
case YR_MAXIMUM:
rp->r_hiyear = ZIC_MAX;
break;
case YR_ONLY:
rp->r_hiyear = rp->r_loyear;
break;
default: unreachable();
} else if (sscanf(cp, "%"SCNdZIC"%c", &rp->r_hiyear, &xs) != 1) {
error(_("invalid ending year"));
return false;
}
if (rp->r_loyear > rp->r_hiyear) {
error(_("starting year greater than ending year"));
return false;
}
if (*typep != '\0') {
error(_("year type \"%s\" is unsupported; use \"-\" instead"),
typep);
return false;
}
/*
** Day work.
** Accept things such as:
** 1
** lastSunday
** last-Sunday (undocumented; warn about this)
** Sun<=20
** Sun>=7
*/
dp = estrdup(dayp);
if ((lp = byword(dp, lasts)) != NULL) {
rp->r_dycode = DC_DOWLEQ;
rp->r_wday = lp->l_value;
rp->r_dayofmonth = len_months[1][rp->r_month];
} else {
if ((ep = strchr(dp, '<')) != 0)
rp->r_dycode = DC_DOWLEQ;
else if ((ep = strchr(dp, '>')) != 0)
rp->r_dycode = DC_DOWGEQ;
else {
ep = dp;
rp->r_dycode = DC_DOM;
}
if (rp->r_dycode != DC_DOM) {
*ep++ = 0;
if (*ep++ != '=') {
error(_("invalid day of month"));
free(dp);
return false;
}
if ((lp = byword(dp, wday_names)) == NULL) {
error(_("invalid weekday name"));
free(dp);
return false;
}
rp->r_wday = lp->l_value;
}
if (sscanf(ep, "%d%c", &rp->r_dayofmonth, &xs) != 1 ||
rp->r_dayofmonth <= 0 ||
(rp->r_dayofmonth > len_months[1][rp->r_month])) {
error(_("invalid day of month"));
free(dp);
return false;
}
}
free(dp);
return true;
}
static void
convert(uint_fast32_t val, char *buf)
{
register int i;
register int shift;
unsigned char *const b = (unsigned char *) buf;
for (i = 0, shift = 24; i < 4; ++i, shift -= 8)
b[i] = (val >> shift) & 0xff;
}
static void
convert64(uint_fast64_t val, char *buf)
{
register int i;
register int shift;
unsigned char *const b = (unsigned char *) buf;
for (i = 0, shift = 56; i < 8; ++i, shift -= 8)
b[i] = (val >> shift) & 0xff;
}
static void
puttzcode(zic_t val, FILE *fp)
{
char buf[4];
convert(val, buf);
fwrite(buf, sizeof buf, 1, fp);
}
static void
puttzcodepass(zic_t val, FILE *fp, int pass)
{
if (pass == 1)
puttzcode(val, fp);
else {
char buf[8];
convert64(val, buf);
fwrite(buf, sizeof buf, 1, fp);
}
}
static int
atcomp(const void *avp, const void *bvp)
{
struct attype const *ap = avp, *bp = bvp;
zic_t a = ap->at, b = bp->at;
return a < b ? -1 : a > b;
}
struct timerange {
int defaulttype;
ptrdiff_t base, count;
int leapbase, leapcount;
bool leapexpiry;
};
static struct timerange
limitrange(struct timerange r, zic_t lo, zic_t hi,
zic_t const *ats, unsigned char const *types)
{
/* Omit ordinary transitions < LO. */
while (0 < r.count && ats[r.base] < lo) {
r.defaulttype = types[r.base];
r.count--;
r.base++;
}
/* Omit as many initial leap seconds as possible, such that the
first leap second in the truncated list is <= LO, and is a
positive leap second if and only if it has a positive correction.
This supports common TZif readers that assume that the first leap
second is positive if and only if its correction is positive. */
while (1 < r.leapcount && trans[r.leapbase + 1] <= lo) {
r.leapcount--;
r.leapbase++;
}
while (0 < r.leapbase
&& ((corr[r.leapbase - 1] < corr[r.leapbase])
!= (0 < corr[r.leapbase]))) {
r.leapcount++;
r.leapbase--;
}
/* Omit ordinary and leap second transitions greater than HI + 1. */
if (hi < max_time) {
while (0 < r.count && hi + 1 < ats[r.base + r.count - 1])
r.count--;
while (0 < r.leapcount && hi + 1 < trans[r.leapbase + r.leapcount - 1])
r.leapcount--;
}
/* Determine whether to append an expiration to the leap second table. */
r.leapexpiry = 0 <= leapexpires && leapexpires - 1 <= hi;
return r;
}
static void
writezone(const char *const name, const char *const string, char version,
int defaulttype)
{
register FILE * fp;
register ptrdiff_t i, j;
register size_t u;
register int pass;
char *tempname = NULL;
char const *outname = name;
/* Allocate the ATS and TYPES arrays via a single malloc,
as this is a bit faster. Do not malloc(0) if !timecnt,
as that might return NULL even on success. */
zic_t *ats = emalloc(align_to(size_product(timecnt + !timecnt,
sizeof *ats + 1),
alignof(zic_t)));
void *typesptr = ats + timecnt;
unsigned char *types = typesptr;
struct timerange rangeall = {0}, range32, range64;
/*
** Sort.
*/
if (timecnt > 1)
qsort(attypes, timecnt, sizeof *attypes, atcomp);
/*
** Optimize.
*/
{
ptrdiff_t fromi, toi;
toi = 0;
fromi = 0;
for ( ; fromi < timecnt; ++fromi) {
if (toi != 0
&& ((attypes[fromi].at
+ utoffs[attypes[toi - 1].type])
<= (attypes[toi - 1].at
+ utoffs[toi == 1 ? 0
: attypes[toi - 2].type]))) {
attypes[toi - 1].type =
attypes[fromi].type;
continue;
}
if (toi == 0
|| attypes[fromi].dontmerge
|| (utoffs[attypes[toi - 1].type]
!= utoffs[attypes[fromi].type])
|| (isdsts[attypes[toi - 1].type]
!= isdsts[attypes[fromi].type])
|| (desigidx[attypes[toi - 1].type]
!= desigidx[attypes[fromi].type]))
attypes[toi++] = attypes[fromi];
}
timecnt = toi;
}
if (noise && timecnt > 1200) {
if (timecnt > TZ_MAX_TIMES)
warning(_("reference clients mishandle"
" more than %d transition times"),
TZ_MAX_TIMES);
else
warning(_("pre-2014 clients may mishandle"
" more than 1200 transition times"));
}
/*
** Transfer.
*/
for (i = 0; i < timecnt; ++i) {
ats[i] = attypes[i].at;
types[i] = attypes[i].type;
}
/*
** Correct for leap seconds.
*/
for (i = 0; i < timecnt; ++i) {
j = leapcnt;
while (--j >= 0)
if (ats[i] > trans[j] - corr[j]) {
ats[i] = tadd(ats[i], corr[j]);
break;
}
}
rangeall.defaulttype = defaulttype;
rangeall.count = timecnt;
rangeall.leapcount = leapcnt;
range64 = limitrange(rangeall, lo_time,
max(hi_time,
redundant_time - (ZIC_MIN < redundant_time)),
ats, types);
range32 = limitrange(range64, ZIC32_MIN, ZIC32_MAX, ats, types);
/* TZif version 4 is needed if a no-op transition is appended to
indicate the expiration of the leap second table, or if the first
leap second transition is not to a +1 or -1 correction. */
for (pass = 1; pass <= 2; pass++) {
struct timerange const *r = pass == 1 ? &range32 : &range64;
if (pass == 1 && !want_bloat())
continue;
if (r->leapexpiry) {
if (noise)
warning(_("%s: pre-2021b clients may mishandle"
" leap second expiry"),
name);
version = '4';
}
if (0 < r->leapcount
&& corr[r->leapbase] != 1 && corr[r->leapbase] != -1) {
if (noise)
warning(_("%s: pre-2021b clients may mishandle"
" leap second table truncation"),
name);
version = '4';
}
if (version == '4')
break;
}
fp = open_outfile(&outname, &tempname);
for (pass = 1; pass <= 2; ++pass) {
register ptrdiff_t thistimei, thistimecnt, thistimelim;
register int thisleapi, thisleapcnt, thisleaplim;
struct tzhead tzh;
int pretranstype = -1, thisdefaulttype;
bool locut, hicut, thisleapexpiry;
zic_t lo, thismin, thismax;
int old0;
char omittype[TZ_MAX_TYPES];
int typemap[TZ_MAX_TYPES];
int thistypecnt, stdcnt, utcnt;
char thischars[TZ_MAX_CHARS];
int thischarcnt;
bool toomanytimes;
int indmap[TZ_MAX_CHARS];
if (pass == 1) {
thisdefaulttype = range32.defaulttype;
thistimei = range32.base;
thistimecnt = range32.count;
toomanytimes = thistimecnt >> 31 >> 1 != 0;
thisleapi = range32.leapbase;
thisleapcnt = range32.leapcount;
thisleapexpiry = range32.leapexpiry;
thismin = ZIC32_MIN;
thismax = ZIC32_MAX;
} else {
thisdefaulttype = range64.defaulttype;
thistimei = range64.base;
thistimecnt = range64.count;
toomanytimes = thistimecnt >> 31 >> 31 >> 2 != 0;
thisleapi = range64.leapbase;
thisleapcnt = range64.leapcount;
thisleapexpiry = range64.leapexpiry;
thismin = min_time;
thismax = max_time;
}
if (toomanytimes)
error(_("too many transition times"));
locut = thismin < lo_time && lo_time <= thismax;
hicut = thismin <= hi_time && hi_time < thismax;
thistimelim = thistimei + thistimecnt;
memset(omittype, true, typecnt);
/* Determine whether to output a transition before the first
transition in range. This is needed when the output is
truncated at the start, and is also useful when catering to
buggy 32-bit clients that do not use time type 0 for
timestamps before the first transition. */
if ((locut || (pass == 1 && thistimei))
&& ! (thistimecnt && ats[thistimei] == lo_time)) {
pretranstype = thisdefaulttype;
omittype[pretranstype] = false;
}
/* Arguably the default time type in the 32-bit data
should be range32.defaulttype, which is suited for
timestamps just before ZIC32_MIN. However, zic
traditionally used the time type of the indefinite
past instead. Internet RFC 8532 says readers should
ignore 32-bit data, so this discrepancy matters only
to obsolete readers where the traditional type might
be more appropriate even if it's "wrong". So, use
the historical zic value, unless -r specifies a low
cutoff that excludes some 32-bit timestamps. */
if (pass == 1 && lo_time <= thismin)
thisdefaulttype = range64.defaulttype;
if (locut)
thisdefaulttype = unspecifiedtype;
omittype[thisdefaulttype] = false;
for (i = thistimei; i < thistimelim; i++)
omittype[types[i]] = false;
if (hicut)
omittype[unspecifiedtype] = false;
/* Reorder types to make THISDEFAULTTYPE type 0.
Use TYPEMAP to swap OLD0 and THISDEFAULTTYPE so that
THISDEFAULTTYPE appears as type 0 in the output instead
of OLD0. TYPEMAP also omits unused types. */
old0 = strlen(omittype);
#ifndef LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH
/*
** For some pre-2011 systems: if the last-to-be-written
** standard (or daylight) type has an offset different from the
** most recently used offset,
** append an (unused) copy of the most recently used type
** (to help get global "altzone" and "timezone" variables
** set correctly).
*/
if (want_bloat()) {
register int mrudst, mrustd, hidst, histd, type;
hidst = histd = mrudst = mrustd = -1;
if (0 <= pretranstype) {
if (isdsts[pretranstype])
mrudst = pretranstype;
else
mrustd = pretranstype;
}
for (i = thistimei; i < thistimelim; i++)
if (isdsts[types[i]])
mrudst = types[i];
else mrustd = types[i];
for (i = old0; i < typecnt; i++) {
int h = (i == old0 ? thisdefaulttype
: i == thisdefaulttype ? old0 : i);
if (!omittype[h]) {
if (isdsts[h])
hidst = i;
else
histd = i;
}
}
if (hidst >= 0 && mrudst >= 0 && hidst != mrudst &&
utoffs[hidst] != utoffs[mrudst]) {
isdsts[mrudst] = -1;
type = addtype(utoffs[mrudst],
&chars[desigidx[mrudst]],
true,
ttisstds[mrudst],
ttisuts[mrudst]);
isdsts[mrudst] = 1;
omittype[type] = false;
}
if (histd >= 0 && mrustd >= 0 && histd != mrustd &&
utoffs[histd] != utoffs[mrustd]) {
isdsts[mrustd] = -1;
type = addtype(utoffs[mrustd],
&chars[desigidx[mrustd]],
false,
ttisstds[mrustd],
ttisuts[mrustd]);
isdsts[mrustd] = 0;
omittype[type] = false;
}
}
#endif /* !defined LEAVE_SOME_PRE_2011_SYSTEMS_IN_THE_LURCH */
thistypecnt = 0;
for (i = old0; i < typecnt; i++)
if (!omittype[i])
typemap[i == old0 ? thisdefaulttype
: i == thisdefaulttype ? old0 : i]
= thistypecnt++;
for (u = 0; u < sizeof indmap / sizeof indmap[0]; ++u)
indmap[u] = -1;
thischarcnt = stdcnt = utcnt = 0;
for (i = old0; i < typecnt; i++) {
register char * thisabbr;
if (omittype[i])
continue;
if (ttisstds[i])
stdcnt = thistypecnt;
if (ttisuts[i])
utcnt = thistypecnt;
if (indmap[desigidx[i]] >= 0)
continue;
thisabbr = &chars[desigidx[i]];
for (j = 0; j < thischarcnt; ++j)
if (strcmp(&thischars[j], thisabbr) == 0)
break;
if (j == thischarcnt) {
strcpy(&thischars[thischarcnt], thisabbr);
thischarcnt += strlen(thisabbr) + 1;
}
indmap[desigidx[i]] = j;
}
if (pass == 1 && !want_bloat()) {
hicut = thisleapexpiry = false;
pretranstype = -1;
thistimecnt = thisleapcnt = 0;
thistypecnt = thischarcnt = 1;
}
#define DO(field) fwrite(tzh.field, sizeof tzh.field, 1, fp)
memset(&tzh, 0, sizeof tzh);
memcpy(tzh.tzh_magic, TZ_MAGIC, sizeof tzh.tzh_magic);
tzh.tzh_version[0] = version;
convert(utcnt, tzh.tzh_ttisutcnt);
convert(stdcnt, tzh.tzh_ttisstdcnt);
convert(thisleapcnt + thisleapexpiry, tzh.tzh_leapcnt);
convert((0 <= pretranstype) + thistimecnt + hicut,
tzh.tzh_timecnt);
convert(thistypecnt, tzh.tzh_typecnt);
convert(thischarcnt, tzh.tzh_charcnt);
DO(tzh_magic);
DO(tzh_version);
DO(tzh_reserved);
DO(tzh_ttisutcnt);
DO(tzh_ttisstdcnt);
DO(tzh_leapcnt);
DO(tzh_timecnt);
DO(tzh_typecnt);
DO(tzh_charcnt);
#undef DO
if (pass == 1 && !want_bloat()) {
/* Output a minimal data block with just one time type. */
puttzcode(0, fp); /* utoff */
putc(0, fp); /* dst */
putc(0, fp); /* index of abbreviation */
putc(0, fp); /* empty-string abbreviation */
continue;
}
/* Output a LO_TIME transition if needed; see limitrange.
But do not go below the minimum representable value
for this pass. */
lo = pass == 1 && lo_time < ZIC32_MIN ? ZIC32_MIN : lo_time;
if (0 <= pretranstype)
puttzcodepass(lo, fp, pass);
for (i = thistimei; i < thistimelim; ++i) {
puttzcodepass(ats[i], fp, pass);
}
if (hicut)
puttzcodepass(hi_time + 1, fp, pass);
if (0 <= pretranstype)
putc(typemap[pretranstype], fp);
for (i = thistimei; i < thistimelim; i++)
putc(typemap[types[i]], fp);
if (hicut)
putc(typemap[unspecifiedtype], fp);
for (i = old0; i < typecnt; i++) {
int h = (i == old0 ? thisdefaulttype
: i == thisdefaulttype ? old0 : i);
if (!omittype[h]) {
puttzcode(utoffs[h], fp);
putc(isdsts[h], fp);
putc(indmap[desigidx[h]], fp);
}
}
if (thischarcnt != 0)
fwrite(thischars, sizeof thischars[0],
thischarcnt, fp);
thisleaplim = thisleapi + thisleapcnt;
for (i = thisleapi; i < thisleaplim; ++i) {
register zic_t todo;
if (roll[i]) {
if (timecnt == 0 || trans[i] < ats[0]) {
j = 0;
while (isdsts[j])
if (++j >= typecnt) {
j = 0;
break;
}
} else {
j = 1;
while (j < timecnt &&
trans[i] >= ats[j])
++j;
j = types[j - 1];
}
todo = tadd(trans[i], -utoffs[j]);
} else todo = trans[i];
puttzcodepass(todo, fp, pass);
puttzcode(corr[i], fp);
}
if (thisleapexpiry) {
/* Append a no-op leap correction indicating when the leap
second table expires. Although this does not conform to
Internet RFC 8536, most clients seem to accept this and
the plan is to amend the RFC to allow this in version 4
TZif files. */
puttzcodepass(leapexpires, fp, pass);
puttzcode(thisleaplim ? corr[thisleaplim - 1] : 0, fp);
}
if (stdcnt != 0)
for (i = old0; i < typecnt; i++)
if (!omittype[i])
putc(ttisstds[i], fp);
if (utcnt != 0)
for (i = old0; i < typecnt; i++)
if (!omittype[i])
putc(ttisuts[i], fp);
}
fprintf(fp, "\n%s\n", string);
close_file(fp, directory, name, tempname);
if (chmod(tempname, mflag) < 0) {
fprintf(stderr, _("cannot change mode of %s to %03o"),
tempname, (unsigned)mflag);
exit(EXIT_FAILURE);
}
if ((uflag != (uid_t)-1 || gflag != (gid_t)-1)
&& chown(tempname, uflag, gflag) < 0) {
fprintf(stderr, _("cannot change ownership of %s"),
tempname);
exit(EXIT_FAILURE);
}
rename_dest(tempname, name);
free(ats);
}
static char const *
abbroffset(char *buf, zic_t offset)
{
char sign = '+';
int seconds, minutes;
if (offset < 0) {
offset = -offset;
sign = '-';
}
seconds = offset % SECSPERMIN;
offset /= SECSPERMIN;
minutes = offset % MINSPERHOUR;
offset /= MINSPERHOUR;
if (100 <= offset) {
error(_("%%z UT offset magnitude exceeds 99:59:59"));
return "%z";
} else {
char *p = buf;
*p++ = sign;
*p++ = '0' + offset / 10;
*p++ = '0' + offset % 10;
if (minutes | seconds) {
*p++ = '0' + minutes / 10;
*p++ = '0' + minutes % 10;
if (seconds) {
*p++ = '0' + seconds / 10;
*p++ = '0' + seconds % 10;
}
}
*p = '\0';
return buf;
}
}
static char const disable_percent_s[] = "";
static ptrdiff_t
doabbr(char *abbr, struct zone const *zp, char const *letters,
bool isdst, zic_t save, bool doquotes)
{
register char * cp;
register char * slashp;
ptrdiff_t len;
char const *format = zp->z_format;
slashp = strchr(format, '/');
if (slashp == NULL) {
char letterbuf[PERCENT_Z_LEN_BOUND + 1];
if (zp->z_format_specifier == 'z')
letters = abbroffset(letterbuf, zp->z_stdoff + save);
else if (!letters)
letters = "%s";
else if (letters == disable_percent_s)
return 0;
sprintf(abbr, format, letters);
} else if (isdst) {
strcpy(abbr, slashp + 1);
} else {
memcpy(abbr, format, slashp - format);
abbr[slashp - format] = '\0';
}
len = strlen(abbr);
if (!doquotes)
return len;
for (cp = abbr; is_alpha(*cp); cp++)
continue;
if (len > 0 && *cp == '\0')
return len;
abbr[len + 2] = '\0';
abbr[len + 1] = '>';
memmove(abbr + 1, abbr, len);
abbr[0] = '<';
return len + 2;
}
static void
updateminmax(const zic_t x)
{
if (min_year > x)
min_year = x;
if (max_year < x)
max_year = x;
}
static int
stringoffset(char *result, zic_t offset)
{
register int hours;
register int minutes;
register int seconds;
bool negative = offset < 0;
int len = negative;
if (negative) {
offset = -offset;
result[0] = '-';
}
seconds = offset % SECSPERMIN;
offset /= SECSPERMIN;
minutes = offset % MINSPERHOUR;
offset /= MINSPERHOUR;
hours = offset;
if (hours >= HOURSPERDAY * DAYSPERWEEK) {
result[0] = '\0';
return 0;
}
len += sprintf(result + len, "%d", hours);
if (minutes != 0 || seconds != 0) {
len += sprintf(result + len, ":%02d", minutes);
if (seconds != 0)
len += sprintf(result + len, ":%02d", seconds);
}
return len;
}
static int
stringrule(char *result, struct rule *const rp, zic_t save, zic_t stdoff)
{
register zic_t tod = rp->r_tod;
register int compat = 0;
if (rp->r_dycode == DC_DOM) {
register int month, total;
if (rp->r_dayofmonth == 29 && rp->r_month == TM_FEBRUARY)
return -1;
total = 0;
for (month = 0; month < rp->r_month; ++month)
total += len_months[0][month];
/* Omit the "J" in Jan and Feb, as that's shorter. */
if (rp->r_month <= 1)
result += sprintf(result, "%d", total + rp->r_dayofmonth - 1);
else
result += sprintf(result, "J%d", total + rp->r_dayofmonth);
} else {
register int week;
register int wday = rp->r_wday;
register int wdayoff;
if (rp->r_dycode == DC_DOWGEQ) {
wdayoff = (rp->r_dayofmonth - 1) % DAYSPERWEEK;
if (wdayoff)
compat = 2013;
wday -= wdayoff;
tod += wdayoff * SECSPERDAY;
week = 1 + (rp->r_dayofmonth - 1) / DAYSPERWEEK;
} else if (rp->r_dycode == DC_DOWLEQ) {
if (rp->r_dayofmonth == len_months[1][rp->r_month])
week = 5;
else {
wdayoff = rp->r_dayofmonth % DAYSPERWEEK;
if (wdayoff)
compat = 2013;
wday -= wdayoff;
tod += wdayoff * SECSPERDAY;
week = rp->r_dayofmonth / DAYSPERWEEK;
}
} else return -1; /* "cannot happen" */
if (wday < 0)
wday += DAYSPERWEEK;
result += sprintf(result, "M%d.%d.%d",
rp->r_month + 1, week, wday);
}
if (rp->r_todisut)
tod += stdoff;
if (rp->r_todisstd && !rp->r_isdst)
tod += save;
if (tod != 2 * SECSPERMIN * MINSPERHOUR) {
*result++ = '/';
if (! stringoffset(result, tod))
return -1;
if (tod < 0) {
if (compat < 2013)
compat = 2013;
} else if (SECSPERDAY <= tod) {
if (compat < 1994)
compat = 1994;
}
}
return compat;
}
static int
rule_cmp(struct rule const *a, struct rule const *b)
{
if (!a)
return -!!b;
if (!b)
return 1;
if (a->r_hiyear != b->r_hiyear)
return a->r_hiyear < b->r_hiyear ? -1 : 1;
if (a->r_hiyear == ZIC_MAX)
return 0;
if (a->r_month - b->r_month != 0)
return a->r_month - b->r_month;
return a->r_dayofmonth - b->r_dayofmonth;
}
static int
stringzone(char *result, struct zone const *zpfirst, ptrdiff_t zonecount)
{
register const struct zone * zp;
register struct rule * rp;
register struct rule * stdrp;
register struct rule * dstrp;
register ptrdiff_t i;
register int compat = 0;
register int c;
int offsetlen;
struct rule stdr, dstr;
ptrdiff_t len;
int dstcmp;
struct rule *lastrp[2] = { NULL, NULL };
struct zone zstr[2];
struct zone const *stdzp;
struct zone const *dstzp;
result[0] = '\0';
/* Internet RFC 8536 section 5.1 says to use an empty TZ string if
future timestamps are truncated. */
if (hi_time < max_time)
return -1;
zp = zpfirst + zonecount - 1;
for (i = 0; i < zp->z_nrules; ++i) {
struct rule **last;
int cmp;
rp = &zp->z_rules[i];
last = &lastrp[rp->r_isdst];
cmp = rule_cmp(*last, rp);
if (cmp < 0)
*last = rp;
else if (cmp == 0)
return -1;
}
stdrp = lastrp[false];
dstrp = lastrp[true];
dstcmp = zp->z_nrules ? rule_cmp(dstrp, stdrp) : zp->z_isdst ? 1 : -1;
stdzp = dstzp = zp;
if (dstcmp < 0) {
/* Standard time all year. */
dstrp = NULL;
} else if (0 < dstcmp) {
/* DST all year. Use an abbreviation like
"XXX3EDT4,0/0,J365/23" for EDT (-04) all year. */
zic_t save = dstrp ? dstrp->r_save : zp->z_save;
if (0 <= save)
{
/* Positive DST, the typical case for all-year DST.
Fake a timezone with negative DST. */
stdzp = &zstr[0];
dstzp = &zstr[1];
zstr[0].z_stdoff = zp->z_stdoff + 2 * save;
zstr[0].z_format = "XXX"; /* Any 3 letters will do. */
zstr[0].z_format_specifier = 0;
zstr[1].z_stdoff = zstr[0].z_stdoff;
zstr[1].z_format = zp->z_format;
zstr[1].z_format_specifier = zp->z_format_specifier;
}
dstr.r_month = TM_JANUARY;
dstr.r_dycode = DC_DOM;
dstr.r_dayofmonth = 1;
dstr.r_tod = 0;
dstr.r_todisstd = dstr.r_todisut = false;
dstr.r_isdst = true;
dstr.r_save = save < 0 ? save : -save;
dstr.r_abbrvar = dstrp ? dstrp->r_abbrvar : NULL;
stdr.r_month = TM_DECEMBER;
stdr.r_dycode = DC_DOM;
stdr.r_dayofmonth = 31;
stdr.r_tod = SECSPERDAY + dstr.r_save;
stdr.r_todisstd = stdr.r_todisut = false;
stdr.r_isdst = false;
stdr.r_save = 0;
stdr.r_abbrvar = save < 0 && stdrp ? stdrp->r_abbrvar : NULL;
dstrp = &dstr;
stdrp = &stdr;
}
len = doabbr(result, stdzp, stdrp ? stdrp->r_abbrvar : NULL,
false, 0, true);
offsetlen = stringoffset(result + len, - stdzp->z_stdoff);
if (! offsetlen) {
result[0] = '\0';
return -1;
}
len += offsetlen;
if (dstrp == NULL)
return compat;
len += doabbr(result + len, dstzp, dstrp->r_abbrvar,
dstrp->r_isdst, dstrp->r_save, true);
if (dstrp->r_save != SECSPERMIN * MINSPERHOUR) {
offsetlen = stringoffset(result + len,
- (dstzp->z_stdoff + dstrp->r_save));
if (! offsetlen) {
result[0] = '\0';
return -1;
}
len += offsetlen;
}
result[len++] = ',';
c = stringrule(result + len, dstrp, dstrp->r_save, stdzp->z_stdoff);
if (c < 0) {
result[0] = '\0';
return -1;
}
if (compat < c)
compat = c;
len += strlen(result + len);
result[len++] = ',';
c = stringrule(result + len, stdrp, dstrp->r_save, stdzp->z_stdoff);
if (c < 0) {
result[0] = '\0';
return -1;
}
if (compat < c)
compat = c;
return compat;
}
static void
outzone(const struct zone *zpfirst, ptrdiff_t zonecount)
{
register ptrdiff_t i, j;
register zic_t starttime, untiltime;
register bool startttisstd;
register bool startttisut;
register char * startbuf;
register char * ab;
register char * envvar;
register int max_abbr_len;
register int max_envvar_len;
register bool prodstic; /* all rules are min to max */
register int compat;
register bool do_extend;
register char version;
ptrdiff_t lastatmax = -1;
zic_t max_year0;
int defaulttype = -1;
check_for_signal();
/* This cannot overflow; see FORMAT_LEN_GROWTH_BOUND. */
max_abbr_len = 2 + max_format_len + max_abbrvar_len;
max_envvar_len = 2 * max_abbr_len + 5 * 9;
startbuf = emalloc(max_abbr_len + 1);
ab = emalloc(max_abbr_len + 1);
envvar = emalloc(max_envvar_len + 1);
INITIALIZE(untiltime);
INITIALIZE(starttime);
/*
** Now. . .finally. . .generate some useful data!
*/
timecnt = 0;
typecnt = 0;
charcnt = 0;
prodstic = zonecount == 1;
/*
** Thanks to Earl Chew
** for noting the need to unconditionally initialize startttisstd.
*/
startttisstd = false;
startttisut = false;
min_year = max_year = EPOCH_YEAR;
if (leapseen) {
updateminmax(leapminyear);
updateminmax(leapmaxyear + (leapmaxyear < ZIC_MAX));
}
for (i = 0; i < zonecount; ++i) {
struct zone const *zp = &zpfirst[i];
if (i < zonecount - 1)
updateminmax(zp->z_untilrule.r_loyear);
for (j = 0; j < zp->z_nrules; ++j) {
struct rule *rp = &zp->z_rules[j];
if (rp->r_lowasnum)
updateminmax(rp->r_loyear);
if (rp->r_hiwasnum)
updateminmax(rp->r_hiyear);
if (rp->r_lowasnum || rp->r_hiwasnum)
prodstic = false;
}
}
/*
** Generate lots of data if a rule can't cover all future times.
*/
compat = stringzone(envvar, zpfirst, zonecount);
version = compat < 2013 ? '2' : '3';
do_extend = compat < 0;
if (noise) {
if (!*envvar)
warning("%s %s",
_("no POSIX environment variable for zone"),
zpfirst->z_name);
else if (compat != 0) {
/* Circa-COMPAT clients, and earlier clients, might
not work for this zone when given dates before
1970 or after 2038. */
warning(_("%s: pre-%d clients may mishandle"
" distant timestamps"),
zpfirst->z_name, compat);
}
}
if (do_extend) {
/*
** Search through a couple of extra years past the obvious
** 400, to avoid edge cases. For example, suppose a non-POSIX
** rule applies from 2012 onwards and has transitions in March
** and September, plus some one-off transitions in November
** 2013. If zic looked only at the last 400 years, it would
** set max_year=2413, with the intent that the 400 years 2014
** through 2413 will be repeated. The last transition listed
** in the tzfile would be in 2413-09, less than 400 years
** after the last one-off transition in 2013-11. Two years
** might be overkill, but with the kind of edge cases
** available we're not sure that one year would suffice.
*/
enum { years_of_observations = YEARSPERREPEAT + 2 };
if (min_year >= ZIC_MIN + years_of_observations)
min_year -= years_of_observations;
else min_year = ZIC_MIN;
if (max_year <= ZIC_MAX - years_of_observations)
max_year += years_of_observations;
else max_year = ZIC_MAX;
/*
** Regardless of any of the above,
** for a "proDSTic" zone which specifies that its rules
** always have and always will be in effect,
** we only need one cycle to define the zone.
*/
if (prodstic) {
min_year = 1900;
max_year = min_year + years_of_observations;
}
}
max_year = max(max_year, (redundant_time / (SECSPERDAY * DAYSPERNYEAR)
+ EPOCH_YEAR + 1));
max_year0 = max_year;
if (want_bloat()) {
/* For the benefit of older systems,
generate data from 1900 through 2038. */
if (min_year > 1900)
min_year = 1900;
if (max_year < 2038)
max_year = 2038;
}
if (min_time < lo_time || hi_time < max_time)
unspecifiedtype = addtype(0, "-00", false, false, false);
for (i = 0; i < zonecount; ++i) {
struct rule *prevrp = NULL;
/*
** A guess that may well be corrected later.
*/
zic_t save = 0;
struct zone const *zp = &zpfirst[i];
bool usestart = i > 0 && (zp - 1)->z_untiltime > min_time;
bool useuntil = i < (zonecount - 1);
zic_t stdoff = zp->z_stdoff;
zic_t startoff = stdoff;
zic_t prevktime;
INITIALIZE(prevktime);
if (useuntil && zp->z_untiltime <= min_time)
continue;
eat(zp->z_filenum, zp->z_linenum);
*startbuf = '\0';
if (zp->z_nrules == 0) {
int type;
save = zp->z_save;
doabbr(startbuf, zp, NULL, zp->z_isdst, save, false);
type = addtype(oadd(zp->z_stdoff, save),
startbuf, zp->z_isdst, startttisstd,
startttisut);
if (usestart) {
addtt(starttime, type);
usestart = false;
} else
defaulttype = type;
} else {
zic_t year;
for (year = min_year; year <= max_year; ++year) {
if (useuntil && year > zp->z_untilrule.r_hiyear)
break;
/*
** Mark which rules to do in the current year.
** For those to do, calculate rpytime(rp, year);
** The former TYPE field was also considered here.
*/
for (j = 0; j < zp->z_nrules; ++j) {
zic_t one = 1;
zic_t y2038_boundary = one << 31;
struct rule *rp = &zp->z_rules[j];
eats(zp->z_filenum, zp->z_linenum,
rp->r_filenum, rp->r_linenum);
rp->r_todo = year >= rp->r_loyear &&
year <= rp->r_hiyear;
if (rp->r_todo) {
rp->r_temp = rpytime(rp, year);
rp->r_todo
= (rp->r_temp < y2038_boundary
|| year <= max_year0);
}
}
for ( ; ; ) {
register ptrdiff_t k;
register zic_t jtime, ktime;
register zic_t offset;
struct rule *rp;
int type;
INITIALIZE(ktime);
if (useuntil) {
/*
** Turn untiltime into UT
** assuming the current stdoff and
** save values.
*/
untiltime = zp->z_untiltime;
if (!zp->z_untilrule.r_todisut)
untiltime = tadd(untiltime,
-stdoff);
if (!zp->z_untilrule.r_todisstd)
untiltime = tadd(untiltime,
-save);
}
/*
** Find the rule (of those to do, if any)
** that takes effect earliest in the year.
*/
k = -1;
for (j = 0; j < zp->z_nrules; ++j) {
struct rule *r = &zp->z_rules[j];
if (!r->r_todo)
continue;
eats(zp->z_filenum, zp->z_linenum,
r->r_filenum, r->r_linenum);
offset = r->r_todisut ? 0 : stdoff;
if (!r->r_todisstd)
offset = oadd(offset, save);
jtime = r->r_temp;
if (jtime == min_time ||
jtime == max_time)
continue;
jtime = tadd(jtime, -offset);
if (k < 0 || jtime < ktime) {
k = j;
ktime = jtime;
} else if (jtime == ktime) {
char const *dup_rules_msg =
_("two rules for same instant");
eats(zp->z_filenum, zp->z_linenum,
r->r_filenum, r->r_linenum);
warning("%s", dup_rules_msg);
r = &zp->z_rules[k];
eats(zp->z_filenum, zp->z_linenum,
r->r_filenum, r->r_linenum);
error("%s", dup_rules_msg);
}
}
if (k < 0)
break; /* go on to next year */
rp = &zp->z_rules[k];
rp->r_todo = false;
if (useuntil && ktime >= untiltime) {
if (!*startbuf
&& (oadd(zp->z_stdoff, rp->r_save)
== startoff))
doabbr(startbuf, zp, rp->r_abbrvar,
rp->r_isdst, rp->r_save,
false);
break;
}
save = rp->r_save;
if (usestart && ktime == starttime)
usestart = false;
if (usestart) {
if (ktime < starttime) {
startoff = oadd(zp->z_stdoff,
save);
doabbr(startbuf, zp,
rp->r_abbrvar,
rp->r_isdst,
rp->r_save,
false);
continue;
}
if (*startbuf == '\0'
&& startoff == oadd(zp->z_stdoff,
save)) {
doabbr(startbuf,
zp,
rp->r_abbrvar,
rp->r_isdst,
rp->r_save,
false);
}
}
eats(zp->z_filenum, zp->z_linenum,
rp->r_filenum, rp->r_linenum);
doabbr(ab, zp, rp->r_abbrvar,
rp->r_isdst, rp->r_save, false);
offset = oadd(zp->z_stdoff, rp->r_save);
if (!want_bloat() && !useuntil && !do_extend
&& prevrp && lo_time <= prevktime
&& redundant_time <= ktime
&& rp->r_hiyear == ZIC_MAX
&& prevrp->r_hiyear == ZIC_MAX)
break;
type = addtype(offset, ab, rp->r_isdst,
rp->r_todisstd, rp->r_todisut);
if (defaulttype < 0 && !rp->r_isdst)
defaulttype = type;
if (rp->r_hiyear == ZIC_MAX
&& ! (0 <= lastatmax
&& ktime < attypes[lastatmax].at))
lastatmax = timecnt;
addtt(ktime, type);
prevrp = rp;
prevktime = ktime;
}
}
}
if (usestart) {
bool isdst = startoff != zp->z_stdoff;
if (*startbuf == '\0' && zp->z_format)
doabbr(startbuf, zp, disable_percent_s,
isdst, save, false);
eat(zp->z_filenum, zp->z_linenum);
if (*startbuf == '\0')
error(_("can't determine time zone abbreviation to use just after until time"));
else {
int type = addtype(startoff, startbuf, isdst,
startttisstd, startttisut);
if (defaulttype < 0 && !isdst)
defaulttype = type;
addtt(starttime, type);
}
}
/*
** Now we may get to set starttime for the next zone line.
*/
if (useuntil) {
startttisstd = zp->z_untilrule.r_todisstd;
startttisut = zp->z_untilrule.r_todisut;
starttime = zp->z_untiltime;
if (!startttisstd)
starttime = tadd(starttime, -save);
if (!startttisut)
starttime = tadd(starttime, -stdoff);
}
}
if (defaulttype < 0)
defaulttype = 0;
if (0 <= lastatmax)
attypes[lastatmax].dontmerge = true;
if (do_extend) {
/*
** If we're extending the explicitly listed observations
** for 400 years because we can't fill the POSIX-TZ field,
** check whether we actually ended up explicitly listing
** observations through that period. If there aren't any
** near the end of the 400-year period, add a redundant
** one at the end of the final year, to make it clear
** that we are claiming to have definite knowledge of
** the lack of transitions up to that point.
*/
struct rule xr;
struct attype *lastat;
xr.r_month = TM_JANUARY;
xr.r_dycode = DC_DOM;
xr.r_dayofmonth = 1;
xr.r_tod = 0;
for (lastat = attypes, i = 1; i < timecnt; i++)
if (attypes[i].at > lastat->at)
lastat = &attypes[i];
if (!lastat || lastat->at < rpytime(&xr, max_year - 1)) {
addtt(rpytime(&xr, max_year + 1),
lastat ? lastat->type : defaulttype);
attypes[timecnt - 1].dontmerge = true;
}
}
writezone(zpfirst->z_name, envvar, version, defaulttype);
free(startbuf);
free(ab);
free(envvar);
}
static void
addtt(zic_t starttime, int type)
{
attypes = growalloc(attypes, sizeof *attypes, timecnt, &timecnt_alloc);
attypes[timecnt].at = starttime;
attypes[timecnt].dontmerge = false;
attypes[timecnt].type = type;
++timecnt;
}
static int
addtype(zic_t utoff, char const *abbr, bool isdst, bool ttisstd, bool ttisut)
{
register int i, j;
if (! (-1L - 2147483647L <= utoff && utoff <= 2147483647L)) {
error(_("UT offset out of range"));
exit(EXIT_FAILURE);
}
if (!want_bloat())
ttisstd = ttisut = false;
for (j = 0; j < charcnt; ++j)
if (strcmp(&chars[j], abbr) == 0)
break;
if (j == charcnt)
newabbr(abbr);
else {
/* If there's already an entry, return its index. */
for (i = 0; i < typecnt; i++)
if (utoff == utoffs[i] && isdst == isdsts[i] && j == desigidx[i]
&& ttisstd == ttisstds[i] && ttisut == ttisuts[i])
return i;
}
/*
** There isn't one; add a new one, unless there are already too
** many.
*/
if (typecnt >= TZ_MAX_TYPES) {
error(_("too many local time types"));
exit(EXIT_FAILURE);
}
i = typecnt++;
utoffs[i] = utoff;
isdsts[i] = isdst;
ttisstds[i] = ttisstd;
ttisuts[i] = ttisut;
desigidx[i] = j;
return i;
}
static void
leapadd(zic_t t, int correction, int rolling)
{
register int i;
if (TZ_MAX_LEAPS <= leapcnt) {
error(_("too many leap seconds"));
exit(EXIT_FAILURE);
}
if (rolling && (lo_time != min_time || hi_time != max_time)) {
error(_("Rolling leap seconds not supported with -r"));
exit(EXIT_FAILURE);
}
for (i = 0; i < leapcnt; ++i)
if (t <= trans[i])
break;
memmove(&trans[i + 1], &trans[i], (leapcnt - i) * sizeof *trans);
memmove(&corr[i + 1], &corr[i], (leapcnt - i) * sizeof *corr);
memmove(&roll[i + 1], &roll[i], (leapcnt - i) * sizeof *roll);
trans[i] = t;
corr[i] = correction;
roll[i] = rolling;
++leapcnt;
}
static void
adjleap(void)
{
register int i;
register zic_t last = 0;
register zic_t prevtrans = 0;
/*
** propagate leap seconds forward
*/
for (i = 0; i < leapcnt; ++i) {
if (trans[i] - prevtrans < 28 * SECSPERDAY) {
error(_("Leap seconds too close together"));
exit(EXIT_FAILURE);
}
prevtrans = trans[i];
trans[i] = tadd(trans[i], last);
last = corr[i] += last;
}
if (0 <= leapexpires) {
leapexpires = oadd(leapexpires, last);
if (! (leapcnt == 0 || (trans[leapcnt - 1] < leapexpires))) {
error(_("last Leap time does not precede Expires time"));
exit(EXIT_FAILURE);
}
}
}
/* Is A a space character in the C locale? */
static bool
is_space(char a)
{
switch (a) {
default:
return false;
case ' ': case '\f': case '\n': case '\r': case '\t': case '\v':
return true;
}
}
/* Is A an alphabetic character in the C locale? */
static bool
is_alpha(char a)
{
switch (a) {
default:
return false;
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':
return true;
}
}
/* If A is an uppercase character in the C locale, return its lowercase
counterpart. Otherwise, return A. */
static char
lowerit(char a)
{
switch (a) {
default: return a;
case 'A': return 'a'; case 'B': return 'b'; case 'C': return 'c';
case 'D': return 'd'; case 'E': return 'e'; case 'F': return 'f';
case 'G': return 'g'; case 'H': return 'h'; case 'I': return 'i';
case 'J': return 'j'; case 'K': return 'k'; case 'L': return 'l';
case 'M': return 'm'; case 'N': return 'n'; case 'O': return 'o';
case 'P': return 'p'; case 'Q': return 'q'; case 'R': return 'r';
case 'S': return 's'; case 'T': return 't'; case 'U': return 'u';
case 'V': return 'v'; case 'W': return 'w'; case 'X': return 'x';
case 'Y': return 'y'; case 'Z': return 'z';
}
}
/* case-insensitive equality */
-static ATTRIBUTE_REPRODUCIBLE bool
+ATTRIBUTE_REPRODUCIBLE static bool
ciequal(register const char *ap, register const char *bp)
{
while (lowerit(*ap) == lowerit(*bp++))
if (*ap++ == '\0')
return true;
return false;
}
-static ATTRIBUTE_REPRODUCIBLE bool
+ATTRIBUTE_REPRODUCIBLE static bool
itsabbr(register const char *abbr, register const char *word)
{
if (lowerit(*abbr) != lowerit(*word))
return false;
++word;
while (*++abbr != '\0')
do {
if (*word == '\0')
return false;
} while (lowerit(*word++) != lowerit(*abbr));
return true;
}
/* Return true if ABBR is an initial prefix of WORD, ignoring ASCII case. */
-static ATTRIBUTE_REPRODUCIBLE bool
+ATTRIBUTE_REPRODUCIBLE static bool
ciprefix(char const *abbr, char const *word)
{
do
if (!*abbr)
return true;
while (lowerit(*abbr++) == lowerit(*word++));
return false;
}
static const struct lookup *
byword(const char *word, const struct lookup *table)
{
register const struct lookup * foundlp;
register const struct lookup * lp;
if (word == NULL || table == NULL)
return NULL;
/* If TABLE is LASTS and the word starts with "last" followed
by a non-'-', skip the "last" and look in WDAY_NAMES instead.
Warn about any usage of the undocumented prefix "last-". */
if (table == lasts && ciprefix("last", word) && word[4]) {
if (word[4] == '-')
warning(_("\"%s\" is undocumented; use \"last%s\" instead"),
word, word + 5);
else {
word += 4;
table = wday_names;
}
}
/*
** Look for exact match.
*/
for (lp = table; lp->l_word != NULL; ++lp)
if (ciequal(word, lp->l_word))
return lp;
/*
** Look for inexact match.
*/
foundlp = NULL;
for (lp = table; lp->l_word != NULL; ++lp)
if (ciprefix(word, lp->l_word)) {
if (foundlp == NULL)
foundlp = lp;
else return NULL; /* multiple inexact matches */
}
if (foundlp && noise) {
/* Warn about any backward-compatibility issue with pre-2017c zic. */
bool pre_2017c_match = false;
for (lp = table; lp->l_word; lp++)
if (itsabbr(word, lp->l_word)) {
if (pre_2017c_match) {
warning(_("\"%s\" is ambiguous in pre-2017c zic"), word);
break;
}
pre_2017c_match = true;
}
}
return foundlp;
}
static int
getfields(char *cp, char **array, int arrayelts)
{
register char * dp;
register int nsubs;
nsubs = 0;
for ( ; ; ) {
char *dstart;
while (is_space(*cp))
++cp;
if (*cp == '\0' || *cp == '#')
break;
dstart = dp = cp;
do {
if ((*dp = *cp++) != '"')
++dp;
else while ((*dp = *cp++) != '"')
if (*dp != '\0')
++dp;
else {
error(_("Odd number of quotation marks"));
exit(EXIT_FAILURE);
}
} while (*cp && *cp != '#' && !is_space(*cp));
if (is_space(*cp))
++cp;
*dp = '\0';
if (nsubs == arrayelts) {
error(_("Too many input fields"));
exit(EXIT_FAILURE);
}
array[nsubs++] = dstart + (*dstart == '-' && dp == dstart + 1);
}
return nsubs;
}
-static ATTRIBUTE_NORETURN void
+ATTRIBUTE_NORETURN static void
time_overflow(void)
{
error(_("time overflow"));
exit(EXIT_FAILURE);
}
-static ATTRIBUTE_REPRODUCIBLE zic_t
+ATTRIBUTE_REPRODUCIBLE static zic_t
oadd(zic_t t1, zic_t t2)
{
#ifdef ckd_add
zic_t sum;
if (!ckd_add(&sum, t1, t2))
return sum;
#else
if (t1 < 0 ? ZIC_MIN - t1 <= t2 : t2 <= ZIC_MAX - t1)
return t1 + t2;
#endif
time_overflow();
}
-static ATTRIBUTE_REPRODUCIBLE zic_t
+ATTRIBUTE_REPRODUCIBLE static zic_t
tadd(zic_t t1, zic_t t2)
{
#ifdef ckd_add
zic_t sum;
if (!ckd_add(&sum, t1, t2) && min_time <= sum && sum <= max_time)
return sum;
#else
if (t1 < 0 ? min_time - t1 <= t2 : t2 <= max_time - t1)
return t1 + t2;
#endif
if (t1 == min_time || t1 == max_time)
return t1;
time_overflow();
}
/*
** Given a rule, and a year, compute the date (in seconds since January 1,
** 1970, 00:00 LOCAL time) in that year that the rule refers to.
*/
static zic_t
rpytime(const struct rule *rp, zic_t wantedy)
{
register int m, i;
register zic_t dayoff; /* with a nod to Margaret O. */
register zic_t t, y;
int yrem;
if (wantedy == ZIC_MIN)
return min_time;
if (wantedy == ZIC_MAX)
return max_time;
m = TM_JANUARY;
y = EPOCH_YEAR;
/* dayoff = floor((wantedy - y) / YEARSPERREPEAT) * DAYSPERREPEAT,
sans overflow. */
yrem = wantedy % YEARSPERREPEAT - y % YEARSPERREPEAT;
dayoff = ((wantedy / YEARSPERREPEAT - y / YEARSPERREPEAT
+ yrem / YEARSPERREPEAT - (yrem % YEARSPERREPEAT < 0))
* DAYSPERREPEAT);
/* wantedy = y + ((wantedy - y) mod YEARSPERREPEAT), sans overflow. */
wantedy = y + (yrem + 2 * YEARSPERREPEAT) % YEARSPERREPEAT;
while (wantedy != y) {
i = len_years[isleap(y)];
dayoff = oadd(dayoff, i);
y++;
}
while (m != rp->r_month) {
i = len_months[isleap(y)][m];
dayoff = oadd(dayoff, i);
++m;
}
i = rp->r_dayofmonth;
if (m == TM_FEBRUARY && i == 29 && !isleap(y)) {
if (rp->r_dycode == DC_DOWLEQ)
--i;
else {
error(_("use of 2/29 in non leap-year"));
exit(EXIT_FAILURE);
}
}
--i;
dayoff = oadd(dayoff, i);
if (rp->r_dycode == DC_DOWGEQ || rp->r_dycode == DC_DOWLEQ) {
/*
** Don't trust mod of negative numbers.
*/
zic_t wday = ((EPOCH_WDAY + dayoff % DAYSPERWEEK + DAYSPERWEEK)
% DAYSPERWEEK);
while (wday != rp->r_wday)
if (rp->r_dycode == DC_DOWGEQ) {
dayoff = oadd(dayoff, 1);
if (++wday >= DAYSPERWEEK)
wday = 0;
++i;
} else {
dayoff = oadd(dayoff, -1);
if (--wday < 0)
wday = DAYSPERWEEK - 1;
--i;
}
if (i < 0 || i >= len_months[isleap(y)][m]) {
if (noise)
warning(_("rule goes past start/end of month; \
will not work with pre-2004 versions of zic"));
}
}
if (dayoff < min_time / SECSPERDAY)
return min_time;
if (dayoff > max_time / SECSPERDAY)
return max_time;
t = (zic_t) dayoff * SECSPERDAY;
return tadd(t, rp->r_tod);
}
static void
newabbr(const char *string)
{
register int i;
if (strcmp(string, GRANDPARENTED) != 0) {
register const char * cp;
const char * mp;
cp = string;
mp = NULL;
while (is_alpha(*cp) || ('0' <= *cp && *cp <= '9')
|| *cp == '-' || *cp == '+')
++cp;
if (noise && cp - string < 3)
mp = _("time zone abbreviation has fewer than 3 characters");
if (cp - string > ZIC_MAX_ABBR_LEN_WO_WARN)
mp = _("time zone abbreviation has too many characters");
if (*cp != '\0')
mp = _("time zone abbreviation differs from POSIX standard");
if (mp != NULL)
warning("%s (%s)", mp, string);
}
i = strlen(string) + 1;
if (charcnt + i > TZ_MAX_CHARS) {
error(_("too many, or too long, time zone abbreviations"));
exit(EXIT_FAILURE);
}
strcpy(&chars[charcnt], string);
charcnt += i;
}
/* Ensure that the directories of ARGNAME exist, by making any missing
ones. If ANCESTORS, do this only for ARGNAME's ancestors; otherwise,
do it for ARGNAME too. Exit with failure if there is trouble.
Do not consider an existing file to be trouble. */
static void
mkdirs(char const *argname, bool ancestors)
{
/*
* If -D was specified, do not create directories. A subsequent
* file operation will fail and produce an appropriate error
* message.
*/
if (Dflag)
return;
char *name = estrdup(argname);
char *cp = name;
/* On MS-Windows systems, do not worry about drive letters or
backslashes, as this should suffice in practice. Time zone
names do not use drive letters and backslashes. If the -d
option of zic does not name an already-existing directory,
it can use slashes to separate the already-existing
ancestor prefix from the to-be-created subdirectories. */
/* Do not mkdir a root directory, as it must exist. */
while (*cp == '/')
cp++;
while (cp && ((cp = strchr(cp, '/')) || !ancestors)) {
if (cp)
*cp = '\0';
/*
** Try to create it. It's OK if creation fails because
** the directory already exists, perhaps because some
** other process just created it. For simplicity do
** not check first whether it already exists, as that
** is checked anyway if the mkdir fails.
*/
if (mkdir(name, MKDIR_UMASK) != 0) {
/* Do not report an error if err == EEXIST, because
some other process might have made the directory
in the meantime. Likewise for ENOSYS, because
Solaris 10 mkdir fails with ENOSYS if the
directory is an automounted mount point.
Likewise for EACCES, since mkdir can fail
with EACCES merely because the parent directory
is unwritable. Likewise for most other error
numbers. */
int err = errno;
if (err == ELOOP || err == ENAMETOOLONG
|| err == ENOENT || err == ENOTDIR) {
error(_("%s: Can't create directory %s: %s"),
progname, name, strerror(err));
exit(EXIT_FAILURE);
}
}
if (cp)
*cp++ = '/';
}
free(name);
}
#include
#include
static void
setgroup(gid_t *flag, const char *name)
{
struct group *gr;
if (*flag != (gid_t)-1) {
fprintf(stderr, _("multiple -g flags specified"));
exit(EXIT_FAILURE);
}
gr = getgrnam(name);
if (gr == 0) {
char *ep;
unsigned long ul;
ul = strtoul(name, &ep, 10);
if (ul == (unsigned long)(gid_t)ul && *ep == '\0') {
*flag = ul;
return;
}
fprintf(stderr, _("group `%s' not found"), name);
exit(EXIT_FAILURE);
}
*flag = gr->gr_gid;
}
static void
setuser(uid_t *flag, const char *name)
{
struct passwd *pw;
if (*flag != (gid_t)-1) {
fprintf(stderr, _("multiple -u flags specified"));
exit(EXIT_FAILURE);
}
pw = getpwnam(name);
if (pw == 0) {
char *ep;
unsigned long ul;
ul = strtoul(name, &ep, 10);
if (ul == (unsigned long)(gid_t)ul && *ep == '\0') {
*flag = ul;
return;
}
fprintf(stderr, _("user `%s' not found"), name);
exit(EXIT_FAILURE);
}
*flag = pw->pw_uid;
}
/*
** UNIX was a registered trademark of The Open Group in 2003.
*/
diff --git a/lib/libc/stdtime/Symbol.map b/lib/libc/stdtime/Symbol.map
index bc113c650d2f..acdd3bbb89b4 100644
--- a/lib/libc/stdtime/Symbol.map
+++ b/lib/libc/stdtime/Symbol.map
@@ -1,34 +1,35 @@
/*
*/
FBSD_1.0 {
_time32_to_time;
_time_to_time32;
_time64_to_time;
_time_to_time64;
_time_to_long;
_long_to_time;
_time_to_int;
_int_to_time;
strptime;
strftime;
tzname;
tzsetwall;
tzset;
localtime;
localtime_r;
gmtime;
gmtime_r;
offtime;
+ offtime_r;
ctime;
ctime_r;
mktime;
timelocal;
timegm;
timeoff;
time2posix;
posix2time;
difftime;
asctime_r;
asctime;
};