Commit b06917a4 authored by Paul Eggert's avatar Paul Eggert

decode-time now returns subsec too

The list that decode-time returns now contains an extra
trailing component that counts the subseconds part of the
original timestamp (Bug#36549).
This builds on a suggestion by Lars Ingebrigtsen in:
https://lists.gnu.org/r/emacs-devel/2019-07/msg00734.html
* doc/lispref/os.texi (Time Conversion):
* doc/misc/emacs-mime.texi (time-date):
* etc/NEWS: Document this.
* 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 (make-decoded-time)
(decoded-time-set-defaults):
* lisp/org/org.el (org-fix-decoded-time)
(org-parse-time-string):
* src/timefns.c (Fdecode_time):
Generate subsec member for decoded time.
* lisp/calendar/time-date.el (decoded-time-add)
Add the decoded subsec too.
* lisp/simple.el (decoded-time): New subsec member.
* src/data.c (Frem): Simplify zero-check to match that of new Fmod.
(integer_mod): New function, with most of the guts of the old Fmod.
Remove redundant zero-check.
(Fmod): Use it.
* src/timefns.c (Fencode_time): Handle new subsec member
or (with the obsolescent calling convention) subsec arg.
It defaults to 0.
* 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):
Adjust to match new behavior.
parent 89c63b35
Pipeline #2770 failed with stage
in 51 minutes and 58 seconds
......@@ -1482,10 +1482,11 @@ Although @code{(time-convert nil nil)} is equivalent to
This function converts a time value into calendrical information. If
you don't specify @var{time}, it decodes the current time, and similarly
@var{zone} defaults to the current time zone rule. @xref{Time Zone Rules}.
The return value is a list of nine elements, as follows:
The return value is a list of ten elements, as follows:
@example
(@var{seconds} @var{minutes} @var{hour} @var{day} @var{month} @var{year} @var{dow} @var{dst} @var{utcoff})
(@var{seconds} @var{minutes} @var{hour} @var{day} @var{month} @var{year}
@var{dow} @var{dst} @var{utcoff} @var{subsec})
@end example
Here is what the elements mean:
......@@ -1513,17 +1514,22 @@ in effect, and @minus{}1 if this information is not available.
@item utcoff
An integer indicating the Universal Time offset in seconds, i.e., the number of
seconds east of Greenwich.
@item subsec
The number of subseconds past the second, as either 0 or a Lisp
timestamp @code{(@var{ticks} . @var{hz})} representing a nonnegative
fraction less than 1.
@end table
@strong{Common Lisp Note:} Common Lisp has different meanings for
@var{dow} and @var{utcoff}.
@var{dow} and @var{utcoff}, and lacks @var{subsec}.
To access (or alter) the elements in the time value, the
@code{decoded-time-second}, @code{decoded-time-minute},
@code{decoded-time-hour}, @code{decoded-time-day},
@code{decoded-time-month}, @code{decoded-time-year},
@code{decoded-time-weekday}, @code{decoded-time-dst} and
@code{decoded-time-zone} accessors can be used.
@code{decoded-time-weekday}, @code{decoded-time-dst},
@code{decoded-time-zone} and @code{decoded-time-subsec}
accessors can be used.
For instance, to increase the year in a decoded time, you could say:
......@@ -1579,21 +1585,22 @@ It can act as the inverse of @code{decode-time}.
Ordinarily the first argument is a list
@code{(@var{second} @var{minute} @var{hour} @var{day} @var{month}
@var{year} @var{ignored} @var{dst} @var{zone})} that specifies a
@var{year} @var{ignored} @var{dst} @var{zone} @var{subsec})} 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}.
As an obsolescent calling convention, this function can be given six
or more arguments. The first six arguments @var{second},
through ten 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} takes its default value.
specify most of the components of a decoded time. If there are seven
through nine arguments the @emph{last} argument is used as @var{zone},
and if there are ten arguments the ninth specifies @var{zone} and the
tenth specifies @var{subsec}; in either case any other extra arguments
are ignored, so that @code{(apply #'encode-time (decode-time ...))}
works. In this obsolescent convention, @var{zone} defaults to the
current time zone rule (@pxref{Time Zone Rules}), @var{subsec}
defaults to 0, and @var{dst} is treated as if it was @minus{}1.
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
......@@ -1608,8 +1615,9 @@ the latter to the former as follows:
@end example
You can perform simple date arithmetic by using out-of-range values for
@var{seconds}, @var{minutes}, @var{hour}, @var{day}, and @var{month};
for example, day 0 means the day preceding the given month.
@var{seconds}, @var{minutes}, @var{hour}, @var{day}, @var{month}, and
@var{subsec}; for example, day 0 means the day preceding the given
month.
The operating system puts limits on the range of possible time values;
if the limits are exceeded while encoding the time, an error results.
......
......@@ -1535,7 +1535,7 @@ Here's a bunch of time/date/second/day examples:
@example
(parse-time-string "Sat Sep 12 12:21:54 1998 +0200")
@result{} (54 21 12 12 9 1998 6 -1 7200)
@result{} (54 21 12 12 9 1998 6 -1 7200 0)
(time-convert
(date-to-time "Sat Sep 12 12:21:54 1998 +0200")
......
......@@ -2092,6 +2092,12 @@ format may change and that programs should use functions like
probing the innards of a timestamp directly, or creating a timestamp
by hand.
+++
*** Decoded (calendrical) timestamps now have a new subsecond member.
This affects functions like decode-time and parse-time-string that
generate these timestamps, and functions like encode-time that accept
them.
+++
*** 'encode-time' supports a new API '(encode-time TIME)'.
The old 'encode-time' API is still supported.
......@@ -2123,8 +2129,8 @@ with POSIX.1-2017.
*** To access (or alter) the elements a decoded 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' and 'decoded-time-zone'
accessors can be used.
'decoded-time-weekday', 'decoded-time-dst', 'decoded-time-zone',
and 'decoded-time-subsec' accessors can be used.
*** The new functions 'date-days-in-month' (which will say how many
days there are in a month in a specific year), 'date-ordinal-to-time'
......
......@@ -644,7 +644,7 @@ FIXME: multiple comma-separated values should be allowed!"
;; create the decoded date-time
;; FIXME!?!
(let ((decoded-time (list second minute hour day month year
nil -1 zone)))
nil -1 zone 0)))
(condition-case nil
(decode-time (encode-time decoded-time))
(error
......
......@@ -129,7 +129,8 @@ well as variants like \"2008W32\" (week number) and
(let ((time (iso8601-parse-time time-string)))
(setf (decoded-time-hour date) (decoded-time-hour time))
(setf (decoded-time-minute date) (decoded-time-minute time))
(setf (decoded-time-second date) (decoded-time-second time))))
(setf (decoded-time-second date) (decoded-time-second time))
(setf (decoded-time-subsec date) (decoded-time-subsec time))))
;; The time zone is optional.
(when zone-string
(setf (decoded-time-zone date)
......@@ -236,6 +237,8 @@ well as variants like \"2008W32\" (week number) and
(iso8601--decoded-time :hour hour
:minute (or minute 0)
:second (or second 0)
;; FIXME: Support subsec.
:subsec 0
:zone (and zone
(* 60 (iso8601-parse-zone
zone)))))))))
......@@ -274,7 +277,9 @@ Return the number of minutes."
:day (or (match-string 3 string) 0)
:hour (or (match-string 5 string) 0)
:minute (or (match-string 6 string) 0)
:second (or (match-string 7 string) 0)))
:second (or (match-string 7 string) 0)
;; FIXME: Support subsec.
:subsec 0))
;; PnW: Weeks.
((iso8601--match iso8601--duration-week-match string)
(let ((weeks (string-to-number (match-string 1 string))))
......@@ -336,7 +341,7 @@ Return the number of minutes."
(cl-defun iso8601--decoded-time (&key second minute hour
day month year
dst zone)
dst zone subsec)
(list (iso8601--value second)
(iso8601--value minute)
(iso8601--value hour)
......@@ -345,7 +350,8 @@ Return the number of minutes."
(iso8601--value year)
nil
dst
zone))
zone
subsec))
(defun iso8601--encode-time (time)
"Like `encode-time', but fill in nil values in TIME."
......
......@@ -27,7 +27,7 @@
;; Emacs. However, parsing time strings is still largely a matter of
;; heuristics and no common interface has been designed.
;; `parse-time-string' parses a time in a string and returns a list of 9
;; `parse-time-string' parses a time in a string and returns a list of
;; values, just like `decode-time', where unspecified elements in the
;; string are returned as nil (except unspecfied DST is returned as -1).
;; `encode-time' may be applied on these values to obtain an internal
......@@ -148,7 +148,7 @@ letters, digits, plus or minus signs or colons."
;;;###autoload
(defun parse-time-string (string)
"Parse the time-string STRING into (SEC MIN HOUR DAY MON YEAR DOW DST TZ).
"Parse the time in STRING into (SEC MIN HOUR DAY MON YEAR DOW DST TZ SUBSEC).
STRING should be something resembling an RFC 822 (or later) date-time, e.g.,
\"Fri, 25 Mar 2016 16:24:56 +0100\", but this function is
somewhat liberal in what format it accepts, and will attempt to
......@@ -156,7 +156,7 @@ return a \"likely\" value even for somewhat malformed strings.
The values returned are identical to those of `decode-time', but
any unknown values other than DST are returned as nil, and an
unknown DST value is returned as -1."
(let ((time (list nil nil nil nil nil nil nil -1 nil))
(let ((time (list nil nil nil nil nil nil nil -1 nil nil))
(temp (parse-time-tokenize (downcase string))))
(while temp
(let ((parse-time-elt (pop temp))
......@@ -193,6 +193,10 @@ unknown DST value is returned as -1."
(funcall this)))
parse-time-val)))
(setf (nth (pop slots) time) new-val))))))))
;; FIXME: Currently parse-time-string does not parse subseconds.
;; So if seconds were found, set subseconds to zero.
(when (nth 0 time)
(setf (nth 9 time) 0))
time))
(defun parse-iso8601-time-string (date-string)
......
......@@ -423,6 +423,13 @@ changes in daylight saving time are not taken into account."
(setq seconds (+ (* (or (decoded-time-hour delta) 0) 3600)
(* (or (decoded-time-minute delta) 0) 60)
(or (decoded-time-second delta) 0)))
(when (decoded-time-subsec delta)
(let* ((subsec (time-convert (time-add (decoded-time-subsec time)
(decoded-time-subsec delta))
t))
(s (time-convert subsec 'integer)))
(setq seconds (+ seconds s))
(setf (decoded-time-subsec time) (time-subtract subsec s))))
;; Time zone adjustments are basically the same as time adjustments.
(setq seconds (+ seconds (or (decoded-time-zone delta) 0)))
......@@ -494,9 +501,9 @@ changes in daylight saving time are not taken into account."
(cl-defun make-decoded-time (&key second minute hour
day month year
dst zone)
dst zone subsec)
"Return a `decoded-time' structure with only the keywords given filled out."
(list second minute hour day month year nil dst zone))
(list second minute hour day month year nil dst zone subsec))
(defun decoded-time-set-defaults (time &optional default-zone)
"Set any nil values in `decoded-time' TIME to default values.
......@@ -526,6 +533,9 @@ TIME is modified and returned."
(when (and (not (decoded-time-zone time))
default-zone)
(setf (decoded-time-zone time) 0))
(unless (decoded-time-subsec time)
(setf (decoded-time-subsec time) 0))
time)
(provide 'time-date)
......
......@@ -561,7 +561,8 @@ gMonthDay, gDay or gMonth.
Return a list in a format (SEC MINUTE HOUR DAY MONTH YEAR
SEC-FRACTION DATATYPE ZONE). This format is meant to be similar
to that returned by `decode-time' (and compatible with
`encode-time'). The differences are the DOW (day-of-week) field
`encode-time'). The differences are the SUBSEC (fractional
seconds) field is omitted, the DOW (day-of-week) field
is replaced with SEC-FRACTION, a float representing the
fractional seconds, and the DST (daylight savings time) field is
replaced with DATATYPE, a symbol representing the XSD primitive
......
......@@ -17292,10 +17292,10 @@ The command returns the inserted time stamp."
(put-text-property beg end 'display str)))
(defun org-fix-decoded-time (time)
"Set 0 instead of nil for the first 6 elements of time.
"Set 0 instead of nil for the time-related elements of time.
Don't touch the rest."
(let ((n 0))
(mapcar (lambda (x) (if (< (setq n (1+ n)) 7) (or x 0) x)) time)))
(mapcar (lambda (x) (if (or (< (setq n (1+ n)) 7) (= n 10)) (or x 0) x)) time)))
(defun org-time-stamp-to-now (timestamp-string &optional seconds)
"Difference between TIMESTAMP-STRING and now in days.
......@@ -17779,7 +17779,7 @@ NODEFAULT, hour and minute fields will be nil if not given."
(string-to-number (match-string 4 s))
(string-to-number (match-string 3 s))
(string-to-number (match-string 2 s))
nil nil nil))
nil nil nil 0))
((string-match "^<[^>]+>$" s)
;; FIXME: `decode-time' needs to be called with ZONE as its
;; second argument. However, this requires at least Emacs
......
......@@ -9089,6 +9089,9 @@ available.")
(zone nil :documentation "\
This is an integer indicating the UTC offset in seconds, i.e.,
the number of seconds east of Greenwich.")
(subsec nil :documentation "\
This is 0, or is an integer pair (TICKS . HZ) indicating TICKS/HZ seconds,
where HZ is positive and TICKS is nonnegative and less than HZ.")
)
......
......@@ -3067,7 +3067,7 @@ Both must be integers or markers. */)
CHECK_INTEGER_COERCE_MARKER (y);
/* A bignum can never be 0, so don't check that case. */
if (FIXNUMP (y) && XFIXNUM (y) == 0)
if (EQ (y, make_fixnum (0)))
xsignal0 (Qarith_error);
if (FIXNUMP (x) && FIXNUMP (y))
......@@ -3081,30 +3081,14 @@ Both must be integers or markers. */)
}
}
DEFUN ("mod", Fmod, Smod, 2, 2, 0,
doc: /* Return X modulo Y.
The result falls between zero (inclusive) and Y (exclusive).
Both X and Y must be numbers or markers. */)
(register Lisp_Object x, Lisp_Object y)
/* Return X mod Y. Both must be integers and Y must be nonzero. */
Lisp_Object
integer_mod (Lisp_Object x, Lisp_Object y)
{
CHECK_NUMBER_COERCE_MARKER (x);
CHECK_NUMBER_COERCE_MARKER (y);
/* Note that a bignum can never be 0, so we don't need to check that
case. */
if (FIXNUMP (y) && XFIXNUM (y) == 0)
xsignal0 (Qarith_error);
if (FLOATP (x) || FLOATP (y))
return fmod_float (x, y);
if (FIXNUMP (x) && FIXNUMP (y))
{
EMACS_INT i1 = XFIXNUM (x), i2 = XFIXNUM (y);
if (i2 == 0)
xsignal0 (Qarith_error);
i1 %= i2;
/* If the "remainder" comes out with the wrong sign, fix it. */
......@@ -3128,6 +3112,22 @@ Both X and Y must be numbers or markers. */)
}
}
DEFUN ("mod", Fmod, Smod, 2, 2, 0,
doc: /* Return X modulo Y.
The result falls between zero (inclusive) and Y (exclusive).
Both X and Y must be numbers or markers. */)
(Lisp_Object x, Lisp_Object y)
{
CHECK_NUMBER_COERCE_MARKER (x);
CHECK_NUMBER_COERCE_MARKER (y);
/* A bignum can never be 0, so don't check that case. */
if (EQ (y, make_fixnum (0)))
xsignal0 (Qarith_error);
return (FLOATP (x) || FLOATP (y) ? fmod_float : integer_mod) (x, y);
}
static Lisp_Object
minmax_driver (ptrdiff_t nargs, Lisp_Object *args,
enum Arith_Comparison comparison)
......
......@@ -3581,6 +3581,7 @@ extern void set_default_internal (Lisp_Object, Lisp_Object,
extern Lisp_Object expt_integer (Lisp_Object, Lisp_Object);
extern void syms_of_data (void);
extern void swap_in_global_binding (struct Lisp_Symbol *);
extern Lisp_Object integer_mod (Lisp_Object, Lisp_Object);
/* Defined in cmds.c */
extern void syms_of_cmds (void);
......
......@@ -1296,7 +1296,7 @@ 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).
doc: /* Decode a time value as (SEC MINUTE HOUR DAY MONTH YEAR DOW DST UTCOFF SUBSEC).
The optional TIME is the time value to convert. See
`format-time-string' for the various forms of a time value.
......@@ -1309,10 +1309,10 @@ without consideration for daylight saving time.
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' and `decoded-time-zone'
accessors can be used.
`decoded-time-weekday', `decoded-time-dst', `decoded-time-zone' and
`decoded-time-subsec' accessors can be used.
The list has the following nine members: SEC is an integer between 0
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
between 0 and 23. DAY is an integer between 1 and 31. MONTH is an
......@@ -1321,13 +1321,20 @@ 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.)
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.)
usage: (decode-time &optional TIME ZONE) */)
(Lisp_Object specified_time, Lisp_Object zone)
{
time_t time_spec = lisp_seconds_argument (specified_time);
struct lisp_time lt = lisp_time_struct (specified_time, 0);
struct timespec ts = lisp_to_timespec (lt);
if (! timespec_valid_p (ts))
time_overflow ();
time_t time_spec = ts.tv_sec;
struct tm local_tm, gmt_tm;
timezone_t tz = tzlookup (zone, false);
struct tm *tm = emacs_localtime_rz (tz, &time_spec, &local_tm);
......@@ -1367,7 +1374,10 @@ 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));
: Qnil),
(EQ (lt.hz, make_fixnum (1))
? make_fixnum (0)
: Fcons (integer_mod (lt.ticks, lt.hz), lt.hz)));
}
/* Return OBJ - OFFSET, checking that OBJ is a valid integer and that
......@@ -1398,7 +1408,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).
TIME is a list (SECOND MINUTE HOUR DAY MONTH YEAR IGNORED DST ZONE SUBSEC).
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
......@@ -1407,15 +1417,18 @@ 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.
saving flag to be guessed. SUBSEC is either 0 or a Lisp timestamp
in (TICKS . HZ) form.
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
(decode-time ...)) works; otherwise ZONE is assumed to be nil.
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.
......@@ -1429,14 +1442,14 @@ usage: (encode-time TIME &rest OBSOLESCENT-ARGUMENTS) */)
(ptrdiff_t nargs, Lisp_Object *args)
{
struct tm tm;
Lisp_Object zone = Qnil;
Lisp_Object zone = Qnil, subsec = make_fixnum (0);
Lisp_Object a = args[0];
tm.tm_isdst = -1;
if (nargs == 1)
{
Lisp_Object tail = a;
for (int i = 0; i < 9; i++, tail = XCDR (tail))
for (int i = 0; i < 10; 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);
......@@ -1445,11 +1458,11 @@ usage: (encode-time TIME &rest OBSOLESCENT-ARGUMENTS) */)
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);
Lisp_Object dstflag = XCAR (a);
a = XCDR (a);
zone = XCAR (a);
Lisp_Object dstflag = XCAR (a); a = XCDR (a);
zone = XCAR (a); a = XCDR (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));
......@@ -1457,6 +1470,11 @@ 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);
......@@ -1474,9 +1492,25 @@ usage: (encode-time TIME &rest OBSOLESCENT-ARGUMENTS) */)
if (tm.tm_wday < 0)
time_error (mktime_errno);
return (CURRENT_TIME_LIST
? list2 (hi_time (value), lo_time (value))
: INT_TO_INTEGER (value));
if (CONSP (subsec))
{
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);
}
}
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,
......
......@@ -477,18 +477,18 @@ END:VEVENT
;; testcase: no time zone in input -> keep time as is
;; 1 Jan 2013 10:00
(should (equal '(0 0 10 1 1 2013 2 nil 7200)
(should (equal '(0 0 10 1 1 2013 2 nil 7200 0)
(icalendar--decode-isodatetime "20130101T100000")))
;; 1 Aug 2013 10:00 (DST)
(should (equal '(0 0 10 1 8 2013 4 t 10800)
(should (equal '(0 0 10 1 8 2013 4 t 10800 0)
(icalendar--decode-isodatetime "20130801T100000")))
;; testcase: UTC time zone specifier in input -> convert to local time
;; 31 Dec 2013 23:00 UTC -> 1 Jan 2013 01:00 EET
(should (equal '(0 0 1 1 1 2014 3 nil 7200)
(should (equal '(0 0 1 1 1 2014 3 nil 7200 0)
(icalendar--decode-isodatetime "20131231T230000Z")))
;; 1 Aug 2013 10:00 UTC -> 1 Aug 2013 13:00 EEST
(should (equal '(0 0 13 1 8 2013 4 t 10800)
(should (equal '(0 0 13 1 8 2013 4 t 10800 0)
(icalendar--decode-isodatetime "20130801T100000Z")))
)
......
This diff is collapsed.
......@@ -28,23 +28,23 @@
(ert-deftest parse-time-tests ()
(should (equal (parse-time-string "Mon, 22 Feb 2016 19:35:42 +0100")
'(42 35 19 22 2 2016 1 -1 3600)))
'(42 35 19 22 2 2016 1 -1 3600 0)))
(should (equal (parse-time-string "22 Feb 2016 19:35:42 +0100")
'(42 35 19 22 2 2016 nil -1 3600)))
'(42 35 19 22 2 2016 nil -1 3600 0)))
(should (equal (parse-time-string "22 Feb 2016 +0100")
'(nil nil nil 22 2 2016 nil -1 3600)))
'(nil nil nil 22 2 2016 nil -1 3600 nil)))
(should (equal (parse-time-string "Mon, 22 Feb 16 19:35:42 +0100")
'(42 35 19 22 2 2016 1 -1 3600)))
'(42 35 19 22 2 2016 1 -1 3600 0)))
(should (equal (parse-time-string "Mon, 22 February 2016 19:35:42 +0100")
'(42 35 19 22 2 2016 1 -1 3600)))
'(42 35 19 22 2 2016 1 -1 3600 0)))
(should (equal (parse-time-string "Mon, 22 feb 2016 19:35:42 +0100")
'(42 35 19 22 2 2016 1 -1 3600)))
'(42 35 19 22 2 2016 1 -1 3600 0)))
(should (equal (parse-time-string "Monday, 22 february 2016 19:35:42 +0100")
'(42 35 19 22 2 2016 1 -1 3600)))
'(42 35 19 22 2 2016 1 -1 3600 0)))
(should (equal (parse-time-string "Monday, 22 february 2016 19:35:42 PST")
'(42 35 19 22 2 2016 1 nil -28800)))
'(42 35 19 22 2 2016 1 nil -28800 0)))
(should (equal (parse-time-string "Friday, 21 Sep 2018 13:47:58 PDT")
'(58 47 13 21 9 2018 5 t -25200)))
'(58 47 13 21 9 2018 5 t -25200 0)))
(should (equal (format-time-string
"%Y-%m-%d %H:%M:%S"
(parse-iso8601-time-string "1998-09-12T12:21:54-0200") t)
......
......@@ -40,23 +40,25 @@
(7879679999900 . 100000)
(78796799999999999999 . 1000000000000)))
;; UTC.
(let ((subsec (time-subtract (time-convert look t)
(time-convert look 'integer))))
(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)))
(list 59 59 23 30 6 1972 5 nil 0 subsec)))
;; "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)))
(list 59 59 23 30 6 1972 5 nil 0 subsec)))
;; 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)))
(list 59 59 15 30 6 1972 5 nil -28800 subsec)))
;; Negative UTC offset, as a Lisp integer.
(should (string-equal
(format-time-string format look -28800)
......@@ -66,13 +68,13 @@
"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)))
(list 59 59 15 30 6 1972 5 nil -28800 subsec)))
;; 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))))))
(list 59 29 5 1 7 1972 6 nil 19800 subsec)))))))
(ert-deftest decode-then-encode-time ()
(let ((time-values (list 0 -2 1 0.0 -0.0 -2.0 1.0
......@@ -146,5 +148,5 @@
(ert-deftest encode-time-dst-numeric-zone ()
"Check for Bug#35502."
(should (time-equal-p
(encode-time '(29 31 17 30 4 2019 2 t 7200))
(encode-time '(29 31 17 30 4 2019 2 t 7200 0))
'(23752 27217))))