Bug#1143499: America/Vancounter PDT change in tzdata breaks gmtime(3)

Aurelien Jarno <[email protected]> Mon, 3 Aug 2026 23:42:16 +0200
Newsgroups gmane.linux.debian.devel.glibc
Message-ID <anELMzC2svV94Az-__1210.99525583617$1785793532$gmane$org@aurel32.net>
--BFHkXHUpgSf7h6ol
Content-Type: text/plain; charset=us-ascii
Content-Disposition: inline
Content-Transfer-Encoding: quoted-printable

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

Hi,

On 2026-08-03 00:55, Mason Loring Bliss wrote:
> Package: tzdata
> Version: 2026b-0+deb13u1
> Severity: important
>=20
> 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,=20
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 =3D Sun Mar  8 01:59:59 2026 =
PST isdst=3D0 gmtoff=3D-28800
>  /etc/localtime  Sun Mar  8 10:00:00 2026 UT =3D Sun Mar  8 03:00:00 2026=
 PDT isdst=3D1 gmtoff=3D-25200
>  /etc/localtime  Sun Nov  1 08:59:59 2026 UT =3D Sun Nov  1 01:59:59 2026=
 PDT isdst=3D1 gmtoff=3D-25200
> -/etc/localtime  Sun Nov  1 09:00:00 2026 UT =3D Sun Nov  1 01:00:00 2026=
 PST isdst=3D0 gmtoff=3D-28800
> +/etc/localtime  Sun Nov  1 09:00:00 2026 UT =3D Sun Nov  1 02:00:00 2026=
 MST isdst=3D0 gmtoff=3D-25200
>=20
> 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:
>=20
> -------------------------------------------------------------------------=
--
> #include <stdio.h>
> #include <time.h>
>=20
> int main(int argc, char *argv[])
> {
>     time_t t;
>     struct tm *tmptr;
>     int tzdiff, tzdiff_hour, tzdiff_min;
>=20
>     time(&t);
>     tmptr =3D localtime(&t);
>=20
>     tzdiff =3D t - mktime(gmtime(&t));

The main issue is there. gmtime() returns a tm structure with tm_isdst=20
set to 0, which is not correct as the time (at least when executed=20
currently) includes a DST. With the previous tzdata version, the=20
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 o=
r 1
(the behaviour was not fully clear for earlier POSIX versions).

>     tzdiff_hour =3D (int)(tzdiff / 60);
>     tzdiff_min =3D tzdiff_hour % 60;
>     tzdiff_hour /=3D 60;
>=20
>     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 =3D localtime(&t);

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

    tzdiff_hour =3D (int)(tzdiff / 60);
    tzdiff_min =3D tzdiff_hour % 60;
    tzdiff_hour /=3D 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

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

--BFHkXHUpgSf7h6ol
Content-Type: application/pgp-signature; name=signature.asc

-----BEGIN PGP SIGNATURE-----

iQIzBAEBCgAdFiEEUryGlb40+QrX1Ay4E4jA+JnoM2sFAmpxCzMACgkQE4jA+Jno
M2sDEw//ez7rKc9tcbKrUnhdL811VtuW/OMMB81eq3WQJiFLr797r3cSvJB0onqg
0sqdShfrcfdZubmQV7FEQcvQ6bjq404issru/XexSY/WYY84gdBfB0GtV5YQfOlC
9AMg0XLX55b8X11TsjquW9xWUDSMrS2QrZiq9pzhtbrtNm/6N5VBBRCzYSqwbsdp
I0T7zgbyv/G09sAP2D47Sn7fLfvLxwCG/F/zIkIudSU3oxiQ0wmMrVUvxw9+fblo
8NI1Fjq5GWjpJHtlYS5i+a+WkbFaBvEBYP5p4AT4D88eBwek/+1F3CiP1C3pPEj2
0s9ka7eLdZv9vU3J6thtQ1BFe2Es4DRV0Qn52dX4aSDQ/WOJJOiEyVQyqjnyopDh
kiLs7Rwy2jM0WXu0bDivJSdH+3NcF6iv2+3Co9e6mUW1AEkq9biWzawbcmBdS6op
I1TLIWvS8+sHO8CSvxJnvgVsMZ5P6aBBiiZH/QWSsVSZL2yCMUwrSJHL9k47AEMX
0tKYVhg7P+Y5Cu/Dzk5howudEq6BqMgJdS0hqRooVspOysjGIVfs2oOCJI30B/g9
gX4rFbk5hJIoEgaj54qJIPHPC8kXJQGKrpb6ViI9r/8I1k9x3VzSCLwt3hAOWVCH
QMRTT0hUOwXIH07GA5qPLLjqFUbmnpZORzkplBqOOTdcqURXNGE=
=G+9X
-----END PGP SIGNATURE-----

--BFHkXHUpgSf7h6ol--