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--