mktime() returns an incorrect value right after entering DST

264 views Asked by At

The following snippet code is from rtc.c in busybox-1.22.1.

In my case the utc is always 0, so this function is just doing a conversion from struct tm to time_t.

time_t FAST_FUNC rtc_tm2time(struct tm *ptm, int utc)
{
    //fprintf(stdout, "ptm->tm_hour: %d\n", ptm->tm_hour);
    char *oldtz = oldtz; /* for compiler */
    time_t t;

    if (utc) {
        oldtz = getenv("TZ");
        putenv((char*)"TZ=UTC0");
        tzset();
    }

    t = mktime(ptm); //problem here
    //struct tm* temp = localtime(&t);
    //fprintf(stdout, "temp->tm_hour: %d\n", temp->tm_hour);

    if (utc) {
        unsetenv("TZ");
        if (oldtz)
        {
            putenv(oldtz - 3);
        }
        tzset();
    }

    return t;
}

Also, there is a file /etc/TZ displaying timezone and DST information.

~ # cat /etc/TZ
LMT0:00LMT-1:00,M8.5.1/10,M12.5.1/10

Then, I set system time to 2021/8/30, 9:59:30 (30 seconds earlier than DST start date), and sync to hwclock.

date -s 2021.08.30-09:59:30 >/dev/null 2>/dev/null //set system time
hwclock -w //sync RTC to system time

Entering hwclock continuously while observing the output on CLI.

~ # hwclock
ptm->tm_hour : 9
temp->tm_hour : 9
Mon Aug 30 09:59:58 2021  0.000000 seconds

~ # hwclock
ptm->tm_hour : 10
temp->tm_hour : 11 //why not 10?
Mon Aug 30 11:00:00 2021  0.000000 seconds

Why the return value from mktime is added by 1 when entering DST? Shouldn't it be affected by DST?

1

There are 1 answers

0
n0741337 On

According to the mktime() man pages, mktime() is allowed to update the tm_isdst value. The starting value may cause the mktime() algorithm to branch differently:

The value specified in the tm_isdst field informs mktime() whether or not 
daylight saving time (DST) is in effect for the time supplied in the tm 
structure: a positive value means DST is in effect; zero means that DST is not in 
effect; and a negative value means that mktime() should (use timezone information 
and system databases to) attempt to determine whether DST is in effect at the 
specified time.

and then update the value of tm_isdst accordingly:

tm_isdst is set (regardless of its initial value) to a positive value or to 0, 
respectively, to indicate whether DST is or is not in effect at the specified time.

In other words, I'd check the tm_isdst value before and after the mktime() call.

One way I've dealt with this in the past is to

  • call mktime() with a known value of tm_isdst (e.g. zero or one)
  • call localtime() on the returned time_t value, then check tm_isdst on the struct tm pointer localtime() returns.
  • if the tm_isdst has been changed from the prior known value, change the original struct tm to use the new tm_isdt value then call mktime() again with it before trusting the time_t it returns.

It's definitely less efficient but it's possible to know when the DST change occurs by expecting it and checking for it.

Another option would be to set tm_isdst to -1 before calling mktime() and trusting its lookup of timezone and set tm_isdst appropriately.