control: reassign -1 libc6/2.36-9
control: retitle -1 mktime() heuristics fail with the America/Vancouver change
control: severity -1 normal

Hi,

On 2026-08-03 00:55, Mason Loring Bliss wrote:
> Package: tzdata
> Version: 2026b-0+deb13u1
> Severity: important
> 
> There's an issue with upstream tzdata 2026b that breaks date calculations
> with America/Vancouver. Observed in mailx, but there are probably other
> programs that do it. I haven't dug up just what's happening yet, but I'm
> working on that. In the meantime, what I see is that gmtime(3) returns the
> wrong data now if you're in America/Vancouver. As nothing should have
> changed for now (July) gmtime(3) should return the same data for today -
> the change takes effect in November:

The change is actually causing the mktime() heuristics in glibc to fail, 
it's not linked to gmtime(3). But the code you submitted is also buggy.

> /etc/localtime  Sun Mar  8 09:59:59 2026 UT = Sun Mar  8 01:59:59 2026 PST 
> isdst=0 gmtoff=-28800
>  /etc/localtime  Sun Mar  8 10:00:00 2026 UT = Sun Mar  8 03:00:00 2026 PDT 
> isdst=1 gmtoff=-25200
>  /etc/localtime  Sun Nov  1 08:59:59 2026 UT = Sun Nov  1 01:59:59 2026 PDT 
> isdst=1 gmtoff=-25200
> -/etc/localtime  Sun Nov  1 09:00:00 2026 UT = Sun Nov  1 01:00:00 2026 PST 
> isdst=0 gmtoff=-28800
> +/etc/localtime  Sun Nov  1 09:00:00 2026 UT = Sun Nov  1 02:00:00 2026 MST 
> isdst=0 gmtoff=-25200
> 
> As it stands, mailx will issue the wrong time offset, which can be
> problematic for things. An observed example involved monitoring emails
> discarded because they were assumed to be over an hour old. The following
> short program models what mailx does to calculate the date offset:
> 
> ---------------------------------------------------------------------------
> #include <stdio.h>
> #include <time.h>
> 
> int main(int argc, char *argv[])
> {
>     time_t t;
>     struct tm *tmptr;
>     int tzdiff, tzdiff_hour, tzdiff_min;
> 
>     time(&t);
>     tmptr = localtime(&t);
> 
>     tzdiff = t - mktime(gmtime(&t));

The main issue is there. gmtime() returns a tm structure with tm_isdst 
set to 0, which is not correct as the time (at least when executed 
currently) includes a DST. With the previous tzdata version, the 
mktime heuristics were able to fix it, but it is not the case anymore for
dates after 2026-06-06). It is not clear yet why.

The correct way is to force tm_isdst to -1, which ensure that the DST is
determined by mktime(). This will also work correctly with the POSIX 2024
version of mktime, which forbids applying any heuristics if tm_isdst is 0 or 1
(the behaviour was not fully clear for earlier POSIX versions).

>     tzdiff_hour = (int)(tzdiff / 60);
>     tzdiff_min = tzdiff_hour % 60;
>     tzdiff_hour /= 60;
> 
>     if (tmptr->tm_isdst > 0)
>         tzdiff_hour++;

With the above change, this should not be needed anymore. Anyway, this is
dangerous as tmptr got overwritten by the call to gmtime().

>     printf("t (epoch seconds) is %d\n", t);
>     printf("mktime(gmtime(&t)) is %d\n", mktime(gmtime(&t)));
>     printf("t - mktime(gmtime(&t)) is %d\n", tzdiff);
>     printf("tzdiff_hour (that / 3600) is %d\n", tzdiff_hour);
>     printf("tm_isdst is %d\n", tmptr->tm_isdst);
>     printf("%+05d\n", tzdiff_hour * 100 + tzdiff_min);
> }

The following updated code should work in all cases:

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

int main(int argc, char *argv[])
{
    time_t t;
    struct tm *tmptr;
    struct tm *gmtr;
    int tzdiff, tzdiff_hour, tzdiff_min;

    time(&t);
    tmptr = localtime(&t);

    gmtr = gmtime(&t);
    gmtr->tm_isdst = -1;
    tzdiff = t - mktime(gmtr);

    tzdiff_hour = (int)(tzdiff / 60);
    tzdiff_min = tzdiff_hour % 60;
    tzdiff_hour /= 60;

    printf("t (epoch seconds) is %d\n", t);
    printf("mktime(gmtime(&t)) is %d\n", mktime(gmtime(&t)));
    printf("t - mktime(gmtime(&t)) is %d\n", tzdiff);
    printf("tzdiff_hour (that / 3600) is %d\n", tzdiff_hour);
    printf("tm_isdst is %d\n", tmptr->tm_isdst);
    printf("%+05d\n", tzdiff_hour * 100 + tzdiff_min);
}

Regards
Aurelien

-- 
Aurelien Jarno                          GPG: 4096R/1DDD8C9B
[email protected]                     http://aurel32.net

Attachment: signature.asc
Description: PGP signature

Reply via email to