libast/tm: DST offset and contemporaneous time zone fixes.#1008
Conversation
|
Tested on Slackware64 -current (glibc), Void (musl), FreeBSD, OpenBSD, omnios (attributes and printf only) and NetBSD. I don't currently have a macOS VM. |
|
8b003db broadens the scope a bit. I can revert it and make a different PR after this one is merged if that would be more convenient. |
|
Many thanks for this solidly executed PR. I don't think there's a real need to split the PR; your commit message does a nice job of explaining every distinct change. If it becomes necessary, we can always go back to the commit history of this PR to pick it apart further. Unfortunately I don't have the time and inclination to become expert enough at the tm(3) code myself to give this a proper review, but your explanations do make sense to me. I've tested the PR and everything seems to work as described. There is good regression test coverage. So, in it goes. One thing I will edit first, though, is some of those one-letter variable names, to change them to more descriptive names. I'm really not a fan of that aspect of Glenn Fowler's coding style; it badly reduces the legibility of the code. |
Apart from routine housekeeping: src/lib/libast/tm/tminit.c: - Rename one-letter variables for legibility. src/lib/libast/tm/tmxdate.c: - Move the check for historical time zone changes to the end of tmxdate(). This allows eliminating the 'first' and 'good_check' variables.
|
Changed a couple of things:
Does that look OK to you? |
|
Yes, very sensible. |
|
Actually, one very small point about the NEWS entry. The contemporaneous timezone issue wasn't musl-only. |
|
Fixed, thanks. |
Pre-u+m code in tminit.c sets the standard (ST) and daylight
savings time (DST) offsets by checking times over the previous year
of the time requested to compare DST status and offsets from UTC.
If no offset change is detected, the DST offset is set equal to the
ST offset.
Although assigning the DST offset in this way is counter-intuitive,
it does not result in incorrect times being reported so long as two
implicit assumptions are met:
* If the requested time is DST, a change from ST occurred in the
previous year.
* If the offset changed over the previous year, this was the
result of a DST transition.
The first assumption fails on Linux systems with musl libc (which
would not appear until well after the code in question was
written). musl interprets POSIX timezone specifications with a DST
name but without rules as being DST year-round. tminit.c therefore
calculates both the DST and ST offsets as equal to the overall UTC
offset, which is applied twice when reporting times. Because musl
apparently disregards the top zoneinfo directory, even common
aliases such as PST8PDT return wildly inaccurate results (rather
than the one-hour errors that /bin/date would suggest on musl
systems).
The second assumption is broken at all times and places between a
historical timezone change and a DST shift or one year later,
whichever comes first. Although Pacific/Apia between 2011-12-31 and
2012-04-01 yields the most striking results, there are many other
examples.
Fixing these problems involves relatively minor changes to tminit.c
and related tests:
src/lib/libast/tm/tminit.c:
- tmlocal(): Save and restore the UTC offset and DST status of the
requested time; only break offset calculations for a DST shift.
- Rename the related one-letter variables for legibility.
src/cmd/ksh93/tests/attributes.sh:
- Use full POSIX timezone specifications in lieu of 'EST5EDT' and
'PST8PDT'. This is necessary because musl applies DST for the
entire year and the time of the test is ST.
src/cmd/ksh93/tests/printf.sh:
- Add several TZ tests to account for different combinations of DST
and ST designations across historical timezone changes. Also add
an EST5EDT test in DST.
In addition, although commit 580bc2d introduced a fix for looking
up times in zones with historical changes, it applied only to
searches using seconds from the epoch. Other lookup formats still
fail to change to contemporaneous zones. A convenient reproducer
is:
TZ=Europe/London printf "%(%s)T\n" "1970-01-01 01:00:00"
The output should be '0' and not '3600'.
src/lib/libast/tm/tmxdate.c:
- After running through tmxdate() for the first time, compare the
values of 'west', 'dst' and 'isdst' to ensure that a proper zone
has been chosen. If the zones differ, this means that a
historical change has occurred. Apply an offset and run again,
repeating as necessary.
src/cmd/ksh93/tests/printf.sh:
- Add 'reverse' tests looking up epoch seconds by date.
Co-authored-by: Martijn Dekker <martijn@inlv.org>
Resolves: #976
RE #976, 98800e9
Pre-u+m code in
tminit.csets the standard (ST) and daylight savings time (DST) offsets by checking times over the previous year of the time requested to compare DST status and offsets from UTC. If no offset change is detected, the DST offset is set equal to the ST offset.Although assigning the DST offset in this way is counter-intuitive, it does not result in incorrect times being reported so long as two implicit assumptions are met:
The first assumption fails on
muslLinux systems (which would not appear until well after the code in question was written).muslinterprets POSIX timezone specifications with a DST name but without rules as being DST year-round.tminit.ctherefore calculates both the DST and ST offsets as equal to the overall UTC offset, which is applied twice when reporting times. Becausemuslapparently disregards the topzoneinfodirectory, even common aliases such asPST8PDTreturn wildly inaccurate results (rather than the one-hour errors that/bin/datewould suggest onmuslsystems).The second assumption is broken at all times and places between a historical timezone change and a DST shift or one year later, whichever comes first. Although
Pacific/Apiabetween 2011-12-31 and 2012-04-01 yields the most striking results, there are many other examples.Fixing these problems involves relatively minor changes to
tminit.cand related tests:tminit.c, save and restore the UTC offset and DST status of the requested time; only break offset calculations for a DST shift.attributes.sh, use full POSIX timezone specifications in lieu ofEST5EDTandPST8PDT. This is necessary becausemuslapplies DST for the entire year and the time of the test is ST.printf.sh, add severalTZtests to account for different combinations of DST and ST designations across historical timezone changes. Also add anEST5EDTtest in DST.In addition, although 98800e9 introduced a fix for looking up times in zones with historical changes, it applied only to searches using seconds from the epoch. Other lookup formats still fail to change to contemporaneous zones. A convenient reproducer is:
The output should be
0and not3600.tmxdate.c, after running throughtmxdatefor the first time, compare the values ofwest,dstandisdstto ensure that a proper zone has been chosen. If the zones differ, this means that a historical change has occurred. Apply an offset and run again, repeating as necessary.printf.sh, add 'reverse' tests looking up epoch seconds by date.