unofficial mirror of bug-gnu-emacs@gnu.org 
 help / color / mirror / code / Atom feed
* bug#32902: Add support for (TIMESTAMP . RESOLUTION) Lisp timestamps
@ 2018-10-02  1:00 Paul Eggert
  2018-10-02  3:04 ` Eli Zaretskii
  0 siblings, 1 reply; 11+ messages in thread
From: Paul Eggert @ 2018-10-02  1:00 UTC (permalink / raw)
  To: 32902

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

The attached patches follow up on a suggestion by Stefan a few weeks 
ago, by adding support for a new Lisp timestamp format (TIMESTAMP . 
FREQUENCY), where TIMESTAMP is an integer that counts clock ticks and 
FREQUENCY is a positive integer that counts ticks per second. For 
brevity the documentation says (TICKS . HZ) instead of (TIMESTAMP . 
FREQUENCY).

Although current-time and similar functions continue to return the (HI 
LO US PS) format, the idea is that Emacs eventually should switch to 
(TICKS . HZ) as it is a better match for an Emacs with bignums (among 
other things, it does not lose information), and in the meantime we can 
better document that the Lisp timestamp format has changed in the past 
and can change in the future, and that user code should not depend on 
the exact timestamp format.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Move-timestamp-related-stuff-to-systime.c.patch --]
[-- Type: text/x-patch; name="0001-Move-timestamp-related-stuff-to-systime.c.patch", Size: 108449 bytes --]

From f75d4efd63cb39b7e653eee68b12c4fd737c09c3 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Mon, 1 Oct 2018 15:31:53 -0700
Subject: [PATCH 1/4] Move timestamp-related stuff to systime.c
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

This does not change behavior; it’s just long-overdue
refactoring.
* src/emacs.c (main): Call syms_of_sysdep.
* src/systime.c: New file, containing timestamp-related stuff
from editfns.c and sysdep.c.
* src/Makefile.in (base_obj): Add systime.o.
* src/editfns.c: Simplify by moving a big chunk to systime.c.
Do not include systime.h, sys/resource.h, sys/param.h,
strftime.h, coding.h.
(HAVE_TZALLOC_BUG, TM_YEAR_BASE, HAVE_TM_GMTOFF, tzeqlen)
(local_tz, utc_tz, emacs_localtime_rz, emacs_mktime_z)
(invalid_time_zone_specification, xtzfree, tzlookup)
(TIME_T_MIN, TIME_T_MAX, time_overflow, invalid_time)
(check_time_validity, hi_time, lo_time, Fcurrent_time)
(time_add, time_subtract, time_arith, Ftime_add)
(Ftime_subtract, Ftime_less_p, Fget_internal_run_time)
(make_lisp_time, disassemble_lisp_time, decode_float_time)
(lisp_to_timespec, lisp_time_struct, lisp_time_argument)
(lisp_seconds_argument, Ffloat_time, emacs_nmemftime)
(Fformat_time_string, format_time_string, Fdecode_time)
(check_tm_member, Fencode_time, Fcurrent_time_string)
(tm_gmtoff, Fcurrent_time_zone, Fset_time_zone_rule)
(emacs_getenv_TZ, emacs_setenv_TZ): Move to systime.c.
* src/emacs.c (main): Adjust to initialization changes.
* src/sysdep.c: Include <sys/resource.h> if it's present.
Regularize includes a bit.
(Fget_internal_run_time): Move here from editfns.c.
(syms_of_sysdep): New function.
* src/w32.h (w32_get_internal_run_time): Move decl here
so that it need not be cloned.
* test/src/editfns-tests.el:
* test/src/editfns-tests.el (format-time-string-with-zone)
(format-time-string-with-outlandish-zone)
(editfns-tests--have-leap-seconds)
(format-time-string-with-bignum-on-32-bit):
Move to ...
* test/src/systime-tests.el: ... this new file.
---
 src/Makefile.in           |    2 +-
 src/editfns.c             | 1289 +------------------------------------
 src/emacs.c               |   10 +-
 src/lisp.h                |    4 +-
 src/sysdep.c              |   87 ++-
 src/systime.c             | 1287 ++++++++++++++++++++++++++++++++++++
 src/systime.h             |    8 +-
 src/w32.c                 |    2 -
 src/w32.h                 |    1 +
 test/src/editfns-tests.el |   59 --
 test/src/systime-tests.el |   79 +++
 11 files changed, 1440 insertions(+), 1388 deletions(-)
 create mode 100644 src/systime.c
 create mode 100644 test/src/systime-tests.el

diff --git a/src/Makefile.in b/src/Makefile.in
index 72f568988a..12ebfb03ad 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -399,7 +399,7 @@ base_obj =
 	eval.o floatfns.o fns.o font.o print.o lread.o $(MODULES_OBJ) \
 	syntax.o $(UNEXEC_OBJ) bytecode.o \
 	process.o gnutls.o callproc.o \
-	region-cache.o sound.o atimer.o \
+	region-cache.o sound.o systime.o atimer.o \
 	doprnt.o intervals.o textprop.o composite.o xml.o lcms.o $(NOTIFY_OBJ) \
 	$(XWIDGETS_OBJ) \
 	profiler.o decompress.o \
diff --git a/src/editfns.c b/src/editfns.c
index 47509c23d0..e995b38a44 100644
--- a/src/editfns.c
+++ b/src/editfns.c
@@ -35,34 +35,13 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 
 #include "lisp.h"
 
-/* systime.h includes <sys/time.h> which, on some systems, is required
-   for <sys/resource.h>; thus systime.h must be included before
-   <sys/resource.h> */
-#include "systime.h"
-
-#if defined HAVE_SYS_RESOURCE_H
-#include <sys/resource.h>
-#endif
-
-#include <errno.h>
 #include <float.h>
 #include <limits.h>
 #include <math.h>
 
-#ifdef HAVE_TIMEZONE_T
-# include <sys/param.h>
-# if defined __NetBSD_Version__ && __NetBSD_Version__ < 700000000
-#  define HAVE_TZALLOC_BUG true
-# endif
-#endif
-#ifndef HAVE_TZALLOC_BUG
-# define HAVE_TZALLOC_BUG false
-#endif
-
 #include <c-ctype.h>
 #include <intprops.h>
 #include <stdlib.h>
-#include <strftime.h>
 #include <verify.h>
 
 #include "composite.h"
@@ -70,34 +49,12 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #include "ptr-bounds.h"
 #include "character.h"
 #include "buffer.h"
-#include "coding.h"
 #include "window.h"
 #include "blockinput.h"
 
-#define TM_YEAR_BASE 1900
-
-#ifdef WINDOWSNT
-extern Lisp_Object w32_get_internal_run_time (void);
-#endif
-
-static struct lisp_time lisp_time_struct (Lisp_Object, int *);
-static Lisp_Object format_time_string (char const *, ptrdiff_t, struct timespec,
-				       Lisp_Object, struct tm *);
-static long int tm_gmtoff (struct tm *);
-static int tm_diff (struct tm *, struct tm *);
 static void update_buffer_properties (ptrdiff_t, ptrdiff_t);
 static Lisp_Object styled_format (ptrdiff_t, Lisp_Object *, bool);
 
-#ifndef HAVE_TM_GMTOFF
-# define HAVE_TM_GMTOFF false
-#endif
-
-enum { tzeqlen = sizeof "TZ=" - 1 };
-
-/* Time zones equivalent to current local time and to UTC, respectively.  */
-static timezone_t local_tz;
-static timezone_t const utc_tz = 0;
-
 /* The cached value of Vsystem_name.  This is used only to compare it
    to Vsystem_name, so it need not be visible to the GC.  */
 static Lisp_Object cached_system_name;
@@ -109,153 +66,9 @@ init_and_cache_system_name (void)
   cached_system_name = Vsystem_name;
 }
 
-static struct tm *
-emacs_localtime_rz (timezone_t tz, time_t const *t, struct tm *tm)
-{
-  tm = localtime_rz (tz, t, tm);
-  if (!tm && errno == ENOMEM)
-    memory_full (SIZE_MAX);
-  return tm;
-}
-
-static time_t
-emacs_mktime_z (timezone_t tz, struct tm *tm)
-{
-  errno = 0;
-  time_t t = mktime_z (tz, tm);
-  if (t == (time_t) -1 && errno == ENOMEM)
-    memory_full (SIZE_MAX);
-  return t;
-}
-
-static _Noreturn void
-invalid_time_zone_specification (Lisp_Object zone)
-{
-  xsignal2 (Qerror, build_string ("Invalid time zone specification"), zone);
-}
-
-/* Free a timezone, except do not free the time zone for local time.
-   Freeing utc_tz is also a no-op.  */
-static void
-xtzfree (timezone_t tz)
-{
-  if (tz != local_tz)
-    tzfree (tz);
-}
-
-/* Convert the Lisp time zone rule ZONE to a timezone_t object.
-   The returned value either is 0, or is LOCAL_TZ, or is newly allocated.
-   If SETTZ, set Emacs local time to the time zone rule; otherwise,
-   the caller should eventually pass the returned value to xtzfree.  */
-static timezone_t
-tzlookup (Lisp_Object zone, bool settz)
-{
-  static char const tzbuf_format[] = "<%+.*"pI"d>%s%"pI"d:%02d:%02d";
-  char const *trailing_tzbuf_format = tzbuf_format + sizeof "<%+.*"pI"d" - 1;
-  char tzbuf[sizeof tzbuf_format + 2 * INT_STRLEN_BOUND (EMACS_INT)];
-  char const *zone_string;
-  timezone_t new_tz;
-
-  if (NILP (zone))
-    return local_tz;
-  else if (EQ (zone, Qt) || EQ (zone, make_fixnum (0)))
-    {
-      zone_string = "UTC0";
-      new_tz = utc_tz;
-    }
-  else
-    {
-      bool plain_integer = FIXNUMP (zone);
-
-      if (EQ (zone, Qwall))
-	zone_string = 0;
-      else if (STRINGP (zone))
-	zone_string = SSDATA (ENCODE_SYSTEM (zone));
-      else if (plain_integer || (CONSP (zone) && FIXNUMP (XCAR (zone))
-				 && CONSP (XCDR (zone))))
-	{
-	  Lisp_Object abbr UNINIT;
-	  if (!plain_integer)
-	    {
-	      abbr = XCAR (XCDR (zone));
-	      zone = XCAR (zone);
-	    }
-
-	  EMACS_INT abszone = eabs (XFIXNUM (zone)), hour = abszone / (60 * 60);
-	  int hour_remainder = abszone % (60 * 60);
-	  int min = hour_remainder / 60, sec = hour_remainder % 60;
-
-	  if (plain_integer)
-	    {
-	      int prec = 2;
-	      EMACS_INT numzone = hour;
-	      if (hour_remainder != 0)
-		{
-		  prec += 2, numzone = 100 * numzone + min;
-		  if (sec != 0)
-		    prec += 2, numzone = 100 * numzone + sec;
-		}
-	      sprintf (tzbuf, tzbuf_format, prec,
-		       XFIXNUM (zone) < 0 ? -numzone : numzone,
-		       &"-"[XFIXNUM (zone) < 0], hour, min, sec);
-	      zone_string = tzbuf;
-	    }
-	  else
-	    {
-	      AUTO_STRING (leading, "<");
-	      AUTO_STRING_WITH_LEN (trailing, tzbuf,
-				    sprintf (tzbuf, trailing_tzbuf_format,
-					     &"-"[XFIXNUM (zone) < 0],
-					     hour, min, sec));
-	      zone_string = SSDATA (concat3 (leading, ENCODE_SYSTEM (abbr),
-					     trailing));
-	    }
-	}
-      else
-	invalid_time_zone_specification (zone);
-
-      new_tz = tzalloc (zone_string);
-
-      if (HAVE_TZALLOC_BUG && !new_tz && errno != ENOMEM && plain_integer
-	  && XFIXNUM (zone) % (60 * 60) == 0)
-	{
-	  /* tzalloc mishandles POSIX strings; fall back on tzdb if
-	     possible (Bug#30738).  */
-	  sprintf (tzbuf, "Etc/GMT%+"pI"d", - (XFIXNUM (zone) / (60 * 60)));
-	  new_tz = tzalloc (zone_string);
-	}
-
-      if (!new_tz)
-	{
-	  if (errno == ENOMEM)
-	    memory_full (SIZE_MAX);
-	  invalid_time_zone_specification (zone);
-	}
-    }
-
-  if (settz)
-    {
-      block_input ();
-      emacs_setenv_TZ (zone_string);
-      tzset ();
-      timezone_t old_tz = local_tz;
-      local_tz = new_tz;
-      tzfree (old_tz);
-      unblock_input ();
-    }
-
-  return new_tz;
-}
-
 void
-init_editfns (bool dumping)
+init_editfns (void)
 {
-#if !defined CANNOT_DUMP
-  /* A valid but unlikely setting for the TZ environment variable.
-     It is OK (though a bit slower) if the user chooses this value.  */
-  static char dump_tz_string[] = "TZ=UtC0";
-#endif
-
   const char *user_name;
   register char *p;
   struct passwd *pw;	/* password entry for the current user */
@@ -264,37 +77,6 @@ init_editfns (bool dumping)
   /* Set up system_name even when dumping.  */
   init_and_cache_system_name ();
 
-#ifndef CANNOT_DUMP
-  /* When just dumping out, set the time zone to a known unlikely value
-     and skip the rest of this function.  */
-  if (dumping)
-    {
-      xputenv (dump_tz_string);
-      tzset ();
-      return;
-    }
-#endif
-
-  char *tz = getenv ("TZ");
-
-#if !defined CANNOT_DUMP
-  /* If the execution TZ happens to be the same as the dump TZ,
-     change it to some other value and then change it back,
-     to force the underlying implementation to reload the TZ info.
-     This is needed on implementations that load TZ info from files,
-     since the TZ file contents may differ between dump and execution.  */
-  if (tz && strcmp (tz, &dump_tz_string[tzeqlen]) == 0)
-    {
-      ++*tz;
-      tzset ();
-      --*tz;
-    }
-#endif
-
-  /* Set the time zone rule now, so that the call to putenv is done
-     before multiple threads are active.  */
-  tzlookup (tz ? build_string (tz) : Qwall, true);
-
   pw = getpwuid (getuid ());
 #ifdef MSDOS
   /* We let the real user name default to "root" because that's quite
@@ -1349,7 +1131,7 @@ of the user with that uid, or nil if there is no such user.  */)
      (That can happen if Emacs is dumpable
      but you decide to run `temacs -l loadup' and not dump.  */
   if (NILP (Vuser_login_name))
-    init_editfns (false);
+    init_editfns ();
 
   if (NILP (uid))
     return Vuser_login_name;
@@ -1372,7 +1154,7 @@ This ignores the environment variables LOGNAME and USER, so it differs from
      (That can happen if Emacs is dumpable
      but you decide to run `temacs -l loadup' and not dump.  */
   if (NILP (Vuser_login_name))
-    init_editfns (false);
+    init_editfns ();
   return Vuser_real_login_name;
 }
 
@@ -1494,1058 +1276,6 @@ Value is a fixnum, if it's small enough, otherwise a bignum.  */)
 }
 
 \f
-
-#ifndef TIME_T_MIN
-# define TIME_T_MIN TYPE_MINIMUM (time_t)
-#endif
-#ifndef TIME_T_MAX
-# define TIME_T_MAX TYPE_MAXIMUM (time_t)
-#endif
-
-/* Report that a time value is out of range for Emacs.  */
-void
-time_overflow (void)
-{
-  error ("Specified time is not representable");
-}
-
-static _Noreturn void
-invalid_time (void)
-{
-  error ("Invalid time specification");
-}
-
-/* Check a return value compatible with that of decode_time_components.  */
-static void
-check_time_validity (int validity)
-{
-  if (validity <= 0)
-    {
-      if (validity < 0)
-	time_overflow ();
-      else
-	invalid_time ();
-    }
-}
-
-/* Return the upper part of the time T (everything but the bottom 16 bits).  */
-static EMACS_INT
-hi_time (time_t t)
-{
-  time_t hi = t >> LO_TIME_BITS;
-  if (FIXNUM_OVERFLOW_P (hi))
-    time_overflow ();
-  return hi;
-}
-
-/* Return the bottom bits of the time T.  */
-static int
-lo_time (time_t t)
-{
-  return t & ((1 << LO_TIME_BITS) - 1);
-}
-
-DEFUN ("current-time", Fcurrent_time, Scurrent_time, 0, 0, 0,
-       doc: /* Return the current time, as the number of seconds since 1970-01-01 00:00:00.
-The time is returned as a list of integers (HIGH LOW USEC PSEC).
-HIGH has the most significant bits of the seconds, while LOW has the
-least significant 16 bits.  USEC and PSEC are the microsecond and
-picosecond counts.  */)
-  (void)
-{
-  return make_lisp_time (current_timespec ());
-}
-
-static struct lisp_time
-time_add (struct lisp_time ta, struct lisp_time tb)
-{
-  EMACS_INT hi = ta.hi + tb.hi;
-  int lo = ta.lo + tb.lo;
-  int us = ta.us + tb.us;
-  int ps = ta.ps + tb.ps;
-  us += (1000000 <= ps);
-  ps -= (1000000 <= ps) * 1000000;
-  lo += (1000000 <= us);
-  us -= (1000000 <= us) * 1000000;
-  hi += (1 << LO_TIME_BITS <= lo);
-  lo -= (1 << LO_TIME_BITS <= lo) << LO_TIME_BITS;
-  return (struct lisp_time) { hi, lo, us, ps };
-}
-
-static struct lisp_time
-time_subtract (struct lisp_time ta, struct lisp_time tb)
-{
-  EMACS_INT hi = ta.hi - tb.hi;
-  int lo = ta.lo - tb.lo;
-  int us = ta.us - tb.us;
-  int ps = ta.ps - tb.ps;
-  us -= (ps < 0);
-  ps += (ps < 0) * 1000000;
-  lo -= (us < 0);
-  us += (us < 0) * 1000000;
-  hi -= (lo < 0);
-  lo += (lo < 0) << LO_TIME_BITS;
-  return (struct lisp_time) { hi, lo, us, ps };
-}
-
-static Lisp_Object
-time_arith (Lisp_Object a, Lisp_Object b, bool subtract)
-{
-  if (FLOATP (a) && !isfinite (XFLOAT_DATA (a)))
-    {
-      double da = XFLOAT_DATA (a);
-      double db = XFLOAT_DATA (Ffloat_time (b));
-      return make_float (subtract ? da - db : da + db);
-    }
-  if (FLOATP (b) && !isfinite (XFLOAT_DATA (b)))
-    return subtract ? make_float (-XFLOAT_DATA (b)) : b;
-
-  int alen, blen;
-  struct lisp_time ta = lisp_time_struct (a, &alen);
-  struct lisp_time tb = lisp_time_struct (b, &blen);
-  struct lisp_time t = (subtract ? time_subtract : time_add) (ta, tb);
-  if (FIXNUM_OVERFLOW_P (t.hi))
-    time_overflow ();
-  Lisp_Object val = Qnil;
-
-  switch (max (alen, blen))
-    {
-    default:
-      val = Fcons (make_fixnum (t.ps), val);
-      FALLTHROUGH;
-    case 3:
-      val = Fcons (make_fixnum (t.us), val);
-      FALLTHROUGH;
-    case 2:
-      val = Fcons (make_fixnum (t.lo), val);
-      val = Fcons (make_fixnum (t.hi), val);
-      break;
-    }
-
-  return val;
-}
-
-DEFUN ("time-add", Ftime_add, Stime_add, 2, 2, 0,
-       doc: /* Return the sum of two time values A and B, as a time value.
-A nil value for either argument stands for the current time.
-See `current-time-string' for the various forms of a time value.  */)
-  (Lisp_Object a, Lisp_Object b)
-{
-  return time_arith (a, b, false);
-}
-
-DEFUN ("time-subtract", Ftime_subtract, Stime_subtract, 2, 2, 0,
-       doc: /* Return the difference between two time values A and B, as a time value.
-Use `float-time' to convert the difference into elapsed seconds.
-A nil value for either argument stands for the current time.
-See `current-time-string' for the various forms of a time value.  */)
-  (Lisp_Object a, Lisp_Object b)
-{
-  return time_arith (a, b, true);
-}
-
-/* Return negative, 0, positive if a < b, a == b, a > b respectively.
-   Return positive if either a or b is a NaN; this is good enough
-   for the current callers.  */
-static int
-time_cmp (Lisp_Object a, Lisp_Object b)
-{
-  if ((FLOATP (a) && !isfinite (XFLOAT_DATA (a)))
-      || (FLOATP (b) && !isfinite (XFLOAT_DATA (b))))
-    {
-      double da = FLOATP (a) ? XFLOAT_DATA (a) : 0;
-      double db = FLOATP (b) ? XFLOAT_DATA (b) : 0;
-      return da < db ? -1 : da != db;
-    }
-
-  int alen, blen;
-  struct lisp_time ta = lisp_time_struct (a, &alen);
-  struct lisp_time tb = lisp_time_struct (b, &blen);
-  return (ta.hi != tb.hi ? (ta.hi < tb.hi ? -1 : 1)
-	  : ta.lo != tb.lo ? (ta.lo < tb.lo ? -1 : 1)
-	  : ta.us != tb.us ? (ta.us < tb.us ? -1 : 1)
-	  : ta.ps < tb.ps ? -1 : ta.ps != tb.ps);
-}
-
-DEFUN ("time-less-p", Ftime_less_p, Stime_less_p, 2, 2, 0,
-       doc: /* Return non-nil if time value T1 is earlier than time value T2.
-A nil value for either argument stands for the current time.
-See `current-time-string' for the various forms of a time value.  */)
-  (Lisp_Object t1, Lisp_Object t2)
-{
-  return time_cmp (t1, t2) < 0 ? Qt : Qnil;
-}
-
-DEFUN ("time-equal-p", Ftime_equal_p, Stime_equal_p, 2, 2, 0,
-       doc: /* Return non-nil if T1 and T2 are equal time values.
-A nil value for either argument stands for the current time.
-See `current-time-string' for the various forms of a time value.  */)
-  (Lisp_Object t1, Lisp_Object t2)
-{
-  return time_cmp (t1, t2) == 0 ? Qt : Qnil;
-}
-
-
-DEFUN ("get-internal-run-time", Fget_internal_run_time, Sget_internal_run_time,
-       0, 0, 0,
-       doc: /* Return the current run time used by Emacs.
-The time is returned as in the style of `current-time'.
-
-On systems that can't determine the run time, `get-internal-run-time'
-does the same thing as `current-time'.  */)
-  (void)
-{
-#ifdef HAVE_GETRUSAGE
-  struct rusage usage;
-  time_t secs;
-  int usecs;
-
-  if (getrusage (RUSAGE_SELF, &usage) < 0)
-    /* This shouldn't happen.  What action is appropriate?  */
-    xsignal0 (Qerror);
-
-  /* Sum up user time and system time.  */
-  secs = usage.ru_utime.tv_sec + usage.ru_stime.tv_sec;
-  usecs = usage.ru_utime.tv_usec + usage.ru_stime.tv_usec;
-  if (usecs >= 1000000)
-    {
-      usecs -= 1000000;
-      secs++;
-    }
-  return make_lisp_time (make_timespec (secs, usecs * 1000));
-#else /* ! HAVE_GETRUSAGE  */
-#ifdef WINDOWSNT
-  return w32_get_internal_run_time ();
-#else /* ! WINDOWSNT  */
-  return Fcurrent_time ();
-#endif /* WINDOWSNT  */
-#endif /* HAVE_GETRUSAGE  */
-}
-\f
-
-/* Make a Lisp list that represents the Emacs time T.  T may be an
-   invalid time, with a slightly negative tv_nsec value such as
-   UNKNOWN_MODTIME_NSECS; in that case, the Lisp list contains a
-   correspondingly negative picosecond count.  */
-Lisp_Object
-make_lisp_time (struct timespec t)
-{
-  time_t s = t.tv_sec;
-  int ns = t.tv_nsec;
-  return list4i (hi_time (s), lo_time (s), ns / 1000, ns % 1000 * 1000);
-}
-
-/* Decode a Lisp list SPECIFIED_TIME that represents a time.
-   Set *PHIGH, *PLOW, *PUSEC, *PPSEC to its parts; do not check their values.
-   Return 2, 3, or 4 to indicate the effective length of SPECIFIED_TIME
-   if successful, 0 if unsuccessful.  */
-static int
-disassemble_lisp_time (Lisp_Object specified_time, Lisp_Object *phigh,
-		       Lisp_Object *plow, Lisp_Object *pusec,
-		       Lisp_Object *ppsec)
-{
-  Lisp_Object high = make_fixnum (0);
-  Lisp_Object low = specified_time;
-  Lisp_Object usec = make_fixnum (0);
-  Lisp_Object psec = make_fixnum (0);
-  int len = 4;
-
-  if (CONSP (specified_time))
-    {
-      high = XCAR (specified_time);
-      low = XCDR (specified_time);
-      if (CONSP (low))
-	{
-	  Lisp_Object low_tail = XCDR (low);
-	  low = XCAR (low);
-	  if (CONSP (low_tail))
-	    {
-	      usec = XCAR (low_tail);
-	      low_tail = XCDR (low_tail);
-	      if (CONSP (low_tail))
-		psec = XCAR (low_tail);
-	      else
-		len = 3;
-	    }
-	  else if (!NILP (low_tail))
-	    {
-	      usec = low_tail;
-	      len = 3;
-	    }
-	  else
-	    len = 2;
-	}
-      else
-	len = 2;
-
-      /* When combining components, require LOW to be an integer,
-	 as otherwise it would be a pain to add up times.  */
-      if (! INTEGERP (low))
-	return 0;
-    }
-  else if (INTEGERP (specified_time))
-    len = 2;
-
-  *phigh = high;
-  *plow = low;
-  *pusec = usec;
-  *ppsec = psec;
-  return len;
-}
-
-/* Convert T into an Emacs time *RESULT, truncating toward minus infinity.
-   Return true if T is in range, false otherwise.  */
-static bool
-decode_float_time (double t, struct lisp_time *result)
-{
-  double lo_multiplier = 1 << LO_TIME_BITS;
-  double emacs_time_min = MOST_NEGATIVE_FIXNUM * lo_multiplier;
-  if (! (emacs_time_min <= t && t < -emacs_time_min))
-    return false;
-
-  double small_t = t / lo_multiplier;
-  EMACS_INT hi = small_t;
-  double t_sans_hi = t - hi * lo_multiplier;
-  int lo = t_sans_hi;
-  long double fracps = (t_sans_hi - lo) * 1e12L;
-#ifdef INT_FAST64_MAX
-  int_fast64_t ifracps = fracps;
-  int us = ifracps / 1000000;
-  int ps = ifracps % 1000000;
-#else
-  int us = fracps / 1e6L;
-  int ps = fracps - us * 1e6L;
-#endif
-  us -= (ps < 0);
-  ps += (ps < 0) * 1000000;
-  lo -= (us < 0);
-  us += (us < 0) * 1000000;
-  hi -= (lo < 0);
-  lo += (lo < 0) << LO_TIME_BITS;
-  result->hi = hi;
-  result->lo = lo;
-  result->us = us;
-  result->ps = ps;
-  return true;
-}
-
-/* From the time components HIGH, LOW, USEC and PSEC taken from a Lisp
-   list, generate the corresponding time value.
-   If LOW is floating point, the other components should be zero.
-
-   If RESULT is not null, store into *RESULT the converted time.
-   If *DRESULT is not null, store into *DRESULT the number of
-   seconds since the start of the POSIX Epoch.
-
-   Return 1 if successful, 0 if the components are of the
-   wrong type, and -1 if the time is out of range.  */
-int
-decode_time_components (Lisp_Object high, Lisp_Object low, Lisp_Object usec,
-			Lisp_Object psec,
-			struct lisp_time *result, double *dresult)
-{
-  EMACS_INT hi, us, ps;
-  intmax_t lo;
-  if (! (FIXNUMP (high)
-	 && FIXNUMP (usec) && FIXNUMP (psec)))
-    return 0;
-  if (! INTEGERP (low))
-    {
-      if (FLOATP (low))
-	{
-	  double t = XFLOAT_DATA (low);
-	  if (result && ! decode_float_time (t, result))
-	    return -1;
-	  if (dresult)
-	    *dresult = t;
-	  return 1;
-	}
-      else if (NILP (low))
-	{
-	  struct timespec now = current_timespec ();
-	  if (result)
-	    {
-	      result->hi = hi_time (now.tv_sec);
-	      result->lo = lo_time (now.tv_sec);
-	      result->us = now.tv_nsec / 1000;
-	      result->ps = now.tv_nsec % 1000 * 1000;
-	    }
-	  if (dresult)
-	    *dresult = now.tv_sec + now.tv_nsec / 1e9;
-	  return 1;
-	}
-      else
-	return 0;
-    }
-
-  hi = XFIXNUM (high);
-  if (! integer_to_intmax (low, &lo))
-    return -1;
-  us = XFIXNUM (usec);
-  ps = XFIXNUM (psec);
-
-  /* Normalize out-of-range lower-order components by carrying
-     each overflow into the next higher-order component.  */
-  us += ps / 1000000 - (ps % 1000000 < 0);
-  lo += us / 1000000 - (us % 1000000 < 0);
-  if (INT_ADD_WRAPV (lo >> LO_TIME_BITS, hi, &hi))
-    return -1;
-  ps = ps % 1000000 + 1000000 * (ps % 1000000 < 0);
-  us = us % 1000000 + 1000000 * (us % 1000000 < 0);
-  lo &= (1 << LO_TIME_BITS) - 1;
-
-  if (result)
-    {
-      if (FIXNUM_OVERFLOW_P (hi))
-	return -1;
-      result->hi = hi;
-      result->lo = lo;
-      result->us = us;
-      result->ps = ps;
-    }
-
-  if (dresult)
-    {
-      double dhi = hi;
-      *dresult = (us * 1e6 + ps) / 1e12 + lo + dhi * (1 << LO_TIME_BITS);
-    }
-
-  return 1;
-}
-
-struct timespec
-lisp_to_timespec (struct lisp_time t)
-{
-  if (! ((TYPE_SIGNED (time_t) ? TIME_T_MIN >> LO_TIME_BITS <= t.hi : 0 <= t.hi)
-	 && t.hi <= TIME_T_MAX >> LO_TIME_BITS))
-    return invalid_timespec ();
-  time_t s = (t.hi << LO_TIME_BITS) + t.lo;
-  int ns = t.us * 1000 + t.ps / 1000;
-  return make_timespec (s, ns);
-}
-
-/* Decode a Lisp list SPECIFIED_TIME that represents a time.
-   Store its effective length into *PLEN.
-   If SPECIFIED_TIME is nil, use the current time.
-   Signal an error if SPECIFIED_TIME does not represent a time.  */
-static struct lisp_time
-lisp_time_struct (Lisp_Object specified_time, int *plen)
-{
-  Lisp_Object high, low, usec, psec;
-  struct lisp_time t;
-  int len = disassemble_lisp_time (specified_time, &high, &low, &usec, &psec);
-  if (!len)
-    invalid_time ();
-  int val = decode_time_components (high, low, usec, psec, &t, 0);
-  check_time_validity (val);
-  *plen = len;
-  return t;
-}
-
-/* Like lisp_time_struct, except return a struct timespec.
-   Discard any low-order digits.  */
-struct timespec
-lisp_time_argument (Lisp_Object specified_time)
-{
-  int len;
-  struct lisp_time lt = lisp_time_struct (specified_time, &len);
-  struct timespec t = lisp_to_timespec (lt);
-  if (! timespec_valid_p (t))
-    time_overflow ();
-  return t;
-}
-
-/* Like lisp_time_argument, except decode only the seconds part,
-   and do not check the subseconds part.  */
-static time_t
-lisp_seconds_argument (Lisp_Object specified_time)
-{
-  Lisp_Object high, low, usec, psec;
-  struct lisp_time t;
-
-  int val = disassemble_lisp_time (specified_time, &high, &low, &usec, &psec);
-  if (val != 0)
-    {
-      val = decode_time_components (high, low, make_fixnum (0),
-				    make_fixnum (0), &t, 0);
-      if (0 < val
-	  && ! ((TYPE_SIGNED (time_t)
-		 ? TIME_T_MIN >> LO_TIME_BITS <= t.hi
-		 : 0 <= t.hi)
-		&& t.hi <= TIME_T_MAX >> LO_TIME_BITS))
-	val = -1;
-    }
-  check_time_validity (val);
-  return (t.hi << LO_TIME_BITS) + t.lo;
-}
-
-DEFUN ("float-time", Ffloat_time, Sfloat_time, 0, 1, 0,
-       doc: /* Return the current time, as a float number of seconds since the epoch.
-If SPECIFIED-TIME is given, it is the time to convert to float
-instead of the current time.  The argument should have the form
-\(HIGH LOW) or (HIGH LOW USEC) or (HIGH LOW USEC PSEC).  Thus,
-you can use times from `current-time' and from `file-attributes'.
-SPECIFIED-TIME can also have the form (HIGH . LOW), but this is
-considered obsolete.
-
-WARNING: Since the result is floating point, it may not be exact.
-If precise time stamps are required, use either `current-time',
-or (if you need time as a string) `format-time-string'.  */)
-  (Lisp_Object specified_time)
-{
-  double t;
-  Lisp_Object high, low, usec, psec;
-  if (! (disassemble_lisp_time (specified_time, &high, &low, &usec, &psec)
-	 && decode_time_components (high, low, usec, psec, 0, &t)))
-    invalid_time ();
-  return make_float (t);
-}
-
-/* Write information into buffer S of size MAXSIZE, according to the
-   FORMAT of length FORMAT_LEN, using time information taken from *TP.
-   Use the time zone specified by TZ.
-   Use NS as the number of nanoseconds in the %N directive.
-   Return the number of bytes written, not including the terminating
-   '\0'.  If S is NULL, nothing will be written anywhere; so to
-   determine how many bytes would be written, use NULL for S and
-   ((size_t) -1) for MAXSIZE.
-
-   This function behaves like nstrftime, except it allows null
-   bytes in FORMAT and it does not support nanoseconds.  */
-static size_t
-emacs_nmemftime (char *s, size_t maxsize, const char *format,
-		 size_t format_len, const struct tm *tp, timezone_t tz, int ns)
-{
-  size_t total = 0;
-
-  /* Loop through all the null-terminated strings in the format
-     argument.  Normally there's just one null-terminated string, but
-     there can be arbitrarily many, concatenated together, if the
-     format contains '\0' bytes.  nstrftime stops at the first
-     '\0' byte so we must invoke it separately for each such string.  */
-  for (;;)
-    {
-      size_t len;
-      size_t result;
-
-      if (s)
-	s[0] = '\1';
-
-      result = nstrftime (s, maxsize, format, tp, tz, ns);
-
-      if (s)
-	{
-	  if (result == 0 && s[0] != '\0')
-	    return 0;
-	  s += result + 1;
-	}
-
-      maxsize -= result + 1;
-      total += result;
-      len = strlen (format);
-      if (len == format_len)
-	return total;
-      total++;
-      format += len + 1;
-      format_len -= len + 1;
-    }
-}
-
-DEFUN ("format-time-string", Fformat_time_string, Sformat_time_string, 1, 3, 0,
-       doc: /* Use FORMAT-STRING to format the time TIME, or now if omitted or nil.
-TIME is specified as (HIGH LOW USEC PSEC), as returned by
-`current-time' or `file-attributes'.  It can also be a single integer
-number of seconds since the epoch.  The obsolete form (HIGH . LOW) is
-also still accepted.
-
-The optional ZONE is omitted or 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
-`current-time-zone') or an integer (as from `decode-time') applied
-without consideration for daylight saving time.
-
-The value is a copy of FORMAT-STRING, but with certain constructs replaced
-by text that describes the specified date and time in TIME:
-
-%Y is the year, %y within the century, %C the century.
-%G is the year corresponding to the ISO week, %g within the century.
-%m is the numeric month.
-%b and %h are the locale's abbreviated month name, %B the full name.
- (%h is not supported on MS-Windows.)
-%d is the day of the month, zero-padded, %e is blank-padded.
-%u is the numeric day of week from 1 (Monday) to 7, %w from 0 (Sunday) to 6.
-%a is the locale's abbreviated name of the day of week, %A the full name.
-%U is the week number starting on Sunday, %W starting on Monday,
- %V according to ISO 8601.
-%j is the day of the year.
-
-%H is the hour on a 24-hour clock, %I is on a 12-hour clock, %k is like %H
- only blank-padded, %l is like %I blank-padded.
-%p is the locale's equivalent of either AM or PM.
-%q is the calendar quarter (1–4).
-%M is the minute (00-59).
-%S is the second (00-59; 00-60 on platforms with leap seconds)
-%s is the number of seconds since 1970-01-01 00:00:00 +0000.
-%N is the nanosecond, %6N the microsecond, %3N the millisecond, etc.
-%Z is the time zone abbreviation, %z is the numeric form.
-
-%c is the locale's date and time format.
-%x is the locale's "preferred" date format.
-%D is like "%m/%d/%y".
-%F is the ISO 8601 date format (like "%Y-%m-%d").
-
-%R is like "%H:%M", %T is like "%H:%M:%S", %r is like "%I:%M:%S %p".
-%X is the locale's "preferred" time format.
-
-Finally, %n is a newline, %t is a tab, %% is a literal %, and
-unrecognized %-sequences stand for themselves.
-
-Certain flags and modifiers are available with some format controls.
-The flags are `_', `-', `^' and `#'.  For certain characters X,
-%_X is like %X, but padded with blanks; %-X is like %X,
-but without padding.  %^X is like %X, but with all textual
-characters up-cased; %#X is like %X, but with letter-case of
-all textual characters reversed.
-%NX (where N stands for an integer) is like %X,
-but takes up at least N (a number) positions.
-The modifiers are `E' and `O'.  For certain characters X,
-%EX is a locale's alternative version of %X;
-%OX is like %X, but uses the locale's number symbols.
-
-For example, to produce full ISO 8601 format, use "%FT%T%z".
-
-usage: (format-time-string FORMAT-STRING &optional TIME ZONE)  */)
-  (Lisp_Object format_string, Lisp_Object timeval, Lisp_Object zone)
-{
-  struct timespec t = lisp_time_argument (timeval);
-  struct tm tm;
-
-  CHECK_STRING (format_string);
-  format_string = code_convert_string_norecord (format_string,
-						Vlocale_coding_system, 1);
-  return format_time_string (SSDATA (format_string), SBYTES (format_string),
-			     t, zone, &tm);
-}
-
-static Lisp_Object
-format_time_string (char const *format, ptrdiff_t formatlen,
-		    struct timespec t, Lisp_Object zone, struct tm *tmp)
-{
-  char buffer[4000];
-  char *buf = buffer;
-  ptrdiff_t size = sizeof buffer;
-  size_t len;
-  int ns = t.tv_nsec;
-  USE_SAFE_ALLOCA;
-
-  timezone_t tz = tzlookup (zone, false);
-  /* On some systems, like 32-bit MinGW, tv_sec of struct timespec is
-     a 64-bit type, but time_t is a 32-bit type.  emacs_localtime_rz
-     expects a pointer to time_t value.  */
-  time_t tsec = t.tv_sec;
-  tmp = emacs_localtime_rz (tz, &tsec, tmp);
-  if (! tmp)
-    {
-      xtzfree (tz);
-      time_overflow ();
-    }
-  synchronize_system_time_locale ();
-
-  while (true)
-    {
-      buf[0] = '\1';
-      len = emacs_nmemftime (buf, size, format, formatlen, tmp, tz, ns);
-      if ((0 < len && len < size) || (len == 0 && buf[0] == '\0'))
-	break;
-
-      /* Buffer was too small, so make it bigger and try again.  */
-      len = emacs_nmemftime (NULL, SIZE_MAX, format, formatlen, tmp, tz, ns);
-      if (STRING_BYTES_BOUND <= len)
-	{
-	  xtzfree (tz);
-	  string_overflow ();
-	}
-      size = len + 1;
-      buf = SAFE_ALLOCA (size);
-    }
-
-  xtzfree (tz);
-  AUTO_STRING_WITH_LEN (bufstring, buf, len);
-  Lisp_Object result = code_convert_string_norecord (bufstring,
-						     Vlocale_coding_system, 0);
-  SAFE_FREE ();
-  return result;
-}
-
-DEFUN ("decode-time", Fdecode_time, Sdecode_time, 0, 2, 0,
-       doc: /* Decode a time value as (SEC MINUTE HOUR DAY MONTH YEAR DOW DST UTCOFF).
-The optional TIME should be a list of (HIGH LOW . IGNORED),
-as from `current-time' and `file-attributes', or nil to use the
-current time.  It can also be a single integer number of seconds since
-the epoch.  The obsolete form (HIGH . LOW) is also still accepted.
-
-The optional ZONE is omitted or 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
-`current-time-zone') or an integer (the UTC offset in seconds) applied
-without consideration for daylight saving time.
-
-The list has the following nine members: SEC is an integer between 0
-and 60; SEC is 60 for a leap second, which only some operating systems
-support.  MINUTE is an integer between 0 and 59.  HOUR is an integer
-between 0 and 23.  DAY is an integer between 1 and 31.  MONTH is an
-integer between 1 and 12.  YEAR is an integer indicating the
-four-digit year.  DOW is the day of week, an integer between 0 and 6,
-where 0 is Sunday.  DST is t if daylight saving time is in effect,
-nil if it is not in effect, and -1 if this information is
-not available.  UTCOFF is an integer indicating the UTC offset in
-seconds, i.e., the number of seconds east of Greenwich.  (Note that
-Common Lisp has different meanings for DOW and UTCOFF.)
-
-usage: (decode-time &optional TIME ZONE)  */)
-  (Lisp_Object specified_time, Lisp_Object zone)
-{
-  time_t time_spec = lisp_seconds_argument (specified_time);
-  struct tm local_tm, gmt_tm;
-  timezone_t tz = tzlookup (zone, false);
-  struct tm *tm = emacs_localtime_rz (tz, &time_spec, &local_tm);
-  xtzfree (tz);
-
-  if (! (tm
-	 && MOST_NEGATIVE_FIXNUM - TM_YEAR_BASE <= local_tm.tm_year
-	 && local_tm.tm_year <= MOST_POSITIVE_FIXNUM - TM_YEAR_BASE))
-    time_overflow ();
-
-  /* Avoid overflow when INT_MAX < EMACS_INT_MAX.  */
-  EMACS_INT tm_year_base = TM_YEAR_BASE;
-
-  return CALLN (Flist,
-		make_fixnum (local_tm.tm_sec),
-		make_fixnum (local_tm.tm_min),
-		make_fixnum (local_tm.tm_hour),
-		make_fixnum (local_tm.tm_mday),
-		make_fixnum (local_tm.tm_mon + 1),
-		make_fixnum (local_tm.tm_year + tm_year_base),
-		make_fixnum (local_tm.tm_wday),
-		(local_tm.tm_isdst < 0 ? make_fixnum (-1)
-		 : local_tm.tm_isdst == 0 ? Qnil : Qt),
-		(HAVE_TM_GMTOFF
-		 ? make_fixnum (tm_gmtoff (&local_tm))
-		 : gmtime_r (&time_spec, &gmt_tm)
-		 ? make_fixnum (tm_diff (&local_tm, &gmt_tm))
-		 : Qnil));
-}
-
-/* Return OBJ - OFFSET, checking that OBJ is a valid fixnum and that
-   the result is representable as an int.  */
-static int
-check_tm_member (Lisp_Object obj, int offset)
-{
-  CHECK_FIXNUM (obj);
-  EMACS_INT n = XFIXNUM (obj);
-  int result;
-  if (INT_SUBTRACT_WRAPV (n, offset, &result))
-    time_overflow ();
-  return result;
-}
-
-DEFUN ("encode-time", Fencode_time, Sencode_time, 6, MANY, 0,
-       doc: /* Convert SECOND, MINUTE, HOUR, DAY, MONTH, YEAR and ZONE to internal time.
-This is the reverse operation of `decode-time', which see.
-
-The optional ZONE is omitted or 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
-`current-time-zone') or an integer (as from `decode-time') applied
-without consideration for daylight saving time.
-
-You can pass more than 7 arguments; then the first six arguments
-are used as SECOND through YEAR, and the *last* argument is used as ZONE.
-The intervening arguments are ignored.
-This feature lets (apply \\='encode-time (decode-time ...)) work.
-
-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.
-Year numbers less than 100 are treated just like other year numbers.
-If you want them to stand for years in this century, you must do that yourself.
-
-Years before 1970 are not guaranteed to work.  On some systems,
-year values as low as 1901 do work.
-
-usage: (encode-time SECOND MINUTE HOUR DAY MONTH YEAR &optional ZONE)  */)
-  (ptrdiff_t nargs, Lisp_Object *args)
-{
-  time_t value;
-  struct tm tm;
-  Lisp_Object zone = (nargs > 6 ? args[nargs - 1] : Qnil);
-
-  tm.tm_sec  = check_tm_member (args[0], 0);
-  tm.tm_min  = check_tm_member (args[1], 0);
-  tm.tm_hour = check_tm_member (args[2], 0);
-  tm.tm_mday = check_tm_member (args[3], 0);
-  tm.tm_mon  = check_tm_member (args[4], 1);
-  tm.tm_year = check_tm_member (args[5], TM_YEAR_BASE);
-  tm.tm_isdst = -1;
-
-  timezone_t tz = tzlookup (zone, false);
-  value = emacs_mktime_z (tz, &tm);
-  xtzfree (tz);
-
-  if (value == (time_t) -1)
-    time_overflow ();
-
-  return list2i (hi_time (value), lo_time (value));
-}
-
-DEFUN ("current-time-string", Fcurrent_time_string, Scurrent_time_string,
-       0, 2, 0,
-       doc: /* Return the current local time, as a human-readable string.
-Programs can use this function to decode a time,
-since the number of columns in each field is fixed
-if the year is in the range 1000-9999.
-The format is `Sun Sep 16 01:03:52 1973'.
-However, see also the functions `decode-time' and `format-time-string'
-which provide a much more powerful and general facility.
-
-If SPECIFIED-TIME is given, it is a time to format instead of the
-current time.  The argument should have the form (HIGH LOW . IGNORED).
-Thus, you can use times obtained from `current-time' and from
-`file-attributes'.  SPECIFIED-TIME can also be a single integer number
-of seconds since the epoch.  The obsolete form (HIGH . LOW) is also
-still accepted.
-
-The optional ZONE is omitted or 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
-`current-time-zone') or an integer (as from `decode-time') applied
-without consideration for daylight saving time.  */)
-  (Lisp_Object specified_time, Lisp_Object zone)
-{
-  time_t value = lisp_seconds_argument (specified_time);
-  timezone_t tz = tzlookup (zone, false);
-
-  /* Convert to a string in ctime format, except without the trailing
-     newline, and without the 4-digit year limit.  Don't use asctime
-     or ctime, as they might dump core if the year is outside the
-     range -999 .. 9999.  */
-  struct tm tm;
-  struct tm *tmp = emacs_localtime_rz (tz, &value, &tm);
-  xtzfree (tz);
-  if (! tmp)
-    time_overflow ();
-
-  static char const wday_name[][4] =
-    { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
-  static char const mon_name[][4] =
-    { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
-      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
-  printmax_t year_base = TM_YEAR_BASE;
-  char buf[sizeof "Mon Apr 30 12:49:17 " + INT_STRLEN_BOUND (int) + 1];
-  int len = sprintf (buf, "%s %s%3d %02d:%02d:%02d %"pMd,
-		     wday_name[tm.tm_wday], mon_name[tm.tm_mon], tm.tm_mday,
-		     tm.tm_hour, tm.tm_min, tm.tm_sec,
-		     tm.tm_year + year_base);
-
-  return make_unibyte_string (buf, len);
-}
-
-/* Yield A - B, measured in seconds.
-   This function is copied from the GNU C Library.  */
-static int
-tm_diff (struct tm *a, struct tm *b)
-{
-  /* Compute intervening leap days correctly even if year is negative.
-     Take care to avoid int overflow in leap day calculations,
-     but it's OK to assume that A and B are close to each other.  */
-  int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
-  int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
-  int a100 = a4 / 25 - (a4 % 25 < 0);
-  int b100 = b4 / 25 - (b4 % 25 < 0);
-  int a400 = a100 >> 2;
-  int b400 = b100 >> 2;
-  int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
-  int years = a->tm_year - b->tm_year;
-  int days = (365 * years + intervening_leap_days
-	      + (a->tm_yday - b->tm_yday));
-  return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
-		+ (a->tm_min - b->tm_min))
-	  + (a->tm_sec - b->tm_sec));
-}
-
-/* Yield A's UTC offset, or an unspecified value if unknown.  */
-static long int
-tm_gmtoff (struct tm *a)
-{
-#if HAVE_TM_GMTOFF
-  return a->tm_gmtoff;
-#else
-  return 0;
-#endif
-}
-
-DEFUN ("current-time-zone", Fcurrent_time_zone, Scurrent_time_zone, 0, 2, 0,
-       doc: /* Return the offset and name for the local time zone.
-This returns a list of the form (OFFSET NAME).
-OFFSET is an integer number of seconds ahead of UTC (east of Greenwich).
-    A negative value means west of Greenwich.
-NAME is a string giving the name of the time zone.
-If SPECIFIED-TIME is given, the time zone offset is determined from it
-instead of using the current time.  The argument should have the form
-\(HIGH LOW . IGNORED).  Thus, you can use times obtained from
-`current-time' and from `file-attributes'.  SPECIFIED-TIME can also be
-a single integer number of seconds since the epoch.  The obsolete form
-(HIGH . LOW) is also still accepted.
-
-The optional ZONE is omitted or 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
-`current-time-zone') or an integer (as from `decode-time') applied
-without consideration for daylight saving time.
-
-Some operating systems cannot provide all this information to Emacs;
-in this case, `current-time-zone' returns a list containing nil for
-the data it can't find.  */)
-  (Lisp_Object specified_time, Lisp_Object zone)
-{
-  struct timespec value;
-  struct tm local_tm, gmt_tm;
-  Lisp_Object zone_offset, zone_name;
-
-  zone_offset = Qnil;
-  value = make_timespec (lisp_seconds_argument (specified_time), 0);
-  zone_name = format_time_string ("%Z", sizeof "%Z" - 1, value,
-				  zone, &local_tm);
-
-  /* gmtime_r expects a pointer to time_t, but tv_sec of struct
-     timespec on some systems (MinGW) is a 64-bit field.  */
-  time_t tsec = value.tv_sec;
-  if (HAVE_TM_GMTOFF || gmtime_r (&tsec, &gmt_tm))
-    {
-      long int offset = (HAVE_TM_GMTOFF
-			 ? tm_gmtoff (&local_tm)
-			 : tm_diff (&local_tm, &gmt_tm));
-      zone_offset = make_fixnum (offset);
-      if (SCHARS (zone_name) == 0)
-	{
-	  /* No local time zone name is available; use numeric zone instead.  */
-	  long int hour = offset / 3600;
-	  int min_sec = offset % 3600;
-	  int amin_sec = min_sec < 0 ? - min_sec : min_sec;
-	  int min = amin_sec / 60;
-	  int sec = amin_sec % 60;
-	  int min_prec = min_sec ? 2 : 0;
-	  int sec_prec = sec ? 2 : 0;
-	  char buf[sizeof "+0000" + INT_STRLEN_BOUND (long int)];
-	  zone_name = make_formatted_string (buf, "%c%.2ld%.*d%.*d",
-					     (offset < 0 ? '-' : '+'),
-					     hour, min_prec, min, sec_prec, sec);
-	}
-    }
-
-  return list2 (zone_offset, zone_name);
-}
-
-DEFUN ("set-time-zone-rule", Fset_time_zone_rule, Sset_time_zone_rule, 1, 1, 0,
-       doc: /* Set the Emacs local time zone using TZ, a string specifying a time zone rule.
-If TZ is nil or `wall', use system wall clock time; this differs from
-the usual Emacs convention where nil means current local time.  If TZ
-is t, use Universal Time.  If TZ is a list (as from
-`current-time-zone') or an integer (as from `decode-time'), use the
-specified time zone without consideration for daylight saving time.
-
-Instead of calling this function, you typically want something else.
-To temporarily use a different time zone rule for just one invocation
-of `decode-time', `encode-time', or `format-time-string', pass the
-function a ZONE argument.  To change local time consistently
-throughout Emacs, call (setenv "TZ" TZ): this changes both the
-environment of the Emacs process and the variable
-`process-environment', whereas `set-time-zone-rule' affects only the
-former.  */)
-  (Lisp_Object tz)
-{
-  tzlookup (NILP (tz) ? Qwall : tz, true);
-  return Qnil;
-}
-
-/* A buffer holding a string of the form "TZ=value", intended
-   to be part of the environment.  If TZ is supposed to be unset,
-   the buffer string is "tZ=".  */
- static char *tzvalbuf;
-
-/* Get the local time zone rule.  */
-char *
-emacs_getenv_TZ (void)
-{
-  return tzvalbuf[0] == 'T' ? tzvalbuf + tzeqlen : 0;
-}
-
-/* Set the local time zone rule to TZSTRING, which can be null to
-   denote wall clock time.  Do not record the setting in LOCAL_TZ.
-
-   This function is not thread-safe, in theory because putenv is not,
-   but mostly because of the static storage it updates.  Other threads
-   that invoke localtime etc. may be adversely affected while this
-   function is executing.  */
-
-int
-emacs_setenv_TZ (const char *tzstring)
-{
-  static ptrdiff_t tzvalbufsize;
-  ptrdiff_t tzstringlen = tzstring ? strlen (tzstring) : 0;
-  char *tzval = tzvalbuf;
-  bool new_tzvalbuf = tzvalbufsize <= tzeqlen + tzstringlen;
-
-  if (new_tzvalbuf)
-    {
-      /* Do not attempt to free the old tzvalbuf, since another thread
-	 may be using it.  In practice, the first allocation is large
-	 enough and memory does not leak.  */
-      tzval = xpalloc (NULL, &tzvalbufsize,
-		       tzeqlen + tzstringlen - tzvalbufsize + 1, -1, 1);
-      tzvalbuf = tzval;
-      tzval[1] = 'Z';
-      tzval[2] = '=';
-    }
-
-  if (tzstring)
-    {
-      /* Modify TZVAL in place.  Although this is dicey in a
-	 multithreaded environment, we know of no portable alternative.
-	 Calling putenv or setenv could crash some other thread.  */
-      tzval[0] = 'T';
-      strcpy (tzval + tzeqlen, tzstring);
-    }
-  else
-    {
-      /* Turn 'TZ=whatever' into an empty environment variable 'tZ='.
-	 Although this is also dicey, calling unsetenv here can crash Emacs.
-	 See Bug#8705.  */
-      tzval[0] = 't';
-      tzval[tzeqlen] = 0;
-    }
-
-
-#ifndef WINDOWSNT
-  /* Modifying *TZVAL merely requires calling tzset (which is the
-     caller's responsibility).  However, modifying TZVAL requires
-     calling putenv; although this is not thread-safe, in practice this
-     runs only on startup when there is only one thread.  */
-  bool need_putenv = new_tzvalbuf;
-#else
-  /* MS-Windows 'putenv' copies the argument string into a block it
-     allocates, so modifying *TZVAL will not change the environment.
-     However, the other threads run by Emacs on MS-Windows never call
-     'xputenv' or 'putenv' or 'unsetenv', so the original cause for the
-     dicey in-place modification technique doesn't exist there in the
-     first place.  */
-  bool need_putenv = true;
-#endif
-  if (need_putenv)
-    xputenv (tzval);
-
-  return 0;
-}
-\f
 /* Insert NARGS Lisp objects in the array ARGS by calling INSERT_FUNC
    (if a type of object is Lisp_Int) or INSERT_FROM_STRING_FUNC (if a
    type of object is Lisp_String).  INHERIT is passed to
@@ -5764,19 +4494,6 @@ it to be non-nil.  */);
   defsubr (&Sgroup_real_gid);
   defsubr (&Suser_full_name);
   defsubr (&Semacs_pid);
-  defsubr (&Scurrent_time);
-  defsubr (&Stime_add);
-  defsubr (&Stime_subtract);
-  defsubr (&Stime_equal_p);
-  defsubr (&Stime_less_p);
-  defsubr (&Sget_internal_run_time);
-  defsubr (&Sformat_time_string);
-  defsubr (&Sfloat_time);
-  defsubr (&Sdecode_time);
-  defsubr (&Sencode_time);
-  defsubr (&Scurrent_time_string);
-  defsubr (&Scurrent_time_zone);
-  defsubr (&Sset_time_zone_rule);
   defsubr (&Ssystem_name);
   defsubr (&Smessage);
   defsubr (&Smessage_box);
diff --git a/src/emacs.c b/src/emacs.c
index b1c96d1828..a633c01b09 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -1512,6 +1512,8 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
       syms_of_minibuf ();
       syms_of_process ();
       syms_of_search ();
+      syms_of_sysdep ();
+      syms_of_systime ();
       syms_of_frame ();
       syms_of_syntax ();
       syms_of_terminal ();
@@ -1653,9 +1655,11 @@ Using an Emacs configured with --with-x-toolkit=lucid does not have this problem
 
   init_charset ();
 
-  /* This calls putenv and so must precede init_process_emacs.  Also,
-     it sets Voperating_system_release, which init_process_emacs uses.  */
-  init_editfns (dumping);
+  /* This calls putenv and so must precede init_process_emacs.  */
+  init_systime (dumping);
+
+  /* This sets Voperating_system_release, which init_process_emacs uses.  */
+  init_editfns ();
 
   /* These two call putenv.  */
 #ifdef HAVE_DBUS
diff --git a/src/lisp.h b/src/lisp.h
index bb190b691b..ae329268dc 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -4014,11 +4014,10 @@ extern void save_excursion_save (union specbinding *);
 extern void save_excursion_restore (Lisp_Object, Lisp_Object);
 extern Lisp_Object save_restriction_save (void);
 extern void save_restriction_restore (Lisp_Object);
-extern _Noreturn void time_overflow (void);
 extern Lisp_Object make_buffer_string (ptrdiff_t, ptrdiff_t, bool);
 extern Lisp_Object make_buffer_string_both (ptrdiff_t, ptrdiff_t, ptrdiff_t,
 					    ptrdiff_t, bool);
-extern void init_editfns (bool);
+extern void init_editfns (void);
 extern void syms_of_editfns (void);
 
 /* Defined in buffer.c.  */
@@ -4355,6 +4354,7 @@ extern ptrdiff_t emacs_write_quit (int, void const *, ptrdiff_t);
 extern void emacs_perror (char const *);
 extern int renameat_noreplace (int, char const *, int, char const *);
 extern int str_collate (Lisp_Object, Lisp_Object, Lisp_Object, Lisp_Object);
+extern void syms_of_sysdep (void);
 
 /* Defined in filelock.c.  */
 extern void lock_file (Lisp_Object);
diff --git a/src/sysdep.c b/src/sysdep.c
index 722d8138de..0695686361 100644
--- a/src/sysdep.c
+++ b/src/sysdep.c
@@ -91,13 +91,19 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #include <sys/file.h>
 #include <fcntl.h>
 
+#include "syssignal.h"
+#include "systime.h"
 #include "systty.h"
 #include "syswait.h"
 
+#ifdef HAVE_SYS_RESOURCE_H
+# include <sys/resource.h>
+#endif
+
 #ifdef HAVE_SYS_UTSNAME_H
-#include <sys/utsname.h>
-#include <memory.h>
-#endif /* HAVE_SYS_UTSNAME_H */
+# include <sys/utsname.h>
+# include <memory.h>
+#endif
 
 #include "keyboard.h"
 #include "frame.h"
@@ -118,18 +124,15 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #endif
 
 #ifdef WINDOWSNT
-#include <direct.h>
+# include <direct.h>
 /* In process.h which conflicts with the local copy.  */
-#define _P_WAIT 0
+# define _P_WAIT 0
 int _cdecl _spawnlp (int, const char *, const char *, ...);
 /* The following is needed for O_CLOEXEC, F_SETFD, FD_CLOEXEC, and
    several prototypes of functions called below.  */
-#include <sys/socket.h>
+# include <sys/socket.h>
 #endif
 
-#include "syssignal.h"
-#include "systime.h"
-
 /* ULLONG_MAX is missing on Red Hat Linux 7.3; see Bug#11781.  */
 #ifndef ULLONG_MAX
 #define ULLONG_MAX TYPE_MAXIMUM (unsigned long long int)
@@ -2704,30 +2707,6 @@ emacs_perror (char const *message)
   errno = err;
 }
 \f
-/* Return a struct timeval that is roughly equivalent to T.
-   Use the least timeval not less than T.
-   Return an extremal value if the result would overflow.  */
-struct timeval
-make_timeval (struct timespec t)
-{
-  struct timeval tv;
-  tv.tv_sec = t.tv_sec;
-  tv.tv_usec = t.tv_nsec / 1000;
-
-  if (t.tv_nsec % 1000 != 0)
-    {
-      if (tv.tv_usec < 999999)
-	tv.tv_usec++;
-      else if (tv.tv_sec < TYPE_MAXIMUM (time_t))
-	{
-	  tv.tv_sec++;
-	  tv.tv_usec = 0;
-	}
-    }
-
-  return tv;
-}
-
 /* Set the access and modification time stamps of FD (a.k.a. FILE) to be
    ATIME and MTIME, respectively.
    FD must be either negative -- in which case it is ignored --
@@ -3911,6 +3890,42 @@ system_process_attributes (Lisp_Object pid)
 }
 
 #endif	/* !defined (WINDOWSNT) */
+
+DEFUN ("get-internal-run-time", Fget_internal_run_time, Sget_internal_run_time,
+       0, 0, 0,
+       doc: /* Return the current run time used by Emacs.
+The time is returned as in the style of `current-time'.
+
+On systems that can't determine the run time, `get-internal-run-time'
+does the same thing as `current-time'.  */)
+  (void)
+{
+#ifdef HAVE_GETRUSAGE
+  struct rusage usage;
+  time_t secs;
+  int usecs;
+
+  if (getrusage (RUSAGE_SELF, &usage) < 0)
+    /* This shouldn't happen.  What action is appropriate?  */
+    xsignal0 (Qerror);
+
+  /* Sum up user time and system time.  */
+  secs = usage.ru_utime.tv_sec + usage.ru_stime.tv_sec;
+  usecs = usage.ru_utime.tv_usec + usage.ru_stime.tv_usec;
+  if (usecs >= 1000000)
+    {
+      usecs -= 1000000;
+      secs++;
+    }
+  return make_lisp_time (make_timespec (secs, usecs * 1000));
+#else /* ! HAVE_GETRUSAGE  */
+#ifdef WINDOWSNT
+  return w32_get_internal_run_time ();
+#else /* ! WINDOWSNT  */
+  return Fcurrent_time ();
+#endif /* WINDOWSNT  */
+#endif /* HAVE_GETRUSAGE  */
+}
 \f
 /* Wide character string collation.  */
 
@@ -4116,3 +4131,9 @@ str_collate (Lisp_Object s1, Lisp_Object s2,
   return res;
 }
 #endif	/* WINDOWSNT */
+
+void
+syms_of_sysdep (void)
+{
+  defsubr (&Sget_internal_run_time);
+}
diff --git a/src/systime.c b/src/systime.c
new file mode 100644
index 0000000000..acd193bbf5
--- /dev/null
+++ b/src/systime.c
@@ -0,0 +1,1287 @@
+/* Timestamp functions for Emacs
+
+Copyright (C) 1985-1987, 1989, 1993-2018 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include "systime.h"
+
+#include "blockinput.h"
+#include "coding.h"
+#include "lisp.h"
+
+#include <strftime.h>
+
+#include <errno.h>
+#include <math.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#ifdef HAVE_TIMEZONE_T
+# include <sys/param.h>
+# if defined __NetBSD_Version__ && __NetBSD_Version__ < 700000000
+#  define HAVE_TZALLOC_BUG true
+# endif
+#endif
+#ifndef HAVE_TZALLOC_BUG
+# define HAVE_TZALLOC_BUG false
+#endif
+
+#define TM_YEAR_BASE 1900
+
+#ifndef HAVE_TM_GMTOFF
+# define HAVE_TM_GMTOFF false
+#endif
+
+#ifndef TIME_T_MIN
+# define TIME_T_MIN TYPE_MINIMUM (time_t)
+#endif
+#ifndef TIME_T_MAX
+# define TIME_T_MAX TYPE_MAXIMUM (time_t)
+#endif
+
+/* Return a struct timeval that is roughly equivalent to T.
+   Use the least timeval not less than T.
+   Return an extremal value if the result would overflow.  */
+struct timeval
+make_timeval (struct timespec t)
+{
+  struct timeval tv;
+  tv.tv_sec = t.tv_sec;
+  tv.tv_usec = t.tv_nsec / 1000;
+
+  if (t.tv_nsec % 1000 != 0)
+    {
+      if (tv.tv_usec < 999999)
+	tv.tv_usec++;
+      else if (tv.tv_sec < TYPE_MAXIMUM (time_t))
+	{
+	  tv.tv_sec++;
+	  tv.tv_usec = 0;
+	}
+    }
+
+  return tv;
+}
+
+/* Yield A's UTC offset, or an unspecified value if unknown.  */
+static long int
+tm_gmtoff (struct tm *a)
+{
+#if HAVE_TM_GMTOFF
+  return a->tm_gmtoff;
+#else
+  return 0;
+#endif
+}
+
+/* Yield A - B, measured in seconds.
+   This function is copied from the GNU C Library.  */
+static int
+tm_diff (struct tm *a, struct tm *b)
+{
+  /* Compute intervening leap days correctly even if year is negative.
+     Take care to avoid int overflow in leap day calculations,
+     but it's OK to assume that A and B are close to each other.  */
+  int a4 = (a->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (a->tm_year & 3);
+  int b4 = (b->tm_year >> 2) + (TM_YEAR_BASE >> 2) - ! (b->tm_year & 3);
+  int a100 = a4 / 25 - (a4 % 25 < 0);
+  int b100 = b4 / 25 - (b4 % 25 < 0);
+  int a400 = a100 >> 2;
+  int b400 = b100 >> 2;
+  int intervening_leap_days = (a4 - b4) - (a100 - b100) + (a400 - b400);
+  int years = a->tm_year - b->tm_year;
+  int days = (365 * years + intervening_leap_days
+	      + (a->tm_yday - b->tm_yday));
+  return (60 * (60 * (24 * days + (a->tm_hour - b->tm_hour))
+		+ (a->tm_min - b->tm_min))
+	  + (a->tm_sec - b->tm_sec));
+}
+
+enum { tzeqlen = sizeof "TZ=" - 1 };
+
+/* Time zones equivalent to current local time and to UTC, respectively.  */
+static timezone_t local_tz;
+static timezone_t const utc_tz = 0;
+
+static struct tm *
+emacs_localtime_rz (timezone_t tz, time_t const *t, struct tm *tm)
+{
+  tm = localtime_rz (tz, t, tm);
+  if (!tm && errno == ENOMEM)
+    memory_full (SIZE_MAX);
+  return tm;
+}
+
+static time_t
+emacs_mktime_z (timezone_t tz, struct tm *tm)
+{
+  errno = 0;
+  time_t t = mktime_z (tz, tm);
+  if (t == (time_t) -1 && errno == ENOMEM)
+    memory_full (SIZE_MAX);
+  return t;
+}
+
+static _Noreturn void
+invalid_time_zone_specification (Lisp_Object zone)
+{
+  xsignal2 (Qerror, build_string ("Invalid time zone specification"), zone);
+}
+
+/* Free a timezone, except do not free the time zone for local time.
+   Freeing utc_tz is also a no-op.  */
+static void
+xtzfree (timezone_t tz)
+{
+  if (tz != local_tz)
+    tzfree (tz);
+}
+
+/* Convert the Lisp time zone rule ZONE to a timezone_t object.
+   The returned value either is 0, or is LOCAL_TZ, or is newly allocated.
+   If SETTZ, set Emacs local time to the time zone rule; otherwise,
+   the caller should eventually pass the returned value to xtzfree.  */
+static timezone_t
+tzlookup (Lisp_Object zone, bool settz)
+{
+  static char const tzbuf_format[] = "<%+.*"pI"d>%s%"pI"d:%02d:%02d";
+  char const *trailing_tzbuf_format = tzbuf_format + sizeof "<%+.*"pI"d" - 1;
+  char tzbuf[sizeof tzbuf_format + 2 * INT_STRLEN_BOUND (EMACS_INT)];
+  char const *zone_string;
+  timezone_t new_tz;
+
+  if (NILP (zone))
+    return local_tz;
+  else if (EQ (zone, Qt) || EQ (zone, make_fixnum (0)))
+    {
+      zone_string = "UTC0";
+      new_tz = utc_tz;
+    }
+  else
+    {
+      bool plain_integer = FIXNUMP (zone);
+
+      if (EQ (zone, Qwall))
+	zone_string = 0;
+      else if (STRINGP (zone))
+	zone_string = SSDATA (ENCODE_SYSTEM (zone));
+      else if (plain_integer || (CONSP (zone) && FIXNUMP (XCAR (zone))
+				 && CONSP (XCDR (zone))))
+	{
+	  Lisp_Object abbr UNINIT;
+	  if (!plain_integer)
+	    {
+	      abbr = XCAR (XCDR (zone));
+	      zone = XCAR (zone);
+	    }
+
+	  EMACS_INT abszone = eabs (XFIXNUM (zone)), hour = abszone / (60 * 60);
+	  int hour_remainder = abszone % (60 * 60);
+	  int min = hour_remainder / 60, sec = hour_remainder % 60;
+
+	  if (plain_integer)
+	    {
+	      int prec = 2;
+	      EMACS_INT numzone = hour;
+	      if (hour_remainder != 0)
+		{
+		  prec += 2, numzone = 100 * numzone + min;
+		  if (sec != 0)
+		    prec += 2, numzone = 100 * numzone + sec;
+		}
+	      sprintf (tzbuf, tzbuf_format, prec,
+		       XFIXNUM (zone) < 0 ? -numzone : numzone,
+		       &"-"[XFIXNUM (zone) < 0], hour, min, sec);
+	      zone_string = tzbuf;
+	    }
+	  else
+	    {
+	      AUTO_STRING (leading, "<");
+	      AUTO_STRING_WITH_LEN (trailing, tzbuf,
+				    sprintf (tzbuf, trailing_tzbuf_format,
+					     &"-"[XFIXNUM (zone) < 0],
+					     hour, min, sec));
+	      zone_string = SSDATA (concat3 (leading, ENCODE_SYSTEM (abbr),
+					     trailing));
+	    }
+	}
+      else
+	invalid_time_zone_specification (zone);
+
+      new_tz = tzalloc (zone_string);
+
+      if (HAVE_TZALLOC_BUG && !new_tz && errno != ENOMEM && plain_integer
+	  && XFIXNUM (zone) % (60 * 60) == 0)
+	{
+	  /* tzalloc mishandles POSIX strings; fall back on tzdb if
+	     possible (Bug#30738).  */
+	  sprintf (tzbuf, "Etc/GMT%+"pI"d", - (XFIXNUM (zone) / (60 * 60)));
+	  new_tz = tzalloc (zone_string);
+	}
+
+      if (!new_tz)
+	{
+	  if (errno == ENOMEM)
+	    memory_full (SIZE_MAX);
+	  invalid_time_zone_specification (zone);
+	}
+    }
+
+  if (settz)
+    {
+      block_input ();
+      emacs_setenv_TZ (zone_string);
+      tzset ();
+      timezone_t old_tz = local_tz;
+      local_tz = new_tz;
+      tzfree (old_tz);
+      unblock_input ();
+    }
+
+  return new_tz;
+}
+
+void
+init_systime (bool dumping)
+{
+#ifndef CANNOT_DUMP
+  /* A valid but unlikely setting for the TZ environment variable.
+     It is OK (though a bit slower) if the user chooses this value.  */
+  static char dump_tz_string[] = "TZ=UtC0";
+
+  /* When just dumping out, set the time zone to a known unlikely value
+     and skip the rest of this function.  */
+  if (dumping)
+    {
+      xputenv (dump_tz_string);
+      tzset ();
+      return;
+    }
+#endif
+
+  char *tz = getenv ("TZ");
+
+#if !defined CANNOT_DUMP
+  /* If the execution TZ happens to be the same as the dump TZ,
+     change it to some other value and then change it back,
+     to force the underlying implementation to reload the TZ info.
+     This is needed on implementations that load TZ info from files,
+     since the TZ file contents may differ between dump and execution.  */
+  if (tz && strcmp (tz, &dump_tz_string[tzeqlen]) == 0)
+    {
+      ++*tz;
+      tzset ();
+      --*tz;
+    }
+#endif
+
+  /* Set the time zone rule now, so that the call to putenv is done
+     before multiple threads are active.  */
+  tzlookup (tz ? build_string (tz) : Qwall, true);
+}
+
+/* Report that a time value is out of range for Emacs.  */
+void
+time_overflow (void)
+{
+  error ("Specified time is not representable");
+}
+
+static _Noreturn void
+invalid_time (void)
+{
+  error ("Invalid time specification");
+}
+
+/* Check a return value compatible with that of decode_time_components.  */
+static void
+check_time_validity (int validity)
+{
+  if (validity <= 0)
+    {
+      if (validity < 0)
+	time_overflow ();
+      else
+	invalid_time ();
+    }
+}
+
+/* Return the upper part of the time T (everything but the bottom 16 bits).  */
+static EMACS_INT
+hi_time (time_t t)
+{
+  time_t hi = t >> LO_TIME_BITS;
+  if (FIXNUM_OVERFLOW_P (hi))
+    time_overflow ();
+  return hi;
+}
+
+/* Return the bottom bits of the time T.  */
+static int
+lo_time (time_t t)
+{
+  return t & ((1 << LO_TIME_BITS) - 1);
+}
+
+/* Decode a Lisp list SPECIFIED_TIME that represents a time.
+   Set *PHIGH, *PLOW, *PUSEC, *PPSEC to its parts; do not check their values.
+   Return 2, 3, or 4 to indicate the effective length of SPECIFIED_TIME
+   if successful, 0 if unsuccessful.  */
+static int
+disassemble_lisp_time (Lisp_Object specified_time, Lisp_Object *phigh,
+		       Lisp_Object *plow, Lisp_Object *pusec,
+		       Lisp_Object *ppsec)
+{
+  Lisp_Object high = make_fixnum (0);
+  Lisp_Object low = specified_time;
+  Lisp_Object usec = make_fixnum (0);
+  Lisp_Object psec = make_fixnum (0);
+  int len = 4;
+
+  if (CONSP (specified_time))
+    {
+      high = XCAR (specified_time);
+      low = XCDR (specified_time);
+      if (CONSP (low))
+	{
+	  Lisp_Object low_tail = XCDR (low);
+	  low = XCAR (low);
+	  if (CONSP (low_tail))
+	    {
+	      usec = XCAR (low_tail);
+	      low_tail = XCDR (low_tail);
+	      if (CONSP (low_tail))
+		psec = XCAR (low_tail);
+	      else
+		len = 3;
+	    }
+	  else if (!NILP (low_tail))
+	    {
+	      usec = low_tail;
+	      len = 3;
+	    }
+	  else
+	    len = 2;
+	}
+      else
+	len = 2;
+
+      /* When combining components, require LOW to be an integer,
+	 as otherwise it would be a pain to add up times.  */
+      if (! INTEGERP (low))
+	return 0;
+    }
+  else if (INTEGERP (specified_time))
+    len = 2;
+
+  *phigh = high;
+  *plow = low;
+  *pusec = usec;
+  *ppsec = psec;
+  return len;
+}
+
+/* Convert T into an Emacs time *RESULT, truncating toward minus infinity.
+   Return true if T is in range, false otherwise.  */
+static bool
+decode_float_time (double t, struct lisp_time *result)
+{
+  double lo_multiplier = 1 << LO_TIME_BITS;
+  double emacs_time_min = MOST_NEGATIVE_FIXNUM * lo_multiplier;
+  if (! (emacs_time_min <= t && t < -emacs_time_min))
+    return false;
+
+  double small_t = t / lo_multiplier;
+  EMACS_INT hi = small_t;
+  double t_sans_hi = t - hi * lo_multiplier;
+  int lo = t_sans_hi;
+  long double fracps = (t_sans_hi - lo) * 1e12L;
+#ifdef INT_FAST64_MAX
+  int_fast64_t ifracps = fracps;
+  int us = ifracps / 1000000;
+  int ps = ifracps % 1000000;
+#else
+  int us = fracps / 1e6L;
+  int ps = fracps - us * 1e6L;
+#endif
+  us -= (ps < 0);
+  ps += (ps < 0) * 1000000;
+  lo -= (us < 0);
+  us += (us < 0) * 1000000;
+  hi -= (lo < 0);
+  lo += (lo < 0) << LO_TIME_BITS;
+  result->hi = hi;
+  result->lo = lo;
+  result->us = us;
+  result->ps = ps;
+  return true;
+}
+
+/* From the time components HIGH, LOW, USEC and PSEC taken from a Lisp
+   list, generate the corresponding time value.
+   If LOW is floating point, the other components should be zero.
+
+   If RESULT is not null, store into *RESULT the converted time.
+   If *DRESULT is not null, store into *DRESULT the number of
+   seconds since the start of the POSIX Epoch.
+
+   Return 1 if successful, 0 if the components are of the
+   wrong type, and -1 if the time is out of range.  */
+int
+decode_time_components (Lisp_Object high, Lisp_Object low, Lisp_Object usec,
+			Lisp_Object psec,
+			struct lisp_time *result, double *dresult)
+{
+  EMACS_INT hi, us, ps;
+  intmax_t lo;
+  if (! (FIXNUMP (high)
+	 && FIXNUMP (usec) && FIXNUMP (psec)))
+    return 0;
+  if (! INTEGERP (low))
+    {
+      if (FLOATP (low))
+	{
+	  double t = XFLOAT_DATA (low);
+	  if (result && ! decode_float_time (t, result))
+	    return -1;
+	  if (dresult)
+	    *dresult = t;
+	  return 1;
+	}
+      else if (NILP (low))
+	{
+	  struct timespec now = current_timespec ();
+	  if (result)
+	    {
+	      result->hi = hi_time (now.tv_sec);
+	      result->lo = lo_time (now.tv_sec);
+	      result->us = now.tv_nsec / 1000;
+	      result->ps = now.tv_nsec % 1000 * 1000;
+	    }
+	  if (dresult)
+	    *dresult = now.tv_sec + now.tv_nsec / 1e9;
+	  return 1;
+	}
+      else
+	return 0;
+    }
+
+  hi = XFIXNUM (high);
+  if (! integer_to_intmax (low, &lo))
+    return -1;
+  us = XFIXNUM (usec);
+  ps = XFIXNUM (psec);
+
+  /* Normalize out-of-range lower-order components by carrying
+     each overflow into the next higher-order component.  */
+  us += ps / 1000000 - (ps % 1000000 < 0);
+  lo += us / 1000000 - (us % 1000000 < 0);
+  if (INT_ADD_WRAPV (lo >> LO_TIME_BITS, hi, &hi))
+    return -1;
+  ps = ps % 1000000 + 1000000 * (ps % 1000000 < 0);
+  us = us % 1000000 + 1000000 * (us % 1000000 < 0);
+  lo &= (1 << LO_TIME_BITS) - 1;
+
+  if (result)
+    {
+      if (FIXNUM_OVERFLOW_P (hi))
+	return -1;
+      result->hi = hi;
+      result->lo = lo;
+      result->us = us;
+      result->ps = ps;
+    }
+
+  if (dresult)
+    {
+      double dhi = hi;
+      *dresult = (us * 1e6 + ps) / 1e12 + lo + dhi * (1 << LO_TIME_BITS);
+    }
+
+  return 1;
+}
+
+struct timespec
+lisp_to_timespec (struct lisp_time t)
+{
+  if (! ((TYPE_SIGNED (time_t) ? TIME_T_MIN >> LO_TIME_BITS <= t.hi : 0 <= t.hi)
+	 && t.hi <= TIME_T_MAX >> LO_TIME_BITS))
+    return invalid_timespec ();
+  time_t s = (t.hi << LO_TIME_BITS) + t.lo;
+  int ns = t.us * 1000 + t.ps / 1000;
+  return make_timespec (s, ns);
+}
+
+/* Decode a Lisp list SPECIFIED_TIME that represents a time.
+   Store its effective length into *PLEN.
+   If SPECIFIED_TIME is nil, use the current time.
+   Signal an error if SPECIFIED_TIME does not represent a time.  */
+static struct lisp_time
+lisp_time_struct (Lisp_Object specified_time, int *plen)
+{
+  Lisp_Object high, low, usec, psec;
+  struct lisp_time t;
+  int len = disassemble_lisp_time (specified_time, &high, &low, &usec, &psec);
+  if (!len)
+    invalid_time ();
+  int val = decode_time_components (high, low, usec, psec, &t, 0);
+  check_time_validity (val);
+  *plen = len;
+  return t;
+}
+
+/* Like lisp_time_struct, except return a struct timespec.
+   Discard any low-order digits.  */
+struct timespec
+lisp_time_argument (Lisp_Object specified_time)
+{
+  int len;
+  struct lisp_time lt = lisp_time_struct (specified_time, &len);
+  struct timespec t = lisp_to_timespec (lt);
+  if (! timespec_valid_p (t))
+    time_overflow ();
+  return t;
+}
+
+/* Like lisp_time_argument, except decode only the seconds part,
+   and do not check the subseconds part.  */
+static time_t
+lisp_seconds_argument (Lisp_Object specified_time)
+{
+  Lisp_Object high, low, usec, psec;
+  struct lisp_time t;
+
+  int val = disassemble_lisp_time (specified_time, &high, &low, &usec, &psec);
+  if (val != 0)
+    {
+      val = decode_time_components (high, low, make_fixnum (0),
+				    make_fixnum (0), &t, 0);
+      if (0 < val
+	  && ! ((TYPE_SIGNED (time_t)
+		 ? TIME_T_MIN >> LO_TIME_BITS <= t.hi
+		 : 0 <= t.hi)
+		&& t.hi <= TIME_T_MAX >> LO_TIME_BITS))
+	val = -1;
+    }
+  check_time_validity (val);
+  return (t.hi << LO_TIME_BITS) + t.lo;
+}
+
+static struct lisp_time
+time_add (struct lisp_time ta, struct lisp_time tb)
+{
+  EMACS_INT hi = ta.hi + tb.hi;
+  int lo = ta.lo + tb.lo;
+  int us = ta.us + tb.us;
+  int ps = ta.ps + tb.ps;
+  us += (1000000 <= ps);
+  ps -= (1000000 <= ps) * 1000000;
+  lo += (1000000 <= us);
+  us -= (1000000 <= us) * 1000000;
+  hi += (1 << LO_TIME_BITS <= lo);
+  lo -= (1 << LO_TIME_BITS <= lo) << LO_TIME_BITS;
+  return (struct lisp_time) { hi, lo, us, ps };
+}
+
+static struct lisp_time
+time_subtract (struct lisp_time ta, struct lisp_time tb)
+{
+  EMACS_INT hi = ta.hi - tb.hi;
+  int lo = ta.lo - tb.lo;
+  int us = ta.us - tb.us;
+  int ps = ta.ps - tb.ps;
+  us -= (ps < 0);
+  ps += (ps < 0) * 1000000;
+  lo -= (us < 0);
+  us += (us < 0) * 1000000;
+  hi -= (lo < 0);
+  lo += (lo < 0) << LO_TIME_BITS;
+  return (struct lisp_time) { hi, lo, us, ps };
+}
+
+static Lisp_Object
+time_arith (Lisp_Object a, Lisp_Object b, bool subtract)
+{
+  if (FLOATP (a) && !isfinite (XFLOAT_DATA (a)))
+    {
+      double da = XFLOAT_DATA (a);
+      double db = XFLOAT_DATA (Ffloat_time (b));
+      return make_float (subtract ? da - db : da + db);
+    }
+  if (FLOATP (b) && !isfinite (XFLOAT_DATA (b)))
+    return subtract ? make_float (-XFLOAT_DATA (b)) : b;
+
+  int alen, blen;
+  struct lisp_time ta = lisp_time_struct (a, &alen);
+  struct lisp_time tb = lisp_time_struct (b, &blen);
+  struct lisp_time t = (subtract ? time_subtract : time_add) (ta, tb);
+  if (FIXNUM_OVERFLOW_P (t.hi))
+    time_overflow ();
+  Lisp_Object val = Qnil;
+
+  switch (max (alen, blen))
+    {
+    default:
+      val = Fcons (make_fixnum (t.ps), val);
+      FALLTHROUGH;
+    case 3:
+      val = Fcons (make_fixnum (t.us), val);
+      FALLTHROUGH;
+    case 2:
+      val = Fcons (make_fixnum (t.lo), val);
+      val = Fcons (make_fixnum (t.hi), val);
+      break;
+    }
+
+  return val;
+}
+
+DEFUN ("time-add", Ftime_add, Stime_add, 2, 2, 0,
+       doc: /* Return the sum of two time values A and B, as a time value.
+A nil value for either argument stands for the current time.
+See `current-time-string' for the various forms of a time value.  */)
+  (Lisp_Object a, Lisp_Object b)
+{
+  return time_arith (a, b, false);
+}
+
+DEFUN ("time-subtract", Ftime_subtract, Stime_subtract, 2, 2, 0,
+       doc: /* Return the difference between two time values A and B, as a time value.
+Use `float-time' to convert the difference into elapsed seconds.
+A nil value for either argument stands for the current time.
+See `current-time-string' for the various forms of a time value.  */)
+  (Lisp_Object a, Lisp_Object b)
+{
+  return time_arith (a, b, true);
+}
+
+/* Return negative, 0, positive if a < b, a == b, a > b respectively.
+   Return positive if either a or b is a NaN; this is good enough
+   for the current callers.  */
+static int
+time_cmp (Lisp_Object a, Lisp_Object b)
+{
+  if ((FLOATP (a) && !isfinite (XFLOAT_DATA (a)))
+      || (FLOATP (b) && !isfinite (XFLOAT_DATA (b))))
+    {
+      double da = FLOATP (a) ? XFLOAT_DATA (a) : 0;
+      double db = FLOATP (b) ? XFLOAT_DATA (b) : 0;
+      return da < db ? -1 : da != db;
+    }
+
+  int alen, blen;
+  struct lisp_time ta = lisp_time_struct (a, &alen);
+  struct lisp_time tb = lisp_time_struct (b, &blen);
+  return (ta.hi != tb.hi ? (ta.hi < tb.hi ? -1 : 1)
+	  : ta.lo != tb.lo ? (ta.lo < tb.lo ? -1 : 1)
+	  : ta.us != tb.us ? (ta.us < tb.us ? -1 : 1)
+	  : ta.ps < tb.ps ? -1 : ta.ps != tb.ps);
+}
+
+DEFUN ("time-less-p", Ftime_less_p, Stime_less_p, 2, 2, 0,
+       doc: /* Return non-nil if time value T1 is earlier than time value T2.
+A nil value for either argument stands for the current time.
+See `current-time-string' for the various forms of a time value.  */)
+  (Lisp_Object t1, Lisp_Object t2)
+{
+  return time_cmp (t1, t2) < 0 ? Qt : Qnil;
+}
+
+DEFUN ("time-equal-p", Ftime_equal_p, Stime_equal_p, 2, 2, 0,
+       doc: /* Return non-nil if T1 and T2 are equal time values.
+A nil value for either argument stands for the current time.
+See `current-time-string' for the various forms of a time value.  */)
+  (Lisp_Object t1, Lisp_Object t2)
+{
+  return time_cmp (t1, t2) == 0 ? Qt : Qnil;
+}
+\f
+
+/* Make a Lisp list that represents the Emacs time T.  T may be an
+   invalid time, with a slightly negative tv_nsec value such as
+   UNKNOWN_MODTIME_NSECS; in that case, the Lisp list contains a
+   correspondingly negative picosecond count.  */
+Lisp_Object
+make_lisp_time (struct timespec t)
+{
+  time_t s = t.tv_sec;
+  int ns = t.tv_nsec;
+  return list4i (hi_time (s), lo_time (s), ns / 1000, ns % 1000 * 1000);
+}
+
+DEFUN ("float-time", Ffloat_time, Sfloat_time, 0, 1, 0,
+       doc: /* Return the current time, as a float number of seconds since the epoch.
+If SPECIFIED-TIME is given, it is the time to convert to float
+instead of the current time.  The argument should have the form
+\(HIGH LOW) or (HIGH LOW USEC) or (HIGH LOW USEC PSEC).  Thus,
+you can use times from `current-time' and from `file-attributes'.
+SPECIFIED-TIME can also have the form (HIGH . LOW), but this is
+considered obsolete.
+
+WARNING: Since the result is floating point, it may not be exact.
+If precise time stamps are required, use either `current-time',
+or (if you need time as a string) `format-time-string'.  */)
+  (Lisp_Object specified_time)
+{
+  double t;
+  Lisp_Object high, low, usec, psec;
+  if (! (disassemble_lisp_time (specified_time, &high, &low, &usec, &psec)
+	 && decode_time_components (high, low, usec, psec, 0, &t)))
+    invalid_time ();
+  return make_float (t);
+}
+
+/* Write information into buffer S of size MAXSIZE, according to the
+   FORMAT of length FORMAT_LEN, using time information taken from *TP.
+   Use the time zone specified by TZ.
+   Use NS as the number of nanoseconds in the %N directive.
+   Return the number of bytes written, not including the terminating
+   '\0'.  If S is NULL, nothing will be written anywhere; so to
+   determine how many bytes would be written, use NULL for S and
+   ((size_t) -1) for MAXSIZE.
+
+   This function behaves like nstrftime, except it allows null
+   bytes in FORMAT and it does not support nanoseconds.  */
+static size_t
+emacs_nmemftime (char *s, size_t maxsize, const char *format,
+		 size_t format_len, const struct tm *tp, timezone_t tz, int ns)
+{
+  size_t total = 0;
+
+  /* Loop through all the null-terminated strings in the format
+     argument.  Normally there's just one null-terminated string, but
+     there can be arbitrarily many, concatenated together, if the
+     format contains '\0' bytes.  nstrftime stops at the first
+     '\0' byte so we must invoke it separately for each such string.  */
+  for (;;)
+    {
+      size_t len;
+      size_t result;
+
+      if (s)
+	s[0] = '\1';
+
+      result = nstrftime (s, maxsize, format, tp, tz, ns);
+
+      if (s)
+	{
+	  if (result == 0 && s[0] != '\0')
+	    return 0;
+	  s += result + 1;
+	}
+
+      maxsize -= result + 1;
+      total += result;
+      len = strlen (format);
+      if (len == format_len)
+	return total;
+      total++;
+      format += len + 1;
+      format_len -= len + 1;
+    }
+}
+
+static Lisp_Object
+format_time_string (char const *format, ptrdiff_t formatlen,
+		    struct timespec t, Lisp_Object zone, struct tm *tmp)
+{
+  char buffer[4000];
+  char *buf = buffer;
+  ptrdiff_t size = sizeof buffer;
+  size_t len;
+  int ns = t.tv_nsec;
+  USE_SAFE_ALLOCA;
+
+  timezone_t tz = tzlookup (zone, false);
+  /* On some systems, like 32-bit MinGW, tv_sec of struct timespec is
+     a 64-bit type, but time_t is a 32-bit type.  emacs_localtime_rz
+     expects a pointer to time_t value.  */
+  time_t tsec = t.tv_sec;
+  tmp = emacs_localtime_rz (tz, &tsec, tmp);
+  if (! tmp)
+    {
+      xtzfree (tz);
+      time_overflow ();
+    }
+  synchronize_system_time_locale ();
+
+  while (true)
+    {
+      buf[0] = '\1';
+      len = emacs_nmemftime (buf, size, format, formatlen, tmp, tz, ns);
+      if ((0 < len && len < size) || (len == 0 && buf[0] == '\0'))
+	break;
+
+      /* Buffer was too small, so make it bigger and try again.  */
+      len = emacs_nmemftime (NULL, SIZE_MAX, format, formatlen, tmp, tz, ns);
+      if (STRING_BYTES_BOUND <= len)
+	{
+	  xtzfree (tz);
+	  string_overflow ();
+	}
+      size = len + 1;
+      buf = SAFE_ALLOCA (size);
+    }
+
+  xtzfree (tz);
+  AUTO_STRING_WITH_LEN (bufstring, buf, len);
+  Lisp_Object result = code_convert_string_norecord (bufstring,
+						     Vlocale_coding_system, 0);
+  SAFE_FREE ();
+  return result;
+}
+
+DEFUN ("format-time-string", Fformat_time_string, Sformat_time_string, 1, 3, 0,
+       doc: /* Use FORMAT-STRING to format the time TIME, or now if omitted or nil.
+TIME is specified as (HIGH LOW USEC PSEC), as returned by
+`current-time' or `file-attributes'.  It can also be a single integer
+number of seconds since the epoch.  The obsolete form (HIGH . LOW) is
+also still accepted.
+
+The optional ZONE is omitted or 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
+`current-time-zone') or an integer (as from `decode-time') applied
+without consideration for daylight saving time.
+
+The value is a copy of FORMAT-STRING, but with certain constructs replaced
+by text that describes the specified date and time in TIME:
+
+%Y is the year, %y within the century, %C the century.
+%G is the year corresponding to the ISO week, %g within the century.
+%m is the numeric month.
+%b and %h are the locale's abbreviated month name, %B the full name.
+ (%h is not supported on MS-Windows.)
+%d is the day of the month, zero-padded, %e is blank-padded.
+%u is the numeric day of week from 1 (Monday) to 7, %w from 0 (Sunday) to 6.
+%a is the locale's abbreviated name of the day of week, %A the full name.
+%U is the week number starting on Sunday, %W starting on Monday,
+ %V according to ISO 8601.
+%j is the day of the year.
+
+%H is the hour on a 24-hour clock, %I is on a 12-hour clock, %k is like %H
+ only blank-padded, %l is like %I blank-padded.
+%p is the locale's equivalent of either AM or PM.
+%q is the calendar quarter (1–4).
+%M is the minute (00-59).
+%S is the second (00-59; 00-60 on platforms with leap seconds)
+%s is the number of seconds since 1970-01-01 00:00:00 +0000.
+%N is the nanosecond, %6N the microsecond, %3N the millisecond, etc.
+%Z is the time zone abbreviation, %z is the numeric form.
+
+%c is the locale's date and time format.
+%x is the locale's "preferred" date format.
+%D is like "%m/%d/%y".
+%F is the ISO 8601 date format (like "%Y-%m-%d").
+
+%R is like "%H:%M", %T is like "%H:%M:%S", %r is like "%I:%M:%S %p".
+%X is the locale's "preferred" time format.
+
+Finally, %n is a newline, %t is a tab, %% is a literal %, and
+unrecognized %-sequences stand for themselves.
+
+Certain flags and modifiers are available with some format controls.
+The flags are `_', `-', `^' and `#'.  For certain characters X,
+%_X is like %X, but padded with blanks; %-X is like %X,
+but without padding.  %^X is like %X, but with all textual
+characters up-cased; %#X is like %X, but with letter-case of
+all textual characters reversed.
+%NX (where N stands for an integer) is like %X,
+but takes up at least N (a number) positions.
+The modifiers are `E' and `O'.  For certain characters X,
+%EX is a locale's alternative version of %X;
+%OX is like %X, but uses the locale's number symbols.
+
+For example, to produce full ISO 8601 format, use "%FT%T%z".
+
+usage: (format-time-string FORMAT-STRING &optional TIME ZONE)  */)
+  (Lisp_Object format_string, Lisp_Object timeval, Lisp_Object zone)
+{
+  struct timespec t = lisp_time_argument (timeval);
+  struct tm tm;
+
+  CHECK_STRING (format_string);
+  format_string = code_convert_string_norecord (format_string,
+						Vlocale_coding_system, 1);
+  return format_time_string (SSDATA (format_string), SBYTES (format_string),
+			     t, zone, &tm);
+}
+
+DEFUN ("decode-time", Fdecode_time, Sdecode_time, 0, 2, 0,
+       doc: /* Decode a time value as (SEC MINUTE HOUR DAY MONTH YEAR DOW DST UTCOFF).
+The optional TIME should be a list of (HIGH LOW . IGNORED),
+as from `current-time' and `file-attributes', or nil to use the
+current time.  It can also be a single integer number of seconds since
+the epoch.  The obsolete form (HIGH . LOW) is also still accepted.
+
+The optional ZONE is omitted or 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
+`current-time-zone') or an integer (the UTC offset in seconds) applied
+without consideration for daylight saving time.
+
+The list has the following nine members: SEC is an integer between 0
+and 60; SEC is 60 for a leap second, which only some operating systems
+support.  MINUTE is an integer between 0 and 59.  HOUR is an integer
+between 0 and 23.  DAY is an integer between 1 and 31.  MONTH is an
+integer between 1 and 12.  YEAR is an integer indicating the
+four-digit year.  DOW is the day of week, an integer between 0 and 6,
+where 0 is Sunday.  DST is t if daylight saving time is in effect,
+nil if it is not in effect, and -1 if daylight saving information is
+not available.  UTCOFF is an integer indicating the UTC offset in
+seconds, i.e., the number of seconds east of Greenwich.  (Note that
+Common Lisp has different meanings for DOW and UTCOFF.)
+
+usage: (decode-time &optional TIME ZONE)  */)
+  (Lisp_Object specified_time, Lisp_Object zone)
+{
+  time_t time_spec = lisp_seconds_argument (specified_time);
+  struct tm local_tm, gmt_tm;
+  timezone_t tz = tzlookup (zone, false);
+  struct tm *tm = emacs_localtime_rz (tz, &time_spec, &local_tm);
+  xtzfree (tz);
+
+  if (! (tm
+	 && MOST_NEGATIVE_FIXNUM - TM_YEAR_BASE <= local_tm.tm_year
+	 && local_tm.tm_year <= MOST_POSITIVE_FIXNUM - TM_YEAR_BASE))
+    time_overflow ();
+
+  /* Avoid overflow when INT_MAX < EMACS_INT_MAX.  */
+  EMACS_INT tm_year_base = TM_YEAR_BASE;
+
+  return CALLN (Flist,
+		make_fixnum (local_tm.tm_sec),
+		make_fixnum (local_tm.tm_min),
+		make_fixnum (local_tm.tm_hour),
+		make_fixnum (local_tm.tm_mday),
+		make_fixnum (local_tm.tm_mon + 1),
+		make_fixnum (local_tm.tm_year + tm_year_base),
+		make_fixnum (local_tm.tm_wday),
+		(local_tm.tm_isdst < 0 ? make_fixnum (-1)
+		 : local_tm.tm_isdst == 0 ? Qnil : Qt),
+		(HAVE_TM_GMTOFF
+		 ? make_fixnum (tm_gmtoff (&local_tm))
+		 : gmtime_r (&time_spec, &gmt_tm)
+		 ? make_fixnum (tm_diff (&local_tm, &gmt_tm))
+		 : Qnil));
+}
+
+/* Return OBJ - OFFSET, checking that OBJ is a valid fixnum and that
+   the result is representable as an int.  */
+static int
+check_tm_member (Lisp_Object obj, int offset)
+{
+  CHECK_FIXNUM (obj);
+  EMACS_INT n = XFIXNUM (obj);
+  int result;
+  if (INT_SUBTRACT_WRAPV (n, offset, &result))
+    time_overflow ();
+  return result;
+}
+
+DEFUN ("encode-time", Fencode_time, Sencode_time, 6, MANY, 0,
+       doc: /* Convert SECOND, MINUTE, HOUR, DAY, MONTH, YEAR and ZONE to internal time.
+This is the reverse operation of `decode-time', which see.
+
+The optional ZONE is omitted or 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
+`current-time-zone') or an integer (as from `decode-time') applied
+without consideration for daylight saving time.
+
+You can pass more than 7 arguments; then the first six arguments
+are used as SECOND through YEAR, and the *last* argument is used as ZONE.
+The intervening arguments are ignored.
+This feature lets (apply \\='encode-time (decode-time ...)) work.
+
+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.
+Year numbers less than 100 are treated just like other year numbers.
+If you want them to stand for years in this century, you must do that yourself.
+
+Years before 1970 are not guaranteed to work.  On some systems,
+year values as low as 1901 do work.
+
+usage: (encode-time SECOND MINUTE HOUR DAY MONTH YEAR &optional ZONE)  */)
+  (ptrdiff_t nargs, Lisp_Object *args)
+{
+  time_t value;
+  struct tm tm;
+  Lisp_Object zone = (nargs > 6 ? args[nargs - 1] : Qnil);
+
+  tm.tm_sec  = check_tm_member (args[0], 0);
+  tm.tm_min  = check_tm_member (args[1], 0);
+  tm.tm_hour = check_tm_member (args[2], 0);
+  tm.tm_mday = check_tm_member (args[3], 0);
+  tm.tm_mon  = check_tm_member (args[4], 1);
+  tm.tm_year = check_tm_member (args[5], TM_YEAR_BASE);
+  tm.tm_isdst = -1;
+
+  timezone_t tz = tzlookup (zone, false);
+  value = emacs_mktime_z (tz, &tm);
+  xtzfree (tz);
+
+  if (value == (time_t) -1)
+    time_overflow ();
+
+  return list2i (hi_time (value), lo_time (value));
+}
+
+DEFUN ("current-time", Fcurrent_time, Scurrent_time, 0, 0, 0,
+       doc: /* Return the current time, as the number of seconds since 1970-01-01 00:00:00.
+The time is returned as a list of integers (HIGH LOW USEC PSEC).
+HIGH has the most significant bits of the seconds, while LOW has the
+least significant 16 bits.  USEC and PSEC are the microsecond and
+picosecond counts.  */)
+  (void)
+{
+  return make_lisp_time (current_timespec ());
+}
+
+DEFUN ("current-time-string", Fcurrent_time_string, Scurrent_time_string,
+       0, 2, 0,
+       doc: /* Return the current local time, as a human-readable string.
+Programs can use this function to decode a time,
+since the number of columns in each field is fixed
+if the year is in the range 1000-9999.
+The format is `Sun Sep 16 01:03:52 1973'.
+However, see also the functions `decode-time' and `format-time-string'
+which provide a much more powerful and general facility.
+
+If SPECIFIED-TIME is given, it is a time to format instead of the
+current time.  The argument should have the form (HIGH LOW . IGNORED).
+Thus, you can use times obtained from `current-time' and from
+`file-attributes'.  SPECIFIED-TIME can also be a single integer number
+of seconds since the epoch.  The obsolete form (HIGH . LOW) is also
+still accepted.
+
+The optional ZONE is omitted or 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
+`current-time-zone') or an integer (as from `decode-time') applied
+without consideration for daylight saving time.  */)
+  (Lisp_Object specified_time, Lisp_Object zone)
+{
+  time_t value = lisp_seconds_argument (specified_time);
+  timezone_t tz = tzlookup (zone, false);
+
+  /* Convert to a string in ctime format, except without the trailing
+     newline, and without the 4-digit year limit.  Don't use asctime
+     or ctime, as they might dump core if the year is outside the
+     range -999 .. 9999.  */
+  struct tm tm;
+  struct tm *tmp = emacs_localtime_rz (tz, &value, &tm);
+  xtzfree (tz);
+  if (! tmp)
+    time_overflow ();
+
+  static char const wday_name[][4] =
+    { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };
+  static char const mon_name[][4] =
+    { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
+      "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+  printmax_t year_base = TM_YEAR_BASE;
+  char buf[sizeof "Mon Apr 30 12:49:17 " + INT_STRLEN_BOUND (int) + 1];
+  int len = sprintf (buf, "%s %s%3d %02d:%02d:%02d %"pMd,
+		     wday_name[tm.tm_wday], mon_name[tm.tm_mon], tm.tm_mday,
+		     tm.tm_hour, tm.tm_min, tm.tm_sec,
+		     tm.tm_year + year_base);
+
+  return make_unibyte_string (buf, len);
+}
+
+DEFUN ("current-time-zone", Fcurrent_time_zone, Scurrent_time_zone, 0, 2, 0,
+       doc: /* Return the offset and name for the local time zone.
+This returns a list of the form (OFFSET NAME).
+OFFSET is an integer number of seconds ahead of UTC (east of Greenwich).
+    A negative value means west of Greenwich.
+NAME is a string giving the name of the time zone.
+If SPECIFIED-TIME is given, the time zone offset is determined from it
+instead of using the current time.  The argument should have the form
+\(HIGH LOW . IGNORED).  Thus, you can use times obtained from
+`current-time' and from `file-attributes'.  SPECIFIED-TIME can also be
+a single integer number of seconds since the epoch.  The obsolete form
+(HIGH . LOW) is also still accepted.
+
+The optional ZONE is omitted or 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
+`current-time-zone') or an integer (as from `decode-time') applied
+without consideration for daylight saving time.
+
+Some operating systems cannot provide all this information to Emacs;
+in this case, `current-time-zone' returns a list containing nil for
+the data it can't find.  */)
+  (Lisp_Object specified_time, Lisp_Object zone)
+{
+  struct timespec value;
+  struct tm local_tm, gmt_tm;
+  Lisp_Object zone_offset, zone_name;
+
+  zone_offset = Qnil;
+  value = make_timespec (lisp_seconds_argument (specified_time), 0);
+  zone_name = format_time_string ("%Z", sizeof "%Z" - 1, value,
+				  zone, &local_tm);
+
+  /* gmtime_r expects a pointer to time_t, but tv_sec of struct
+     timespec on some systems (MinGW) is a 64-bit field.  */
+  time_t tsec = value.tv_sec;
+  if (HAVE_TM_GMTOFF || gmtime_r (&tsec, &gmt_tm))
+    {
+      long int offset = (HAVE_TM_GMTOFF
+			 ? tm_gmtoff (&local_tm)
+			 : tm_diff (&local_tm, &gmt_tm));
+      zone_offset = make_fixnum (offset);
+      if (SCHARS (zone_name) == 0)
+	{
+	  /* No local time zone name is available; use numeric zone instead.  */
+	  long int hour = offset / 3600;
+	  int min_sec = offset % 3600;
+	  int amin_sec = min_sec < 0 ? - min_sec : min_sec;
+	  int min = amin_sec / 60;
+	  int sec = amin_sec % 60;
+	  int min_prec = min_sec ? 2 : 0;
+	  int sec_prec = sec ? 2 : 0;
+	  char buf[sizeof "+0000" + INT_STRLEN_BOUND (long int)];
+	  zone_name = make_formatted_string (buf, "%c%.2ld%.*d%.*d",
+					     (offset < 0 ? '-' : '+'),
+					     hour, min_prec, min, sec_prec, sec);
+	}
+    }
+
+  return list2 (zone_offset, zone_name);
+}
+
+DEFUN ("set-time-zone-rule", Fset_time_zone_rule, Sset_time_zone_rule, 1, 1, 0,
+       doc: /* Set the Emacs local time zone using TZ, a string specifying a time zone rule.
+If TZ is nil or `wall', use system wall clock time; this differs from
+the usual Emacs convention where nil means current local time.  If TZ
+is t, use Universal Time.  If TZ is a list (as from
+`current-time-zone') or an integer (as from `decode-time'), use the
+specified time zone without consideration for daylight saving time.
+
+Instead of calling this function, you typically want something else.
+To temporarily use a different time zone rule for just one invocation
+of `decode-time', `encode-time', or `format-time-string', pass the
+function a ZONE argument.  To change local time consistently
+throughout Emacs, call (setenv "TZ" TZ): this changes both the
+environment of the Emacs process and the variable
+`process-environment', whereas `set-time-zone-rule' affects only the
+former.  */)
+  (Lisp_Object tz)
+{
+  tzlookup (NILP (tz) ? Qwall : tz, true);
+  return Qnil;
+}
+
+/* A buffer holding a string of the form "TZ=value", intended
+   to be part of the environment.  If TZ is supposed to be unset,
+   the buffer string is "tZ=".  */
+ static char *tzvalbuf;
+
+/* Get the local time zone rule.  */
+char *
+emacs_getenv_TZ (void)
+{
+  return tzvalbuf[0] == 'T' ? tzvalbuf + tzeqlen : 0;
+}
+
+/* Set the local time zone rule to TZSTRING, which can be null to
+   denote wall clock time.  Do not record the setting in LOCAL_TZ.
+
+   This function is not thread-safe, in theory because putenv is not,
+   but mostly because of the static storage it updates.  Other threads
+   that invoke localtime etc. may be adversely affected while this
+   function is executing.  */
+
+int
+emacs_setenv_TZ (const char *tzstring)
+{
+  static ptrdiff_t tzvalbufsize;
+  ptrdiff_t tzstringlen = tzstring ? strlen (tzstring) : 0;
+  char *tzval = tzvalbuf;
+  bool new_tzvalbuf = tzvalbufsize <= tzeqlen + tzstringlen;
+
+  if (new_tzvalbuf)
+    {
+      /* Do not attempt to free the old tzvalbuf, since another thread
+	 may be using it.  In practice, the first allocation is large
+	 enough and memory does not leak.  */
+      tzval = xpalloc (NULL, &tzvalbufsize,
+		       tzeqlen + tzstringlen - tzvalbufsize + 1, -1, 1);
+      tzvalbuf = tzval;
+      tzval[1] = 'Z';
+      tzval[2] = '=';
+    }
+
+  if (tzstring)
+    {
+      /* Modify TZVAL in place.  Although this is dicey in a
+	 multithreaded environment, we know of no portable alternative.
+	 Calling putenv or setenv could crash some other thread.  */
+      tzval[0] = 'T';
+      strcpy (tzval + tzeqlen, tzstring);
+    }
+  else
+    {
+      /* Turn 'TZ=whatever' into an empty environment variable 'tZ='.
+	 Although this is also dicey, calling unsetenv here can crash Emacs.
+	 See Bug#8705.  */
+      tzval[0] = 't';
+      tzval[tzeqlen] = 0;
+    }
+
+
+#ifndef WINDOWSNT
+  /* Modifying *TZVAL merely requires calling tzset (which is the
+     caller's responsibility).  However, modifying TZVAL requires
+     calling putenv; although this is not thread-safe, in practice this
+     runs only on startup when there is only one thread.  */
+  bool need_putenv = new_tzvalbuf;
+#else
+  /* MS-Windows 'putenv' copies the argument string into a block it
+     allocates, so modifying *TZVAL will not change the environment.
+     However, the other threads run by Emacs on MS-Windows never call
+     'xputenv' or 'putenv' or 'unsetenv', so the original cause for the
+     dicey in-place modification technique doesn't exist there in the
+     first place.  */
+  bool need_putenv = true;
+#endif
+  if (need_putenv)
+    xputenv (tzval);
+
+  return 0;
+}
+
+void
+syms_of_systime (void)
+{
+  defsubr (&Scurrent_time);
+  defsubr (&Stime_add);
+  defsubr (&Stime_subtract);
+  defsubr (&Stime_less_p);
+  defsubr (&Stime_equal_p);
+  defsubr (&Sformat_time_string);
+  defsubr (&Sfloat_time);
+  defsubr (&Sdecode_time);
+  defsubr (&Sencode_time);
+  defsubr (&Scurrent_time_string);
+  defsubr (&Scurrent_time_zone);
+  defsubr (&Sset_time_zone_rule);
+}
diff --git a/src/systime.h b/src/systime.h
index ad5ab85730..f907194b9d 100644
--- a/src/systime.h
+++ b/src/systime.h
@@ -19,6 +19,7 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #ifndef EMACS_SYSTIME_H
 #define EMACS_SYSTIME_H
 
+#include "lisp.h"
 #include <timespec.h>
 
 INLINE_HEADER_BEGIN
@@ -66,7 +67,6 @@ timespec_valid_p (struct timespec t)
 
 /* defined in sysdep.c */
 extern int set_file_times (int, const char *, struct timespec, struct timespec);
-extern struct timeval make_timeval (struct timespec) ATTRIBUTE_CONST;
 
 /* defined in keyboard.c */
 extern void set_waiting_for_input (struct timespec *);
@@ -82,12 +82,16 @@ struct lisp_time
   int lo, us, ps;
 };
 
-/* defined in editfns.c */
+/* defined in systime.c */
+extern struct timeval make_timeval (struct timespec) ATTRIBUTE_CONST;
 extern Lisp_Object make_lisp_time (struct timespec);
 extern int decode_time_components (Lisp_Object, Lisp_Object, Lisp_Object,
 				   Lisp_Object, struct lisp_time *, double *);
 extern struct timespec lisp_to_timespec (struct lisp_time);
 extern struct timespec lisp_time_argument (Lisp_Object);
+extern _Noreturn void time_overflow (void);
+extern void init_systime (bool);
+extern void syms_of_systime (void);
 
 INLINE_HEADER_END
 
diff --git a/src/w32.c b/src/w32.c
index 4b57d91641..e643c42150 100644
--- a/src/w32.c
+++ b/src/w32.c
@@ -535,8 +535,6 @@ static Lisp_Object ltime (ULONGLONG);
 /* Get total user and system times for get-internal-run-time.
    Returns a list of integers if the times are provided by the OS
    (NT derivatives), otherwise it returns the result of current-time. */
-Lisp_Object w32_get_internal_run_time (void);
-
 Lisp_Object
 w32_get_internal_run_time (void)
 {
diff --git a/src/w32.h b/src/w32.h
index 9c219cdda6..42b3d98245 100644
--- a/src/w32.h
+++ b/src/w32.h
@@ -195,6 +195,7 @@ extern int  filename_from_ansi (const char *, char *);
 extern int  filename_to_ansi (const char *, char *);
 extern int  filename_from_utf16 (const wchar_t *, char *);
 extern int  filename_to_utf16 (const char *, wchar_t *);
+extern Lisp_Object w32_get_internal_run_time (void);
 extern void w32_init_file_name_codepage (void);
 extern int  codepage_for_filenames (CPINFO *);
 extern Lisp_Object ansi_encode_filename (Lisp_Object);
diff --git a/test/src/editfns-tests.el b/test/src/editfns-tests.el
index 4a840c8d7d..17b2c51073 100644
--- a/test/src/editfns-tests.el
+++ b/test/src/editfns-tests.el
@@ -204,65 +204,6 @@ transpose-test-get-byte-positions
   (should (string-equal (format "%d" 0.9) "0"))
   (should (string-equal (format "%d" 1.1) "1")))
 
-;;; Check format-time-string with various TZ settings.
-;;; Use only POSIX-compatible TZ values, since the tests should work
-;;; even if tzdb is not in use.
-(ert-deftest format-time-string-with-zone ()
-  ;; Don’t use (0 0 0 0) as the test case, as there are too many bugs
-  ;; in MS-Windows (and presumably other) C libraries when formatting
-  ;; time stamps near the Epoch of 1970-01-01 00:00:00 UTC, and this
-  ;; test is for GNU Emacs, not for C runtimes.  Instead, look before
-  ;; you leap: "look" is the timestamp just before the first leap
-  ;; second on 1972-06-30 23:59:60 UTC, so it should format to the
-  ;; same string regardless of whether the underlying C library
-  ;; ignores leap seconds, while avoiding circa-1970 glitches.
-  ;;
-  ;; Similarly, stick to the limited set of time zones that are
-  ;; supported by both POSIX and MS-Windows: exactly 3 ASCII letters
-  ;; in the abbreviation, and no DST.
-  (let ((look '(1202 22527 999999 999999))
-        (format "%Y-%m-%d %H:%M:%S.%3N %z (%Z)"))
-    ;; UTC.
-    (should (string-equal
-             (format-time-string "%Y-%m-%d %H:%M:%S.%3N %z" look t)
-             "1972-06-30 23:59:59.999 +0000"))
-    ;; "UTC0".
-    (should (string-equal
-             (format-time-string format look "UTC0")
-             "1972-06-30 23:59:59.999 +0000 (UTC)"))
-    ;; Negative UTC offset, as a Lisp list.
-    (should (string-equal
-             (format-time-string format look '(-28800 "PST"))
-             "1972-06-30 15:59:59.999 -0800 (PST)"))
-    ;; Negative UTC offset, as a Lisp integer.
-    (should (string-equal
-             (format-time-string format look -28800)
-             ;; MS-Windows build replaces unrecognizable TZ values,
-             ;; such as "-08", with "ZZZ".
-             (if (eq system-type 'windows-nt)
-                 "1972-06-30 15:59:59.999 -0800 (ZZZ)"
-               "1972-06-30 15:59:59.999 -0800 (-08)")))
-    ;; Positive UTC offset that is not an hour multiple, as a string.
-    (should (string-equal
-             (format-time-string format look "IST-5:30")
-             "1972-07-01 05:29:59.999 +0530 (IST)"))))
-
-;;; This should not dump core.
-(ert-deftest format-time-string-with-outlandish-zone ()
-  (should (stringp
-           (format-time-string "%Y-%m-%d %H:%M:%S.%3N %z" nil
-                               (concat (make-string 2048 ?X) "0")))))
-
-(defun editfns-tests--have-leap-seconds ()
-  (string-equal (format-time-string "%Y-%m-%d %H:%M:%S" 78796800 t)
-                "1972-06-30 23:59:60"))
-
-(ert-deftest format-time-string-with-bignum-on-32-bit ()
-  (should (or (string-equal
-               (format-time-string "%Y-%m-%d %H:%M:%S" (- (ash 1 31) 3600) t)
-               "2038-01-19 02:14:08")
-              (editfns-tests--have-leap-seconds))))
-
 (ert-deftest format-with-field ()
   (should (equal (format "First argument %2$s, then %3$s, then %1$s" 1 2 3)
                  "First argument 2, then 3, then 1"))
diff --git a/test/src/systime-tests.el b/test/src/systime-tests.el
new file mode 100644
index 0000000000..6e369aa2aa
--- /dev/null
+++ b/test/src/systime-tests.el
@@ -0,0 +1,79 @@
+;;; systime-tests.el -- tests for systime.c
+
+;; Copyright (C) 2016-2018 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; This program is free software; you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; This program is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with this program.  If not, see <https://www.gnu.org/licenses/>.
+
+(require 'ert)
+
+;;; Check format-time-string with various TZ settings.
+;;; Use only POSIX-compatible TZ values, since the tests should work
+;;; even if tzdb is not in use.
+(ert-deftest format-time-string-with-zone ()
+  ;; Don’t use (0 0 0 0) as the test case, as there are too many bugs
+  ;; in MS-Windows (and presumably other) C libraries when formatting
+  ;; time stamps near the Epoch of 1970-01-01 00:00:00 UTC, and this
+  ;; test is for GNU Emacs, not for C runtimes.  Instead, look before
+  ;; you leap: "look" is the timestamp just before the first leap
+  ;; second on 1972-06-30 23:59:60 UTC, so it should format to the
+  ;; same string regardless of whether the underlying C library
+  ;; ignores leap seconds, while avoiding circa-1970 glitches.
+  ;;
+  ;; Similarly, stick to the limited set of time zones that are
+  ;; supported by both POSIX and MS-Windows: exactly 3 ASCII letters
+  ;; in the abbreviation, and no DST.
+  (let ((look '(1202 22527 999999 999999))
+        (format "%Y-%m-%d %H:%M:%S.%3N %z (%Z)"))
+    ;; UTC.
+    (should (string-equal
+             (format-time-string "%Y-%m-%d %H:%M:%S.%3N %z" look t)
+             "1972-06-30 23:59:59.999 +0000"))
+    ;; "UTC0".
+    (should (string-equal
+             (format-time-string format look "UTC0")
+             "1972-06-30 23:59:59.999 +0000 (UTC)"))
+    ;; Negative UTC offset, as a Lisp list.
+    (should (string-equal
+             (format-time-string format look '(-28800 "PST"))
+             "1972-06-30 15:59:59.999 -0800 (PST)"))
+    ;; Negative UTC offset, as a Lisp integer.
+    (should (string-equal
+             (format-time-string format look -28800)
+             ;; MS-Windows build replaces unrecognizable TZ values,
+             ;; such as "-08", with "ZZZ".
+             (if (eq system-type 'windows-nt)
+                 "1972-06-30 15:59:59.999 -0800 (ZZZ)"
+               "1972-06-30 15:59:59.999 -0800 (-08)")))
+    ;; Positive UTC offset that is not an hour multiple, as a string.
+    (should (string-equal
+             (format-time-string format look "IST-5:30")
+             "1972-07-01 05:29:59.999 +0530 (IST)"))))
+
+;;; This should not dump core.
+(ert-deftest format-time-string-with-outlandish-zone ()
+  (should (stringp
+           (format-time-string "%Y-%m-%d %H:%M:%S.%3N %z" nil
+                               (concat (make-string 2048 ?X) "0")))))
+
+(defun systime-tests--have-leap-seconds ()
+  (string-equal (format-time-string "%Y-%m-%d %H:%M:%S" 78796800 t)
+                "1972-06-30 23:59:60"))
+
+(ert-deftest format-time-string-with-bignum-on-32-bit ()
+  (should (or (string-equal
+               (format-time-string "%Y-%m-%d %H:%M:%S" (- (ash 1 31) 3600) t)
+               "2038-01-19 02:14:08")
+              (systime-tests--have-leap-seconds))))
-- 
2.17.1


[-- Attachment #3: 0002-Coalesce-duplicate-make_lisp_timeval-etc.patch --]
[-- Type: text/x-patch, Size: 1871 bytes --]

From 3e302d76f27f49c5bf46daaeb233fdacd5a2f8f5 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Mon, 1 Oct 2018 15:31:53 -0700
Subject: [PATCH 2/4] Coalesce duplicate make_lisp_timeval etc.

* src/sysdep.c (timeval_to_timespec, make_lisp_timeval):
Coalesce duplicate definitions.
---
 src/sysdep.c | 40 ++++++++++++++++------------------------
 1 file changed, 16 insertions(+), 24 deletions(-)

diff --git a/src/sysdep.c b/src/sysdep.c
index 0695686361..7a0c8a8ab8 100644
--- a/src/sysdep.c
+++ b/src/sysdep.c
@@ -3047,6 +3047,22 @@ list_system_processes (void)
 
 #endif /* !defined (WINDOWSNT) */
 
+
+#if defined __FreeBSD__ || defined DARWIN_OS
+
+static struct timespec
+timeval_to_timespec (struct timeval t)
+{
+  return make_timespec (t.tv_sec, t.tv_usec * 1000);
+}
+static Lisp_Object
+make_lisp_timeval (struct timeval t)
+{
+  return make_lisp_time (timeval_to_timespec (t));
+}
+
+#endif
+
 #if defined GNU_LINUX && defined HAVE_LONG_LONG_INT
 static struct timespec
 time_from_jiffies (unsigned long long tval, long hz)
@@ -3567,18 +3583,6 @@ system_process_attributes (Lisp_Object pid)
 
 #elif defined __FreeBSD__
 
-static struct timespec
-timeval_to_timespec (struct timeval t)
-{
-  return make_timespec (t.tv_sec, t.tv_usec * 1000);
-}
-
-static Lisp_Object
-make_lisp_timeval (struct timeval t)
-{
-  return make_lisp_time (timeval_to_timespec (t));
-}
-
 Lisp_Object
 system_process_attributes (Lisp_Object pid)
 {
@@ -3748,18 +3752,6 @@ system_process_attributes (Lisp_Object pid)
 
 #elif defined DARWIN_OS
 
-static struct timespec
-timeval_to_timespec (struct timeval t)
-{
-  return make_timespec (t.tv_sec, t.tv_usec * 1000);
-}
-
-static Lisp_Object
-make_lisp_timeval (struct timeval t)
-{
-  return make_lisp_time (timeval_to_timespec (t));
-}
-
 Lisp_Object
 system_process_attributes (Lisp_Object pid)
 {
-- 
2.17.1


[-- Attachment #4: 0003-Export-converting-mpz-to-u-intmax.patch --]
[-- Type: text/x-patch, Size: 5232 bytes --]

From 20d92047cf82318c093ff03dbb07a0631a2046aa Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Mon, 1 Oct 2018 15:31:53 -0700
Subject: [PATCH 3/4] Export converting mpz to [u]intmax

This refactoring will help improve timestamp handling later.
* src/bignum.c (mpz_set_uintmax): Move to bignum.h,
and make inline.
(mpz_set_uintmax_slow): Now extern.
(mpz_to_intmax, mpz_to_uintmax): New functions, with
implementation taken from the old bignum_to_intmax
and bignum_to_uintmax.
(bignum_to_intmax, bignum_to_uintmax): Use them.
---
 src/bignum.c | 91 +++++++++++++++++++++++++++++-----------------------
 src/bignum.h | 11 +++++++
 2 files changed, 61 insertions(+), 41 deletions(-)

diff --git a/src/bignum.c b/src/bignum.c
index 1e78d981b7..5d8ab670f2 100644
--- a/src/bignum.c
+++ b/src/bignum.c
@@ -101,18 +101,6 @@ make_bignum (void)
   return make_bignum_bits (mpz_sizeinbase (mpz[0], 2));
 }
 
-static void mpz_set_uintmax_slow (mpz_t, uintmax_t);
-
-/* Set RESULT to V.  */
-static void
-mpz_set_uintmax (mpz_t result, uintmax_t v)
-{
-  if (v <= ULONG_MAX)
-    mpz_set_ui (result, v);
-  else
-    mpz_set_uintmax_slow (result, v);
-}
-
 /* Return a Lisp integer equal to N, which must not be in fixnum range.  */
 Lisp_Object
 make_bigint (intmax_t n)
@@ -183,7 +171,7 @@ mpz_set_intmax_slow (mpz_t result, intmax_t v)
 
   mpz_limbs_finish (result, negative ? -n : n);
 }
-static void
+void
 mpz_set_uintmax_slow (mpz_t result, uintmax_t v)
 {
   int maxlimbs = (UINTMAX_WIDTH + GMP_NUMB_BITS - 1) / GMP_NUMB_BITS;
@@ -200,13 +188,13 @@ mpz_set_uintmax_slow (mpz_t result, uintmax_t v)
   mpz_limbs_finish (result, n);
 }
 
-/* Return the value of the bignum X if it fits, 0 otherwise.
-   A bignum cannot be zero, so 0 indicates failure reliably.  */
-intmax_t
-bignum_to_intmax (Lisp_Object x)
+/* If Z fits into *PI, store its value there and return true.
+   Return false otherwise.  */
+bool
+mpz_to_intmax (mpz_t const z, intmax_t *pi)
 {
-  ptrdiff_t bits = mpz_sizeinbase (XBIGNUM (x)->value, 2);
-  bool negative = mpz_sgn (XBIGNUM (x)->value) < 0;
+  ptrdiff_t bits = mpz_sizeinbase (z, 2);
+  bool negative = mpz_sgn (z) < 0;
 
   if (bits < INTMAX_WIDTH)
     {
@@ -215,39 +203,60 @@ bignum_to_intmax (Lisp_Object x)
 
       do
 	{
-	  intmax_t limb = mpz_getlimbn (XBIGNUM (x)->value, i++);
+	  intmax_t limb = mpz_getlimbn (z, i++);
 	  v += limb << shift;
 	  shift += GMP_NUMB_BITS;
 	}
       while (shift < bits);
 
-      return negative ? -v : v;
+      *pi = negative ? -v : v;
+      return true;
+    }
+  if (bits == INTMAX_WIDTH && INTMAX_MIN < -INTMAX_MAX && negative
+      && mpz_scan1 (z, 0) == INTMAX_WIDTH - 1)
+    {
+      *pi = INTMAX_MIN;
+      return true;
     }
-  return ((bits == INTMAX_WIDTH && INTMAX_MIN < -INTMAX_MAX && negative
-	   && mpz_scan1 (XBIGNUM (x)->value, 0) == INTMAX_WIDTH - 1)
-	  ? INTMAX_MIN : 0);
+  return false;
 }
-uintmax_t
-bignum_to_uintmax (Lisp_Object x)
+bool
+mpz_to_uintmax (mpz_t const z, uintmax_t *pi)
 {
+  if (mpz_sgn (z) < 0)
+    return false;
+  ptrdiff_t bits = mpz_sizeinbase (z, 2);
+  if (UINTMAX_WIDTH < bits)
+    return false;
+
   uintmax_t v = 0;
-  if (0 <= mpz_sgn (XBIGNUM (x)->value))
+  int i = 0, shift = 0;
+
+  do
     {
-      ptrdiff_t bits = mpz_sizeinbase (XBIGNUM (x)->value, 2);
-      if (bits <= UINTMAX_WIDTH)
-	{
-	  int i = 0, shift = 0;
-
-	  do
-	    {
-	      uintmax_t limb = mpz_getlimbn (XBIGNUM (x)->value, i++);
-	      v += limb << shift;
-	      shift += GMP_NUMB_BITS;
-	    }
-	  while (shift < bits);
-	}
+      uintmax_t limb = mpz_getlimbn (z, i++);
+      v += limb << shift;
+      shift += GMP_NUMB_BITS;
     }
-  return v;
+  while (shift < bits);
+
+  *pi = v;
+  return true;
+}
+
+/* Return the value of the bignum X if it fits, 0 otherwise.
+   A bignum cannot be zero, so 0 indicates failure reliably.  */
+intmax_t
+bignum_to_intmax (Lisp_Object x)
+{
+  intmax_t i;
+  return mpz_to_intmax (XBIGNUM (x)->value, &i) ? i : 0;
+}
+uintmax_t
+bignum_to_uintmax (Lisp_Object x)
+{
+  uintmax_t i;
+  return mpz_to_uintmax (XBIGNUM (x)->value, &i) ? i : 0;
 }
 
 /* Yield an upper bound on the buffer size needed to contain a C
diff --git a/src/bignum.h b/src/bignum.h
index e9cd5c0763..fd035e6e14 100644
--- a/src/bignum.h
+++ b/src/bignum.h
@@ -45,7 +45,10 @@ extern mpz_t mpz[4];
 
 extern void init_bignum (void);
 extern Lisp_Object make_integer_mpz (void);
+extern bool mpz_to_intmax (mpz_t const, intmax_t *) ARG_NONNULL ((1, 2));
+extern bool mpz_to_uintmax (mpz_t const, uintmax_t *) ARG_NONNULL ((1, 2));
 extern void mpz_set_intmax_slow (mpz_t, intmax_t) ARG_NONNULL ((1));
+extern void mpz_set_uintmax_slow (mpz_t, uintmax_t) ARG_NONNULL ((1));
 extern double mpz_get_d_rounded (mpz_t const);
 
 INLINE_HEADER_BEGIN
@@ -68,6 +71,14 @@ mpz_set_intmax (mpz_t result, intmax_t v)
   else
     mpz_set_intmax_slow (result, v);
 }
+INLINE void ARG_NONNULL ((1))
+mpz_set_uintmax (mpz_t result, uintmax_t v)
+{
+  if (v <= ULONG_MAX)
+    mpz_set_ui (result, v);
+  else
+    mpz_set_uintmax_slow (result, v);
+}
 
 /* Return a pointer to an mpz_t that is equal to the Lisp integer I.
    If I is a bignum this returns a pointer to I's representation;
-- 
2.17.1


[-- Attachment #5: 0004-New-TICKS-.-HZ-timestamp-format.patch --]
[-- Type: text/x-patch, Size: 88719 bytes --]

From 3a5556ab0897fbe1cc1440dcf2ef9bbcf1083c86 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Mon, 1 Oct 2018 15:31:53 -0700
Subject: [PATCH 4/4] New (TICKS . HZ) timestamp format

This follows on a suggestion by Stefan Monnier in:
https://lists.gnu.org/r/emacs-devel/2018-08/msg00991.html
* doc/lispref/buffers.texi (Modification Time):
* doc/lispref/os.texi (Processor Run Time, Time Calculations)
* doc/lispref/processes.texi (System Processes):
* doc/lispref/text.texi (Undo):
Let the "Time of Day" section cover timestamp format details.
* doc/lispref/os.texi (Time of Day):
Say that timestamp internal format should not be assumed.
Document new (ticks . hz) format.  Omit mention of seconds-to-time
since it is now just an alias for encode-time.
(Time Conversion): Document encode-time extension.
* etc/NEWS: Mention changes.
* lisp/calendar/cal-dst.el (calendar-system-time-basis): Now const.
* lisp/calendar/cal-dst.el (calendar-absolute-from-time)
(calendar-time-from-absolute)
(calendar-next-time-zone-transition):
* lisp/emacs-lisp/timer.el (timer-next-integral-multiple-of-time):
Simplify by using bignums, (TICKS . HZ), and new encode-time.
* lisp/emacs-lisp/timer.el (timer-next-integral-multiple-of-time):
Simplify by using bignums and new encode-time.
* lisp/calendar/parse-time.el (parse-iso8601-time-string):
Handle DST more accurately, by using new encode-time.
* lisp/calendar/time-date.el (seconds-to-time):
* lisp/calendar/timeclock.el (timeclock-seconds-to-time):
Now just an alias for encode-time.
* lisp/calendar/time-date.el (days-to-time):
* lisp/emacs-lisp/timer.el (timer--time-setter):
* lisp/net/ntlm.el (ntlm-compute-timestamp):
* lisp/obsolete/vc-arch.el (vc-arch-add-tagline):
* lisp/org/org-id.el (org-id-uuid, org-id-time-to-b36):
* lisp/tar-mode (tar-octal-time):
Don't assume timestamps default to list form.
* lisp/tar-mode.el (tar-parse-octal-long-integer):
Now an obsolete alias for tar-parse-octal-integer.
* src/keyboard.c (decode_timer): Adjust to changes to
time decoding functions elsewhere.
* src/systime.c: Include bignum.h, limits.h.
(FASTER_SYSTIME): New macro.
(WARN_OBSOLETE_TIMESTAMPS, CURRENT_TIME_LIST)
(timespec_hz, trillion, ztrillion):
New constants.
(make_timeval): Use TIME_T_MAX instead of its definiens.
(check_time_validity, time_add, time_subtract):
Remove.  All uses removed.
(disassemble_lisp_time): Remove; old code now folded into
decode_lisp_time.  All callers changed.
(invalid_hz, s_ns_to_double, ticks_hz_list4, mpz_set_time)
(timespec_mpz, timespec_ticks, time_hz_ticks)
(lisp_time_hz_ticks, lisp_time_seconds)
(time_form_stamp, lisp_time_form_stamp, decode_ticks_hz)
(decode_lisp_time, mpz_time, list4_to_timespec):
New functions.
(decode_float_time, decode_time_components, lisp_to_timespec):
Adjust to new struct lisp_time, which does not lose
information like the old one did.
(enum timeform): New enum.
(decode_time_components): New arg FORM.  All callers changed.
RESULT and DRESULT are now mutually exclusive; no callers need
to change because of this.
(decode_time_components, lisp_time_struct)
(lisp_seconds_argument, time_arith, make_lisp_time, Ffloat_time)
(Fencode_time):
Add support for (TICKS . HZ) form.
(DECODE_SECS_ONLY): New constant.
(lisp_time_struct): 2nd arg is now enum timeform, not int.
All callers changed.
(check_tm_member): Support bignums.m
(Fencode_time): Add new two-arg functionality.
* src/systime.h (struct lisp_time): Now ticks+hz rather than
hi+lo+us+ps, since ticks+hz does not lose info.
* test/src/systime-tests.el (time-equal-p-nil-nil):
New test.
---
 doc/lispref/buffers.texi    |   10 +-
 doc/lispref/os.texi         |  157 +++--
 doc/lispref/processes.texi  |   19 +-
 doc/lispref/text.texi       |    5 +-
 doc/misc/emacs-mime.texi    |   69 ++-
 etc/NEWS                    |   15 +
 lisp/calendar/cal-dst.el    |   55 +-
 lisp/calendar/parse-time.el |    5 +-
 lisp/calendar/time-date.el  |   10 +-
 lisp/calendar/timeclock.el  |    3 +-
 lisp/emacs-lisp/timer.el    |   42 +-
 lisp/net/ntlm.el            |    3 +-
 lisp/obsolete/vc-arch.el    |    3 +-
 lisp/org/org-id.el          |    4 +-
 lisp/tar-mode.el            |   28 +-
 src/bignum.c                |    2 +-
 src/keyboard.c              |   11 +-
 src/systime.c               | 1137 ++++++++++++++++++++++++-----------
 src/systime.h               |   15 +-
 test/src/systime-tests.el   |    3 +
 20 files changed, 1033 insertions(+), 563 deletions(-)

diff --git a/doc/lispref/buffers.texi b/doc/lispref/buffers.texi
index 1acf4baedb..8789a8d56f 100644
--- a/doc/lispref/buffers.texi
+++ b/doc/lispref/buffers.texi
@@ -648,10 +648,7 @@ Modification Time
 
 @defun visited-file-modtime
 This function returns the current buffer's recorded last file
-modification time, as a list of the form @code{(@var{high} @var{low}
-@var{microsec} @var{picosec})}.  (This is the same format that
-@code{file-attributes} uses to return time values; @pxref{File
-Attributes}.)
+modification time, as a Lisp timestamp (@pxref{Time of Day}).
 
 If the buffer has no recorded last modification time, this function
 returns zero.  This case occurs, for instance, if the buffer is not
@@ -671,9 +668,8 @@ Modification Time
 visited file.
 
 If @var{time} is neither @code{nil} nor an integer flag returned
-by @code{visited-file-modtime}, it should have the form
-@code{(@var{high} @var{low} @var{microsec} @var{picosec})},
-the format used by @code{current-time} (@pxref{Time of Day}).
+by @code{visited-file-modtime}, it should be a Lisp time value
+(@pxref{Time of Day}).
 
 This function is useful if the buffer was not read from the file
 normally, or if the file itself has been changed for some known benign
diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi
index 8ce5a5ed6d..ea6915350e 100644
--- a/doc/lispref/os.texi
+++ b/doc/lispref/os.texi
@@ -1233,11 +1233,44 @@ Time of Day
   This section explains how to determine the current time and time
 zone.
 
+@cindex Lisp timestamp
+@cindex timestamp, Lisp
+  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.
+
+  Although traditionally Lisp timestamps were integer pairs, their
+form has evolved and programs ordinarily should not depend on the
+current default form.  If your program needs a particular timestamp
+form, you can use the @code{encode-time} function to convert it to the
+needed form.  @xref{Time Conversion}.
+
 @cindex epoch
-  Most of these functions represent time as a list of four integers
-@code{(@var{sec-high} @var{sec-low} @var{microsec} @var{picosec})}.
-This represents the number of seconds from the @dfn{epoch} (January
-1, 1970 at 00:00 UTC), using the formula:
+  There are currently three forms of Lisp timestamps, each of
+which represents a number of seconds:
+
+@itemize @bullet
+@item
+An integer.  Although this is the simplest form, it cannot represent
+subsecond timestamps.
+
+@item
+A pair of integers @code{(@var{ticks} . @var{hz})}, where @var{hz} is
+positive.  This represents @var{ticks}/@var{hz} seconds, which is the
+same time as plain @var{ticks} if @var{hz} is 1.  A common value for
+@var{hz} is 1000000000, for a nanosecond-resolution
+clock.@footnote{Currently @var{hz} should be at least 65536 to avoid
+compatibility warnings when the timestamp is passed to standard
+functions, as previous versions of Emacs would interpret such a
+timestamps differently due to backward-compatibility concerns.  These
+warnings are intended to be removed in a future Emacs version.}
+
+@item
+A list of four integers @code{(@var{high} @var{low} @var{micro}
+@var{pico})}, where 0 @leq{} @var{low} < 65536, 0 @leq{} @var{micro} <
+1000000, and 0 @leq{} @var{pico} < 1000000.
+This represents the number of seconds using the formula:
 @ifnottex
 @var{high} * 2**16 + @var{low} + @var{micro} * 10**@minus{}6 +
 @var{pico} * 10**@minus{}12.
@@ -1245,21 +1278,23 @@ Time of Day
 @tex
 $high*2^{16} + low + micro*10^{-6} + pico*10^{-12}$.
 @end tex
-The return value of @code{current-time} represents time using this
-form, as do the timestamps in the return values of other functions
-such as @code{file-attributes} (@pxref{Definition of
-file-attributes}).  In some cases, functions may return two- or
+In some cases, functions may default to returning two- or
 three-element lists, with omitted @var{microsec} and @var{picosec}
 components defaulting to zero.
+On all current machines @var{picosec} is a multiple of 1000, but this
+may change as higher-resolution clocks become available.
+@end itemize
 
 @cindex time value
   Function arguments, e.g., the @var{time} argument to
 @code{current-time-string}, accept a more-general @dfn{time value}
-format, which can be a list of integers as above, or a single number
-for seconds since the epoch, or @code{nil} for the current time.  You
-can convert a time value into a human-readable string using
-@code{current-time-string} and @code{format-time-string}, into a list
-of integers using @code{seconds-to-time}, and into other forms using
+format, which can be a Lisp timestamp, @code{nil} for the current
+time, a single floating-point number for seconds, or a list
+@code{(@var{high} @var{low} @var{micro})} or @code{(@var{high}
+@var{low})} that is a truncated list timestamp with missing elements
+taken to be zero.  You can convert a time value into
+a human-readable string using @code{format-time-string}, into a Lisp
+timestamp using @code{encode-time}, and into other forms using
 @code{decode-time} and @code{float-time}.  These functions are
 described in the following sections.
 
@@ -1287,12 +1322,7 @@ Time of Day
 @end defun
 
 @defun current-time
-This function returns the current time, represented as a list of four
-integers @code{(@var{sec-high} @var{sec-low} @var{microsec} @var{picosec})}.
-These integers have trailing zeros on systems that return time with
-lower resolutions.  On all current machines @var{picosec} is a
-multiple of 1000, but this may change as higher-resolution clocks
-become available.
+This function returns the current time as a Lisp timestamp.
 @end defun
 
 @defun float-time &optional time
@@ -1306,13 +1336,6 @@ Time of Day
 @code{time-to-seconds} is an alias for this function.
 @end defun
 
-@defun seconds-to-time time
-This function converts a time value to list-of-integer form.
-For example, if @var{time} is a number, @code{(time-to-seconds
-(seconds-to-time @var{time}))} equals the number unless overflow
-or rounding errors occur.
-@end defun
-
 @node Time Zone Rules
 @section Time Zone Rules
 @cindex time zone rules
@@ -1434,32 +1457,63 @@ Time Conversion
 @var{dow} and @var{utcoff}.
 @end defun
 
-@defun encode-time seconds minutes hour day month year &optional zone
-This function is the inverse of @code{decode-time}.  It converts seven
-items of calendrical data into a list-of-integer time value.  For the
-meanings of the arguments, see the table above under
-@code{decode-time}.
+@defun encode-time time &optional form
+This function converts @var{time} to a Lisp timestamp.
+It can act as the inverse of @code{decode-time}.
+
+The first argument can be a Lisp time value such as @code{nil} for the
+current time, a number of seconds, a pair @code{(@var{ticks}
+. @var{hz})}, or a list @code{(@var{high} @var{low} @var{micro}
+@var{pico})} (@pxref{Time of Day}).  It can also be 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}.
+
+The optional @var{form} argument specifies the desired timestamp form
+to be returned.  If @var{form} is the symbol @code{integer}, this
+function returns an integer count of seconds.  If @var{form} is a
+positive integer, it specifies a clock frequency and this function
+returns an integer-pair timestamp @code{(@var{ticks}
+. @var{form})}.@footnote{Currently a positive integer @var{form}
+should be at least 65536 if the returned value is intended to be given
+to standard functions expecting Lisp timestamps.}  If @var{form} is
+@code{t}, this function treats it as a positive integer suitable for
+representing the timestamp; for example, it is treated as 1000000000
+if the platform timestamp has nanosecond resolution.  If @var{form} is
+@code{list}, this function returns an integer list @code{(@var{high}
+@var{low} @var{micro} @var{pico})}.  Although an omitted or @code{nil}
+@var{form} currently acts like @code{list}, this is planned to change
+in a future Emacs version, so callers requiring list timestamps should
+pass @code{list} explicitly.
+
+As an obsolescent calling convention, this function can be given six
+or more arguments.  The first six arguments @var{second},
+@var{minute}, @var{hour}, @var{day}, @var{month}, and @var{year}
+specify most of the components of a decoded time.  If there are more
+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; otherwise @var{zone} defaults
+to the current time zone rule (@pxref{Time Zone Rules}).  The decoded
+time's @var{dst} component is treated as if it was @minus{}1, and
+@var{form} so it takes its default value.
 
 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 optional argument @var{zone} defaults to the current time zone rule.
-@xref{Time Zone Rules}.
-
-If you pass more than seven arguments to @code{encode-time}, the first
-six are used as @var{seconds} through @var{year}, the last argument is
-used as @var{zone}, and the arguments in between are ignored.  This
-feature makes it possible to use the elements of a list returned by
-@code{decode-time} as the arguments to @code{encode-time}, like this:
+The @code{encode-time} function acts as a rough inverse to
+@code{decode-time}.  For example, you can pass the output of
+the latter to the former as follows:
 
 @example
-(apply 'encode-time (decode-time @dots{}))
+(encode-time (decode-time @dots{}))
 @end example
 
 You can perform simple date arithmetic by using out-of-range values for
-the @var{seconds}, @var{minutes}, @var{hour}, @var{day}, and @var{month}
-arguments; for example, day 0 means the day preceding the given month.
+@var{seconds}, @var{minutes}, @var{hour}, @var{day}, and @var{month};
+for example, day 0 means the day preceding the given month.
 
 The operating system puts limits on the range of possible time values;
 if you try to encode a time that is out of range, an error results.
@@ -1474,12 +1528,12 @@ Time Parsing
 @cindex formatting time values
 
   These functions convert time values to text in a string, and vice versa.
-Time values include @code{nil}, numbers, and lists of two to four
-integers (@pxref{Time of Day}).
+Time values include @code{nil}, numbers, and Lisp timestamps
+(@pxref{Time of Day}).
 
 @defun date-to-time string
 This function parses the time-string @var{string} and returns the
-corresponding time value.
+corresponding Lisp timestamp.
 @end defun
 
 @defun format-time-string format-string &optional time zone
@@ -1701,10 +1755,8 @@ Processor Run Time
 @end deffn
 
 @defun get-internal-run-time
-This function returns the processor run time used by Emacs as a list
-of four integers: @code{(@var{sec-high} @var{sec-low} @var{microsec}
-@var{picosec})}, using the same format as @code{current-time}
-(@pxref{Time of Day}).
+This function returns the processor run time used by Emacs, as a Lisp
+timestamp (@pxref{Time of Day}).
 
 Note that the time returned by this function excludes the time Emacs
 was not using the processor, and if the Emacs process has several
@@ -1729,9 +1781,10 @@ Time Calculations
 @cindex calendrical computations
 
   These functions perform calendrical computations using time values
-(@pxref{Time of Day}).  A value of @code{nil} for any of their
+(@pxref{Time of Day}).  As with any time value, a value of
+@code{nil} for any of their
 time-value arguments stands for the current system time, and a single
-integer number stands for the number of seconds since the epoch.
+number stands for the number of seconds since the epoch.
 
 @defun time-less-p t1 t2
 This returns @code{t} if time value @var{t1} is less than time value
@@ -1757,7 +1810,7 @@ Time Calculations
 This returns the sum of two time values, as a time value.
 However, the result is a float if either argument is a float infinity or NaN@.
 One argument should represent a time difference rather than a point in time,
-either as a list or as a single number of elapsed seconds.
+as a time value that is often just a single number of elapsed seconds.
 Here is how to add a number of seconds to a time value:
 
 @example
diff --git a/doc/lispref/processes.texi b/doc/lispref/processes.texi
index 89ad1cf838..e1113e37f1 100644
--- a/doc/lispref/processes.texi
+++ b/doc/lispref/processes.texi
@@ -2158,19 +2158,17 @@ System Processes
 
 @item utime
 Time spent by the process in the user context, for running the
-application's code.  The corresponding @var{value} is in the
-@w{@code{(@var{high} @var{low} @var{microsec} @var{picosec})}} format, the same
-format used by functions @code{current-time} (@pxref{Time of Day,
-current-time}) and @code{file-attributes} (@pxref{File Attributes}).
+application's code.  The corresponding @var{value} is a Lisp
+timestamp (@pxref{Time of Day}).
 
 @item stime
 Time spent by the process in the system (kernel) context, for
-processing system calls.  The corresponding @var{value} is in the same
-format as for @code{utime}.
+processing system calls.  The corresponding @var{value} is a Lisp
+timestamp.
 
 @item time
 The sum of @code{utime} and @code{stime}.  The corresponding
-@var{value} is in the same format as for @code{utime}.
+@var{value} is a Lisp timestamp.
 
 @item cutime
 @itemx cstime
@@ -2189,13 +2187,10 @@ System Processes
 The number of threads in the process.
 
 @item start
-The time when the process was started, in the same
-@code{(@var{high} @var{low} @var{microsec} @var{picosec})} format used by
-@code{file-attributes} and @code{current-time}.
+The time when the process was started, as a Lisp timestamp.
 
 @item etime
-The time elapsed since the process started, in the format @code{(@var{high}
-@var{low} @var{microsec} @var{picosec})}.
+The time elapsed since the process started, as a Lisp timestamp.
 
 @item vsize
 The virtual memory size of the process, measured in kilobytes.
diff --git a/doc/lispref/text.texi b/doc/lispref/text.texi
index 825827095b..6c38d8eed0 100644
--- a/doc/lispref/text.texi
+++ b/doc/lispref/text.texi
@@ -1327,9 +1327,8 @@ Undo
 
 @item (t . @var{time-flag})
 This kind of element indicates that an unmodified buffer became
-modified.  A @var{time-flag} of the form
-@code{(@var{sec-high} @var{sec-low} @var{microsec}
-@var{picosec})} represents the visited file's modification time as of
+modified.  A @var{time-flag} that is a non-integer Lisp timestamp
+represents the visited file's modification time as of
 when it was previously visited or saved, using the same format as
 @code{current-time}; see @ref{Time of Day}.
 A @var{time-flag} of 0 means the buffer does not correspond to any file;
diff --git a/doc/misc/emacs-mime.texi b/doc/misc/emacs-mime.texi
index 9280311b5c..f46b2a7fc1 100644
--- a/doc/misc/emacs-mime.texi
+++ b/doc/misc/emacs-mime.texi
@@ -1524,12 +1524,12 @@ time-date
 @section time-date
 
 While not really a part of the @acronym{MIME} library, it is convenient to
-document this library here.  It deals with parsing @code{Date} headers
+document time conversion functions often used when parsing @code{Date} headers
 and manipulating time.  (Not by using tesseracts, though, I'm sorry to
 say.)
 
-These functions convert between five formats: A date string, an Emacs
-time structure, a decoded time list, a second number, and a day number.
+These functions convert between five formats: A date string, a Lisp
+timestamp, a decoded time list, a second number, and a day number.
 
 Here's a bunch of time/date/second/day examples:
 
@@ -1537,35 +1537,41 @@ time-date
 (parse-time-string "Sat Sep 12 12:21:54 1998 +0200")
 @result{} (54 21 12 12 9 1998 6 -1 7200)
 
-(date-to-time "Sat Sep 12 12:21:54 1998 +0200")
-@result{} (13818 19266)
+(encode-time (date-to-time "Sat Sep 12 12:21:54 1998 +0200")
+             1000000)
+@result{} (905595714000000 . 1000000)
 
-(parse-iso8601-time-string "1998-09-12T12:21:54+0200")
-@result{} (13818 19266)
+(encode-time (parse-iso8601-time-string "1998-09-12T12:21:54+0200")
+             1000000)
+@result{} (905595714000000 . 1000000)
 
-(float-time '(13818 19266))
+(float-time '(905595714000000 . 1000000))
 @result{} 905595714.0
 
-(seconds-to-time 905595714.0)
-@result{} (13818 19266 0 0)
+(encode-time 905595714.0 1000000)
+@result{} (905595714000000 . 1000000)
 
-(time-to-days '(13818 19266))
+(time-to-days '(905595714000000 . 1000000))
 @result{} 729644
 
-(days-to-time 729644)
-@result{} (961933 512)
+(encode-time (days-to-time 729644) 1000000)
+@result{} (63041241600000000 . 1000000)
 
-(time-since '(13818 19266))
-@result{} (6797 9607 984839 247000)
+(encode-time (time-since '(905595714000000 . 1000000))
+             1000000)
+@result{} (631963244775642171 . 1000000000)
 
-(time-less-p '(13818 19266) '(13818 19145))
+(time-less-p '(905595714000000 . 1000000)
+             '(905595593000000000 . 1000000000))
 @result{} nil
 
-(time-equal-p '(13818 19266) '(13818 19145))
-@result{} nil
+(time-equal-p '(905595593000000000 . 1000000000)
+              '(905595593000000    . 1000000   ))
+@result{} t
 
-(time-subtract '(13818 19266) '(13818 19145))
-@result{} (0 121)
+(time-subtract '(905595714000000 . 1000000)
+               '(905595593000000000 . 1000000000))
+@result{} (121000000000 . 1000000000)
 
 (days-between "Sat Sep 12 12:21:54 1998 +0200"
               "Sat Sep 07 12:21:54 1998 +0200")
@@ -1574,13 +1580,13 @@ time-date
 (date-leap-year-p 2000)
 @result{} t
 
-(time-to-day-in-year '(13818 19266))
+(time-to-day-in-year '(905595714000000 . 1000000))
 @result{} 255
 
 (time-to-number-of-days
  (time-since
   (date-to-time "Mon, 01 Jan 2001 02:22:26 GMT")))
-@result{} 4314.095589286675
+@result{} 6472.722661506652
 @end example
 
 And finally, we have @code{safe-date-to-time}, which does the same as
@@ -1595,22 +1601,24 @@ time-date
 12:21:54 1998 +0200"}.
 
 @item time
-An internal Emacs time.  For instance: @code{(13818 26466 0 0)}.
+A Lisp timestamp.
+For instance: @code{(905595714000000 . 1000000)}.
 
 @item seconds
-A floating point representation of the internal Emacs time.  For
-instance: @code{905595714.0}.
+An integer or floating point count of seconds.  For instance:
+@code{905595714.0}, @code{905595714}.
 
 @item days
 An integer number representing the number of days since 00000101.  For
 instance: @code{729644}.
 
 @item decoded time
-A list of decoded time.  For instance: @code{(54 21 12 12 9 1998 6 t
+A list of decoded time.  For instance: @code{(54 21 12 12 9 1998 6 nil
 7200)}.
 @end table
 
-All the examples above represent the same moment.
+All the examples above represent the same moment, except that
+@var{days} represents the day containing the moment.
 
 These are the functions available:
 
@@ -1621,8 +1629,9 @@ time-date
 @item float-time
 Take a time and return seconds.  (This is a built-in function.)
 
-@item seconds-to-time
-Take seconds and return a time.
+@item encode-time
+Take seconds (and other ways to represent time, notably decoded time
+lists), and return a time.
 
 @item time-to-days
 Take a time and return days.
@@ -1645,7 +1654,7 @@ time-date
 than the second time.  (This is a built-in function.)
 
 @item time-equal-p
-Check, whether two time values are equal.  The time values must not be
+Check whether two time values are equal.  The time values need not be
 in the same format.  (This is a built-in function.)
 
 @item time-since
diff --git a/etc/NEWS b/etc/NEWS
index daacf49e62..020450c957 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -995,6 +995,21 @@ like file-attributes that compute file sizes and other attributes,
 functions like process-id that compute process IDs, and functions like
 user-uid and group-gid that compute user and group IDs.
 
++++
+** Although the default timestamp format is still (HI LO US PS),
+it is planned to change in a future Emacs version, to exploit bignums.
+The documentation has been updated to mention that the timestamp
+format may change and that programs should use functions like
+format-time-string, decode-time, and encode-time rather than probing
+the innards of a timestamp directly, or creating a timestamp by hand.
+
++++
+** encode-time supports a new API (encode-time TIME &optional FORM).
+This can convert decoded times and Lisp time values to Lisp timestamps
+of various forms, including a new timestamp form (TICKS . HZ), where
+TICKS is an integer and HZ is a positive integer denoting a clock
+frequency.  The old encode-time API is still supported.
+
 +++
 ** 'time-add', 'time-subtract', and 'time-less-p' now accept
 infinities and NaNs too, and propagate them or return nil like
diff --git a/lisp/calendar/cal-dst.el b/lisp/calendar/cal-dst.el
index 00a8e7498a..25264bda09 100644
--- a/lisp/calendar/cal-dst.el
+++ b/lisp/calendar/cal-dst.el
@@ -97,62 +97,48 @@ calendar-current-time-zone-cache
 ;;;###autoload
 (put 'calendar-current-time-zone-cache 'risky-local-variable t)
 
-(defvar calendar-system-time-basis
+(defconst calendar-system-time-basis
   (calendar-absolute-from-gregorian '(1 1 1970))
   "Absolute date of starting date of system clock.")
 
 (defun calendar-absolute-from-time (x utc-diff)
   "Absolute local date of time X; local time is UTC-DIFF seconds from UTC.
 
-X is (HIGH . LOW) or (HIGH LOW . IGNORED) where HIGH and LOW are the
-high and low 16 bits, respectively, of the number of seconds since
-1970-01-01 00:00:00 UTC, ignoring leap seconds.
+X is the number of seconds since 1970-01-01 00:00:00 UTC,
+ignoring leap seconds.
 
 Returns the pair (ABS-DATE . SECONDS) where SECONDS after local midnight on
 absolute date ABS-DATE is the equivalent moment to X."
-  (let* ((h (car x))
-         (xtail (cdr x))
-         (l (+ utc-diff (if (numberp xtail) xtail (car xtail))))
-         (u (+ (* 512 (mod h 675)) (floor l 128))))
-    ;; Overflow is a terrible thing!
-    (cons (+ calendar-system-time-basis
-             ;; floor((2^16 h +l) / (60*60*24))
-             (* 512 (floor h 675)) (floor u 675))
-          ;; (2^16 h +l) mod (60*60*24)
-          (+ (* (mod u 675) 128) (mod l 128)))))
+  (let ((secsperday 86400)
+        (local (+ x utc-diff)))
+    (cons (+ calendar-system-time-basis (floor local secsperday))
+          (mod local secsperday))))
 
 (defun calendar-time-from-absolute (abs-date s)
   "Time of absolute date ABS-DATE, S seconds after midnight.
 
-Returns the list (HIGH LOW) where HIGH and LOW are the high and low
-16 bits, respectively, of the number of seconds 1970-01-01 00:00:00 UTC,
-ignoring leap seconds, that is the equivalent moment to S seconds after
-midnight UTC on absolute date ABS-DATE."
-  (let* ((a (- abs-date calendar-system-time-basis))
-         (u (+ (* 163 (mod a 512)) (floor s 128))))
-    ;; Overflow is a terrible thing!
-    (list
-     ;; floor((60*60*24*a + s) / 2^16)
-     (+ a (* 163 (floor a 512)) (floor u 512))
-     ;; (60*60*24*a + s) mod 2^16
-     (+ (* 128 (mod u 512)) (mod s 128)))))
+Return the number of seconds since 1970-01-01 00:00:00 UTC,
+ignoring leap seconds, that is the equivalent moment to S seconds
+after midnight UTC on absolute date ABS-DATE."
+  (let ((secsperday 86400))
+    (+ s (* secsperday (- abs-date calendar-system-time-basis)))))
 
 (defun calendar-next-time-zone-transition (time)
   "Return the time of the next time zone transition after TIME.
 Both TIME and the result are acceptable arguments to `current-time-zone'.
 Return nil if no such transition can be found."
-  (let* ((base 65536)           ; 2^16 = base of current-time output
-         (quarter-multiple 120) ; approx = (seconds per quarter year) / base
+  (let* ((time (encode-time time 'integer))
          (time-zone (current-time-zone time))
          (time-utc-diff (car time-zone))
          hi
          hi-zone
          (hi-utc-diff time-utc-diff)
+         (quarter-seconds 7889238) ; Average seconds per 1/4 Gregorian year.
          (quarters '(2 1 3)))
     ;; Heuristic: probe the time zone offset in the next three calendar
     ;; quarters, looking for a time zone offset different from TIME.
     (while (and quarters (eq time-utc-diff hi-utc-diff))
-      (setq hi (cons (+ (car time) (* (car quarters) quarter-multiple)) 0)
+      (setq hi (+ time (* (car quarters) quarter-seconds))
             hi-zone (current-time-zone hi)
             hi-utc-diff (car hi-zone)
             quarters (cdr quarters)))
@@ -163,23 +149,16 @@ calendar-next-time-zone-transition
      ;; Now HI is after the next time zone transition.
      ;; Set LO to TIME, and then binary search to increase LO and decrease HI
      ;; until LO is just before and HI is just after the time zone transition.
-     (let* ((tail (cdr time))
-            (lo (cons (car time) (if (numberp tail) tail (car tail))))
+     (let* ((lo time)
             probe)
        (while
            ;; Set PROBE to halfway between LO and HI, rounding down.
            ;; If PROBE equals LO, we are done.
-           (let* ((lsum (+ (cdr lo) (cdr hi)))
-                  (hsum (+ (car lo) (car hi) (/ lsum base)))
-                  (hsumodd (logand 1 hsum)))
-             (setq probe (cons (/ (- hsum hsumodd) 2)
-                               (/ (+ (* hsumodd base) (% lsum base)) 2)))
-             (not (equal lo probe)))
+           (not (= lo (setq probe (/ (+ lo hi) 2))))
          ;; Set either LO or HI to PROBE, depending on probe results.
          (if (eq (car (current-time-zone probe)) hi-utc-diff)
              (setq hi probe)
            (setq lo probe)))
-       (setcdr hi (list (cdr hi)))
        hi))))
 
 (autoload 'calendar-persian-to-absolute "cal-persia")
diff --git a/lisp/calendar/parse-time.el b/lisp/calendar/parse-time.el
index d6c1e9ea16..9443fde4c9 100644
--- a/lisp/calendar/parse-time.el
+++ b/lisp/calendar/parse-time.el
@@ -227,7 +227,7 @@ parse-iso8601-time-string
 	 (tz-re (nth 2 parse-time-iso8601-regexp))
          re-start
          time seconds minute hour
-         day month year day-of-week dst tz)
+         day month year day-of-week (dst -1) tz)
     ;; We need to populate 'time' with
     ;; (SEC MIN HOUR DAY MON YEAR DOW DST TZ)
 
@@ -243,6 +243,7 @@ parse-iso8601-time-string
 	      seconds (string-to-number (match-string 3 date-string))
 	      re-start (match-end 0))
 	(when (string-match tz-re date-string re-start)
+          (setq dst nil)
           (if (string= "Z" (match-string 1 date-string))
               (setq tz 0)  ;; UTC timezone indicated by Z
             (setq tz (+
@@ -260,7 +261,7 @@ parse-iso8601-time-string
       (setq time (parse-time-string date-string)))
 
     (and time
-	 (apply 'encode-time time))))
+	 (encode-time time))))
 
 (provide 'parse-time)
 
diff --git a/lisp/calendar/time-date.el b/lisp/calendar/time-date.el
index 74c607ccb6..c3898e0257 100644
--- a/lisp/calendar/time-date.el
+++ b/lisp/calendar/time-date.el
@@ -168,15 +168,15 @@ date-to-time
 (defalias 'time-to-seconds 'float-time)
 
 ;;;###autoload
-(defun seconds-to-time (seconds)
-  "Convert SECONDS to a time value."
-  (time-add 0 seconds))
+(defalias 'seconds-to-time 'encode-time)
 
 ;;;###autoload
 (defun days-to-time (days)
   "Convert DAYS into a time value."
-  (let ((time (seconds-to-time (* 86400 days))))
-    (if (integerp days)
+  (let ((time (encode-time (* 86400 days))))
+    ;; Traditionally, this returned a two-element list if DAYS was an integer.
+    ;; Keep that tradition if encode-time outputs timestamps in list form.
+    (if (and (integerp days) (consp (cdr time)))
 	(setcdr (cdr time) nil))
     time))
 
diff --git a/lisp/calendar/timeclock.el b/lisp/calendar/timeclock.el
index b46e7732fd..ddc297604e 100644
--- a/lisp/calendar/timeclock.el
+++ b/lisp/calendar/timeclock.el
@@ -534,8 +534,7 @@ timeclock-workday-elapsed-string
       string)))
 
 (define-obsolete-function-alias 'timeclock-time-to-seconds 'float-time "26.1")
-(define-obsolete-function-alias 'timeclock-seconds-to-time 'seconds-to-time
-  "26.1")
+(define-obsolete-function-alias 'timeclock-seconds-to-time 'encode-time "26.1")
 
 ;; Should today-only be removed in favor of timeclock-relative? - gm
 (defsubst timeclock-when-to-leave (&optional today-only)
diff --git a/lisp/emacs-lisp/timer.el b/lisp/emacs-lisp/timer.el
index 74d37b0eae..927e640fea 100644
--- a/lisp/emacs-lisp/timer.el
+++ b/lisp/emacs-lisp/timer.el
@@ -57,17 +57,11 @@ timer--check
 
 (defun timer--time-setter (timer time)
   (timer--check timer)
-  (setf (timer--high-seconds timer) (pop time))
-  (let ((low time) (usecs 0) (psecs 0))
-    (when (consp time)
-      (setq low (pop time))
-      (when time
-        (setq usecs (pop time))
-        (when time
-          (setq psecs (car time)))))
-    (setf (timer--low-seconds timer) low)
-    (setf (timer--usecs timer) usecs)
-    (setf (timer--psecs timer) psecs)
+  (let ((lt (encode-time time 'list)))
+    (setf (timer--high-seconds timer) (nth 0 lt))
+    (setf (timer--low-seconds timer) (nth 1 lt))
+    (setf (timer--usecs timer) (nth 2 lt))
+    (setf (timer--psecs timer) (nth 3 lt))
     time))
 
 ;; Pseudo field `time'.
@@ -102,24 +96,14 @@ timer-next-integral-multiple-of-time
   "Yield the next value after TIME that is an integral multiple of SECS.
 More precisely, the next value, after TIME, that is an integral multiple
 of SECS seconds since the epoch.  SECS may be a fraction."
-  (let* ((trillion 1000000000000)
-	 (time-sec (+ (nth 1 time)
-		      (* 65536 (nth 0 time))))
-	 (delta-sec (mod (- time-sec) secs))
-	 (next-sec (+ time-sec (floor delta-sec)))
-	 (next-sec-psec (floor (* trillion (mod delta-sec 1))))
-	 (sub-time-psec (+ (or (nth 3 time) 0)
-			   (* 1000000 (nth 2 time))))
-	 (psec-diff (- sub-time-psec next-sec-psec)))
-    (if (and (<= next-sec time-sec) (< 0 psec-diff))
-	(setq next-sec-psec (+ sub-time-psec
-			       (mod (- psec-diff) (* trillion secs)))))
-    (setq next-sec (+ next-sec (floor next-sec-psec trillion)))
-    (setq next-sec-psec (mod next-sec-psec trillion))
-    (list (floor next-sec 65536)
-	  (floor (mod next-sec 65536))
-	  (floor next-sec-psec 1000000)
-	  (floor (mod next-sec-psec 1000000)))))
+  (let* ((ticks-hz (if (and (consp time) (integerp (car time))
+			    (integerp (cdr time)) (< 0 (cdr time)))
+		       time
+		     (encode-time time 1000000000000)))
+	 (hz (cdr ticks-hz))
+	 (s-ticks (* secs hz))
+	 (more-ticks (+ (car ticks-hz) s-ticks)))
+    (encode-time (cons (- more-ticks (% more-ticks s-ticks)) hz))))
 
 (defun timer-relative-time (time secs &optional usecs psecs)
   "Advance TIME by SECS seconds and optionally USECS microseconds
diff --git a/lisp/net/ntlm.el b/lisp/net/ntlm.el
index 217f0b859f..7a68c68ab6 100644
--- a/lisp/net/ntlm.el
+++ b/lisp/net/ntlm.el
@@ -155,8 +155,7 @@ ntlm-compute-timestamp
 				    ;; tenths of microseconds between
 				    ;; 1601-01-01 and 1970-01-01
 				    "116444736000000000)")
-		 ;; add trailing zeros to support old current-time formats
-		 'rawnum (append (current-time) '(0 0))))
+		 'rawnum (encode-time nil 'list)))
 	 result-bytes)
     (dotimes (byte 8)
       (push (calc-eval "and($1,16#FF)" 'rawnum tenths-of-us-since-jan-1-1601)
diff --git a/lisp/obsolete/vc-arch.el b/lisp/obsolete/vc-arch.el
index 9860c9d3fa..e4c52d5146 100644
--- a/lisp/obsolete/vc-arch.el
+++ b/lisp/obsolete/vc-arch.el
@@ -133,7 +133,8 @@ vc-arch-add-tagline
 	(file-error (insert (format "%s <%s> %s"
 				    (current-time-string)
 				    user-mail-address
-				    (+ (nth 2 (current-time))
+				    (+ (% (car (encode-time nil 1000000))
+					  1000000)
 				       (buffer-size)))))))
     (comment-region beg (point))))
 
diff --git a/lisp/org/org-id.el b/lisp/org/org-id.el
index 26b203ff06..ad9b7d1ec7 100644
--- a/lisp/org/org-id.el
+++ b/lisp/org/org-id.el
@@ -357,7 +357,7 @@ org-id-uuid
   "Return string with random (version 4) UUID."
   (let ((rnd (md5 (format "%s%s%s%s%s%s%s"
 			  (random)
-			  (current-time)
+			  (encode-time nil 'list)
 			  (user-uid)
 			  (emacs-pid)
 			  (user-full-name)
@@ -416,7 +416,7 @@ org-id-time-to-b36
   "Encode TIME as a 10-digit string.
 This string holds the time to micro-second accuracy, and can be decoded
 using `org-id-decode'."
-  (setq time (or time (current-time)))
+  (setq time (encode-time time 'list))
   (concat (org-id-int-to-b36 (nth 0 time) 4)
 	  (org-id-int-to-b36 (nth 1 time) 4)
 	  (org-id-int-to-b36 (or (nth 2 time) 0) 4)))
diff --git a/lisp/tar-mode.el b/lisp/tar-mode.el
index 19e5159816..cf4e53abef 100644
--- a/lisp/tar-mode.el
+++ b/lisp/tar-mode.el
@@ -304,7 +304,7 @@ tar-header-block-tokenize
            (tar-parse-octal-integer string tar-uid-offset tar-gid-offset)
            (tar-parse-octal-integer string tar-gid-offset tar-size-offset)
            (tar-parse-octal-integer string tar-size-offset tar-time-offset)
-           (tar-parse-octal-long-integer string tar-time-offset tar-chk-offset)
+           (tar-parse-octal-integer string tar-time-offset tar-chk-offset)
            (tar-parse-octal-integer string tar-chk-offset tar-linkp-offset)
            link-p
            linkname
@@ -342,20 +342,8 @@ tar-parse-octal-integer
 	      start (1+ start)))
       n)))
 
-(defun tar-parse-octal-long-integer (string &optional start end)
-  (if (null start) (setq start 0))
-  (if (null end) (setq end (length string)))
-  (if (= (aref string start) 0)
-      (list 0 0)
-    (let ((lo 0)
-	  (hi 0))
-      (while (< start end)
-	(if (>= (aref string start) ?0)
-	    (setq lo (+ (* lo 8) (- (aref string start) ?0))
-		  hi (+ (* hi 8) (ash lo -16))
-		  lo (logand lo 65535)))
-	(setq start (1+ start)))
-      (list hi lo))))
+(define-obsolete-function-alias 'tar-parse-octal-long-integer
+  'tar-parse-octal-integer "27.1")
 
 (defun tar-parse-octal-integer-safe (string)
   (if (zerop (length string)) (error "empty string"))
@@ -1276,14 +1264,8 @@ tar-alter-one-field
 
 
 (defun tar-octal-time (timeval)
-  ;; Format a timestamp as 11 octal digits.  Ghod, I hope this works...
-  (let ((hibits (car timeval)) (lobits (car (cdr timeval))))
-    (format "%05o%01o%05o"
-	    (ash hibits -2)
-	    (logior (ash (logand 3 hibits) 1)
-		    (if (> (logand lobits 32768) 0) 1 0))
-	    (logand 32767 lobits)
-	    )))
+  ;; Format a timestamp as 11 octal digits.
+  (format "%011o" (encode-time timeval 'integer)))
 
 (defun tar-subfile-save-buffer ()
   "In tar subfile mode, save this buffer into its parent tar-file buffer.
diff --git a/src/bignum.c b/src/bignum.c
index 5d8ab670f2..0ab8de3ab7 100644
--- a/src/bignum.c
+++ b/src/bignum.c
@@ -31,7 +31,7 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
    storage is exhausted.  Admittedly this is not ideal.  An mpz value
    in a temporary is made permanent by mpz_swapping it with a bignum's
    value.  Although typically at most two temporaries are needed,
-   rounding_driver and rounddiv_q need four altogther.  */
+   time_arith, rounddiv_q and rounding_driver each need four.  */
 
 mpz_t mpz[4];
 
diff --git a/src/keyboard.c b/src/keyboard.c
index 1c1f1514ae..8a975d380b 100644
--- a/src/keyboard.c
+++ b/src/keyboard.c
@@ -4162,18 +4162,13 @@ decode_timer (Lisp_Object timer, struct timespec *result)
   Lisp_Object *vec;
 
   if (! (VECTORP (timer) && ASIZE (timer) == 9))
-    return 0;
+    return false;
   vec = XVECTOR (timer)->contents;
   if (! NILP (vec[0]))
-    return 0;
-  if (! FIXNUMP (vec[2]))
     return false;
-
-  struct lisp_time t;
-  if (decode_time_components (vec[1], vec[2], vec[3], vec[8], &t, 0) <= 0)
+  if (! FIXNUMP (vec[2]))
     return false;
-  *result = lisp_to_timespec (t);
-  return timespec_valid_p (*result);
+  return list4_to_timespec (vec[1], vec[2], vec[3], vec[8], result);
 }
 
 
diff --git a/src/systime.c b/src/systime.c
index acd193bbf5..c01b5a6d33 100644
--- a/src/systime.c
+++ b/src/systime.c
@@ -22,12 +22,14 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 #include "systime.h"
 
 #include "blockinput.h"
+#include "bignum.h"
 #include "coding.h"
 #include "lisp.h"
 
 #include <strftime.h>
 
 #include <errno.h>
+#include <limits.h>
 #include <math.h>
 #include <stdio.h>
 #include <stdlib.h>
@@ -55,6 +57,47 @@ along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
 # define TIME_T_MAX TYPE_MAXIMUM (time_t)
 #endif
 
+/* Compile with -DFASTER_SYSTIME=0 to disable common optimizations and
+   allow easier testing of some slow-path code.  */
+#ifndef FASTER_SYSTIME
+# define FASTER_SYSTIME 1
+#endif
+
+/* Whether to warn about Lisp timestamps (TICKS . HZ) that may be
+   instances of obsolete-format timestamps (HI . LO) where HI is
+   the high-order bits and LO the low-order 16 bits.  Currently this
+   is true, but it should change to false in a future version of
+   Emacs.  Compile with -DWARN_OBSOLETE_TIMESTAMPS=0 to see what the
+   future will be like.  */
+#ifndef WARN_OBSOLETE_TIMESTAMPS
+enum { WARN_OBSOLETE_TIMESTAMPS = true };
+#endif
+
+/* Although current-time etc. generate list-format timestamps
+   (HI LO US PS), the plan is to change these functions to generate
+   frequency-based timestamps (TICKS . HZ) in a future release.
+   To try this now, compile with -DCURRENT_TIME_LIST=0.  */
+#ifndef CURRENT_TIME_LIST
+enum { CURRENT_TIME_LIST = true };
+#endif
+
+#if FIXNUM_OVERFLOW_P (1000000000)
+static Lisp_Object timespec_hz;
+#else
+# define timespec_hz make_fixnum (TIMESPEC_HZ)
+#endif
+
+#define TRILLION 1000000000000
+#if FIXNUM_OVERFLOW_P (TRILLION)
+static Lisp_Object trillion;
+# define ztrillion (XBIGNUM (trillion)->value)
+#else
+# define trillion make_fixnum (TRILLION)
+# if ULONG_MAX < TRILLION || !FASTER_SYSTIME
+mpz_t ztrillion;
+# endif
+#endif
+
 /* Return a struct timeval that is roughly equivalent to T.
    Use the least timeval not less than T.
    Return an extremal value if the result would overflow.  */
@@ -69,7 +112,7 @@ make_timeval (struct timespec t)
     {
       if (tv.tv_usec < 999999)
 	tv.tv_usec++;
-      else if (tv.tv_sec < TYPE_MAXIMUM (time_t))
+      else if (tv.tv_sec < TIME_T_MAX)
 	{
 	  tv.tv_sec++;
 	  tv.tv_usec = 0;
@@ -309,52 +352,430 @@ invalid_time (void)
   error ("Invalid time specification");
 }
 
-/* Check a return value compatible with that of decode_time_components.  */
-static void
-check_time_validity (int validity)
+static _Noreturn void
+invalid_hz (Lisp_Object hz)
 {
-  if (validity <= 0)
-    {
-      if (validity < 0)
-	time_overflow ();
-      else
-	invalid_time ();
-    }
+  xsignal2 (Qerror, build_string ("Invalid time frequency"), hz);
 }
 
 /* Return the upper part of the time T (everything but the bottom 16 bits).  */
-static EMACS_INT
+static Lisp_Object
 hi_time (time_t t)
 {
-  time_t hi = t >> LO_TIME_BITS;
-  if (FIXNUM_OVERFLOW_P (hi))
-    time_overflow ();
-  return hi;
+  return INT_TO_INTEGER (t >> LO_TIME_BITS);
 }
 
 /* Return the bottom bits of the time T.  */
-static int
+static Lisp_Object
 lo_time (time_t t)
 {
-  return t & ((1 << LO_TIME_BITS) - 1);
+  return make_fixnum (t & ((1 << LO_TIME_BITS) - 1));
 }
 
-/* Decode a Lisp list SPECIFIED_TIME that represents a time.
-   Set *PHIGH, *PLOW, *PUSEC, *PPSEC to its parts; do not check their values.
-   Return 2, 3, or 4 to indicate the effective length of SPECIFIED_TIME
-   if successful, 0 if unsuccessful.  */
-static int
-disassemble_lisp_time (Lisp_Object specified_time, Lisp_Object *phigh,
-		       Lisp_Object *plow, Lisp_Object *pusec,
-		       Lisp_Object *ppsec)
+/* Convert T into an Emacs time *RESULT, truncating toward minus infinity.
+   Return true if T is in range, false otherwise.  */
+static bool
+decode_float_time (double t, struct lisp_time *result)
+{
+  if (!isfinite (t))
+    return false;
+  /* Actual hz unknown; guess TIMESPEC_HZ.  */
+  mpz_set_d (mpz[1], t);
+  mpz_set_si (mpz[0], floor ((t - trunc (t)) * TIMESPEC_HZ));
+  mpz_addmul_ui (mpz[0], mpz[1], TIMESPEC_HZ);
+  result->ticks = make_integer_mpz ();
+  result->hz = timespec_hz;
+  return true;
+}
+
+/* Compute S + NS/TIMESPEC_HZ as a double.
+   Calls to this function suffer from double-rounding;
+   work around some of the problem by using long double.  */
+static double
+s_ns_to_double (long double s, long double ns)
+{
+  return s + ns / TIMESPEC_HZ;
+}
+
+/* Make a 4-element timestamp (HI LO US PS) from TICKS and HZ.
+   Drop any excess precision.  */
+static Lisp_Object
+ticks_hz_list4 (Lisp_Object ticks, Lisp_Object hz)
+{
+  mpz_t *zticks = bignum_integer (&mpz[0], ticks);
+#if FASTER_SYSTIME && TRILLION <= ULONG_MAX
+  mpz_mul_ui (mpz[0], *zticks, TRILLION);
+#else
+  mpz_mul (mpz[0], *zticks, ztrillion);
+#endif
+  mpz_fdiv_q (mpz[0], mpz[0], *bignum_integer (&mpz[1], hz));
+#if FASTER_SYSTIME && TRILLION <= ULONG_MAX
+  unsigned long int fullps = mpz_fdiv_q_ui (mpz[0], mpz[0], TRILLION);
+  int us = fullps / 1000000;
+  int ps = fullps % 1000000;
+#else
+  mpz_fdiv_qr (mpz[0], mpz[1], mpz[0], ztrillion);
+  int ps = mpz_fdiv_q_ui (mpz[1], mpz[1], 1000000);
+  int us = mpz_get_ui (mpz[1]);
+#endif
+  unsigned long ulo = mpz_get_ui (mpz[0]);
+  if (mpz_sgn (mpz[0]) < 0)
+    ulo = -ulo;
+  int lo = ulo & ((1 << LO_TIME_BITS) - 1);
+  mpz_fdiv_q_2exp (mpz[0], mpz[0], LO_TIME_BITS);
+  return list4 (make_integer_mpz (), make_fixnum (lo),
+		make_fixnum (us), make_fixnum (ps));
+}
+
+/* Set ROP to T.  */
+static void
+mpz_set_time (mpz_t rop, time_t t)
+{
+  if (EXPR_SIGNED (t))
+    mpz_set_intmax (rop, t);
+  else
+    mpz_set_uintmax (rop, t);
+}
+
+/* Store into mpz[0] a clock tick count for T, assuming a
+   TIMESPEC_HZ-frequency clock.  Use mpz[1] as a temp.  */
+static void
+timespec_mpz (struct timespec t)
+{
+  mpz_set_ui (mpz[0], t.tv_nsec);
+  mpz_set_time (mpz[1], t.tv_sec);
+  mpz_addmul_ui (mpz[0], mpz[1], TIMESPEC_HZ);
+}
+
+/* Convert T to a Lisp integer counting TIMESPEC_HZ ticks.  */
+static Lisp_Object
+timespec_ticks (struct timespec t)
+{
+  intmax_t accum;
+  if (FASTER_SYSTIME
+      && !INT_MULTIPLY_WRAPV (t.tv_sec, TIMESPEC_HZ, &accum)
+      && !INT_ADD_WRAPV (t.tv_nsec, accum, &accum))
+    return make_int (accum);
+  timespec_mpz (t);
+  return make_integer_mpz ();
+}
+
+/* Convert T to a Lisp integer counting HZ ticks, taking the floor.
+   Assume T is valid, but check HZ.  */
+static Lisp_Object
+time_hz_ticks (time_t t, Lisp_Object hz)
+{
+  if (FIXNUMP (hz))
+    {
+      if (XFIXNUM (hz) <= 0)
+	invalid_hz (hz);
+      intmax_t ticks;
+      if (FASTER_SYSTIME && !INT_MULTIPLY_WRAPV (t, XFIXNUM (hz), &ticks))
+	return make_int (ticks);
+    }
+  else if (! (BIGNUMP (hz) && 0 < mpz_sgn (XBIGNUM (hz)->value)))
+    invalid_hz (hz);
+
+  mpz_set_time (mpz[0], t);
+  mpz_mul (mpz[0], mpz[0], *bignum_integer (&mpz[1], hz));
+  return make_integer_mpz ();
+}
+static Lisp_Object
+lisp_time_hz_ticks (struct lisp_time t, Lisp_Object hz)
+{
+  if (FASTER_SYSTIME && EQ (t.hz, hz))
+    return t.ticks;
+  if (FIXNUMP (hz))
+    {
+      if (XFIXNUM (hz) <= 0)
+	invalid_hz (hz);
+      intmax_t ticks;
+      if (FASTER_SYSTIME && FIXNUMP (t.ticks) && FIXNUMP (t.hz)
+	  && !INT_MULTIPLY_WRAPV (XFIXNUM (t.ticks), XFIXNUM (hz), &ticks))
+	return make_int (ticks / XFIXNUM (t.hz)
+			 - (ticks % XFIXNUM (t.hz) < 0));
+    }
+  else if (! (BIGNUMP (hz) && 0 < mpz_sgn (XBIGNUM (hz)->value)))
+    invalid_hz (hz);
+
+  mpz_mul (mpz[0],
+	   *bignum_integer (&mpz[0], t.ticks),
+	   *bignum_integer (&mpz[1], hz));
+  mpz_fdiv_q (mpz[0], mpz[0], *bignum_integer (&mpz[1], t.hz));
+  return make_integer_mpz ();
+}
+
+/* Convert T to a Lisp integer counting seconds, taking the floor.  */
+static Lisp_Object
+lisp_time_seconds (struct lisp_time t)
+{
+  if (!FASTER_SYSTIME)
+    return lisp_time_hz_ticks (t, make_fixnum (1));
+  if (FIXNUMP (t.ticks) && FIXNUMP (t.hz))
+    return make_fixnum (XFIXNUM (t.ticks) / XFIXNUM (t.hz)
+			- (XFIXNUM (t.ticks) % XFIXNUM (t.hz) < 0));
+  mpz_fdiv_q (mpz[0],
+	      *bignum_integer (&mpz[0], t.ticks),
+	      *bignum_integer (&mpz[1], t.hz));
+  return make_integer_mpz ();
+}
+
+/* Convert T to a Lisp timestamp.  */
+Lisp_Object
+make_lisp_time (struct timespec t)
+{
+  if (CURRENT_TIME_LIST)
+    {
+      time_t s = t.tv_sec;
+      int ns = t.tv_nsec;
+      return list4 (hi_time (s), lo_time (s),
+		    make_fixnum (ns / 1000), make_fixnum (ns % 1000 * 1000));
+    }
+  else
+    return Fcons (timespec_ticks (t), timespec_hz);
+}
+
+/* Convert T to a Lisp timestamp.  FORM specifies the timestamp format.  */
+static Lisp_Object
+time_form_stamp (time_t t, Lisp_Object form)
+{
+  if (NILP (form))
+    form = CURRENT_TIME_LIST ? Qlist : Qt;
+  if (EQ (form, Qlist))
+    return list2 (hi_time (t), lo_time (t));
+  if (EQ (form, Qt) || EQ (form, Qinteger))
+    return INT_TO_INTEGER (t);
+  return Fcons (time_hz_ticks (t, form), form);
+}
+static Lisp_Object
+lisp_time_form_stamp (struct lisp_time t, Lisp_Object form)
+{
+  if (NILP (form))
+    form = CURRENT_TIME_LIST ? Qlist : Qt;
+  if (EQ (form, Qlist))
+    return ticks_hz_list4 (t.ticks, t.hz);
+  if (EQ (form, Qinteger))
+    return lisp_time_seconds (t);
+  if (EQ (form, Qt))
+    form = t.hz;
+  return Fcons (lisp_time_hz_ticks (t, form), form);
+}
+
+/* From what should be a valid timestamp (TICKS . HZ), generate the
+   corresponding time values.
+
+   If RESULT is not null, store into *RESULT the converted time.
+   Otherwise, store into *DRESULT the number of seconds since the
+   start of the POSIX Epoch.  Unsuccessful calls may or may not store
+   results.
+
+   Return true if successful, false if (TICKS . HZ) would not
+   be a valid new-format timestamp.  */
+static bool
+decode_ticks_hz (Lisp_Object ticks, Lisp_Object hz,
+		 struct lisp_time *result, double *dresult)
+{
+  int ns;
+  mpz_t *q = &mpz[0];
+
+  if (! (INTEGERP (ticks)
+	 && ((FIXNUMP (hz) && 0 < XFIXNUM (hz))
+	     || (BIGNUMP (hz) && 0 < mpz_sgn (XBIGNUM (hz)->value)))))
+    return false;
+
+  if (result)
+    {
+      result->ticks = ticks;
+      result->hz = hz;
+    }
+  else
+    {
+      if (FASTER_SYSTIME && EQ (hz, timespec_hz))
+	{
+	  if (FIXNUMP (ticks))
+	    {
+	      verify (1 < TIMESPEC_HZ);
+	      EMACS_INT s = XFIXNUM (ticks) / TIMESPEC_HZ;
+	      ns = XFIXNUM (ticks) % TIMESPEC_HZ;
+	      if (ns < 0)
+		s--, ns += TIMESPEC_HZ;
+	      *dresult = s_ns_to_double (s, ns);
+	      return true;
+	    }
+	  ns = mpz_fdiv_q_ui (*q, XBIGNUM (ticks)->value, TIMESPEC_HZ);
+	}
+      else if (FASTER_SYSTIME && EQ (hz, make_fixnum (1)))
+	{
+	  ns = 0;
+	  if (FIXNUMP (ticks))
+	    {
+	      *dresult = XFIXNUM (ticks);
+	      return true;
+	    }
+	  q = &XBIGNUM (ticks)->value;
+	}
+      else
+	{
+	  mpz_mul_ui (*q, *bignum_integer (&mpz[1], ticks), TIMESPEC_HZ);
+	  mpz_fdiv_q (*q, *q, *bignum_integer (&mpz[1], hz));
+	  ns = mpz_fdiv_q_ui (*q, *q, TIMESPEC_HZ);
+	}
+
+      *dresult = s_ns_to_double (mpz_get_d (*q), ns);
+    }
+
+  return true;
+}
+
+/* Lisp timestamp classification.  */
+enum timeform
+  {
+   TIMEFORM_INVALID = 0,
+   TIMEFORM_HI_LO, /* seconds in the form (HI << LO_TIME_BITS) + LO.  */
+   TIMEFORM_HI_LO_US, /* seconds plus microseconds (HI LO US) */
+   TIMEFORM_NIL, /* current time in nanoseconds */
+   TIMEFORM_HI_LO_US_PS, /* seconds plus micro and picoseconds (HI LO US PS) */
+   TIMEFORM_FLOAT, /* time as a float */
+   TIMEFORM_TICKS_HZ /* fractional time: HI is ticks, LO is ticks per second */
+  };
+
+/* From the valid form FORM and the time components HIGH, LOW, USEC
+   and PSEC, generate the corresponding time value.  If LOW is
+   floating point, the other components should be zero and FORM should
+   not be TIMEFORM_TICKS_HZ.
+
+   If RESULT is not null, store into *RESULT the converted time.
+   Otherwise, store into *DRESULT the number of seconds since the
+   start of the POSIX Epoch.  Unsuccessful calls may or may not store
+   results.
+
+   Return true if successful, false if the components are of the wrong
+   type.  */
+static bool
+decode_time_components (enum timeform form,
+			Lisp_Object high, Lisp_Object low,
+			Lisp_Object usec, Lisp_Object psec,
+			struct lisp_time *result, double *dresult)
+{
+  switch (form)
+    {
+    case TIMEFORM_INVALID:
+      return false;
+
+    case TIMEFORM_TICKS_HZ:
+      return decode_ticks_hz (high, low, result, dresult);
+
+    case TIMEFORM_FLOAT:
+      {
+	double t = XFLOAT_DATA (low);
+	if (result)
+	  return decode_float_time (t, result);
+	else
+	  {
+	    *dresult = t;
+	    return true;
+	  }
+      }
+
+    case TIMEFORM_NIL:
+      {
+	struct timespec now = current_timespec ();
+	if (result)
+	  {
+	    result->ticks = timespec_ticks (now);
+	    result->hz = timespec_hz;
+	  }
+	else
+	  *dresult = s_ns_to_double (now.tv_sec, now.tv_nsec);
+	return true;
+      }
+
+    default:
+      break;
+    }
+
+  if (! (INTEGERP (high) && INTEGERP (low)
+	 && FIXNUMP (usec) && FIXNUMP (psec)))
+    return false;
+  EMACS_INT us = XFIXNUM (usec);
+  EMACS_INT ps = XFIXNUM (psec);
+
+  /* Normalize out-of-range lower-order components by carrying
+     each overflow into the next higher-order component.  */
+  us += ps / 1000000 - (ps % 1000000 < 0);
+  mpz_set_intmax (mpz[0], us / 1000000 - (us % 1000000 < 0));
+  mpz_add (mpz[0], mpz[0], *bignum_integer (&mpz[1], low));
+  mpz_addmul_ui (mpz[0], *bignum_integer (&mpz[1], high), 1 << LO_TIME_BITS);
+  ps = ps % 1000000 + 1000000 * (ps % 1000000 < 0);
+  us = us % 1000000 + 1000000 * (us % 1000000 < 0);
+
+  if (result)
+    {
+      switch (form)
+	{
+	case TIMEFORM_HI_LO:
+	  /* Floats and nil were handled above, so it was an integer.  */
+	  result->hz = make_fixnum (1);
+	  break;
+
+	case TIMEFORM_HI_LO_US:
+	  mpz_mul_ui (mpz[0], mpz[0], 1000000);
+	  mpz_add_ui (mpz[0], mpz[0], us);
+	  result->hz = make_fixnum (1000000);
+	  break;
+
+	case TIMEFORM_HI_LO_US_PS:
+	  mpz_mul_ui (mpz[0], mpz[0], 1000000);
+	  mpz_add_ui (mpz[0], mpz[0], us);
+	  mpz_mul_ui (mpz[0], mpz[0], 1000000);
+	  mpz_add_ui (mpz[0], mpz[0], ps);
+	  result->hz = trillion;
+	  break;
+
+	default:
+	  eassume (false);
+	}
+      result->ticks = make_integer_mpz ();
+    }
+  else
+    *dresult = mpz_get_d (mpz[0]) + (us * 1e6L + ps) / 1e12L;
+
+  return true;
+}
+
+enum { DECODE_SECS_ONLY = WARN_OBSOLETE_TIMESTAMPS + 1 };
+
+/* Decode a Lisp timestamp SPECIFIED_TIME that represents a time.
+
+   FLAGS specifies conversion flags.  If FLAGS & DECODE_SECS_ONLY,
+   ignore and do not validate any sub-second components of an
+   old-format SPECIFIED_TIME.  If FLAGS & WARN_OBSOLETE_TIMESTAMPS,
+   diagnose what could be obsolete (HIGH . LOW) timestamps.
+
+   If PFORM is not null, store into *PFORM the form of SPECIFIED-TIME.
+   If RESULT is not null, store into *RESULT the converted time;
+   otherwise, store into *DRESULT the number of seconds since the
+   start of the POSIX Epoch.  Unsuccessful calls may or may not store
+   results.
+
+   Return true if successful, false if SPECIFIED_TIME is
+   not a valid Lisp timestamp.  */
+static bool
+decode_lisp_time (Lisp_Object specified_time, int flags,
+		  enum timeform *pform,
+		  struct lisp_time *result, double *dresult)
 {
   Lisp_Object high = make_fixnum (0);
   Lisp_Object low = specified_time;
   Lisp_Object usec = make_fixnum (0);
   Lisp_Object psec = make_fixnum (0);
-  int len = 4;
+  enum timeform form = TIMEFORM_HI_LO;
 
-  if (CONSP (specified_time))
+  if (NILP (specified_time))
+    form = TIMEFORM_NIL;
+  else if (FLOATP (specified_time))
+    form = TIMEFORM_FLOAT;
+  else if (CONSP (specified_time))
     {
       high = XCAR (specified_time);
       low = XCDR (specified_time);
@@ -362,259 +783,185 @@ disassemble_lisp_time (Lisp_Object specified_time, Lisp_Object *phigh,
 	{
 	  Lisp_Object low_tail = XCDR (low);
 	  low = XCAR (low);
-	  if (CONSP (low_tail))
+	  if (! (flags & DECODE_SECS_ONLY))
 	    {
-	      usec = XCAR (low_tail);
-	      low_tail = XCDR (low_tail);
 	      if (CONSP (low_tail))
-		psec = XCAR (low_tail);
-	      else
-		len = 3;
-	    }
-	  else if (!NILP (low_tail))
-	    {
-	      usec = low_tail;
-	      len = 3;
+		{
+		  usec = XCAR (low_tail);
+		  low_tail = XCDR (low_tail);
+		  if (CONSP (low_tail))
+		    {
+		      psec = XCAR (low_tail);
+		      form = TIMEFORM_HI_LO_US_PS;
+		    }
+		  else
+		    form = TIMEFORM_HI_LO_US;
+		}
+	      else if (!NILP (low_tail))
+		{
+		  usec = low_tail;
+		  form = TIMEFORM_HI_LO_US;
+		}
 	    }
-	  else
-	    len = 2;
 	}
       else
-	len = 2;
+	{
+	  if (flags & WARN_OBSOLETE_TIMESTAMPS
+	      && RANGED_FIXNUMP (0, low, (1 << LO_TIME_BITS) - 1))
+	    message ("obsolete timestamp with cdr %"pI"d", XFIXNUM (low));
+	  form = TIMEFORM_TICKS_HZ;
+	}
 
-      /* When combining components, require LOW to be an integer,
-	 as otherwise it would be a pain to add up times.  */
+      /* Require LOW to be an integer, as otherwise the computation
+	 would be considerably trickier.  */
       if (! INTEGERP (low))
-	return 0;
+	form = TIMEFORM_INVALID;
     }
-  else if (INTEGERP (specified_time))
-    len = 2;
-
-  *phigh = high;
-  *plow = low;
-  *pusec = usec;
-  *ppsec = psec;
-  return len;
+
+  if (pform)
+    *pform = form;
+  return decode_time_components (form, high, low, usec, psec, result, dresult);
 }
 
-/* Convert T into an Emacs time *RESULT, truncating toward minus infinity.
-   Return true if T is in range, false otherwise.  */
+/* Convert Z to time_t, returning true if it fits.  */
 static bool
-decode_float_time (double t, struct lisp_time *result)
+mpz_time (mpz_t const z, time_t *t)
 {
-  double lo_multiplier = 1 << LO_TIME_BITS;
-  double emacs_time_min = MOST_NEGATIVE_FIXNUM * lo_multiplier;
-  if (! (emacs_time_min <= t && t < -emacs_time_min))
-    return false;
-
-  double small_t = t / lo_multiplier;
-  EMACS_INT hi = small_t;
-  double t_sans_hi = t - hi * lo_multiplier;
-  int lo = t_sans_hi;
-  long double fracps = (t_sans_hi - lo) * 1e12L;
-#ifdef INT_FAST64_MAX
-  int_fast64_t ifracps = fracps;
-  int us = ifracps / 1000000;
-  int ps = ifracps % 1000000;
-#else
-  int us = fracps / 1e6L;
-  int ps = fracps - us * 1e6L;
-#endif
-  us -= (ps < 0);
-  ps += (ps < 0) * 1000000;
-  lo -= (us < 0);
-  us += (us < 0) * 1000000;
-  hi -= (lo < 0);
-  lo += (lo < 0) << LO_TIME_BITS;
-  result->hi = hi;
-  result->lo = lo;
-  result->us = us;
-  result->ps = ps;
+  if (TYPE_SIGNED (time_t))
+    {
+      intmax_t i;
+      if (! (mpz_to_intmax (z, &i) && TIME_T_MIN <= i && i <= TIME_T_MAX))
+	return false;
+      *t = i;
+    }
+  else
+    {
+      uintmax_t i;
+      if (! (mpz_to_uintmax (z, &i) && i <= TIME_T_MAX))
+	return false;
+      *t = i;
+    }
   return true;
 }
 
-/* From the time components HIGH, LOW, USEC and PSEC taken from a Lisp
-   list, generate the corresponding time value.
-   If LOW is floating point, the other components should be zero.
-
-   If RESULT is not null, store into *RESULT the converted time.
-   If *DRESULT is not null, store into *DRESULT the number of
-   seconds since the start of the POSIX Epoch.
-
-   Return 1 if successful, 0 if the components are of the
-   wrong type, and -1 if the time is out of range.  */
-int
-decode_time_components (Lisp_Object high, Lisp_Object low, Lisp_Object usec,
-			Lisp_Object psec,
-			struct lisp_time *result, double *dresult)
+/* Convert T to struct timespec, returning an invalid timespec
+   if T does not fit.  */
+static struct timespec
+lisp_to_timespec (struct lisp_time t)
 {
-  EMACS_INT hi, us, ps;
-  intmax_t lo;
-  if (! (FIXNUMP (high)
-	 && FIXNUMP (usec) && FIXNUMP (psec)))
-    return 0;
-  if (! INTEGERP (low))
+  struct timespec result = invalid_timespec ();
+  int ns;
+  mpz_t *q = &mpz[0];
+
+  if (FASTER_SYSTIME && EQ (t.hz, timespec_hz))
     {
-      if (FLOATP (low))
-	{
-	  double t = XFLOAT_DATA (low);
-	  if (result && ! decode_float_time (t, result))
-	    return -1;
-	  if (dresult)
-	    *dresult = t;
-	  return 1;
-	}
-      else if (NILP (low))
+      if (FIXNUMP (t.ticks))
 	{
-	  struct timespec now = current_timespec ();
-	  if (result)
+	  EMACS_INT s = XFIXNUM (t.ticks) / TIMESPEC_HZ;
+	  ns = XFIXNUM (t.ticks) % TIMESPEC_HZ;
+	  if (ns < 0)
+	    s--, ns += TIMESPEC_HZ;
+	  if ((TYPE_SIGNED (time_t) ? TIME_T_MIN <= s : 0 <= s)
+	      && s <= TIME_T_MAX)
 	    {
-	      result->hi = hi_time (now.tv_sec);
-	      result->lo = lo_time (now.tv_sec);
-	      result->us = now.tv_nsec / 1000;
-	      result->ps = now.tv_nsec % 1000 * 1000;
+	      result.tv_sec = s;
+	      result.tv_nsec = ns;
 	    }
-	  if (dresult)
-	    *dresult = now.tv_sec + now.tv_nsec / 1e9;
-	  return 1;
+	  return result;
 	}
       else
-	return 0;
+	ns = mpz_fdiv_q_ui (*q, XBIGNUM (t.ticks)->value, TIMESPEC_HZ);
     }
-
-  hi = XFIXNUM (high);
-  if (! integer_to_intmax (low, &lo))
-    return -1;
-  us = XFIXNUM (usec);
-  ps = XFIXNUM (psec);
-
-  /* Normalize out-of-range lower-order components by carrying
-     each overflow into the next higher-order component.  */
-  us += ps / 1000000 - (ps % 1000000 < 0);
-  lo += us / 1000000 - (us % 1000000 < 0);
-  if (INT_ADD_WRAPV (lo >> LO_TIME_BITS, hi, &hi))
-    return -1;
-  ps = ps % 1000000 + 1000000 * (ps % 1000000 < 0);
-  us = us % 1000000 + 1000000 * (us % 1000000 < 0);
-  lo &= (1 << LO_TIME_BITS) - 1;
-
-  if (result)
+  else if (FASTER_SYSTIME && EQ (t.hz, make_fixnum (1)))
     {
-      if (FIXNUM_OVERFLOW_P (hi))
-	return -1;
-      result->hi = hi;
-      result->lo = lo;
-      result->us = us;
-      result->ps = ps;
+      ns = 0;
+      if (FIXNUMP (t.ticks))
+	{
+	  EMACS_INT s = XFIXNUM (t.ticks);
+	  if ((TYPE_SIGNED (time_t) ? TIME_T_MIN <= s : 0 <= s)
+	      && s <= TIME_T_MAX)
+	    {
+	      result.tv_sec = s;
+	      result.tv_nsec = ns;
+	    }
+	  return result;
+	}
+      else
+	q = &XBIGNUM (t.ticks)->value;
     }
-
-  if (dresult)
+  else
     {
-      double dhi = hi;
-      *dresult = (us * 1e6 + ps) / 1e12 + lo + dhi * (1 << LO_TIME_BITS);
+      mpz_mul_ui (*q, *bignum_integer (q, t.ticks), TIMESPEC_HZ);
+      mpz_fdiv_q (*q, *q, *bignum_integer (&mpz[1], t.hz));
+      ns = mpz_fdiv_q_ui (*q, *q, TIMESPEC_HZ);
     }
 
-  return 1;
+  if (mpz_time (*q, &result.tv_sec))
+    result.tv_nsec = ns;
+  return result;
 }
 
-struct timespec
-lisp_to_timespec (struct lisp_time t)
+/* Convert (HIGH LOW USEC PSEC) to struct timespec.
+   Return true if successful.  */
+bool
+list4_to_timespec (Lisp_Object high, Lisp_Object low,
+		   Lisp_Object usec, Lisp_Object psec,
+		   struct timespec *result)
 {
-  if (! ((TYPE_SIGNED (time_t) ? TIME_T_MIN >> LO_TIME_BITS <= t.hi : 0 <= t.hi)
-	 && t.hi <= TIME_T_MAX >> LO_TIME_BITS))
-    return invalid_timespec ();
-  time_t s = (t.hi << LO_TIME_BITS) + t.lo;
-  int ns = t.us * 1000 + t.ps / 1000;
-  return make_timespec (s, ns);
+  struct lisp_time t;
+  if (! decode_time_components (TIMEFORM_HI_LO_US_PS, high, low, usec, psec,
+				&t, 0))
+    return false;
+  *result = lisp_to_timespec (t);
+  return timespec_valid_p (*result);
 }
 
 /* Decode a Lisp list SPECIFIED_TIME that represents a time.
-   Store its effective length into *PLEN.
    If SPECIFIED_TIME is nil, use the current time.
    Signal an error if SPECIFIED_TIME does not represent a time.  */
 static struct lisp_time
-lisp_time_struct (Lisp_Object specified_time, int *plen)
+lisp_time_struct (Lisp_Object specified_time, enum timeform *pform)
 {
-  Lisp_Object high, low, usec, psec;
+  int flags = WARN_OBSOLETE_TIMESTAMPS;
   struct lisp_time t;
-  int len = disassemble_lisp_time (specified_time, &high, &low, &usec, &psec);
-  if (!len)
+  if (! decode_lisp_time (specified_time, flags, pform, &t, 0))
     invalid_time ();
-  int val = decode_time_components (high, low, usec, psec, &t, 0);
-  check_time_validity (val);
-  *plen = len;
   return t;
 }
 
-/* Like lisp_time_struct, except return a struct timespec.
-   Discard any low-order digits.  */
+/* Decode a Lisp list SPECIFIED_TIME that represents a time.
+   Discard any low-order (sub-ns) resolution.
+   If SPECIFIED_TIME is nil, use the current time.
+   Signal an error if SPECIFIED_TIME does not represent a timespec.  */
 struct timespec
 lisp_time_argument (Lisp_Object specified_time)
 {
-  int len;
-  struct lisp_time lt = lisp_time_struct (specified_time, &len);
+  struct lisp_time lt = lisp_time_struct (specified_time, 0);
   struct timespec t = lisp_to_timespec (lt);
   if (! timespec_valid_p (t))
     time_overflow ();
   return t;
 }
 
-/* Like lisp_time_argument, except decode only the seconds part,
-   and do not check the subseconds part.  */
+/* Like lisp_time_argument, except decode only the seconds part, and
+   do not check the subseconds part.  */
 static time_t
 lisp_seconds_argument (Lisp_Object specified_time)
 {
-  Lisp_Object high, low, usec, psec;
-  struct lisp_time t;
-
-  int val = disassemble_lisp_time (specified_time, &high, &low, &usec, &psec);
-  if (val != 0)
-    {
-      val = decode_time_components (high, low, make_fixnum (0),
-				    make_fixnum (0), &t, 0);
-      if (0 < val
-	  && ! ((TYPE_SIGNED (time_t)
-		 ? TIME_T_MIN >> LO_TIME_BITS <= t.hi
-		 : 0 <= t.hi)
-		&& t.hi <= TIME_T_MAX >> LO_TIME_BITS))
-	val = -1;
-    }
-  check_time_validity (val);
-  return (t.hi << LO_TIME_BITS) + t.lo;
-}
-
-static struct lisp_time
-time_add (struct lisp_time ta, struct lisp_time tb)
-{
-  EMACS_INT hi = ta.hi + tb.hi;
-  int lo = ta.lo + tb.lo;
-  int us = ta.us + tb.us;
-  int ps = ta.ps + tb.ps;
-  us += (1000000 <= ps);
-  ps -= (1000000 <= ps) * 1000000;
-  lo += (1000000 <= us);
-  us -= (1000000 <= us) * 1000000;
-  hi += (1 << LO_TIME_BITS <= lo);
-  lo -= (1 << LO_TIME_BITS <= lo) << LO_TIME_BITS;
-  return (struct lisp_time) { hi, lo, us, ps };
-}
-
-static struct lisp_time
-time_subtract (struct lisp_time ta, struct lisp_time tb)
-{
-  EMACS_INT hi = ta.hi - tb.hi;
-  int lo = ta.lo - tb.lo;
-  int us = ta.us - tb.us;
-  int ps = ta.ps - tb.ps;
-  us -= (ps < 0);
-  ps += (ps < 0) * 1000000;
-  lo -= (us < 0);
-  us += (us < 0) * 1000000;
-  hi -= (lo < 0);
-  lo += (lo < 0) << LO_TIME_BITS;
-  return (struct lisp_time) { hi, lo, us, ps };
+  int flags = WARN_OBSOLETE_TIMESTAMPS | DECODE_SECS_ONLY;
+  struct lisp_time lt;
+  if (! decode_lisp_time (specified_time, flags, 0, &lt, 0))
+    invalid_time ();
+  struct timespec t = lisp_to_timespec (lt);
+  if (! timespec_valid_p (t))
+    time_overflow ();
+  return t.tv_sec;
 }
 
+/* Given Lisp operands A and B, add their values, and return the
+   result as a Lisp timestamp that is in (TICKS . HZ) form if either A
+   or B are in that form, (HI LO US PS) form otherwise.  Subtract
+   instead of adding if SUBTRACT.  */
 static Lisp_Object
 time_arith (Lisp_Object a, Lisp_Object b, bool subtract)
 {
@@ -627,45 +974,80 @@ time_arith (Lisp_Object a, Lisp_Object b, bool subtract)
   if (FLOATP (b) && !isfinite (XFLOAT_DATA (b)))
     return subtract ? make_float (-XFLOAT_DATA (b)) : b;
 
-  int alen, blen;
-  struct lisp_time ta = lisp_time_struct (a, &alen);
-  struct lisp_time tb = lisp_time_struct (b, &blen);
-  struct lisp_time t = (subtract ? time_subtract : time_add) (ta, tb);
-  if (FIXNUM_OVERFLOW_P (t.hi))
-    time_overflow ();
-  Lisp_Object val = Qnil;
+  enum timeform aform, bform;
+  struct lisp_time ta = lisp_time_struct (a, &aform);
+  struct lisp_time tb = lisp_time_struct (b, &bform);
+  Lisp_Object ticks, hz;
 
-  switch (max (alen, blen))
+  if (FASTER_SYSTIME && EQ (ta.hz, tb.hz))
     {
-    default:
-      val = Fcons (make_fixnum (t.ps), val);
-      FALLTHROUGH;
-    case 3:
-      val = Fcons (make_fixnum (t.us), val);
-      FALLTHROUGH;
-    case 2:
-      val = Fcons (make_fixnum (t.lo), val);
-      val = Fcons (make_fixnum (t.hi), val);
-      break;
+      hz = ta.hz;
+      if (FIXNUMP (ta.ticks) && FIXNUMP (tb.ticks))
+	ticks = make_int (subtract
+			  ? XFIXNUM (ta.ticks) - XFIXNUM (tb.ticks)
+			  : XFIXNUM (ta.ticks) + XFIXNUM (tb.ticks));
+      else
+	{
+	  (subtract ? mpz_sub : mpz_add)
+	    (mpz[0],
+	     *bignum_integer (&mpz[0], ta.ticks),
+	     *bignum_integer (&mpz[1], tb.ticks));
+	  ticks = make_integer_mpz ();
+	}
+    }
+  else
+    {
+      /* The plan is to decompose ta into na/da and tb into nb/db.
+	 Start by computing da and db.  */
+      mpz_t *da = bignum_integer (&mpz[1], ta.hz);
+      mpz_t *db = bignum_integer (&mpz[2], tb.hz);
+
+      /* The plan is to compute (na * (db/g) + nb * (da/g)) / lcm (da, db)
+	 where g = gcd (da, db).  Start by computing g.  */
+      mpz_t *g = &mpz[3];
+      mpz_gcd (*g, *da, *db);
+
+      /* fa = da/g, fb = db/g.  */
+      mpz_t *fa = &mpz[1], *fb = &mpz[3];
+      mpz_tdiv_q (*fa, *da, *g);
+      mpz_tdiv_q (*fb, *db, *g);
+
+      /* FIXME: Maybe omit need for extra temp by computing fa * db here?  */
+
+      /* hz = fa * db.  This is equal to lcm (da, db).  */
+      mpz_mul (mpz[0], *fa, *db);
+      hz = make_integer_mpz ();
+
+      /* ticks = (fb * na) OPER (fa * nb), where OPER is + or -.
+	 OP is the multiply-add or multiply-sub form of OPER.  */
+      mpz_t *na = bignum_integer (&mpz[0], ta.ticks);
+      mpz_mul (mpz[0], *fb, *na);
+      mpz_t *nb = bignum_integer (&mpz[3], tb.ticks);
+      (subtract ? mpz_submul : mpz_addmul) (mpz[0], *fa, *nb);
+      ticks = make_integer_mpz ();
     }
 
-  return val;
+  /* Return the (TICKS . HZ) form if either argument is that way,
+     otherwise the (HI LO US PS) form for backward compatibility.  */
+  return (aform == TIMEFORM_TICKS_HZ || bform == TIMEFORM_TICKS_HZ
+	  ? Fcons (ticks, hz)
+	  : ticks_hz_list4 (ticks, hz));
 }
 
 DEFUN ("time-add", Ftime_add, Stime_add, 2, 2, 0,
-       doc: /* Return the sum of two time values A and B, as a time value.
-A nil value for either argument stands for the current time.
-See `current-time-string' for the various forms of a time value.  */)
+       doc: /* Return the sum of two time values A and B, as a timestamp.
+See Info node `(elisp)Time of Day' for time value formats.
+For example, nil stands for the current time.  */)
   (Lisp_Object a, Lisp_Object b)
 {
   return time_arith (a, b, false);
 }
 
 DEFUN ("time-subtract", Ftime_subtract, Stime_subtract, 2, 2, 0,
-       doc: /* Return the difference between two time values A and B, as a time value.
-Use `float-time' to convert the difference into elapsed seconds.
-A nil value for either argument stands for the current time.
-See `current-time-string' for the various forms of a time value.  */)
+       doc: /* Return the difference between two time values A and B, as a timestamp.
+You can use `float-time' to convert the difference into elapsed seconds.
+See Info node `(elisp)Time of Day' for time value formats.
+For example, nil stands for the current time.  */)
   (Lisp_Object a, Lisp_Object b)
 {
   return time_arith (a, b, true);
@@ -685,54 +1067,52 @@ time_cmp (Lisp_Object a, Lisp_Object b)
       return da < db ? -1 : da != db;
     }
 
-  int alen, blen;
-  struct lisp_time ta = lisp_time_struct (a, &alen);
-  struct lisp_time tb = lisp_time_struct (b, &blen);
-  return (ta.hi != tb.hi ? (ta.hi < tb.hi ? -1 : 1)
-	  : ta.lo != tb.lo ? (ta.lo < tb.lo ? -1 : 1)
-	  : ta.us != tb.us ? (ta.us < tb.us ? -1 : 1)
-	  : ta.ps < tb.ps ? -1 : ta.ps != tb.ps);
+  struct lisp_time ta = lisp_time_struct (a, 0);
+
+  /* Compare nil to nil correctly, and other eq values while we're at it.
+     Compare here rather than earlier, to handle NaNs and check formats.  */
+  if (EQ (a, b))
+    return 0;
+
+  struct lisp_time tb = lisp_time_struct (b, 0);
+  mpz_t *za = bignum_integer (&mpz[0], ta.ticks);
+  mpz_t *zb = bignum_integer (&mpz[1], tb.ticks);
+  if (! (FASTER_SYSTIME && EQ (ta.hz, tb.hz)))
+    {
+      /* This could be sped up by looking at the signs, sizes, and
+	 number of bits of the two sides; see how GMP does mpq_cmp.
+	 It may not be worth the trouble here, though.  */
+      mpz_mul (mpz[0], *za, *bignum_integer (&mpz[2], tb.hz));
+      mpz_mul (mpz[1], *zb, *bignum_integer (&mpz[2], ta.hz));
+      za = &mpz[0];
+      zb = &mpz[1];
+    }
+  return mpz_cmp (*za, *zb);
 }
 
 DEFUN ("time-less-p", Ftime_less_p, Stime_less_p, 2, 2, 0,
-       doc: /* Return non-nil if time value T1 is earlier than time value T2.
-A nil value for either argument stands for the current time.
-See `current-time-string' for the various forms of a time value.  */)
-  (Lisp_Object t1, Lisp_Object t2)
+       doc: /* Return non-nil if time value A is less than time value B.
+See Info node `(elisp)Time of Day' for time value formats.
+For example, nil stands for the current time.  */)
+  (Lisp_Object a, Lisp_Object b)
 {
-  return time_cmp (t1, t2) < 0 ? Qt : Qnil;
+  return time_cmp (a, b) < 0 ? Qt : Qnil;
 }
 
 DEFUN ("time-equal-p", Ftime_equal_p, Stime_equal_p, 2, 2, 0,
-       doc: /* Return non-nil if T1 and T2 are equal time values.
-A nil value for either argument stands for the current time.
-See `current-time-string' for the various forms of a time value.  */)
-  (Lisp_Object t1, Lisp_Object t2)
+       doc: /* Return non-nil if A and B are equal time values.
+See Info node `(elisp)Time of Day' for time value formats.  */)
+  (Lisp_Object a, Lisp_Object b)
 {
-  return time_cmp (t1, t2) == 0 ? Qt : Qnil;
+  return time_cmp (a, b) == 0 ? Qt : Qnil;
 }
 \f
 
-/* Make a Lisp list that represents the Emacs time T.  T may be an
-   invalid time, with a slightly negative tv_nsec value such as
-   UNKNOWN_MODTIME_NSECS; in that case, the Lisp list contains a
-   correspondingly negative picosecond count.  */
-Lisp_Object
-make_lisp_time (struct timespec t)
-{
-  time_t s = t.tv_sec;
-  int ns = t.tv_nsec;
-  return list4i (hi_time (s), lo_time (s), ns / 1000, ns % 1000 * 1000);
-}
-
 DEFUN ("float-time", Ffloat_time, Sfloat_time, 0, 1, 0,
        doc: /* Return the current time, as a float number of seconds since the epoch.
-If SPECIFIED-TIME is given, it is the time to convert to float
-instead of the current time.  The argument should have the form
-\(HIGH LOW) or (HIGH LOW USEC) or (HIGH LOW USEC PSEC).  Thus,
-you can use times from `current-time' and from `file-attributes'.
-SPECIFIED-TIME can also have the form (HIGH . LOW), but this is
-considered obsolete.
+If SPECIFIED-TIME is given, it is a Lisp time value to convert to
+float instead of the current time.  See Info node `(elisp)Time of Day'
+for time value formats.
 
 WARNING: Since the result is floating point, it may not be exact.
 If precise time stamps are required, use either `current-time',
@@ -740,9 +1120,7 @@ or (if you need time as a string) `format-time-string'.  */)
   (Lisp_Object specified_time)
 {
   double t;
-  Lisp_Object high, low, usec, psec;
-  if (! (disassemble_lisp_time (specified_time, &high, &low, &usec, &psec)
-	 && decode_time_components (high, low, usec, psec, 0, &t)))
+  if (! decode_lisp_time (specified_time, 0, 0, 0, &t))
     invalid_time ();
   return make_float (t);
 }
@@ -849,10 +1227,7 @@ format_time_string (char const *format, ptrdiff_t formatlen,
 
 DEFUN ("format-time-string", Fformat_time_string, Sformat_time_string, 1, 3, 0,
        doc: /* Use FORMAT-STRING to format the time TIME, or now if omitted or nil.
-TIME is specified as (HIGH LOW USEC PSEC), as returned by
-`current-time' or `file-attributes'.  It can also be a single integer
-number of seconds since the epoch.  The obsolete form (HIGH . LOW) is
-also still accepted.
+TIME is a Lisp time value; see Info node `(elisp)Time of Day'.
 
 The optional ZONE is omitted or nil for Emacs local time, t for
 Universal Time, `wall' for system wall clock time, or a string as in
@@ -925,10 +1300,8 @@ usage: (format-time-string FORMAT-STRING &optional TIME ZONE)  */)
 
 DEFUN ("decode-time", Fdecode_time, Sdecode_time, 0, 2, 0,
        doc: /* Decode a time value as (SEC MINUTE HOUR DAY MONTH YEAR DOW DST UTCOFF).
-The optional TIME should be a list of (HIGH LOW . IGNORED),
-as from `current-time' and `file-attributes', or nil to use the
-current time.  It can also be a single integer number of seconds since
-the epoch.  The obsolete form (HIGH . LOW) is also still accepted.
+The optional TIME is the Lisp time value to convert.  See Info node
+`(elisp)Time of Day' for time value formats.
 
 The optional ZONE is omitted or nil for Emacs local time, t for
 Universal Time, `wall' for system wall clock time, or a string as in
@@ -983,32 +1356,71 @@ usage: (decode-time &optional TIME ZONE)  */)
 }
 
 /* Return OBJ - OFFSET, checking that OBJ is a valid fixnum and that
-   the result is representable as an int.  */
+   the result is representable as an int.  0 <= OFFSET <= TM_YEAR_BASE.  */
 static int
 check_tm_member (Lisp_Object obj, int offset)
 {
-  CHECK_FIXNUM (obj);
-  EMACS_INT n = XFIXNUM (obj);
-  int result;
-  if (INT_SUBTRACT_WRAPV (n, offset, &result))
-    time_overflow ();
-  return result;
+  if (FASTER_SYSTIME && INT_MAX <= MOST_POSITIVE_FIXNUM - TM_YEAR_BASE)
+    {
+      CHECK_FIXNUM (obj);
+      EMACS_INT n = XFIXNUM (obj);
+      int i;
+      if (INT_SUBTRACT_WRAPV (n, offset, &i))
+	time_overflow ();
+      return i;
+    }
+  else
+    {
+      CHECK_INTEGER (obj);
+      mpz_sub_ui (mpz[0], *bignum_integer (&mpz[0], obj), offset);
+      intmax_t i;
+      if (! (mpz_to_intmax (mpz[0], &i) && INT_MIN <= i && i <= INT_MAX))
+	time_overflow ();
+      return i;
+    }
 }
 
-DEFUN ("encode-time", Fencode_time, Sencode_time, 6, MANY, 0,
-       doc: /* Convert SECOND, MINUTE, HOUR, DAY, MONTH, YEAR and ZONE to internal time.
-This is the reverse operation of `decode-time', which see.
-
-The optional ZONE is omitted or 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
+DEFUN ("encode-time", Fencode_time, Sencode_time, 1, MANY, 0,
+       doc: /* Convert TIME to a timestamp.
+Optional FORM specifies how the returned value should be encoded.
+This can act as the reverse operation of `decode-time', which see.
+
+If TIME is a list (SECOND MINUTE HOUR DAY MONTH YEAR IGNORED DST ZONE)
+it a decoded time in the style of `decode-time', so that (encode-time
+(decode-time ...)) works.  TIME can also be a Lisp time value; see
+Info node `(elisp)Time of Day'.
+
+If FORM is a positive integer, the time is returned as a pair of
+integers (TICKS . FORM), where TICKS is the number of clock ticks and FORM
+is the clock frequency in ticks per second.  (Currently the positive
+integer should be at least 65536 if the returned value is expected to
+be given to standard functions expecting Lisp timestamps.)  If FORM is
+t, the time is returned as (TICKS . PHZ), where PHZ is a
+platform-dependent clock frequency.  If FORM is `integer', the time is
+returned as an integer count of seconds.  If FORM is `list', the time is
+returned as an integer list (HIGH LOW USEC PSEC), where HIGH has the
+most significant bits of the seconds, LOW has the least significant 16
+bits, and USEC and PSEC are the microsecond and picosecond counts.
+Returned values are rounded toward minus infinity.  Although an
+omitted or nil FORM currently acts like `list', this is planned to
+change, so callers requiring list timestamps should specify `list'.
+
+As an obsolescent calling convention, the first 6 arguments SECOND,
+MINUTE, HOUR, DAY, MONTH, and YEAR specify the components of a decoded
+time, where DST assumed to be -1 and FORM is omitted.  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; otherwise ZONE is assumed to be nil.
+
+If the input is a decoded time, ZONE is 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
 `current-time-zone') or an integer (as from `decode-time') applied
 without consideration for daylight saving time.
 
-You can pass more than 7 arguments; then the first six arguments
-are used as SECOND through YEAR, and the *last* argument is used as ZONE.
-The intervening arguments are ignored.
-This feature lets (apply \\='encode-time (decode-time ...)) work.
+If the input is a decoded time and ZONE specifies a time zone with
+daylight-saving transitions, DST is t for daylight saving time and nil
+for standard time.  If DST is -1, the daylight saving flag is guessed.
 
 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.
@@ -1018,21 +1430,55 @@ If you want them to stand for years in this century, you must do that yourself.
 Years before 1970 are not guaranteed to work.  On some systems,
 year values as low as 1901 do work.
 
-usage: (encode-time SECOND MINUTE HOUR DAY MONTH YEAR &optional ZONE)  */)
+usage: (encode-time TIME &optional FORM)  */)
   (ptrdiff_t nargs, Lisp_Object *args)
 {
   time_t value;
   struct tm tm;
-  Lisp_Object zone = (nargs > 6 ? args[nargs - 1] : Qnil);
-
-  tm.tm_sec  = check_tm_member (args[0], 0);
-  tm.tm_min  = check_tm_member (args[1], 0);
-  tm.tm_hour = check_tm_member (args[2], 0);
-  tm.tm_mday = check_tm_member (args[3], 0);
-  tm.tm_mon  = check_tm_member (args[4], 1);
-  tm.tm_year = check_tm_member (args[5], TM_YEAR_BASE);
+  Lisp_Object form = Qnil, zone = Qnil;
+  Lisp_Object a = args[0];
   tm.tm_isdst = -1;
 
+  if (nargs <= 2)
+    {
+      if (nargs == 2)
+	form = args[1];
+      Lisp_Object tail = a;
+      for (int i = 0; i < 9; i++, tail = XCDR (tail))
+	if (! CONSP (tail))
+	  {
+	    struct lisp_time t;
+	    if (! decode_lisp_time (a, 0, 0, &t, 0))
+	      invalid_time ();
+	    return lisp_time_form_stamp (t, form);
+	  }
+      tm.tm_sec  = check_tm_member (XCAR (a), 0); a = XCDR (a);
+      tm.tm_min  = check_tm_member (XCAR (a), 0); a = XCDR (a);
+      tm.tm_hour = check_tm_member (XCAR (a), 0); a = XCDR (a);
+      tm.tm_mday = check_tm_member (XCAR (a), 0); a = XCDR (a);
+      tm.tm_mon  = check_tm_member (XCAR (a), 1); a = XCDR (a);
+      tm.tm_year = check_tm_member (XCAR (a), TM_YEAR_BASE); a = XCDR (a);
+      a = XCDR (a);
+      if (SYMBOLP (XCAR (a)))
+	tm.tm_isdst = !NILP (XCAR (a));
+      a = XCDR (a);
+      zone = XCAR (a);
+    }
+  else if (nargs < 6)
+    xsignal2 (Qwrong_number_of_arguments, Qencode_time, make_fixnum (nargs));
+  else
+    {
+      if (6 < nargs)
+	zone = args[nargs - 1];
+      form = Qnil;
+      tm.tm_sec  = check_tm_member (a, 0);
+      tm.tm_min  = check_tm_member (args[1], 0);
+      tm.tm_hour = check_tm_member (args[2], 0);
+      tm.tm_mday = check_tm_member (args[3], 0);
+      tm.tm_mon  = check_tm_member (args[4], 1);
+      tm.tm_year = check_tm_member (args[5], TM_YEAR_BASE);
+    }
+
   timezone_t tz = tzlookup (zone, false);
   value = emacs_mktime_z (tz, &tm);
   xtzfree (tz);
@@ -1040,15 +1486,17 @@ usage: (encode-time SECOND MINUTE HOUR DAY MONTH YEAR &optional ZONE)  */)
   if (value == (time_t) -1)
     time_overflow ();
 
-  return list2i (hi_time (value), lo_time (value));
+  return time_form_stamp (value, form);
 }
 
 DEFUN ("current-time", Fcurrent_time, Scurrent_time, 0, 0, 0,
-       doc: /* Return the current time, as the number of seconds since 1970-01-01 00:00:00.
-The time is returned as a list of integers (HIGH LOW USEC PSEC).
-HIGH has the most significant bits of the seconds, while LOW has the
-least significant 16 bits.  USEC and PSEC are the microsecond and
-picosecond counts.  */)
+       doc: /* Return the current time, counting the number of seconds since the epoch.
+
+See Info node `(elisp)Time of Day' for the format of the returned
+timestamp.  Although this is currently list format, it may change in
+future versions of Emacs.  Use `encode-time' if you need a particular
+form; for example, (encode-time nil \\='list) returns the current time
+in list form.  */)
   (void)
 {
   return make_lisp_time (current_timespec ());
@@ -1064,12 +1512,9 @@ The format is `Sun Sep 16 01:03:52 1973'.
 However, see also the functions `decode-time' and `format-time-string'
 which provide a much more powerful and general facility.
 
-If SPECIFIED-TIME is given, it is a time to format instead of the
-current time.  The argument should have the form (HIGH LOW . IGNORED).
-Thus, you can use times obtained from `current-time' and from
-`file-attributes'.  SPECIFIED-TIME can also be a single integer number
-of seconds since the epoch.  The obsolete form (HIGH . LOW) is also
-still accepted.
+If SPECIFIED-TIME is given, it is the Lisp time value to format
+instead of the current time.  See Info node `(elisp)Time of Day' for
+time value formats.
 
 The optional ZONE is omitted or nil for Emacs local time, t for
 Universal Time, `wall' for system wall clock time, or a string as in
@@ -1113,11 +1558,8 @@ OFFSET is an integer number of seconds ahead of UTC (east of Greenwich).
     A negative value means west of Greenwich.
 NAME is a string giving the name of the time zone.
 If SPECIFIED-TIME is given, the time zone offset is determined from it
-instead of using the current time.  The argument should have the form
-\(HIGH LOW . IGNORED).  Thus, you can use times obtained from
-`current-time' and from `file-attributes'.  SPECIFIED-TIME can also be
-a single integer number of seconds since the epoch.  The obsolete form
-(HIGH . LOW) is also still accepted.
+instead of using the current time.  The argument should be a Lisp
+time value; see Info node `(elisp)Time of Day'.
 
 The optional ZONE is omitted or nil for Emacs local time, t for
 Universal Time, `wall' for system wall clock time, or a string as in
@@ -1272,6 +1714,21 @@ emacs_setenv_TZ (const char *tzstring)
 void
 syms_of_systime (void)
 {
+#ifndef timespec_hz
+  timespec_hz = make_int (TIMESPEC_HZ);
+  staticpro (&timespec_hz);
+#endif
+#ifndef trillion
+  trillion = make_int (1000000000000);
+  staticpro (&trillion);
+#endif
+#if (ULONG_MAX < TRILLION || !FASTER_SYSTIME) && !defined ztrillion
+  mpz_init_set_ui (ztrillion, 1000000);
+  mpz_mul_ui (ztrillion, ztrillion, 1000000);
+#endif
+
+  DEFSYM (Qencode_time, "encode-time");
+
   defsubr (&Scurrent_time);
   defsubr (&Stime_add);
   defsubr (&Stime_subtract);
diff --git a/src/systime.h b/src/systime.h
index f907194b9d..10123ddea9 100644
--- a/src/systime.h
+++ b/src/systime.h
@@ -75,19 +75,22 @@ extern void set_waiting_for_input (struct timespec *);
    (HI << LO_TIME_BITS) + LO + US / 1e6 + PS / 1e12.  */
 enum { LO_TIME_BITS = 16 };
 
-/* A Lisp time (HI LO US PS), sans the cons cells.  */
+/* Components of a new-format Lisp timestamp.  */
 struct lisp_time
 {
-  EMACS_INT hi;
-  int lo, us, ps;
+  /* Clock count as a Lisp integer.  */
+  Lisp_Object ticks;
+
+  /* Clock frequency (ticks per second) as a positive Lisp integer.
+     (TICKS . HZ) is a valid Lisp timestamp unless HZ < 65536.  */
+  Lisp_Object hz;
 };
 
 /* defined in systime.c */
 extern struct timeval make_timeval (struct timespec) ATTRIBUTE_CONST;
 extern Lisp_Object make_lisp_time (struct timespec);
-extern int decode_time_components (Lisp_Object, Lisp_Object, Lisp_Object,
-				   Lisp_Object, struct lisp_time *, double *);
-extern struct timespec lisp_to_timespec (struct lisp_time);
+extern bool list4_to_timespec (Lisp_Object, Lisp_Object, Lisp_Object,
+			       Lisp_Object, struct timespec *);
 extern struct timespec lisp_time_argument (Lisp_Object);
 extern _Noreturn void time_overflow (void);
 extern void init_systime (bool);
diff --git a/test/src/systime-tests.el b/test/src/systime-tests.el
index 6e369aa2aa..485d379cfe 100644
--- a/test/src/systime-tests.el
+++ b/test/src/systime-tests.el
@@ -77,3 +77,6 @@ systime-tests--have-leap-seconds
                (format-time-string "%Y-%m-%d %H:%M:%S" (- (ash 1 31) 3600) t)
                "2038-01-19 02:14:08")
               (systime-tests--have-leap-seconds))))
+
+(ert-deftest time-equal-p-nil-nil ()
+  (should (time-equal-p nil nil)))
-- 
2.17.1


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

* bug#32902: Add support for (TIMESTAMP . RESOLUTION) Lisp timestamps
  2018-10-02  1:00 bug#32902: Add support for (TIMESTAMP . RESOLUTION) Lisp timestamps Paul Eggert
@ 2018-10-02  3:04 ` Eli Zaretskii
  2018-10-03 18:45   ` Paul Eggert
  0 siblings, 1 reply; 11+ messages in thread
From: Eli Zaretskii @ 2018-10-02  3:04 UTC (permalink / raw)
  To: Paul Eggert; +Cc: 32902

> From: Paul Eggert <eggert@cs.ucla.edu>
> Date: Mon, 1 Oct 2018 18:00:26 -0700
> 
> The attached patches follow up on a suggestion by Stefan a few weeks 
> ago, by adding support for a new Lisp timestamp format (TIMESTAMP . 
> FREQUENCY), where TIMESTAMP is an integer that counts clock ticks and 
> FREQUENCY is a positive integer that counts ticks per second. For 
> brevity the documentation says (TICKS . HZ) instead of (TIMESTAMP . 
> FREQUENCY).

Thanks for working on this.

I'd prefer not to move stuff from editfns.c to a new file, as that
makes forensics significantly harder.  Is it feasible to leave the
time-related code in editfns.c?

>  DEFUN ("current-time", Fcurrent_time, Scurrent_time, 0, 0, 0,
> -       doc: /* Return the current time, as the number of seconds since 1970-01-01 00:00:00.
> -The time is returned as a list of integers (HIGH LOW USEC PSEC).
> -HIGH has the most significant bits of the seconds, while LOW has the
> -least significant 16 bits.  USEC and PSEC are the microsecond and
> -picosecond counts.  */)
> +       doc: /* Return the current time, counting the number of seconds since the epoch.
> +
> +See Info node `(elisp)Time of Day' for the format of the returned
> +timestamp.  Although this is currently list format, it may change in
> +future versions of Emacs.  Use `encode-time' if you need a particular
> +form; for example, (encode-time nil \\='list) returns the current time
> +in list form.  */)

I think this obfuscation of the time values in doc strings (here and
elsewhere in the patch) is not a good idea, and asking users to read
the manual just to understand what kind of data they will get or need
to pass to a function, is a step backwards.  Doc strings only send to
the manuals for additional details and explanations, not for the basic
facts such as these.

The manual and NEWS can (and probably should) explain that this stuff
is in transition, but we shouldn't describe return values as opaque
objects because of that.  IMO, this is not the Emacsy way of dealing
with complex data structures.

I also don't like saying in a doc string that something might change
in a future version: that's stuff for the manual.  Doc strings should
describe the current state of affairs.

> +If TIME is a list (SECOND MINUTE HOUR DAY MONTH YEAR IGNORED DST ZONE)
> +it a decoded time in the style of `decode-time', so that (encode-time
   ^^
A typo: should be "it is" or "it's".

> +If FORM is a positive integer, the time is returned as a pair of
> +integers (TICKS . FORM), where TICKS is the number of clock ticks and FORM
> +is the clock frequency in ticks per second.  (Currently the positive
> +integer should be at least 65536 if the returned value is expected to
> +be given to standard functions expecting Lisp timestamps.)  If FORM is
> +t, the time is returned as (TICKS . PHZ), where PHZ is a
> +platform-dependent clock frequency.

This doesn't tell in what units the clock frequency is returned.

> -usage: (encode-time SECOND MINUTE HOUR DAY MONTH YEAR &optional ZONE)  */)
> +usage: (encode-time TIME &optional FORM)  */)

This makes an impression the function doesn't support more than 2
arguments, which is incorrect.  Can we provide a more accurate 'usage'
form?

I think it would be good to add tests for the functions being
modified, otherwise we might be breaking something without paying
attention.





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

* bug#32902: Add support for (TIMESTAMP . RESOLUTION) Lisp timestamps
  2018-10-02  3:04 ` Eli Zaretskii
@ 2018-10-03 18:45   ` Paul Eggert
  2018-10-04 16:08     ` Eli Zaretskii
  0 siblings, 1 reply; 11+ messages in thread
From: Paul Eggert @ 2018-10-03 18:45 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 32902

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

On 10/1/18 8:04 PM, Eli Zaretskii wrote:

> Doc strings only send to
> the manuals for additional details and explanations, not for the basic
> facts such as these.

The attached proposed additional patch fixes this by referring to 
format-time-string, and by adding the description of time values to 
format-time-string's doc string. (This is better than the old practice 
of referring to current-time-string, which does not deal with subsecond 
info.) It also fixes some minor doc nits I noticed in additional reading.


> This makes an impression the function doesn't support more than 2
> arguments, which is incorrect.  Can we provide a more accurate 'usage' form?

Yes, and I gave that a shot in the attached patch.


> I think it would be good to add tests for the functions being
> modified, otherwise we might be breaking something without paying
> attention.

Good point, and done in the attached.


> Is it feasible to leave the time-related code in editfns.c?

It's feasible, and I could prepare a patch along those lines. However, 
the time code has has nothing to do with edit functions and is growing 
significantly: the proposed src/systime.c is 56 KiB, which is above the 
median size for src/*.c files. Also, this patch changes the time code so 
much that moving it to a new file won't be that much more of a forensics 
annoyance than leaving it in editfns.c. To my mind the clarity in having 
the time code in its own module outweighs the forensics annoyance.


[-- Attachment #2: 0001-Improvements-on-TICKS-.-HZ.patch --]
[-- Type: text/x-patch, Size: 18244 bytes --]

From ebf996b716f99434abe217dafccb950124303adc Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Wed, 3 Oct 2018 09:10:01 -0700
Subject: [PATCH] Improvements on (TICKS . HZ)

This patch is in response to Eli's review (Bug#32902#10).
* src/systime.c: Doc strings of affected functions now refer
to format-time-string instead of to Lisp manual, and
format-time-string's doc string covers time values.
* test/src/systime-tests.el (format-time-string-with-zone):
Check decode-time too.
(decode-then-encode-time, time-arith-tests): New tests.
---
 doc/lispref/os.texi       |  14 ++---
 src/systime.c             |  79 ++++++++++++++------------
 test/src/systime-tests.el | 116 +++++++++++++++++++++++++++++---------
 3 files changed, 139 insertions(+), 70 deletions(-)

diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi
index ea6915350e..64c327c380 100644
--- a/doc/lispref/os.texi
+++ b/doc/lispref/os.texi
@@ -1457,14 +1457,14 @@ Time Conversion
 @var{dow} and @var{utcoff}.
 @end defun
 
-@defun encode-time time &optional form
+@defun encode-time &optional time form &rest obsolescent-arguments
 This function converts @var{time} to a Lisp timestamp.
 It can act as the inverse of @code{decode-time}.
 
-The first argument can be a Lisp time value such as @code{nil} for the
-current time, a number of seconds, a pair @code{(@var{ticks}
-. @var{hz})}, or a list @code{(@var{high} @var{low} @var{micro}
-@var{pico})} (@pxref{Time of Day}).  It can also be a list
+The first argument can be a time value such as a number of seconds, a
+pair @code{(@var{ticks} . @var{hz})}, a list @code{(@var{high}
+@var{low} @var{micro} @var{pico})}, or @code{nil} (the default) for
+the current time (@pxref{Time of Day}).  It can also be 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
@@ -1494,10 +1494,10 @@ Time Conversion
 specify most of the components of a decoded time.  If there are more
 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; otherwise @var{zone} defaults
+#\\='encode-time (decode-time ...))} works; otherwise @var{zone} defaults
 to the current time zone rule (@pxref{Time Zone Rules}).  The decoded
 time's @var{dst} component is treated as if it was @minus{}1, and
-@var{form} so it takes its default value.
+@var{form} takes its default value.
 
 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
diff --git a/src/systime.c b/src/systime.c
index c01b5a6d33..ae1ac04aad 100644
--- a/src/systime.c
+++ b/src/systime.c
@@ -1035,8 +1035,8 @@ time_arith (Lisp_Object a, Lisp_Object b, bool subtract)
 }
 
 DEFUN ("time-add", Ftime_add, Stime_add, 2, 2, 0,
-       doc: /* Return the sum of two time values A and B, as a timestamp.
-See Info node `(elisp)Time of Day' for time value formats.
+       doc: /* Return the sum of two time values A and B, as a time value.
+See `format-time-string' for the various forms of a time value.
 For example, nil stands for the current time.  */)
   (Lisp_Object a, Lisp_Object b)
 {
@@ -1044,9 +1044,9 @@ For example, nil stands for the current time.  */)
 }
 
 DEFUN ("time-subtract", Ftime_subtract, Stime_subtract, 2, 2, 0,
-       doc: /* Return the difference between two time values A and B, as a timestamp.
+       doc: /* Return the difference between two time values A and B, as a time value.
 You can use `float-time' to convert the difference into elapsed seconds.
-See Info node `(elisp)Time of Day' for time value formats.
+See `format-time-string' for the various forms of a time value.
 For example, nil stands for the current time.  */)
   (Lisp_Object a, Lisp_Object b)
 {
@@ -1092,7 +1092,7 @@ time_cmp (Lisp_Object a, Lisp_Object b)
 
 DEFUN ("time-less-p", Ftime_less_p, Stime_less_p, 2, 2, 0,
        doc: /* Return non-nil if time value A is less than time value B.
-See Info node `(elisp)Time of Day' for time value formats.
+See `format-time-string' for the various forms of a time value.
 For example, nil stands for the current time.  */)
   (Lisp_Object a, Lisp_Object b)
 {
@@ -1101,7 +1101,7 @@ For example, nil stands for the current time.  */)
 
 DEFUN ("time-equal-p", Ftime_equal_p, Stime_equal_p, 2, 2, 0,
        doc: /* Return non-nil if A and B are equal time values.
-See Info node `(elisp)Time of Day' for time value formats.  */)
+See `format-time-string' for the various forms of a time value.  */)
   (Lisp_Object a, Lisp_Object b)
 {
   return time_cmp (a, b) == 0 ? Qt : Qnil;
@@ -1110,12 +1110,12 @@ See Info node `(elisp)Time of Day' for time value formats.  */)
 
 DEFUN ("float-time", Ffloat_time, Sfloat_time, 0, 1, 0,
        doc: /* Return the current time, as a float number of seconds since the epoch.
-If SPECIFIED-TIME is given, it is a Lisp time value to convert to
-float instead of the current time.  See Info node `(elisp)Time of Day'
-for time value formats.
+If SPECIFIED-TIME is given, it is a time value to convert to float
+instead of the current time.  See `format-time-string' for the various
+forms of a time value.
 
 WARNING: Since the result is floating point, it may not be exact.
-If precise time stamps are required, use either `current-time',
+If precise time stamps are required, use either `encode-time',
 or (if you need time as a string) `format-time-string'.  */)
   (Lisp_Object specified_time)
 {
@@ -1226,8 +1226,12 @@ format_time_string (char const *format, ptrdiff_t formatlen,
 }
 
 DEFUN ("format-time-string", Fformat_time_string, Sformat_time_string, 1, 3, 0,
-       doc: /* Use FORMAT-STRING to format the time TIME, or now if omitted or nil.
-TIME is a Lisp time value; see Info node `(elisp)Time of Day'.
+       doc: /* Use FORMAT-STRING to format the time value TIME.
+A time value that is omitted or nil stands for the current time,
+a number stands for that many seconds, an integer pair (TICKS . HZ)
+stands for TICKS/HZ seconds, and an integer list (HI LO US PS) stands
+for HI*2**16 + LO + US/10**6 + PS/10**12 seconds.  This function
+treats seconds as time since the epoch of 1970-01-01 00:00:00 UTC.
 
 The optional ZONE is omitted or nil for Emacs local time, t for
 Universal Time, `wall' for system wall clock time, or a string as in
@@ -1300,8 +1304,8 @@ usage: (format-time-string FORMAT-STRING &optional TIME ZONE)  */)
 
 DEFUN ("decode-time", Fdecode_time, Sdecode_time, 0, 2, 0,
        doc: /* Decode a time value as (SEC MINUTE HOUR DAY MONTH YEAR DOW DST UTCOFF).
-The optional TIME is the Lisp time value to convert.  See Info node
-`(elisp)Time of Day' for time value formats.
+The optional TIME is the time value to convert.  See
+`format-time-string' for the various forms of a time value.
 
 The optional ZONE is omitted or nil for Emacs local time, t for
 Universal Time, `wall' for system wall clock time, or a string as in
@@ -1381,22 +1385,23 @@ check_tm_member (Lisp_Object obj, int offset)
 }
 
 DEFUN ("encode-time", Fencode_time, Sencode_time, 1, MANY, 0,
-       doc: /* Convert TIME to a timestamp.
+       doc: /* Convert optional TIME to a timestamp.
 Optional FORM specifies how the returned value should be encoded.
 This can act as the reverse operation of `decode-time', which see.
 
 If TIME is a list (SECOND MINUTE HOUR DAY MONTH YEAR IGNORED DST ZONE)
-it a decoded time in the style of `decode-time', so that (encode-time
-(decode-time ...)) works.  TIME can also be a Lisp time value; see
-Info node `(elisp)Time of Day'.
+it is a decoded time in the style of `decode-time', so that (encode-time
+(decode-time ...)) works.  TIME can also be a time value.
+See `format-time-string' for the various forms of a time value.
+For example, an omitted TIME stands for the current time.
 
 If FORM is a positive integer, the time is returned as a pair of
 integers (TICKS . FORM), where TICKS is the number of clock ticks and FORM
 is the clock frequency in ticks per second.  (Currently the positive
 integer should be at least 65536 if the returned value is expected to
 be given to standard functions expecting Lisp timestamps.)  If FORM is
-t, the time is returned as (TICKS . PHZ), where PHZ is a
-platform-dependent clock frequency.  If FORM is `integer', the time is
+t, the time is returned as (TICKS . PHZ), where PHZ is a platform dependent
+clock frequency in ticks per second.  If FORM is `integer', the time is
 returned as an integer count of seconds.  If FORM is `list', the time is
 returned as an integer list (HIGH LOW USEC PSEC), where HIGH has the
 most significant bits of the seconds, LOW has the least significant 16
@@ -1405,11 +1410,12 @@ Returned values are rounded toward minus infinity.  Although an
 omitted or nil FORM currently acts like `list', this is planned to
 change, so callers requiring list timestamps should specify `list'.
 
-As an obsolescent calling convention, the first 6 arguments SECOND,
-MINUTE, HOUR, DAY, MONTH, and YEAR specify the components of a decoded
-time, where DST assumed to be -1 and FORM is omitted.  If there are more
+As an obsolescent calling convention, if this function is called with
+6 or more arguments, the first 6 arguments are SECOND, MINUTE, HOUR,
+DAY, MONTH, and YEAR, and specify the components of a decoded time,
+where DST assumed to be -1 and FORM is omitted.  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
+extra arguments are ignored, so that (apply #\\='encode-time
 (decode-time ...)) works; otherwise ZONE is assumed to be nil.
 
 If the input is a decoded time, ZONE is nil for Emacs local time, t
@@ -1430,7 +1436,7 @@ If you want them to stand for years in this century, you must do that yourself.
 Years before 1970 are not guaranteed to work.  On some systems,
 year values as low as 1901 do work.
 
-usage: (encode-time TIME &optional FORM)  */)
+usage: (encode-time &optional TIME FORM &rest OBSOLESCENT-ARGUMENTS)  */)
   (ptrdiff_t nargs, Lisp_Object *args)
 {
   time_t value;
@@ -1490,13 +1496,13 @@ usage: (encode-time TIME &optional FORM)  */)
 }
 
 DEFUN ("current-time", Fcurrent_time, Scurrent_time, 0, 0, 0,
-       doc: /* Return the current time, counting the number of seconds since the epoch.
-
-See Info node `(elisp)Time of Day' for the format of the returned
-timestamp.  Although this is currently list format, it may change in
-future versions of Emacs.  Use `encode-time' if you need a particular
-form; for example, (encode-time nil \\='list) returns the current time
-in list form.  */)
+       doc: /* Return the current time, as the number of seconds since 1970-01-01 00:00:00.
+The time is returned as a list of integers (HIGH LOW USEC PSEC).
+HIGH has the most significant bits of the seconds, while LOW has the
+least significant 16 bits.  USEC and PSEC are the microsecond and
+picosecond counts.  Use `encode-time' if you need a particular
+timestamp form; for example, (encode-time nil \\='integer) returns the
+current time in seconds.  */)
   (void)
 {
   return make_lisp_time (current_timespec ());
@@ -1512,9 +1518,9 @@ The format is `Sun Sep 16 01:03:52 1973'.
 However, see also the functions `decode-time' and `format-time-string'
 which provide a much more powerful and general facility.
 
-If SPECIFIED-TIME is given, it is the Lisp time value to format
-instead of the current time.  See Info node `(elisp)Time of Day' for
-time value formats.
+If SPECIFIED-TIME is given, it is the time value to format instead of
+the current time.  See `format-time-string' for the various forms of a
+time value.
 
 The optional ZONE is omitted or nil for Emacs local time, t for
 Universal Time, `wall' for system wall clock time, or a string as in
@@ -1559,7 +1565,8 @@ OFFSET is an integer number of seconds ahead of UTC (east of Greenwich).
 NAME is a string giving the name of the time zone.
 If SPECIFIED-TIME is given, the time zone offset is determined from it
 instead of using the current time.  The argument should be a Lisp
-time value; see Info node `(elisp)Time of Day'.
+time value; see `format-time-string' for the various forms of a time
+value.
 
 The optional ZONE is omitted or nil for Emacs local time, t for
 Universal Time, `wall' for system wall clock time, or a string as in
diff --git a/test/src/systime-tests.el b/test/src/systime-tests.el
index 485d379cfe..3036c44992 100644
--- a/test/src/systime-tests.el
+++ b/test/src/systime-tests.el
@@ -19,7 +19,7 @@
 
 (require 'ert)
 
-;;; Check format-time-string with various TZ settings.
+;;; Check format-time-string and decode-time with various TZ settings.
 ;;; Use only POSIX-compatible TZ values, since the tests should work
 ;;; even if tzdb is not in use.
 (ert-deftest format-time-string-with-zone ()
@@ -35,32 +35,61 @@
   ;; Similarly, stick to the limited set of time zones that are
   ;; supported by both POSIX and MS-Windows: exactly 3 ASCII letters
   ;; in the abbreviation, and no DST.
-  (let ((look '(1202 22527 999999 999999))
-        (format "%Y-%m-%d %H:%M:%S.%3N %z (%Z)"))
-    ;; UTC.
-    (should (string-equal
-             (format-time-string "%Y-%m-%d %H:%M:%S.%3N %z" look t)
-             "1972-06-30 23:59:59.999 +0000"))
-    ;; "UTC0".
-    (should (string-equal
-             (format-time-string format look "UTC0")
-             "1972-06-30 23:59:59.999 +0000 (UTC)"))
-    ;; Negative UTC offset, as a Lisp list.
-    (should (string-equal
-             (format-time-string format look '(-28800 "PST"))
-             "1972-06-30 15:59:59.999 -0800 (PST)"))
-    ;; Negative UTC offset, as a Lisp integer.
-    (should (string-equal
-             (format-time-string format look -28800)
-             ;; MS-Windows build replaces unrecognizable TZ values,
-             ;; such as "-08", with "ZZZ".
-             (if (eq system-type 'windows-nt)
-                 "1972-06-30 15:59:59.999 -0800 (ZZZ)"
-               "1972-06-30 15:59:59.999 -0800 (-08)")))
-    ;; Positive UTC offset that is not an hour multiple, as a string.
-    (should (string-equal
-             (format-time-string format look "IST-5:30")
-             "1972-07-01 05:29:59.999 +0530 (IST)"))))
+  (let ((format "%Y-%m-%d %H:%M:%S.%3N %z (%Z)"))
+    (dolist (look '((1202 22527 999999 999999)
+		    (7879679999900 . 100000)
+		    (78796799999999999999 . 1000000000000)))
+      ;; UTC.
+      (should (string-equal
+	       (format-time-string "%Y-%m-%d %H:%M:%S.%3N %z" look t)
+	       "1972-06-30 23:59:59.999 +0000"))
+      (should (equal (decode-time look t)
+		     '(59 59 23 30 6 1972 5 nil 0)))
+      ;; "UTC0".
+      (should (string-equal
+	       (format-time-string format look "UTC0")
+	       "1972-06-30 23:59:59.999 +0000 (UTC)"))
+      (should (equal (decode-time look "UTC0")
+		     '(59 59 23 30 6 1972 5 nil 0)))
+      ;; Negative UTC offset, as a Lisp list.
+      (should (string-equal
+	       (format-time-string format look '(-28800 "PST"))
+	       "1972-06-30 15:59:59.999 -0800 (PST)"))
+      (should (equal (decode-time look '(-28800 "PST"))
+		     '(59 59 15 30 6 1972 5 nil -28800)))
+      ;; Negative UTC offset, as a Lisp integer.
+      (should (string-equal
+	       (format-time-string format look -28800)
+	       ;; MS-Windows build replaces unrecognizable TZ values,
+	       ;; such as "-08", with "ZZZ".
+	       (if (eq system-type 'windows-nt)
+		   "1972-06-30 15:59:59.999 -0800 (ZZZ)"
+		 "1972-06-30 15:59:59.999 -0800 (-08)")))
+      (should (equal (decode-time look -28800)
+		     '(59 59 15 30 6 1972 5 nil -28800)))
+      ;; Positive UTC offset that is not an hour multiple, as a string.
+      (should (string-equal
+	       (format-time-string format look "IST-5:30")
+	       "1972-07-01 05:29:59.999 +0530 (IST)"))
+      (should (equal (decode-time look "IST-5:30")
+		     '(59 29 5 1 7 1972 6 nil 19800))))))
+
+(ert-deftest decode-then-encode-time ()
+  (let ((time-values (list 0 -2 1 0.0 -0.0 -2.0 1.0
+			   most-negative-fixnum most-positive-fixnum
+			   (1- most-negative-fixnum)
+			   (1+ most-positive-fixnum)
+			   1e+INF -1e+INF 1e+NaN -1e+NaN
+			   '(0 1 0 0) '(1 0 0 0) '(-1 0 0 0)
+			   '(123456789000000 . 1000000)
+			   (cons (1+ most-positive-fixnum) 1000000000000)
+			   (cons 1000000000000 (1+ most-positive-fixnum)))))
+    (dolist (a time-values)
+      (let* ((d (ignore-errors (decode-time a t)))
+	     (e (encode-time d))
+	     (diff (float-time (time-subtract a e))))
+	(should (or (not d)
+		    (and (<= 0 diff) (< diff 1))))))))
 
 ;;; This should not dump core.
 (ert-deftest format-time-string-with-outlandish-zone ()
@@ -80,3 +109,36 @@ systime-tests--have-leap-seconds
 
 (ert-deftest time-equal-p-nil-nil ()
   (should (time-equal-p nil nil)))
+
+(ert-deftest time-arith-tests ()
+  (let ((time-values (list 0 -1 1 0.0 -0.0 -1.0 1.0
+			   most-negative-fixnum most-positive-fixnum
+			   (1- most-negative-fixnum)
+			   (1+ most-positive-fixnum)
+			   1e+INF -1e+INF 1e+NaN -1e+NaN
+			   '(0 0 0 1) '(0 0 1 0) '(0 1 0 0) '(1 0 0 0)
+			   '(-1 0 0 0) '(1 2 3 4) '(-1 2 3 4)
+			   '(-123456789 . 100000) '(123456789 . 1000000)
+			   (cons (1+ most-positive-fixnum) 1000000000000)
+			   (cons 1000000000000 (1+ most-positive-fixnum)))))
+    (dolist (a time-values)
+      (dolist (b time-values)
+	(let ((aa (time-subtract (time-add a b) b)))
+	  (should (or (time-equal-p a aa) (and (floatp aa) (isnan aa)))))
+	(should (= 1 (+ (if (time-less-p a b) 1 0)
+			(if (time-equal-p a b) 1 0)
+			(if (time-less-p b a) 1 0)
+			(if (or (and (floatp a) (isnan a))
+				(and (floatp b) (isnan b)))
+			    1 0))))
+	(should (or (not (time-less-p 0 b))
+		    (time-less-p a (time-add a b))
+		    (time-equal-p a (time-add a b))
+		    (and (floatp (time-add a b)) (isnan (time-add a b)))))
+	(let ((x (float-time (time-add a b)))
+	      (y (+ (float-time a) (float-time b))))
+	  (should (or (and (isnan x) (isnan y))
+		      (= x y)
+		      (< 0.99 (/ x y) 1.01)
+		      (< 0.99 (/ (- (float-time a)) (float-time b))
+			 1.01))))))))
-- 
2.17.1


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

* bug#32902: Add support for (TIMESTAMP . RESOLUTION) Lisp timestamps
  2018-10-03 18:45   ` Paul Eggert
@ 2018-10-04 16:08     ` Eli Zaretskii
  2018-10-07  6:32       ` Paul Eggert
  0 siblings, 1 reply; 11+ messages in thread
From: Eli Zaretskii @ 2018-10-04 16:08 UTC (permalink / raw)
  To: Paul Eggert; +Cc: 32902

> From: Paul Eggert <eggert@cs.ucla.edu>
> Cc: 32902@debbugs.gnu.org
> Date: Wed, 3 Oct 2018 11:45:12 -0700
> 
> > Doc strings only send to
> > the manuals for additional details and explanations, not for the basic
> > facts such as these.
> 
> The attached proposed additional patch fixes this by referring to 
> format-time-string, and by adding the description of time values to 
> format-time-string's doc string. (This is better than the old practice 
> of referring to current-time-string, which does not deal with subsecond 
> info.) It also fixes some minor doc nits I noticed in additional reading.
> 
> > This makes an impression the function doesn't support more than 2
> > arguments, which is incorrect.  Can we provide a more accurate 'usage' form?
> 
> Yes, and I gave that a shot in the attached patch.
> 
> > I think it would be good to add tests for the functions being
> > modified, otherwise we might be breaking something without paying
> > attention.
> 
> Good point, and done in the attached.

Thanks.

> > Is it feasible to leave the time-related code in editfns.c?
> 
> It's feasible, and I could prepare a patch along those lines. However, 
> the time code has has nothing to do with edit functions and is growing 
> significantly: the proposed src/systime.c is 56 KiB, which is above the 
> median size for src/*.c files. Also, this patch changes the time code so 
> much that moving it to a new file won't be that much more of a forensics 
> annoyance than leaving it in editfns.c. To my mind the clarity in having 
> the time code in its own module outweighs the forensics annoyance.

OK, but can we call the new file something like timefns.c?  systime.c
sounds wrong to me (these isn't system-dependent stuff).





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

* bug#32902: Add support for (TIMESTAMP . RESOLUTION) Lisp timestamps
  2018-10-04 16:08     ` Eli Zaretskii
@ 2018-10-07  6:32       ` Paul Eggert
  2018-10-07 17:55         ` Eli Zaretskii
  0 siblings, 1 reply; 11+ messages in thread
From: Paul Eggert @ 2018-10-07  6:32 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 32902-done

Eli Zaretskii wrote:
> OK, but can we call the new file something like timefns.c?

Sure, that's easy enough. I revamped the patches to do that and installed them 
into master. Closing the bug report.





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

* bug#32902: Add support for (TIMESTAMP . RESOLUTION) Lisp timestamps
  2018-10-07  6:32       ` Paul Eggert
@ 2018-10-07 17:55         ` Eli Zaretskii
  2018-10-07 20:05           ` Paul Eggert
  0 siblings, 1 reply; 11+ messages in thread
From: Eli Zaretskii @ 2018-10-07 17:55 UTC (permalink / raw)
  To: Paul Eggert; +Cc: 32902

> Cc: 32902-done@debbugs.gnu.org
> From: Paul Eggert <eggert@cs.ucla.edu>
> Date: Sat, 6 Oct 2018 23:32:00 -0700
> 
> Eli Zaretskii wrote:
> > OK, but can we call the new file something like timefns.c?
> 
> Sure, that's easy enough. I revamped the patches to do that and installed them 
> into master. Closing the bug report.

Thanks.

I saw a compilation problem with mingw.org's MinGW:

  timefns.c: In function 'lisp_to_timespec':
  timefns.c:899:21: warning: passing argument 2 of 'mpz_time' from incompatible pointer type [-Wincompatible-pointer-types]
     if (mpz_time (*q, &result.tv_sec))
		       ^
  timefns.c:828:1: note: expected 'time_t * {aka long int *}' but argument is of type '__time64_t * {aka long long int *}'
   mpz_time (mpz_t const z, time_t *t)
   ^~~~~~~~

I fixed it, but please take a look, perhaps there's a better fix for
this situation.





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

* bug#32902: Add support for (TIMESTAMP . RESOLUTION) Lisp timestamps
  2018-10-07 17:55         ` Eli Zaretskii
@ 2018-10-07 20:05           ` Paul Eggert
  2018-10-08  2:44             ` Eli Zaretskii
  0 siblings, 1 reply; 11+ messages in thread
From: Paul Eggert @ 2018-10-07 20:05 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 32902

Eli Zaretskii wrote:
>    timefns.c: In function 'lisp_to_timespec':
>    timefns.c:899:21: warning: passing argument 2 of 'mpz_time' from incompatible pointer type [-Wincompatible-pointer-types]
>       if (mpz_time (*q, &result.tv_sec))
> 		       ^
>    timefns.c:828:1: note: expected 'time_t * {aka long int *}' but argument is of type '__time64_t * {aka long long int *}'
>     mpz_time (mpz_t const z, time_t *t)
>     ^~~~~~~~
> 
> I fixed it, but please take a look, perhaps there's a better fix for
> this situation.

That's only a very partial fix, unfortunately. I looked into the matter briefly, 
and was dismayed by how much work would be needed for a real fix, even if I 
fixed only timefns.c.

Wouldn't it be much better to remove the "#define _USE_32BIT_TIME_T" from 
nt/inc/mingw_time.h? That is what's causing the problem that your fix attempted 
to paper over. A lot of code assumes that a struct timespec (and struct 
timeval's) tv_sec component is of type time_t, and defining _USE_32BIT_TIME_T 
violates that assumption; furthermore it means that MinGW Emacs stops working in 
2038 (and doesn't work even now for timestamps more than 20 years into the 
future, something that is pretty routine for me and I imagine for other users).

Whatever backward-compatibility mess that defining _USE_32BIT_TIME_T attempts to 
work around, really needs to be worked around in a better way.





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

* bug#32902: Add support for (TIMESTAMP . RESOLUTION) Lisp timestamps
  2018-10-07 20:05           ` Paul Eggert
@ 2018-10-08  2:44             ` Eli Zaretskii
  2018-10-08  5:18               ` Paul Eggert
  0 siblings, 1 reply; 11+ messages in thread
From: Eli Zaretskii @ 2018-10-08  2:44 UTC (permalink / raw)
  To: Paul Eggert; +Cc: 32902

> Cc: 32902@debbugs.gnu.org
> From: Paul Eggert <eggert@cs.ucla.edu>
> Date: Sun, 7 Oct 2018 13:05:58 -0700
> 
> > I fixed it, but please take a look, perhaps there's a better fix for
> > this situation.
> 
> That's only a very partial fix, unfortunately. I looked into the matter briefly, 
> and was dismayed by how much work would be needed for a real fix, even if I 
> fixed only timefns.c.

Can you point out a couple of such places?  I'm not sure I understand
the nature of the problem.  The data width difference notwithstanding,
the values should all fit in 32-bit time_t.

> Wouldn't it be much better to remove the "#define _USE_32BIT_TIME_T" from 
> nt/inc/mingw_time.h? That is what's causing the problem that your fix attempted 
> to paper over.

I will take a closer look, but I'm not sure we can do anything about
that.  Microsoft made an incompatible change in its runtime libraries
around Windows Vista, and switched to 64-bit time_t even on 32-bit
systems.  Since we still try to support older Windows versions, we
must use that kludge, and we must limit ourselves to 32-bit time_t in
32-bit builds.

> A lot of code assumes that a struct timespec (and struct 
> timeval's) tv_sec component is of type time_t, and defining _USE_32BIT_TIME_T 
> violates that assumption; furthermore it means that MinGW Emacs stops working in 
> 2038 (and doesn't work even now for timestamps more than 20 years into the 
> future, something that is pretty routine for me and I imagine for other users).
> 
> Whatever backward-compatibility mess that defining _USE_32BIT_TIME_T attempts to 
> work around, really needs to be worked around in a better way.

The only way is to drop support for older Windows systems.  Which we
will have to do before the year 2038 (unless some other solution could
be found, which I very much doubt).





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

* bug#32902: Add support for (TIMESTAMP . RESOLUTION) Lisp timestamps
  2018-10-08  2:44             ` Eli Zaretskii
@ 2018-10-08  5:18               ` Paul Eggert
  2018-10-09 15:09                 ` Eli Zaretskii
  0 siblings, 1 reply; 11+ messages in thread
From: Paul Eggert @ 2018-10-08  5:18 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: 32902

Eli Zaretskii wrote:

>> That's only a very partial fix, unfortunately. I looked into the matter briefly,
>> and was dismayed by how much work would be needed for a real fix, even if I
>> fixed only timefns.c.
> 
> Can you point out a couple of such places?

Well, for starters there are multiple instances of "time_t s = t.tv_sec;" where 
T is of time struct timespec, and evidently this assigns a 64-bit quantity to a 
32-bit value on the platform in question. There's lots more code like that.

> The data width difference notwithstanding,
> the values should all fit in 32-bit time_t.

Ah, I didn't know that. In that case, the changes I was thinking of might not be 
needed for timestamps before 2038. (I write "might" because I would rather not 
spend my limited time to think this through.)
> Microsoft made an incompatible change in its runtime libraries
> around Windows Vista, and switched to 64-bit time_t even on 32-bit
> systems.  Since we still try to support older Windows versions, we
> must use that kludge, and we must limit ourselves to 32-bit time_t in
> 32-bit builds.

Here are some possible suggestions:

1. Redefine 'struct timespec' and 'clock_gettime' on 32-bit MinGW so that they 
use 32-bit time_t only. The redefinitions would be visible only within Emacs; 
you wouldn't actually change MinGW.

2. Have Emacs w32*.c detect the width of the MS-Windows API's time_t at runtime, 
and if necessary convert between any 32-bit time_t on the MS-Windows side and 
the 64-bit time_t visible to the rest of the Emacs C code.

3. Build one Emacs executable for 32-bit MS-Windows Vista and later (with 64-bit 
time_t), and another one for 32-bit MS-Windows XP and older (with 32-bit time_t).

Any of these would insulate the rest of Emacs from this glitch. You mentioned a 
fourth possibility that would also serve:

> drop support for older Windows systems.

Microsoft itself has dropped support for the older MS-Windows systems in 
question, and it would be fine if Emacs dropped support too. We routinely drop 
support for obsolete and no-longer-maintained operating system versions like 
RHEL 5 and Irix 6.5.





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

* bug#32902: Add support for (TIMESTAMP . RESOLUTION) Lisp timestamps
  2018-10-08  5:18               ` Paul Eggert
@ 2018-10-09 15:09                 ` Eli Zaretskii
       [not found]                   ` <3f0bcf06-83f2-a0e9-c9ab-b06d65417afe@cs.ucla.edu>
  0 siblings, 1 reply; 11+ messages in thread
From: Eli Zaretskii @ 2018-10-09 15:09 UTC (permalink / raw)
  To: Paul Eggert; +Cc: 32902

> Cc: 32902@debbugs.gnu.org
> From: Paul Eggert <eggert@cs.ucla.edu>
> Date: Sun, 7 Oct 2018 22:18:12 -0700
> 
> > Microsoft made an incompatible change in its runtime libraries
> > around Windows Vista, and switched to 64-bit time_t even on 32-bit
> > systems.  Since we still try to support older Windows versions, we
> > must use that kludge, and we must limit ourselves to 32-bit time_t in
> > 32-bit builds.
> 
> Here are some possible suggestions:
> 
> 1. Redefine 'struct timespec' and 'clock_gettime' on 32-bit MinGW so that they 
> use 32-bit time_t only. The redefinitions would be visible only within Emacs; 
> you wouldn't actually change MinGW.
> 
> 2. Have Emacs w32*.c detect the width of the MS-Windows API's time_t at runtime, 
> and if necessary convert between any 32-bit time_t on the MS-Windows side and 
> the 64-bit time_t visible to the rest of the Emacs C code.
> 
> 3. Build one Emacs executable for 32-bit MS-Windows Vista and later (with 64-bit 
> time_t), and another one for 32-bit MS-Windows XP and older (with 32-bit time_t).
> 
> Any of these would insulate the rest of Emacs from this glitch.

The last two are undesirable, since it is generally expected of a
single Windows binary to run on all supported systems; having 2
separate binaries is possible, but complicates the matters.

I will try to look into the first alternative, not sure if its
feasible.

> > drop support for older Windows systems.
> 
> Microsoft itself has dropped support for the older MS-Windows systems in 
> question, and it would be fine if Emacs dropped support too. We routinely drop 
> support for obsolete and no-longer-maintained operating system versions like 
> RHEL 5 and Irix 6.5.

I don't think we should follow Microsoft in their decisions.  Last
time this came up, we decided not to drop support even for Windows 9X,
and here we are talking about XP and older.  We still have a few years
to make that decision.





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

* bug#32902: Add support for (TIMESTAMP . RESOLUTION) Lisp timestamps
       [not found]                   ` <3f0bcf06-83f2-a0e9-c9ab-b06d65417afe@cs.ucla.edu>
@ 2018-10-10  3:49                     ` Eli Zaretskii
  0 siblings, 0 replies; 11+ messages in thread
From: Eli Zaretskii @ 2018-10-10  3:49 UTC (permalink / raw)
  To: Paul Eggert; +Cc: 32902

> Cc: 32902@debbugs.gnu.org
> From: Paul Eggert <eggert@cs.ucla.edu>
> Date: Tue, 9 Oct 2018 13:15:57 -0700
> 
> >> Any of these would insulate the rest of Emacs from this glitch.
> > The last two are undesirable, since it is generally expected of a
> > single Windows binary to run on all supported systems; having 2
> > separate binaries is possible, but complicates the matters.
> 
> 
> Alternative (2) should also let a single binary run on all supported 
> MS-Windows systems, unless I'm misunderstanding something.

You are right, I wasn't paying attention.

> The idea is that Emacs proper uses 64-bit time_t and only a small
> part of w32*.c knows whether the MS-Windows API is using 32- or
> 64-bit time_t. Emacs could do this by using "#define time_t long
> long int" for most of Emacs, and having only the small part of
> w32*.c worry about the conversion.

Yes.  Not sure about the "small" part, though: time_t appears in many
libc functions ('stat' and 'fstat' come to mind), and we currently
still use most of the structures defined in system headers which
reference time_t values.

> Also, don't we already have 2 separate binaries, one for 32-bit and one 
> for 64-bit MS-Windows?

We do, but the 32-bit binaries are expected to run on 64-bit systems.
We cannot avoid having the separate 64-bit binaries, whereas the
additional 32-bit binaries are just a nuisance.  Note that at least
some of the support libraries might also need to be built twice, if
time_t is used in their interfaces, directly or indirectly.

> > Last time this came up, we decided not to drop support even for Windows 9X
> 
> It's your decision since you're the maintainer, and if you want to spend 
> time porting to obsolete operating systems it's your time to spend. That 
> being said, there's vanishingly little real-world need to run the *very 
> latest* version of GNU Emacs on Windows XP and earlier and for security 
> reasons if the documentation for the latest Emacs version discusses 
> these older machines it should be warning Emacs users to not connect 
> these machines to the Internet.

Well, my main development machine still runs XP, so for now this is a
real necessity ;-)





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

end of thread, other threads:[~2018-10-10  3:49 UTC | newest]

Thread overview: 11+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-10-02  1:00 bug#32902: Add support for (TIMESTAMP . RESOLUTION) Lisp timestamps Paul Eggert
2018-10-02  3:04 ` Eli Zaretskii
2018-10-03 18:45   ` Paul Eggert
2018-10-04 16:08     ` Eli Zaretskii
2018-10-07  6:32       ` Paul Eggert
2018-10-07 17:55         ` Eli Zaretskii
2018-10-07 20:05           ` Paul Eggert
2018-10-08  2:44             ` Eli Zaretskii
2018-10-08  5:18               ` Paul Eggert
2018-10-09 15:09                 ` Eli Zaretskii
     [not found]                   ` <3f0bcf06-83f2-a0e9-c9ab-b06d65417afe@cs.ucla.edu>
2018-10-10  3:49                     ` Eli Zaretskii

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.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).