emacs-orgmode@gnu.org archives
 help / color / mirror / code / Atom feed
From: Paul Eggert <eggert@cs.ucla.edu>
To: Max Nikulin <manikulin@gmail.com>
Cc: emacs-orgmode@gnu.org, 54764@debbugs.gnu.org
Subject: Re: bug#54764: encode-time: make DST and TIMEZONE fields of the list argument optional ones
Date: Sat, 16 Apr 2022 18:58:35 -0700	[thread overview]
Message-ID: <3624beb8-71fd-924e-a065-74d0034ed351@cs.ucla.edu> (raw)
In-Reply-To: <2d57e59b-f971-483b-ad65-e0c5ff7883e8@gmail.com>

[-- Attachment #1: Type: text/plain, Size: 375 bytes --]

On 4/16/22 09:26, Max Nikulin wrote:
> Feel free to shorten the added fragment, to change the wording, or to 
> use your variant instead. See the attachment.

Thanks, I installed that and then installed the attached, which merges 
that with some documentation improvements that I drafted based on this 
thread.

It is a messy area but I hope the documentation is clearer now.

[-- Attachment #2: 0001-Document-encode-time-caveats.patch --]
[-- Type: text/x-patch, Size: 13979 bytes --]

From f1ba92448d1e573640547c68d9bed89fe5c43da0 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Sat, 16 Apr 2022 18:48:51 -0700
Subject: [PATCH] Document encode-time caveats
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* doc/lispref/os.texi (Time of Day, Time Conversion):
Move the warnings about DST being -1 to closer to where DST is
discussed, and reword and improve the discussions and warnings.
Be more precise about years before 1969 (possible west of UTC) vs the
Epoch.  Mention some problems due to leap seconds, leap years,
daylight saving transitions, and time zone changes.  Modernize
discussion of OS timestamp range.  Prefer secular ‘BCE’ to religious
‘BC’.  Omit discussion of decoded-time-add and make-decoded-time, as
they are in a library and are not always available; instead, mention
the library.  Warn about common mistakes when doing simple date
arithmetic.
* src/timefns.c (Fencode_time): In doc string, mention date
arithmetic and tighten up the wording a bit.
---
 doc/lispref/os.texi | 153 +++++++++++++++++++-------------------------
 src/timefns.c       |  16 ++---
 2 files changed, 73 insertions(+), 96 deletions(-)

diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi
index 66689f43a9..8366689640 100644
--- a/doc/lispref/os.texi
+++ b/doc/lispref/os.texi
@@ -1303,10 +1303,16 @@ Time of Day
 
 @cindex Lisp timestamp
 @cindex timestamp, Lisp
+@cindex Coordinated Universal Time
+@cindex Universal Time
+@cindex UTC
+@cindex leap seconds
   Many functions like @code{current-time} and @code{file-attributes}
 return @dfn{Lisp timestamp} values that count seconds, and that can
 represent absolute time by counting seconds since the @dfn{epoch} of
-1970-01-01 00:00:00 UTC.
+1970-01-01 00:00:00 UTC (Coordinated Universal Time).  Typically these
+counts ignore leap seconds; however, GNU and some other operating
+systems can be configured to count leap seconds.
 
   Although traditionally Lisp timestamps were integer pairs, their
 form has evolved and programs ordinarily should not depend on the
@@ -1367,8 +1373,8 @@ Time of Day
 Some of these conversions rely on operating system functions that
 limit the range of possible time values, and signal an error such as
 @samp{"Specified time is not representable"} if the
-limits are exceeded.  For instance, a system may not support years
-before 1970, or years before 1901, or years far in the future.
+limits are exceeded.  For instance, a system might not support
+timestamps before the epoch, or years far in the future.
 You can convert a time value into
 a human-readable string using @code{format-time-string}, into a Lisp
 timestamp using @code{time-convert}, and into other forms using
@@ -1434,11 +1440,11 @@ Time Zone Rules
 which is a platform-dependent default time zone.
 
 The set of supported @env{TZ} strings is system-dependent.  GNU and
-many other systems support the tzdata database, e.g.,
+many other systems support TZDB timezones, e.g.,
 @samp{"America/New_York"} specifies the time zone and daylight saving
 time history for locations near New York City.  GNU and most other
 systems support POSIX-style @env{TZ} strings, e.g.,
-@samp{"EST+5EDT,M4.1.0/2,M10.5.0/2"} specifies the rules used in New
+@samp{"EST5EDT,M4.1.0,M10.5.0"} specifies the rules used in New
 York from 1987 through 2006.  All systems support the string
 @samp{"UTC0"} meaning Universal Time.
 
@@ -1490,18 +1496,20 @@ Time Conversion
   These functions convert time values (@pxref{Time of Day}) to Lisp
 timestamps, or into calendrical information and vice versa.
 
-  Many 32-bit operating systems are limited to system times containing
-32 bits of information in their seconds component; these systems
-typically handle only the times from 1901-12-13 20:45:52 through
-2038-01-19 03:14:07 Universal Time.  However, 64-bit and some 32-bit operating
-systems have larger seconds components, and can represent times far in
-the past or future.
-
-  Calendrical conversion functions always use the Gregorian calendar, even
-for dates before the Gregorian calendar was introduced.  Year numbers
-count the number of years since the year 1 BC, and do not skip zero
+  Many operating systems use 64-bit signed integers to count seconds,
+and can represent times far in the past or future.  However, some are
+more limited.  For example, old-fashioned operating systems that use
+32-bit signed integers typically handle only times from 1901-12-13
+20:45:52 through 2038-01-19 03:14:07 Universal Time.
+
+  Calendrical conversion functions use the Gregorian calendar even for
+dates before the Gregorian calendar was introduced, and for dates in
+the far distant past or future for which the Gregorian calendar
+is wildly inaccurate and disagrees with common practice in scientific fields
+like astronomy and paleontology, which use Julian-calendar year lengths.
+Year numbers count since the year 1 BCE, and do not skip zero
 as traditional Gregorian years do; for example, the year number
-@minus{}37 represents the Gregorian year 38 BC@.
+@minus{}37 represents the Gregorian year 38 BCE@.
 
 @defun time-convert time &optional form
 This function converts a time value into a Lisp timestamp.
@@ -1620,53 +1628,6 @@ Time Conversion
 @code{decoded-time-month}, @code{decoded-time-year},
 @code{decoded-time-weekday}, @code{decoded-time-dst} and
 @code{decoded-time-zone} accessors can be used.
-
-For instance, to increase the year in a decoded time, you could say:
-
-@lisp
-(setf (decoded-time-year decoded-time)
-      (+ (decoded-time-year decoded-time) 4))
-@end lisp
-
-Also see the following function.
-
-@end defun
-
-@defun decoded-time-add time delta
-This function takes a decoded time structure and adds @var{delta}
-(also a decoded time structure) to it.  Elements in @var{delta} that
-are @code{nil} are ignored.
-
-For instance, if you want ``same time next month'', you
-could say:
-
-@lisp
-(let ((time (decode-time nil nil t))
-      (delta (make-decoded-time :month 2)))
-   (encode-time (decoded-time-add time delta)))
-@end lisp
-
-If this date doesn't exist (if you're running this on January 31st,
-for instance), then the date will be shifted back until you get a
-valid date (which will be February 28th or 29th, depending).
-
-Fields are added in a most to least significant order, so if the
-adjustment described above happens, it happens before adding days,
-hours, minutes or seconds.
-
-The values in @var{delta} can be negative to subtract values instead.
-
-The return value is a decoded time structure.
-@end defun
-
-@defun make-decoded-time &key second minute hour day month year dst zone
-Return a decoded time structure with only the given keywords filled
-out, leaving the rest @code{nil}.  For instance, to get a structure
-that represents ``two months'', you could say:
-
-@lisp
-(make-decoded-time :month 2)
-@end lisp
 @end defun
 
 @defun encode-time time &rest obsolescent-arguments
@@ -1676,9 +1637,21 @@ Time Conversion
 Ordinarily the first argument is a list
 @code{(@var{second} @var{minute} @var{hour} @var{day} @var{month}
 @var{year} @var{ignored} @var{dst} @var{zone})} that specifies a
-decoded time in the style of @code{decode-time}, so that
-@code{(encode-time (decode-time ...))}  works.  For the meanings of
-these list members, see the table under @code{decode-time}.
+decoded time in the style of @code{decode-time}.  For the meanings of
+these list elements, see the table under @code{decode-time}.
+In particular, @var{dst} says how to interpret timestamps during a
+daylight saving fallback when timestamps are repeated.
+If @var{dst} is @minus{}1, the DST value is guessed; if it
+is @code{t} or @code{nil} the timestamp with that DST value
+is returned, with an error signaled if no such timestamp exists.
+Unfortunately a @var{dst} value of @code{t} or @code{nil} does not
+disambiguate timestamps duplicated when a TZDB-based timezone moves
+further west of Greenwich, such as disambiguating the two
+standard-time timestamps 2020-12-27 01:30 when @var{zone} is
+@samp{"Europe/Volgograd"}, which at 02:00 that day changed
+standard time from 4 to 3 hours east of Greenwich; if you need to
+handle situations like this you can use a numeric @var{zone} to
+disambiguate instead.
 
 As an obsolescent calling convention, this function can be given six
 or more arguments.  The first six arguments @var{second},
@@ -1687,14 +1660,18 @@ Time Conversion
 than six arguments the @emph{last} argument is used as @var{zone} and
 any other extra arguments are ignored, so that @code{(apply
 #'encode-time (decode-time ...))} works.  In this obsolescent
-convention, @var{zone} defaults to the current time zone rule
-(@pxref{Time Zone Rules}), and @var{dst} is treated as if it was
-@minus{}1.
+convention, @var{dst} is @minus{}1 and @var{zone} defaults to the
+current time zone rule (@pxref{Time Zone Rules}).
+When modernizing an obsolescent caller, ensure that the more-modern
+list equivalent contains 9 elements with a a @code{dst} element that
+is @minus{}1, not @code{nil}.
 
 Year numbers less than 100 are not treated specially.  If you want them
 to stand for years above 1900, or years above 2000, you must alter them
 yourself before you call @code{encode-time}.
 The operating system limits the range of time and zone values.
+However, timestamps ranging from the epoch to the near future are
+always supported.
 
 The @code{encode-time} function acts as a rough inverse to
 @code{decode-time}.  For example, you can pass the output of
@@ -1707,25 +1684,27 @@ Time Conversion
 You can perform simple date arithmetic by using out-of-range values for
 @var{seconds}, @var{minutes}, @var{hour}, @var{day}, and @var{month};
 for example, day 0 means the day preceding the given month.
+Take care when doing so, as it is common for this to fail in some cases.
+For example:
+
+@lisp
+;; Try to compute the time four years from now.
+;; Watch out; this might not work as expected.
+(let ((time (decode-time)))
+  (setf (decoded-time-year time)
+        (+ (decoded-time-year time) 4))
+  time)
+@end lisp
 
-The old and the new styles to call @code{encode-time} with the same
-values of time fields may give different results.  While modernizing
-code that uses obsolescent calling convention, ensure that the list
-argument contains 9 elements.  Pay special attention that the @code{dst}
-field does not use @code{nil} expecting that actual value will be
-guessed, pass @samp{-1} instead.  During normalizing of values to
-correct state of daylight saving time users may get time shift and even
-wrong date.  It may take months to discover such problem.  When
-called with multiple arguments, the function ignores equivalent of the
-@code{dst} value and @samp{-1} is effectively used.  The new way to call
-@code{encode-time} has an advantage that it is possible to resolve
-ambiguity around backward time shift by passing @code{nil} or @code{t}.
-Unfortunately there are enough cases across the world when a particular
-area is moved to another time zone with no change of daylight saving
-time state.  @code{encode-time} may signal an error in response to
-@code{t} passed as @code{dst}.  You have to pass @code{zone} explicitly
-as time offset in such case if default ambiguity resolution is not
-acceptable.
+@noindent
+Unfortunately, this code might not work as expected if the resulting
+time is invalid due to daylight saving transitions, time zone changes,
+or missing leap days or leap seconds.  For example, if executed on
+February 29, 2096 this code yields a nonexistent date because 2100 is
+not a leap year.  To avoid some (though not all) of the problem, you
+can base calculations on the middle of the affected unit, e.g., start
+at July 1 when adding years.  Alternatively, you can use the
+@file{calendar} and @file{time-date} libraries.
 @end defun
 
 @node Time Parsing
diff --git a/src/timefns.c b/src/timefns.c
index 9af89a512d..7a4a7075ed 100644
--- a/src/timefns.c
+++ b/src/timefns.c
@@ -1609,11 +1609,11 @@ check_tm_member (Lisp_Object obj, int offset)
 DEFUN ("encode-time", Fencode_time, Sencode_time, 1, MANY, 0,
        doc: /* Convert TIME to a timestamp.
 
-TIME is a list (SECOND MINUTE HOUR DAY MONTH YEAR IGNORED DST ZONE).
+TIME is a list (SECOND MINUTE HOUR DAY MONTH YEAR IGNORED DST ZONE)
 in the style of `decode-time', so that (encode-time (decode-time ...)) works.
 In this list, ZONE can be nil for Emacs local time, t for Universal
 Time, `wall' for system wall clock time, or a string as in the TZ
-environment variable.  It can also be a list (as from
+environment variable.  ZONE can also be a list (as from
 `current-time-zone') or an integer (as from `decode-time') applied
 without consideration for daylight saving time.  If ZONE specifies a
 time zone with daylight-saving transitions, DST is t for daylight
@@ -1626,14 +1626,12 @@ DEFUN ("encode-time", Fencode_time, Sencode_time, 1, MANY, 0,
 If there are more than 6 arguments the *last* argument is used as ZONE
 and any other extra arguments are ignored, so that (apply
 #\\='encode-time (decode-time ...)) works.  In this obsolescent
-convention, DST and ZONE default to -1 and nil respectively.
+convention, DST is -1 and ZONE defaults to nil.
 
-Years before 1970 are not guaranteed to work.  On some systems,
-year values as low as 1901 do work.
-
-See Info node `(elisp)Time Conversion' for description of a pitfall
-that can be faced during migration from the obsolescent to the new
-calling convention due to unconscious usage of nil for the DST argument.
+The range of supported years is at least 1970 to the near future.
+Out-of-range values for SECOND through MONTH are brought into range
+via date arithmetic.  This can be tricky especially when combined with
+DST; see Info node `(elisp)Time Conversion' for details and caveats.
 
 usage: (encode-time TIME &rest OBSOLESCENT-ARGUMENTS)  */)
   (ptrdiff_t nargs, Lisp_Object *args)
-- 
2.32.0


  reply	other threads:[~2022-04-17  2:01 UTC|newest]

Thread overview: 39+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2022-04-07 12:37 bug#54764: encode-time: make DST and TIMEZONE fields of the list argument optional ones Max Nikulin
2022-04-09  7:52 ` Paul Eggert
2022-04-10  3:57   ` Max Nikulin
2022-04-13 14:40   ` Max Nikulin
2022-04-13 18:35     ` Paul Eggert
2022-04-14 13:19       ` Max Nikulin
2022-04-14 22:46         ` Paul Eggert
2022-04-15  2:14           ` Tim Cross
2022-04-15 17:23           ` Max Nikulin
2022-04-16 19:23             ` Paul Eggert
2022-04-21 16:59               ` Max Nikulin
2022-04-19  2:02             ` Paul Eggert
2022-04-19  5:50               ` Eli Zaretskii
2022-04-19 22:22                 ` Paul Eggert
2022-04-20  7:23                   ` Eli Zaretskii
2022-04-20 18:19                     ` Paul Eggert
2022-04-20 18:41                       ` Eli Zaretskii
2022-04-20 19:01                         ` Paul Eggert
2022-04-20 19:14                           ` Eli Zaretskii
2022-04-20 19:23                             ` Paul Eggert
2022-04-20 19:30                               ` Eli Zaretskii
2022-04-21  0:11                                 ` Paul Eggert
2022-04-21  6:44                                   ` Eli Zaretskii
2022-04-21 23:56                                     ` Paul Eggert
2022-04-22  5:01                                       ` Eli Zaretskii
2022-04-23 14:35                       ` Bernhard Voelker
2022-04-20 15:07               ` Max Nikulin
2022-04-20 18:29                 ` Paul Eggert
2022-04-25 15:30                   ` Max Nikulin
2022-04-25 15:37                     ` Paul Eggert
2022-04-25 19:49                       ` Paul Eggert
2022-04-30 11:22                         ` Max Nikulin
2022-05-01  2:32                           ` Paul Eggert
2022-05-01 17:15                             ` Max Nikulin
2022-04-13 15:12   ` Max Nikulin
2022-04-16 16:26   ` Max Nikulin
2022-04-17  1:58     ` Paul Eggert [this message]
2022-04-20 16:56       ` Max Nikulin
2022-04-20 19:17         ` Paul Eggert

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

  List information: https://www.orgmode.org/

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=3624beb8-71fd-924e-a065-74d0034ed351@cs.ucla.edu \
    --to=eggert@cs.ucla.edu \
    --cc=54764@debbugs.gnu.org \
    --cc=emacs-orgmode@gnu.org \
    --cc=manikulin@gmail.com \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
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).