* Observation of hysteresis in a GNU libc time conversion function @ 2023-01-17 17:22 Max Nikulin 2023-01-18 9:54 ` Ihor Radchenko 0 siblings, 1 reply; 5+ messages in thread From: Max Nikulin @ 2023-01-17 17:22 UTC (permalink / raw) To: emacs-orgmode [-- Attachment #1: Type: text/plain, Size: 4131 bytes --] #+title: Observation of hysteresis in a GNU libc time conversion function #+begin_abstract The ~mktime~ function in GNU libc for specific arguments may possess properties similar to ferromagnetic materials. Dependence of returned value on arguments passed during earler calls gives evidences that it has hidden state. #+end_abstract I was experimenting with a code sample posted to the thread on support of time zones in Org. I noticed rather strange behavior of ~encode-time~ and thus underlying ~mktime~ function. At first I considered it as non-deterministic, but later I realized that time zone offset calculated earlier may affect following calls. I was aware that the function is not pure since it calls ~tzset~ to initialize time zone from the ~TZ~ environment variable and e.g. ~tzname~ global variable. It was a surprise that the function can not be considered even as a stable one. Passing same arguments may lead to different results. I am unsure whether it is intentional or at least known property. #+begin_src elisp :exports nil :results silent (require 'ob-shell) (require 'ob-gnuplot) #+end_src Let's take some backward time transition, so with ambiguous local time, where daylight saving time state is not changed and preferably is not in action. As a result it is impossible to disambiguate whether local time is before or after time jump. Attempt to specify ~dst~ as ~t~ (or set ~t.tm_isdst = 1~ for ~mktime~) will lead to an error. #+begin_src sh :results verbatim :exports both zdump -v Africa/Juba | grep 2021 #+end_src #+RESULTS: : 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 We may convert broken down local time representation around 23:30 local time for a sequence of time moments passing values to ~encode-time~ in increasing and decreasing their order. Under the hood the function calls the =mktime(3)= function. #+name: timestamp #+header: :exports code #+begin_src elisp :var tz="Africa/Juba" :var t0='(0 30 23 31 1 2021) (let* ((f (lambda (minutes) (float-time (encode-time (list (nth 0 t0) (+ (nth 1 t0) minutes) (nth 2 t0) (nth 3 t0) (nth 4 t0) (nth 5 t0) nil -1 tz))))) (dt '(-90 -60 -31 -30 -29 -15 0 15 29 30 31 60 90)) (values (mapcar (lambda (m) (cons m (funcall f m))) dt)) (ts0 (cdr (nth (/ (length values) 2) values)))) (mapcar (lambda (pair) (let ((m (car pair))) (list m (- (cdr pair) ts0) (- (funcall f m) ts0)))) (reverse values))) #+end_src #+RESULTS: timestamp | 90 | 9000.0 | 9000.0 | | 60 | 7200.0 | 7200.0 | | 31 | 5460.0 | 5460.0 | | 30 | 5400.0 | 5400.0 | | 29 | 1740.0 | 5340.0 | | 15 | 900.0 | 4500.0 | | 0 | 0.0 | 3600.0 | | -15 | -900.0 | 2700.0 | | -29 | -1740.0 | 1860.0 | | -30 | -1800.0 | 1800.0 | | -31 | -1860.0 | -1860.0 | | -60 | -3600.0 | -3600.0 | | -90 | -5400.0 | -5400.0 | #+begin_src gnuplot :file mktime-hyst.png :var data=timestamp set key bottom right set xlabel 'minutes' set ylabel 'UNIX epoch difference, seconds' set title 'Hysteresis in GNU libc mktime' plot data using 1:2 with lp title 'increasing', \ '' using 1:3 with lp title 'decreasing' #+end_src #+RESULTS: [[file:mktime-hyst.png]] So result for the same arguments may depend on previous calls. Likely it is due to a [[https://sourceware.org/git/?p=glibc.git;a=blob;f=time/mktime.c;h=94a4320e6ca9d935fc534991f9b57a2f1cc185de;hb=HEAD#l544][static variable]] used in the =mktime.c= file. Such a variable appeared in the commit [[https://sourceware.org/git/?p=glibc.git;a=commit;h=80fd73873b][80fd73873b]] #+begin_example Fri Sep 29 03:43:51 1995 Paul Eggert Rewrite mktime from scratch for performance, and for correctness in the presence of leap seconds. #+end_example Perhaps hidden internal state may be used to disambiguate local time values around backward time steps with unchanged daylight saving time. Unfortunately relying on such implementation details hardly can be considered as a robust approach. [-- Attachment #2: mktime-hyst.png --] [-- Type: image/png, Size: 7420 bytes --] ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: Observation of hysteresis in a GNU libc time conversion function 2023-01-17 17:22 Observation of hysteresis in a GNU libc time conversion function Max Nikulin @ 2023-01-18 9:54 ` Ihor Radchenko 2023-01-18 16:32 ` Max Nikulin 0 siblings, 1 reply; 5+ messages in thread From: Ihor Radchenko @ 2023-01-18 9:54 UTC (permalink / raw) To: Max Nikulin; +Cc: emacs-orgmode Max Nikulin <manikulin@gmail.com> writes: > (dt '(-90 -60 -31 -30 -29 -15 0 15 29 30 31 60 90)) This is problematic. You are putting MINUTES out of normal range. Looking at https://codecogs.com/library/computing/c/time.h/ctime.php?alias=mktime, out-of-range minutes are not documented. Also, see https://stackoverflow.com/questions/20104531/weird-mktime-logic-with-negative-seconds Using out-of-range time fields is not advised. -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: Observation of hysteresis in a GNU libc time conversion function 2023-01-18 9:54 ` Ihor Radchenko @ 2023-01-18 16:32 ` Max Nikulin 2023-01-18 16:38 ` Max Nikulin 2023-01-19 10:31 ` Ihor Radchenko 0 siblings, 2 replies; 5+ messages in thread From: Max Nikulin @ 2023-01-18 16:32 UTC (permalink / raw) To: emacs-orgmode On 18/01/2023 16:54, Ihor Radchenko wrote: > Max Nikulin writes: > >> (dt '(-90 -60 -31 -30 -29 -15 0 15 29 30 31 60 90)) > > This is problematic. You are putting MINUTES out of normal range. Actually after some experiments and surprising results I figured out what really happens. I modified your example to get the value that you was likely expecting: (let* ((tz "Europe/Berlin") (t1 (encode-time `(0 1 3 29 10 2023 nil -1 ,tz))) ;; !!! Try to comment out the line below (_ (encode-time `(0 59 1 29 10 2023 nil -1 ,tz))) (t2 (encode-time `(0 59 2 29 10 2023 nil -1 ,tz)))) (list (format-time-string "%F %T %z %Z" t1 tz) (format-time-string "%F %T %z %Z" t2 tz) (time-subtract t1 t2))) ("2023-10-29 03:01:00 +0100 CET" "2023-10-29 02:59:00 +0200 CEST" 3720) No negative minutes were involved. I decided that hysteresis example is more funny. Frankly speaking, I just forgot that I may use (make-decoded-time :minute -40) and `decoded-time-add' since I was not limited by Emacs-26 support. `encode-time' docstring for Emacs-26 has the following statement: > Out-of-range values for SECOND, MINUTE, HOUR, DAY, or MONTH are allowed; > for example, a DAY of 0 means the day preceding the given month. So I do not think it affects anything. I decided to ask GNU libc developers https://inbox.sourceware.org/libc-alpha/tq93sc$p3$1@ciao.gmane.io/T/#u "mktime result may depend on previous calls" > Looking at > https://codecogs.com/library/computing/c/time.h/ctime.php?alias=mktime, > out-of-range minutes are not documented. Looks like a compilation of unspecified sources. The following is similar to ctime(3) man page if structure members are outside their valid interval, they will be normalized (so that, for example, 40 October is changed into 9 November); My reading is that out of range values for other "members" are allowed as well. However it may be tricky. > Also, see > https://stackoverflow.com/questions/20104531/weird-mktime-logic-with-negative-seconds My expectation is that ±1 day (or month) should preserve local time hours (e.g. 11:00 CET -> 11:00 CEST) if such moment of time exists. ±24 hours, ±24*60 minutes, ±24*3600 seconds across DST change should cause appropriate shift of hours (e.g. 11:00 -> 12:00 is possible). Moreover "out of range" month day 0 is the only way to specify last month day to get Jan, 31 <- 1 month -> Feb, 28 (or 29) <- 1 month -> Mar, 31 arithmetic. However it is hardly implementation specific and GNU date(1) CLI utility, PostgreSQL, PHP timelib, JavaScript Date objects behave differently. I still do not think it affects my example though. ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: Observation of hysteresis in a GNU libc time conversion function 2023-01-18 16:32 ` Max Nikulin @ 2023-01-18 16:38 ` Max Nikulin 2023-01-19 10:31 ` Ihor Radchenko 1 sibling, 0 replies; 5+ messages in thread From: Max Nikulin @ 2023-01-18 16:38 UTC (permalink / raw) To: emacs-orgmode > On 18/01/2023 16:54, Ihor Radchenko wrote: >> >> This is problematic. You are putting MINUTES out of normal range. <info:libc#Broken-down Time> (Isn't it really broken?) https://www.gnu.org/software/libc/manual/html_node/Broken_002ddown-Time.html > It uses the values of the other components to determine the calendar > time; it’s permissible for these components to have unnormalized values > outside their normal ranges. ^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: Observation of hysteresis in a GNU libc time conversion function 2023-01-18 16:32 ` Max Nikulin 2023-01-18 16:38 ` Max Nikulin @ 2023-01-19 10:31 ` Ihor Radchenko 1 sibling, 0 replies; 5+ messages in thread From: Ihor Radchenko @ 2023-01-19 10:31 UTC (permalink / raw) To: Max Nikulin; +Cc: emacs-orgmode Max Nikulin <manikulin@gmail.com> writes: >> Also, see >> https://stackoverflow.com/questions/20104531/weird-mktime-logic-with-negative-seconds > > My expectation is that ±1 day (or month) should preserve local time > hours (e.g. 11:00 CET -> 11:00 CEST) if such moment of time exists. ±24 > hours, ±24*60 minutes, ±24*3600 seconds across DST change should cause > appropriate shift of hours (e.g. 11:00 -> 12:00 is possible). > > Moreover "out of range" month day 0 is the only way to specify last > month day to get > > Jan, 31 <- 1 month -> Feb, 28 (or 29) <- 1 month -> Mar, 31 > > arithmetic. Can we expect the same to work on Windows? If so, maybe we don't need all the dancing around rounding off in `org-timestamp-change' and instead simply use time API? Re: 11:00 CET -> 11:00 CEST Note that Org does not exactly offer much of convenience even without considering TZ. <2023-01-31 Thu +1m> -> <2023-03-03 Fri +1m> And nobody really complained so far. So, maybe we don't even need to care about these subtleties in practice? -- Ihor Radchenko // yantar92, Org mode contributor, Learn more about Org mode at <https://orgmode.org/>. Support Org development at <https://liberapay.com/org-mode>, or support my work at <https://liberapay.com/yantar92> ^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2023-01-19 10:31 UTC | newest] Thread overview: 5+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2023-01-17 17:22 Observation of hysteresis in a GNU libc time conversion function Max Nikulin 2023-01-18 9:54 ` Ihor Radchenko 2023-01-18 16:32 ` Max Nikulin 2023-01-18 16:38 ` Max Nikulin 2023-01-19 10:31 ` Ihor Radchenko
Code repositories for project(s) associated with this public inbox https://git.savannah.gnu.org/cgit/emacs/org-mode.git 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).