[tz] question about mktime_tzname()

Kees Dekker Kees.Dekker at infor.com
Thu Jan 5 14:03:25 UTC 2017


I'm running into a potential bug. Please also see the attached C-source file for a reproduction.

Reproduction steps:

1.       Build tz code, in such way that a libtz.a exists

2.       Compile attached source with (e.g. on Linux): gcc -g -o tztestcase2016j tztestcase.c -L <tzlibdir> -ltz

3.       Checking the same test case source code with a system library (instead of IANA libtz.a) can be achieved by omitting -L and -l flags in previous command (see #2).

It does not make much sense which version of tzcode is used, I tried 2013i and 2016j and both show the same behavior.

The mktime() code returns -3600, while I expect 0. Because the TZ zone is explicitly specified, EST5EDT is GMT-5. So the provided time stamp (31-Jan-1969, 19:00) is exactly the same as UTC value 0 (1-Jan-1970, 00:00 GMT). Both Linux and Windows with system libraries return 0 in this specific case (I used these for reference).

The remainder of this email is based on the tzcode2016j source code (e.g. for used line numbers).

The provided TZ value is parsed in tzparse() and results in sp->ttis[0] set as DST value, and sp->ttis[1] as standard time value. This is at least contrary to in the else part of the if (*name == ',' || *name == ';') block (there DST is saved in sp->ttis[1]). Just grep on init_ttinfo() calls in localtime.c. However, in localsub(), around line 1428, the i becomes sp->defaulttype (which is zero), i.e. points to the DST value of sp->ttis[], which is assigned to ttisp. As result, because t is below sp->ats[0], DST takes into effect, which is incorrect, as everything after the last Sunday in October and before the first Sunday in April (see attachment, TZ="EST5EDT,M4.1.0/02:00,M10.5.0/02:00") is assumed to be standard time.

I'm not sure whether I draw the right conclusion, but because the incorrect assumption of using DST, mktime() returns -3600 (on hour off) instead of the expected 0. Is this conclusion correct?

I'm not sure how to fix it. When changing the call to init_ttinfo() and effectively swapping DST and standard time (also for symmetry with the else part) in sp-ttis[] (0 to 1 and 1 to 0) + keeping sp->defaulttype = 0 solved this problem, but introduces other conversion errors around DSTchanges (I don't have yet an reproduction, but can make one if needed). The sp->default type is used (in localsub ()) when if (sp->timecnt == 0 || t < sp->ats[0]). The other approach is to assign sp->defaulttype = 1 when the init_ttinfo() calls are not swapped. Anyhow, there is a tight relationship between the value of sp->defaulttype and the order in which DST/standard times are written in sp->ttis[].

If the previous idea is incorrect (and I guess it is), additional code may be needed in time2sub() if yourtm.tm_isdst == -1. In that case, the provided data in yourtm should be checked whether DST is (or is not) into effect. The found mytm struct should match. Otherwise a DST correction is needed (one-hour shift). The current code mistakenly uses DST, and that is why (if I understand all well) mktime() now returns -3600.

If I was not able to explain the issue correctly, please let me know. I'm not sure to understand what's going wrong now, but may be, the provided attachment will help in giving better understanding. Because the provided date is near 1970, quite few steps are needed to see what's happing in localtime.c.

Kees Dekker

PS. This email has been sent assuming that attachments will not get lost. Otherwise, just copy paste the code below and save to e.g. tztestcase.c.

#ifdef _WIN32

#include <time.h>
#include <stdio.h>
#include <stdlib.h>

int main(void)
                int ret = 0;
                const char *months[] = { "Jan", "Feb", "Mar", "April", "May", "June", "July", "Aug", "Sep", "Oct", "Nov", "Dec" };
                const char *days[] = { "Sun", "Mon", "Tue", "Wed", "Tur", "Fri", "Sat" };

                /* DST switch in April in first Sunday and October, last Sunday */

                /* January, 1970, invalid input for tm_mday (0 instead 1..31) */
                /* non-normalized input for minutes: 1140/60 = 19, so 19:00 EST5EDT time, which is 5 hours before GMT, and should result in t == 0 below */
                /* let system figure out whether DST is effect */
                struct tm tmp = { 0, 1140, 0, 0, 0, 70, -858993460, -858993460, -1};

                printf("enter mktime with: %04d/%s/%02d %02d:%02d:%02d, week day: %d, year day: %d, dst: %d\n",
                                                                tmp.tm_year + 1900, months[tmp.tm_mon], tmp.tm_mday,
                                                                tmp.tm_hour, tmp.tm_min, tmp.tm_sec,
                                                                tmp.tm_wday, tmp.tm_yday, tmp.tm_isdst);

                time_t t = mktime(&tmp);

                /* expect: t == 0, DST is off, time is exactly at the boundary of lowest possible UTC value */
                printf("the return value of mktime is: %lld\n", (long long)t);
                printf("normalized times: %04d/%s/%02d %02d:%02d:%02d, week day: %s, year day: %d, dst: %d\n",
                                                                tmp.tm_year + 1900, months[tmp.tm_mon], tmp.tm_mday,
                                                                tmp.tm_hour, tmp.tm_min, tmp.tm_sec,
                                                                days[tmp.tm_wday], tmp.tm_yday, tmp.tm_isdst);

                if (t >= 0) {
                                printf("tz=%s, %s\n", tzname[0], tzname[1]);

                return 0;
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mm.icann.org/pipermail/tz/attachments/20170105/d66bd990/attachment.html>
-------------- next part --------------
An embedded and charset-unspecified text was scrubbed...
Name: tztestcase.c
URL: <http://mm.icann.org/pipermail/tz/attachments/20170105/d66bd990/tztestcase.c>

More information about the tz mailing list