Bug 23859 - tzname, daylight are modified by localtime() in unexpected way
Summary: tzname, daylight are modified by localtime() in unexpected way
Status: RESOLVED INVALID
Alias: None
Product: glibc
Classification: Unclassified
Component: time (show other bugs)
Version: unspecified
: P2 normal
Target Milestone: ---
Assignee: Not yet assigned to anyone
URL:
Keywords:
Depends on:
Blocks:
 
Reported: 2018-11-04 17:38 UTC by Alexey Izbyshev
Modified: 2018-12-17 19:13 UTC (History)
2 users (show)

See Also:
Host:
Target:
Build:
Last reconfirmed:
fweimer: security-


Attachments
Demostrator of strange localtime() behavior (637 bytes, text/x-csrc)
2018-11-04 17:38 UTC, Alexey Izbyshev
Details

Note You need to log in before you can comment on or make changes to this bug.
Description Alexey Izbyshev 2018-11-04 17:38:58 UTC
Created attachment 11376 [details]
Demostrator of strange localtime() behavior

If I understand localtime() docs correctly, it should behave as if it calls tzset() internally. However, I've discovered that it rather behaves as if it unwinds time to the time_t it receives and only then calls tzset(). Moreover, after it does so, calling tzset() explicitly doesn't restore globals like 'tzname' to the expected state.

I've attached the test case which tests localtime() with two timezones, both of which are notable for changing their DST rules over time. The test sets TZ environment variable, but I've verified that behavior is the same if '/etc/localtime' is set appropriately and TZ is unset instead. The behavior is also the same on (e)glibc-2.19 from Ubuntu 14.04 as well as on the current trunk I've built myself (fc1c7bdc6). Below is the annotated output of the test case.

At start:
GMT GMT 0 0
=== Testing Europe/Moscow ===
After first tzset():
MSK MSD -10800 1 # Why MSD? There is no DST since 2011.
Roughly current time; no DST in MSK since 2011:
2018-11-1 dst: 0 tz: MSK
MSK MSK -10800 0 # Looks like what the first tzset() should have been done. Or should tzname[1] be empty?
Standard time, but DST is still observed in summer:
2009-11-1 dst: 0 tz: MSK
MSK MSD -10800 1 # Changed back to values correct for 2009.
DST:
2009-7-1 dst: 1 tz: MSD
MSK MSD -10800 1
Old TZ name for what is MSK now:
1991-7-1 dst: 1 tz: EEST
EET EEST -10800 1
After last tzset():
EET EEST -10800 1 # Even explicit tzset() doesn't restore the values
=== Testing America/Argentina/Buenos_Aires ===
After first tzset():
-03 -02 10800 1
No DST since 1994:
1995-11-1 dst: 0 tz: -03
-03 -03 10800 1
DST:
1990-11-1 dst: 1 tz: -02
-03 -02 10800 1
No DST:
1990-7-1 dst: 0 tz: -03
-03 -02 10800 1
Different UTC offset:
1960-7-1 dst: 1 tz: -03
-04 -03 10800 1 # OK, localtime() changes globals, but why is timezone still 10800 for -04?
After last tzset():
-04 -03 10800 1

I feel like either I totally misunderstood localtime() and tszet() docs or the output above demonstrates a bug or several. I'd expect that tzname, timezone and daylight stay the same between changes in TZ or /etc/localtime.
Comment 1 Alexey Izbyshev 2018-11-04 21:47:07 UTC
I've built my test with musl 1.1.16. The output is below. It matches my understanding of the correct localtime() behavior.

At start:
(null) (null) 0 0
=== Testing Europe/Moscow ===
After first tzset():
MSK  -10800 0
Roughly current time; no DST in MSK since 2011:
2018-11-1 dst: 0 tz: MSK
MSK  -10800 0
Standard time, but DST is still observed in summer:
2009-11-1 dst: 0 tz: MSK
MSK  -10800 0
DST:
2009-7-1 dst: 1 tz: MSD
MSK  -10800 0
Old TZ name for what is MSK now:
1991-7-1 dst: 1 tz: EEST
MSK  -10800 0
After last tzset():
MSK  -10800 0
=== Testing America/Argentina/Buenos_Aires ===
After first tzset():
-03  10800 0
No DST since 1994:
1995-11-1 dst: 0 tz: -03
-03  10800 0
DST:
1990-11-1 dst: 1 tz: -02
-03  10800 0
No DST:
1990-7-1 dst: 0 tz: -03
-03  10800 0
Different UTC offset:
1960-7-1 dst: 1 tz: -03
-03  10800 0
After last tzset():
-03  10800 0
Comment 2 Andreas Schwab 2018-11-13 13:44:13 UTC
If there is no DST then the value of tzname[1] is not used, thus doesn't matter.
Comment 3 Andreas Schwab 2018-11-13 13:53:54 UTC
The specification for daylight and timezone are ambigous for time zones with changing rules.  POSIX really only specifies the behaviour of POSIX style values for TZ, which cannot change over time.
Comment 4 eggert 2018-12-17 19:13:25 UTC
(In reply to Andreas Schwab from comment #3)
> POSIX really only specifies the behaviour of POSIX style
> values for TZ, which cannot change over time.

This is correct. Programs that want time zone abbreviations, UT offsets, and DST indications even in the presence of non-POSIX TZ settings should use the tm_zone, tm_gmtoff, and tm_isdst members of struct tm. These members are thread-safe whereas tzname, timezone, and daylight are not.

Unfortunately tm_zone and tm_gmtoff are not standardized by POSIX. But for strictly portable POSIX code there should be no problem, since such code shouldn't use non-POSIX TZ settings, and programs like Alexey's work fine when they stick to POSIX TZ settings.

For semi-portable code that runs on POSIX platforms but does the right thing even with non-POSIX TZ settings, Alexey can use strftime "%Z" for time zone abbreviations, subtract gmtime_r from localtime_r for UT offsets, and use tm_isdst to approximate the 'daylight' flag (the actual meaning of the 'daylight' flag is pretty much useless in this more-general context). Admittedly this is a hassle; if this is a problem for Alexey then I suggest lobbying POSIX to get the standard fixed to specify tm_zone and tm_gmtoff.

Closing the bug report as this is not a bug.