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
signature.asc
Description: PGP signature

