Search code examples
ctimeposixunix-timestamputc

Will `gmtime()` report seconds as 60 when in a leap second?


I have a server running in TZ=UTC and I have code like this:

time_t t = time(NULL);
struct tm tm;
gmtime_r(&t, &tm);

The question is will tm.tm_sec == 60 when the server is within a leap second?

For example, if I were in the following time span:

1998-12-31T23:59:60.00 - 915 148 800.00
1998-12-31T23:59:60.25 - 915 148 800.25
1998-12-31T23:59:60.50 - 915 148 800.50
1998-12-31T23:59:60.75 - 915 148 800.75
1999-01-01T00:00:00.00 - 915 148 800.00

would gmtime() return tm == 1998-12-31T23:59:60 for time_t = 915148800 and, once out of the leap second, return tm == 1999-01-01T00:00:00 for the same time_t?


Solution

  • The short answer is, no, practically speaking gmtime_r will never fill in tm_sec with 60. This is unfortunate, but unavoidable.

    The fundamental problem is that time_t is, per the Posix standard, a count of seconds since 1970-01-01 UTC assuming no leap seconds.

    During the most recent leap second, the progression was like this:

    1483228799    2016-12-31 23:59:59
    1483228800    2017-01-01 00:00:00
    

    Yes, there should have been a leap second, 23:59:60, in there. But there's no possible time_t value in between 1483228799 and 1483228800.

    I know of two ways for a gmtime variant to return a time ending in :60:

    1. You can run your OS clock on something other than UTC, typically TAI or TAI-10, and use the so-called "right" timezones to convert to UTC (or local time) for display. See this web page for some discussion on this.

    2. You can use clock_gettime() and define a new clkid value, perhaps CLOCK_UTC, which gets around the time_t problem by using deliberately nonnormalized struct timespec values when necessary. For example, the way to get a time value in between 1483228799 and 1483228800 is to set tv_sec to 1483228799 and tv_nsec to 1000000000. See this web page for more details.

    Way #1 works pretty well, but nobody uses it because nobody wants to run their kernel clock on anything other than the UTC it's supposed to be. (You end up having problems with things like filesystem timestamps, and programs like tar that embed those timestamps.)

    Way #2 is a beautiful idea, IMO, but to my knowledge it has never been implemented in a released OS. (As it happens, I have a working implementation for Linux, but I haven't released my work yet.) For way #2 to work, you need a new gmtime variant, perhaps gmtime_ts_r, which accepts a struct timespec instead of a time_t.


    Addendum: I just reread your question title. You asked, "Will gmtime() report 60 for seconds when the server is on a Leap Second?" We could answer that by saying "yes, but", with the disclaimer that since most servers can't represent time during a leap second properly, they're never "on" a leap second.


    Addendum 2: I forgot to mention that scheme #1 seems to work better for local times -- that is, when you're calling one of the localtime variants -- than for UTC times and gmtime. Clearly the conversions performed by localtime are affected by the setting of the TZ environment variable, but it's not so clear that TZ has any effect on gmtime. I've observed that some gmtime implementations are influenced by TZ and can therefore do leap seconds in accordance with the "right" zones, and some cannot. In particular, the gmtime in GNU glibc seems to pay attention to the leap second information in a "right" zone if TZ specifies one, whereas the gmtime in the IANA tzcode distribution does not.