public inbox for libc-alpha@sourceware.org
 help / color / mirror / Atom feed
* mktime result may depend on previous calls
@ 2023-01-18 15:39 Max Nikulin
  2023-01-18 22:12 ` Paul Eggert
  0 siblings, 1 reply; 6+ messages in thread
From: Max Nikulin @ 2023-01-18 15:39 UTC (permalink / raw)
  To: libc-alpha

Hi,

I have noticed that the mktime function may return different values for 
the same argument. More precisely, result may depend on previous calls.

I am aware that POSIX underspecifies behavior of mktime in the case of 
backward jump of local time (and that is impossible to disambiguate if 
the value before or after time transition should be used when DST is not 
changed), but I am still surprised. Is it intended that hidden state 
affects returned value?

The following output is obtained for glibc-2.31. I am sorry for the 
noise if it has changed in the development version.

The following time transition keeps isdst=0:

zdump -v Africa/Juba | grep 2021
Africa/Juba  Sun Jan 31 20:59:59 2021 UT = Sun Jan 31 23:59:59 2021 EAT 
isdst=0 gmtoff=10800
Africa/Juba  Sun Jan 31 21:00:00 2021 UT = Sun Jan 31 23:00:00 2021 CAT 
isdst=0 gmtoff=7200

Notice that same input on lines 4 and 10 causes different conversion 
result 5-6 and 11-12

      1	input:          +121!00-31 22:00:00 dst:-1 w:0  j:0
      2	     1612119600 +121!00-31 22:00:00 dst:0  w:0  j:30
      3	strptime:       2021-01-31 22:00:00 +0300 EAT

      4	input:          +121!00-31 23:30:00 dst:-1 w:0  j:0
      5	     1612125000 +121!00-31 23:30:00 dst:0  w:0  j:30
      6	strptime:       2021-01-31 23:30:00 +0300 EAT

      7	input:          +121!01-01 01:00:00 dst:-1 w:0  j:0
      8	     1612134000 +121!01-01 01:00:00 dst:0  w:1  j:31
      9	strptime:       2021-02-01 01:00:00 +0200 CAT

     10	input:          +121!00-31 23:30:00 dst:-1 w:0  j:0
     11	     1612128600 +121!00-31 23:30:00 dst:0  w:0  j:30
     12	strptime:       2021-01-31 23:30:00 +0200 CAT

I use the following script and C code to test mktime:

TZ=Africa/Juba ./mktime-state <<EOF | nl
2021  1 31 22  0  0 -1
2021  1 31 23 30  0 -1
2021  2  1  1  0  0 -1
2021  1 31 23 30  0 -1
EOF

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

void print_tm(const struct tm *bt) {
	printf(
		"%+04d!%02d-%02d %02d:%02d:%02d dst:%-2d w:%-2d j:%-3d",
		bt->tm_year, bt->tm_mon, bt->tm_mday,
		bt->tm_hour, bt->tm_min, bt->tm_sec,
		bt->tm_isdst, bt->tm_wday, bt->tm_yday);
}

int main() {
	struct tm bt;
	char buf[64];
	while (1) {
		memset(&bt, 0, sizeof(bt));
		bt.tm_isdst = -1;
		if (7 != scanf(
			"%d%d%d%d%d%d%d",
			&bt.tm_year, &bt.tm_mon, &bt.tm_mday,
			&bt.tm_hour, &bt.tm_min, &bt.tm_sec, &bt.tm_isdst)
		) {
			break;
		}
		bt.tm_year -= 1900;
		bt.tm_mon -= 1;
		printf("%-15s ", "input:");
		print_tm(&bt);
		putchar('\n');
		errno = 0;
		time_t ts = mktime(&bt);
		if (ts == -1 && errno != 0) {
			printf("error: %s", strerror(errno));
			continue;
		}
		printf("%15zd ", ts);
		print_tm(&bt);
		putchar('\n');
		strftime(buf, sizeof(buf) - 1, "%F %T %z %Z", &bt);
		printf("%-15s %s\n\n", "strptime:", buf);
	}
	return 0;
}

At first I experimented with another cases, a usual DST transition and

Europe/Kyiv  Sat Jun 30 21:59:59 1990 UT = Sun Jul  1 01:59:59 1990 MSD 
isdst=1 gmtoff=14400
Europe/Kyiv  Sat Jun 30 22:00:00 1990 UT = Sun Jul  1 01:00:00 1990 EEST 
isdst=1 gmtoff=10800

At some moment I was sure that mktime return value depends on tm_isdst 
of its argument. Later I have realized that I got different results due 
to other calls.

A more funny result is hysteresis loop when time is increased and then 
decreased in steps around time transition moment:
https://list.orgmode.org/tq6lg6$9m2$1@ciao.gmane.io/2-mktime-hyst.png


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: mktime result may depend on previous calls
  2023-01-18 15:39 mktime result may depend on previous calls Max Nikulin
@ 2023-01-18 22:12 ` Paul Eggert
  2023-01-19  3:00   ` Max Nikulin
  0 siblings, 1 reply; 6+ messages in thread
From: Paul Eggert @ 2023-01-18 22:12 UTC (permalink / raw)
  To: Max Nikulin; +Cc: libc-alpha

On 1/18/23 07:39, Max Nikulin via Libc-alpha wrote:
> Is it intended that hidden state affects returned value?

I intended for it, yes. The behavior is allowed by POSIX and by the GNU 
spec, and it helps performance in the typical case.

If there's a faster way to implement mktime that doesn't involve a 
cache, that might be a good thing to implement.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: mktime result may depend on previous calls
  2023-01-18 22:12 ` Paul Eggert
@ 2023-01-19  3:00   ` Max Nikulin
  2023-01-19  4:34     ` Paul Eggert
  0 siblings, 1 reply; 6+ messages in thread
From: Max Nikulin @ 2023-01-19  3:00 UTC (permalink / raw)
  To: libc-alpha

On 19/01/2023 05:12, Paul Eggert wrote:
> On 1/18/23 07:39, Max Nikulin via Libc-alpha wrote:
>> Is it intended that hidden state affects returned value?
> 
> I intended for it, yes. The behavior is allowed by POSIX and by the GNU 
> spec, and it helps performance in the typical case.

Paul, thank you for clarification. Should some note be added to the docs?

---- >8 ----
Notice that in the case of ambiguous local time (around a transition 
with backward step) multiple mktime calls with the same argument may 
return both values. Such kind of uncertainty appears if tm_isdst is set 
to -1 or if local time changes keeping the same DST state.
---- 8< ----

It even might be used as a non-portable way of disambiguation: just add 
an extra call of mktime well aside from the backward time step. Too 
weird to use it in practice though. Unfortunately POSIX does not allow 
anything like

https://www.python.org/dev/peps/pep-0495/
PEP 495 – Local Time Disambiguation (Python)

> If there's a faster way to implement mktime that doesn't involve a 
> cache, that might be a good thing to implement.

I do not mind the cache. There is arbitrary choice to which time moment 
it is valid. However I agree that any changes may cause negative 
performance impact.


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: mktime result may depend on previous calls
  2023-01-19  3:00   ` Max Nikulin
@ 2023-01-19  4:34     ` Paul Eggert
  2023-01-19  7:10       ` Max Nikulin
  0 siblings, 1 reply; 6+ messages in thread
From: Paul Eggert @ 2023-01-19  4:34 UTC (permalink / raw)
  To: Max Nikulin, libc-alpha

On 1/18/23 19:00, Max Nikulin via Libc-alpha wrote:

> Paul, thank you for clarification. Should some note be added to the docs?

The actual behavior is reasonably complicated. As a time nerd I'm 
probably the wrong person to ask about whether it should be documented.

> 
> ---- >8 ----
> Notice that in the case of ambiguous local time (around a transition 
> with backward step) multiple mktime calls with the same argument may 
> return both values. Such kind of uncertainty appears if tm_isdst is set 
> to -1 or if local time changes keeping the same DST state.
> ---- 8< ----

The above wording isn't quite right, as it ignores the role of tm_gmtoff 
in mktime. (Did I say it was complicated? :-)


> It even might be used as a non-portable way of disambiguation: just add 
> an extra call of mktime well aside from the backward time step. Too 
> weird to use it in practice though. Unfortunately POSIX does not allow 
> anything like
> 
> https://www.python.org/dev/peps/pep-0495/
> PEP 495 – Local Time Disambiguation (Python)

POSIX does allow that sort of thing as an extension, and glibc mktime 
does so: it disambiguates via tm_gmtoff.

^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: mktime result may depend on previous calls
  2023-01-19  4:34     ` Paul Eggert
@ 2023-01-19  7:10       ` Max Nikulin
  2023-01-20  9:21         ` Paul Eggert
  0 siblings, 1 reply; 6+ messages in thread
From: Max Nikulin @ 2023-01-19  7:10 UTC (permalink / raw)
  To: libc-alpha

On 19/01/2023 11:34, Paul Eggert wrote:
> On 1/18/23 19:00, Max Nikulin wrote:
> 
>> Notice that in the case of ambiguous local time (around a transition 
>> with backward step) multiple mktime calls with the same argument may 
>> return both values. Such kind of uncertainty appears if tm_isdst is 
>> set to -1 or if local time changes keeping the same DST state.
> 
> The above wording isn't quite right, as it ignores the role of tm_gmtoff 
> in mktime. (Did I say it was complicated? :-)
...
> POSIX does allow that sort of thing as an extension, and glibc mktime 
> does so: it disambiguates via tm_gmtoff.

Has it been fixed recently?

https://www.gnu.org/software/libc/manual/html_node/Broken_002ddown-Time.html#index-mktime

> The mktime function *ignores* the specified contents of the tm_wday,
> tm_yday, *tm_gmtoff*, and tm_zone members of the broken-down time structure

 From the example below I would expect 1612128600 +0200 CAT gmtoff=7200 
on third line, but my glibc is dated

      1612119600 2021-01-31 22:00:00 +0300 EAT gmtoff=10800
      1612125000 2021-01-31 23:30:00 +0300 EAT gmtoff=10800
      1612125000 2021-01-31 23:30:00 +0300 EAT gmtoff=10800


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

void bt_init(struct tm *bt) {
	memset(bt, 0, sizeof(*bt));
	bt->tm_isdst = -1;
	bt->tm_year = 2021 - 1900;
	bt->tm_mon = 1 - 1;
	bt->tm_mday = 31;
	bt->tm_hour = 23;
	bt->tm_min = 30;
	bt->tm_sec = 0;
}

void bt_print(struct tm *bt) {
	char buf[64];
	time_t ts = mktime(bt);
	strftime(buf, sizeof(buf) - 1, "%F %T %z %Z", bt);
	printf("%15zd %s gmtoff=%ld\n", ts, buf, bt->tm_gmtoff);
}

int main() {
	struct tm bt;
	putenv("TZ=Africa/Juba");
	// Fix cached offset to the value before time jump
	bt_init(&bt);
	bt.tm_hour = 22;
	bt.tm_min = 0;
	// Default conversion
	bt_print(&bt);
	bt_init(&bt);
	bt_print(&bt);
	// Attempt to choose +0200 CAT instead of +0300 EAT
	bt_init(&bt);
	bt.tm_gmtoff = 2*3600;
	bt_print(&bt);
	return 0;
}


^ permalink raw reply	[flat|nested] 6+ messages in thread

* Re: mktime result may depend on previous calls
  2023-01-19  7:10       ` Max Nikulin
@ 2023-01-20  9:21         ` Paul Eggert
  0 siblings, 0 replies; 6+ messages in thread
From: Paul Eggert @ 2023-01-20  9:21 UTC (permalink / raw)
  To: Max Nikulin, libc-alpha

On 2023-01-18 23:10, Max Nikulin via Libc-alpha wrote:
> Has it been fixed recently?
> 
> https://www.gnu.org/software/libc/manual/html_node/Broken_002ddown-Time.html#index-mktime

Oops, looks like I was mistaken. I was thinking of the reference 
implementation of mktime in TZDB, not glibc mktime. The latter should be 
fixed to behave more like the former, and to use tm_gmtoff to 
disambiguate in the rare dicey cases.

^ permalink raw reply	[flat|nested] 6+ messages in thread

end of thread, other threads:[~2023-01-20  9:21 UTC | newest]

Thread overview: 6+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-01-18 15:39 mktime result may depend on previous calls Max Nikulin
2023-01-18 22:12 ` Paul Eggert
2023-01-19  3:00   ` Max Nikulin
2023-01-19  4:34     ` Paul Eggert
2023-01-19  7:10       ` Max Nikulin
2023-01-20  9:21         ` Paul Eggert

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).