diff options
author | Paul Eggert <eggert@cs.ucla.edu> | 2019-08-16 22:09:04 -0700 |
---|---|---|
committer | Paul Eggert <eggert@cs.ucla.edu> | 2019-08-16 23:25:07 -0700 |
commit | 37257d6acadff17bd1e52cfa460950bcb684c5c3 (patch) | |
tree | ab7088cfa725561c8456f388cff79466948b3532 /src/timefns.c | |
parent | d7c9ed8445d13de7350be3360d68717362f89929 (diff) | |
download | emacs-37257d6acadff17bd1e52cfa460950bcb684c5c3.tar.gz |
More-compatible subsecond calendrical timestamps
Instead of appending a subseconds member to the result of
‘decode-time’, this keeps the format unchanged unless you give
a new optional argument to ‘decode-time’. Also, the augmented
format now puts the subsecond info in the SECONDS element, so
the total number of elements is unchanged; this is more
compatible with code that expects the traditional 9 elements,
such as ‘(pcase decoded-time (`(,SEC ,MIN ,HOUR ,DAY ,MON
,YEAR ,DOW ,DST ,ZONE) ...) ...)’.
* doc/lispref/os.texi, doc/misc/emacs-mime.texi, etc/NEWS:
* lisp/net/soap-client.el (soap-decode-date-time):
* lisp/simple.el (decoded-time):
Document the new behavior.
* lisp/calendar/icalendar.el (icalendar--decode-isodatetime):
* lisp/calendar/iso8601.el (iso8601-parse)
(iso8601-parse-time, iso8601-parse-duration)
(iso8601--decoded-time):
* lisp/calendar/parse-time.el (parse-time-string):
* lisp/calendar/time-date.el (decoded-time-add)
(decoded-time--alter-second):
* lisp/org/org.el (org-parse-time-string):
* lisp/simple.el (decoded-time):
* src/timefns.c (Fdecode_time, Fencode_time):
* test/lisp/calendar/icalendar-tests.el:
(icalendar--decode-isodatetime):
* test/lisp/calendar/iso8601-tests.el (test-iso8601-date-years)
(test-iso8601-date-dates, test-iso8601-date-obsolete)
(test-iso8601-date-weeks, test-iso8601-date-ordinals)
(test-iso8601-time, test-iso8601-combined)
(test-iso8601-duration, test-iso8601-intervals)
(standard-test-dates, standard-test-time-of-day-fractions)
(standard-test-time-of-day-beginning-of-day)
(standard-test-time-of-day-utc)
(standard-test-time-of-day-zone)
(standard-test-date-and-time-of-day, standard-test-interval):
* test/lisp/calendar/parse-time-tests.el (parse-time-tests):
* test/src/timefns-tests.el (format-time-string-with-zone)
(encode-time-dst-numeric-zone):
Revert recent changes that added a SUBSECS member to
calendrical timestamps, since that component is no longer
present (the info, if any, is now in the SECONDS member).
* lisp/calendar/time-date.el (decoded-time-add)
(decoded-time--alter-second):
Support fractional seconds in the new form. Simplify.
* src/timefns.c (Fdecode_time): Support new arg FORM.
(Fencode_time): Support subsecond resolution.
* test/src/timefns-tests.el (format-time-string-with-zone)
(decode-then-encode-time): Test subsecond calendrical timestamps.
Diffstat (limited to 'src/timefns.c')
-rw-r--r-- | src/timefns.c | 178 |
1 files changed, 105 insertions, 73 deletions
diff --git a/src/timefns.c b/src/timefns.c index 979550c8431..16c39c83493 100644 --- a/src/timefns.c +++ b/src/timefns.c @@ -1374,8 +1374,8 @@ usage: (format-time-string FORMAT-STRING &optional TIME ZONE) */) 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 SUBSEC). +DEFUN ("decode-time", Fdecode_time, Sdecode_time, 0, 3, 0, + doc: /* Decode a time value as (SEC MINUTE HOUR DAY MONTH YEAR DOW DST UTCOFF). The optional TIME is the time value to convert. See `format-time-string' for the various forms of a time value. @@ -1385,29 +1385,33 @@ 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 optional FORM specifies the form of the SEC member. If `integer', +SEC is an integer; if t, SEC uses the same resolution as TIME. An +omitted or nil FORM is currently treated like `integer', but this may +change in future Emacs versions. + To access (or alter) the elements in the time value, the `decoded-time-second', `decoded-time-minute', `decoded-time-hour', `decoded-time-day', `decoded-time-month', `decoded-time-year', -`decoded-time-weekday', `decoded-time-dst', `decoded-time-zone' and -`decoded-time-subsec' accessors can be used. +`decoded-time-weekday', `decoded-time-dst' and `decoded-time-zone' +accessors can be used. -The list has the following ten 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 +The list has the following nine members: SEC is an integer or +Lisp timestamp representing a nonnegative value less than 60 +\(or less than 61 if the operating system supports leap seconds). +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. SUBSEC is -is either 0 or (TICKS . HZ) where HZ is a positive integer clock -resolution and TICKS is a nonnegative integer less than HZ. (Note -that Common Lisp has different meanings for DOW and UTCOFF, and lacks -SUBSEC.) +seconds, i.e., the number of seconds east of Greenwich. (Note that +Common Lisp has different meanings for DOW and UTCOFF, and its +SEC is always an integer between 0 and 59.) -usage: (decode-time &optional TIME ZONE) */) - (Lisp_Object specified_time, Lisp_Object zone) +usage: (decode-time &optional TIME ZONE FORM) */) + (Lisp_Object specified_time, Lisp_Object zone, Lisp_Object form) { struct lisp_time lt = lisp_time_struct (specified_time, 0); struct timespec ts = lisp_to_timespec (lt); @@ -1439,8 +1443,35 @@ usage: (decode-time &optional TIME ZONE) */) year = make_integer_mpz (); } + Lisp_Object hz = lt.hz, sec; + if (EQ (hz, make_fixnum (1)) || !EQ (form, Qt)) + sec = make_fixnum (local_tm.tm_sec); + else + { + Lisp_Object ticks; /* hz * tm_sec + mod (lt.ticks, hz) */ + intmax_t n; + if (FASTER_TIMEFNS && FIXNUMP (lt.ticks) && FIXNUMP (hz) + && !INT_MULTIPLY_WRAPV (XFIXNUM (hz), local_tm.tm_sec, &n) + && ! (INT_ADD_WRAPV + (n, (XFIXNUM (lt.ticks) % XFIXNUM (hz) + + (XFIXNUM (lt.ticks) % XFIXNUM (hz) < 0 + ? XFIXNUM (hz) : 0)), + &n))) + ticks = make_int (n); + else + { + mpz_fdiv_r (mpz[0], + *bignum_integer (&mpz[0], lt.ticks), + *bignum_integer (&mpz[1], hz)); + mpz_addmul_ui (mpz[0], *bignum_integer (&mpz[1], hz), + local_tm.tm_sec); + ticks = make_integer_mpz (); + } + sec = Fcons (ticks, hz); + } + return CALLN (Flist, - make_fixnum (local_tm.tm_sec), + sec, make_fixnum (local_tm.tm_min), make_fixnum (local_tm.tm_hour), make_fixnum (local_tm.tm_mday), @@ -1453,10 +1484,7 @@ usage: (decode-time &optional TIME ZONE) */) ? make_fixnum (tm_gmtoff (&local_tm)) : gmtime_r (&time_spec, &gmt_tm) ? make_fixnum (tm_diff (&local_tm, &gmt_tm)) - : Qnil), - (EQ (lt.hz, make_fixnum (1)) - ? make_fixnum (0) - : Fcons (integer_mod (lt.ticks, lt.hz), lt.hz))); + : Qnil)); } /* Return OBJ - OFFSET, checking that OBJ is a valid integer and that @@ -1487,7 +1515,7 @@ check_tm_member (Lisp_Object obj, int offset) DEFUN ("encode-time", Fencode_time, Sencode_time, 1, MANY, 0, doc: /* Convert TIME to a timestamp. -TIME is a list (SECOND MINUTE HOUR DAY MONTH YEAR IGNORED DST ZONE SUBSEC). +TIME is a list (SECOND MINUTE HOUR DAY MONTH YEAR IGNORED DST ZONE). in the style of `decode-time', so that (encode-time (decode-time ...)) works. In this list, ZONE can be nil for Emacs local time, t for Universal Time, `wall' for system wall clock time, or a string as in the TZ @@ -1496,23 +1524,16 @@ environment variable. It can also be a list (as from without consideration for daylight saving time. If ZONE specifies a time zone with daylight-saving transitions, DST is t for daylight saving time, nil for standard time, and -1 to cause the daylight -saving flag to be guessed. SUBSEC is either 0 or a Lisp timestamp -in (TICKS . HZ) form. +saving flag to be guessed. As an obsolescent calling convention, if this function is called with -6 through 10 arguments, the first 6 arguments are SECOND, MINUTE, -HOUR, DAY, MONTH, and YEAR, and specify the components of a decoded -time. If there are 7 through 9 arguments the *last* argument -specifies ZONE, and if there are 10 arguments the 9th specifies ZONE -and the 10th specifies SUBSEC; in either case any other extra -arguments are ignored, so that (apply #\\='encode-time (decode-time -...)) works. In this obsolescent convention, DST, ZONE, and SUBSEC -default to -1, nil and 0 respectively. - -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. +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 +(decode-time ...)) works. In this obsolescent convention, DST and +ZONE default to -1 and nil respectively. Years before 1970 are not guaranteed to work. On some systems, year values as low as 1901 do work. @@ -1521,27 +1542,27 @@ usage: (encode-time TIME &rest OBSOLESCENT-ARGUMENTS) */) (ptrdiff_t nargs, Lisp_Object *args) { struct tm tm; - Lisp_Object zone = Qnil, subsec = make_fixnum (0); + Lisp_Object zone = Qnil; Lisp_Object a = args[0]; + Lisp_Object secarg, minarg, hourarg, mdayarg, monarg, yeararg; tm.tm_isdst = -1; if (nargs == 1) { Lisp_Object tail = a; - for (int i = 0; i < 10; i++, tail = XCDR (tail)) + for (int i = 0; i < 9; i++, tail = XCDR (tail)) CHECK_CONS (tail); - 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); + secarg = XCAR (a); a = XCDR (a); + minarg = XCAR (a); a = XCDR (a); + hourarg = XCAR (a); a = XCDR (a); + mdayarg = XCAR (a); a = XCDR (a); + monarg = XCAR (a); a = XCDR (a); + yeararg = XCAR (a); a = XCDR (a); a = XCDR (a); Lisp_Object dstflag = XCAR (a); a = XCDR (a); - zone = XCAR (a); a = XCDR (a); + zone = XCAR (a); if (SYMBOLP (dstflag) && !FIXNUMP (zone) && !CONSP (zone)) tm.tm_isdst = !NILP (dstflag); - subsec = XCAR (a); } else if (nargs < 6) xsignal2 (Qwrong_number_of_arguments, Qencode_time, make_fixnum (nargs)); @@ -1549,18 +1570,37 @@ usage: (encode-time TIME &rest OBSOLESCENT-ARGUMENTS) */) { if (6 < nargs) zone = args[nargs - 1]; - if (9 < nargs) - { - zone = args[8]; - subsec = args[9]; - } - 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); + secarg = a; + minarg = args[1]; + hourarg = args[2]; + mdayarg = args[3]; + monarg = args[4]; + yeararg = args[5]; + } + + struct lisp_time lt; + decode_lisp_time (secarg, 0, <, 0); + Lisp_Object hz = lt.hz, sec, subsecticks; + if (FASTER_TIMEFNS && EQ (hz, make_fixnum (1))) + { + sec = lt.ticks; + subsecticks = make_fixnum (0); + } + else + { + mpz_fdiv_qr (mpz[0], mpz[1], + *bignum_integer (&mpz[0], lt.ticks), + *bignum_integer (&mpz[1], hz)); + sec = make_integer_mpz (); + mpz_swap (mpz[0], mpz[1]); + subsecticks = make_integer_mpz (); } + tm.tm_sec = check_tm_member (sec, 0); + tm.tm_min = check_tm_member (minarg, 0); + tm.tm_hour = check_tm_member (hourarg, 0); + tm.tm_mday = check_tm_member (mdayarg, 0); + tm.tm_mon = check_tm_member (monarg, 1); + tm.tm_year = check_tm_member (yeararg, TM_YEAR_BASE); timezone_t tz = tzlookup (zone, false); tm.tm_wday = -1; @@ -1571,25 +1611,17 @@ usage: (encode-time TIME &rest OBSOLESCENT-ARGUMENTS) */) if (tm.tm_wday < 0) time_error (mktime_errno); - if (CONSP (subsec)) + if (EQ (hz, make_fixnum (1))) + return (CURRENT_TIME_LIST + ? list2 (hi_time (value), lo_time (value)) + : INT_TO_INTEGER (value)); + else { - Lisp_Object subsecticks = XCAR (subsec); - if (INTEGERP (subsecticks)) - { - struct lisp_time val1 = { INT_TO_INTEGER (value), make_fixnum (1) }; - Lisp_Object - hz = XCDR (subsec), - secticks = lisp_time_hz_ticks (val1, hz), - ticks = lispint_arith (secticks, subsecticks, false); - return Fcons (ticks, hz); - } + struct lisp_time val1 = { INT_TO_INTEGER (value), make_fixnum (1) }; + Lisp_Object secticks = lisp_time_hz_ticks (val1, hz); + Lisp_Object ticks = lispint_arith (secticks, subsecticks, false); + return Fcons (ticks, hz); } - else if (INTEGERP (subsec)) - return (CURRENT_TIME_LIST && EQ (subsec, make_fixnum (0)) - ? list2 (hi_time (value), lo_time (value)) - : lispint_arith (INT_TO_INTEGER (value), subsec, false)); - - xsignal2 (Qerror, build_string ("Invalid subsec"), subsec); } DEFUN ("time-convert", Ftime_convert, Stime_convert, 1, 2, 0, |