gmtime is not POSIX compliant due to leap seconds

Eitan Adler lists at eitanadler.com
Sun May 20 17:08:45 UTC 2018


Hi all,

FreeBSD's gmtime(3) appears to be (possibly intentionally) non-POSIX compliant.

Background:

in C (ISO/IEC 9899:201x): The gmtime function converts the calendar
time pointed to by timer into a broken- down time, expressed as UTC.
It is implementation defined as to if leap seconds or such are taken
into account.

in POSIX (EEE Std 1003.1-2017):

http://pubs.opengroup.org/onlinepubs/9699919799/functions/gmtime.html
The gmtime() function shall convert the time in seconds since the
Epoch pointed to by timer into a broken-down time, expressed as
Coordinated Universal Time (UTC).  The phrase "seconds since the
Epoch" is noted as
"As represented in seconds since the Epoch, each and every day shall
be accounted for by exactly 86400 seconds."

(see http://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap04.html#tag_04_16)

This means that gmtime ought not to be affected by leap seconds.

The issue:

On a POSIX-compliant system:

∴./gmtime-print 1234567899
year=109
mon=1
mday=13
hour=23
min=31
sec=39
wday=5
yday=43
dst=0
tzone=UTC
gmoff=0


On FreeBSD:
FreeBSD fasteagle 12.0-CURRENT FreeBSD 12.0-CURRENT #9
r333704M-4294967295: Thu May 17 05:01:04 UTC 2018
eax at fasteagle:/srv/obj/fbsd/usr/src/amd64.amd64/sys/EADLER  amd64

∴./gmtime-print 1234567899
year=109
mon=1
mday=13
hour=23
min=31
sec=15
wday=5
yday=43
dst=0
tzone=UTC
gmoff=0

Note the difference in "sec": 39 (correct) vs 15 (incorrect). The
difference is 39-15=24 which is the number of leap seconds until
Feburary 2009.   On both systems TZ is unset.

----


Some spelunking.

The relevant code is contributed, though I'm not sure what the current
upstream is. See head/contrib/tzcode/stdtime.

Following either the threaded or non-threaded logic does not change
the analysis :
                gmtsub(timep, 0L, &tm); vs
gmtsub(timep, 0L, p_tm);

Following gmstub:
        result = timesub(timep, offset, gmtptr, tmp);

The remainder of the code is for prettyprinting the timezone and thus
not relevant.

Following timesub we see that tzh_leapcnt is explicitly handled which
is defined as
tzfile.h:       char    tzh_leapcnt[4];         /* coded number of
leap seconds */

...

Interestingly the code appears to have accounted for this issue and implemented

time_t
time2posix(time_t t)
{
        tzset();
        return t - leapcorr(&t);
}

to help resolve the issue. We also have a man page for it
(time2posix). However, it isn't visible in any header I could:
∴grep -ir time2posix /usr/include

returns empty.



If all of this is correct I'd like to take some of the following
actions though I'm not sure what's best

(a) Modify the code to not take into account leap seconds at all. This
is preferred IMHO unless it will break things in ways I don't expect
(b) Make time2posix actually visible to applications (or at least,
correct the documentation of how to make it visible)
(c) semi-related: I can't find an upstream for this code, so move it
out of contrib and into our libc.

Thoughts?

(originally noticed by myself here https://bugs.python.org/issue33579;
addtl debugging done by others on the bug)
-- 
Eitan Adler


More information about the freebsd-standards mailing list