all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
* Support for sub-second time in decoded time
@ 2019-07-29  9:48 Lars Ingebrigtsen
  2019-07-29 14:03 ` Stefan Monnier
                   ` (2 more replies)
  0 siblings, 3 replies; 45+ messages in thread
From: Lars Ingebrigtsen @ 2019-07-29  9:48 UTC (permalink / raw)
  To: emacs-devel

Currently, encoded time has support for picoseconds.  `decode-time' will
throw away all sub-second time, which makes accurate round-tripping
impossible.

I mentioned this in a bug report, I think, but didn't get any response.
I proposed to extend the list returned by `decode-time' (and accepted by
`encode-time') to accommodate this -- I think this can be done in a
backwards-compatible manner.

`current-time' returns its data as (HIGH LOW USEC PSEC), but now that we
have bignum support, perhaps we don't need to do it this way.  What
about just having a field in decoded times that's the fraction of a
second?  So 34.5603 seconds would be represented as

(34 ... 5603)

Opinions?

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no




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

* Re: Support for sub-second time in decoded time
  2019-07-29  9:48 Support for sub-second time in decoded time Lars Ingebrigtsen
@ 2019-07-29 14:03 ` Stefan Monnier
  2019-07-29 14:12   ` Lars Ingebrigtsen
  2019-07-29 14:23 ` Support for sub-second time in decoded time Eli Zaretskii
  2019-07-29 16:46 ` Paul Eggert
  2 siblings, 1 reply; 45+ messages in thread
From: Stefan Monnier @ 2019-07-29 14:03 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: emacs-devel

> `current-time' returns its data as (HIGH LOW USEC PSEC), but now that we
> have bignum support, perhaps we don't need to do it this way.  What
> about just having a field in decoded times that's the fraction of a
> second?  So 34.5603 seconds would be represented as
>
> (34 ... 5603)

`encode-time` uses a fraction-representation (NOM . DENOM), which is
probably easier to manipulate.

[ Note: I find `encode-time` vs `decode-time` very confusing.  ]


        Stefan




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

* Re: Support for sub-second time in decoded time
  2019-07-29 14:03 ` Stefan Monnier
@ 2019-07-29 14:12   ` Lars Ingebrigtsen
  2019-07-29 14:43     ` Stefan Monnier
  2019-07-29 16:08     ` encode-time vs decode-time Stefan Monnier
  0 siblings, 2 replies; 45+ messages in thread
From: Lars Ingebrigtsen @ 2019-07-29 14:12 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

>> `current-time' returns its data as (HIGH LOW USEC PSEC), but now that we
>> have bignum support, perhaps we don't need to do it this way.  What
>> about just having a field in decoded times that's the fraction of a
>> second?  So 34.5603 seconds would be represented as
>>
>> (34 ... 5603)
>
> `encode-time` uses a fraction-representation (NOM . DENOM), which is
> probably easier to manipulate.

You mean internally?  It doesn't seem to be mentioned in the doc string,
but it's an entire essay now...

> [ Note: I find `encode-time` vs `decode-time` very confusing.  ]

I think the pair makes sense on the same level as
{decode,encode}-coding-string does.  That is, encoded time is what Emacs
uses internally for most things, and decoded time is pretty close to
what you er find in nature.

Or at least it used to be; `endode-time' has gotten really complicated
lately, and the doc string doesn't seem to be correct, ether, for some
of the more odd things it apparently does.

(let ((time (current-time)))
  (equal time (encode-time time)))
=> t

(encode-time)
-> Debugger entered--Lisp error: (wrong-number-of-arguments encode-time 0)

But if we ignore all that, it's still conceptually sound.  :-)

---
encode-time is a built-in function in ‘src/timefns.c’.

(encode-time &optional TIME FORM &rest OBSOLESCENT-ARGUMENTS)

  Probably introduced at or before Emacs version 19.29.
  This function does not change global state, including the match data.

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 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.
---

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no



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

* Re: Support for sub-second time in decoded time
  2019-07-29  9:48 Support for sub-second time in decoded time Lars Ingebrigtsen
  2019-07-29 14:03 ` Stefan Monnier
@ 2019-07-29 14:23 ` Eli Zaretskii
  2019-07-29 14:59   ` Lars Ingebrigtsen
  2019-07-29 16:46 ` Paul Eggert
  2 siblings, 1 reply; 45+ messages in thread
From: Eli Zaretskii @ 2019-07-29 14:23 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: emacs-devel

> From: Lars Ingebrigtsen <larsi@gnus.org>
> Date: Mon, 29 Jul 2019 11:48:02 +0200
> 
> `current-time' returns its data as (HIGH LOW USEC PSEC), but now that we
> have bignum support, perhaps we don't need to do it this way.  What
> about just having a field in decoded times that's the fraction of a
> second?

That'd be backward-incompatible, no?



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

* Re: Support for sub-second time in decoded time
  2019-07-29 14:12   ` Lars Ingebrigtsen
@ 2019-07-29 14:43     ` Stefan Monnier
  2019-07-29 15:00       ` Lars Ingebrigtsen
  2019-07-29 16:08     ` encode-time vs decode-time Stefan Monnier
  1 sibling, 1 reply; 45+ messages in thread
From: Stefan Monnier @ 2019-07-29 14:43 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: emacs-devel

Lars Ingebrigtsen [2019-07-29 16:12:34] wrote:

> Stefan Monnier <monnier@iro.umontreal.ca> writes:
>
>>> `current-time' returns its data as (HIGH LOW USEC PSEC), but now that we
>>> have bignum support, perhaps we don't need to do it this way.  What
>>> about just having a field in decoded times that's the fraction of a
>>> second?  So 34.5603 seconds would be represented as
>>>
>>> (34 ... 5603)
>>
>> `encode-time` uses a fraction-representation (NOM . DENOM), which is
>> probably easier to manipulate.
>
> You mean internally?

No, as yet-another format.

> It doesn't seem to be mentioned in the doc string,
> but it's an entire essay now...

It's here:

    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 in ticks per second.  If FORM is ‘integer’, the time is


-- Stefan




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

* Re: Support for sub-second time in decoded time
  2019-07-29 14:23 ` Support for sub-second time in decoded time Eli Zaretskii
@ 2019-07-29 14:59   ` Lars Ingebrigtsen
  0 siblings, 0 replies; 45+ messages in thread
From: Lars Ingebrigtsen @ 2019-07-29 14:59 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> From: Lars Ingebrigtsen <larsi@gnus.org>
>> Date: Mon, 29 Jul 2019 11:48:02 +0200
>> 
>> `current-time' returns its data as (HIGH LOW USEC PSEC), but now that we
>> have bignum support, perhaps we don't need to do it this way.  What
>> about just having a field in decoded times that's the fraction of a
>> second?
>
> That'd be backward-incompatible, no?

It's an extra field in the structure returned from `decode-time'.  There
may be callers that rely on it being exactly 8 elements...  But I don't
think that's very likely?  Hm...  I guess I could see people saying
stuff like

(destructuring-bind (....) (decode-time)
  )

and that would break, but on the other hand, you don't see many people
using those constructs in Emacs Lisp code.

We could add an optional parameter to `decode-time'
INCLUDE-FRACTIONAL-SECONDS and only include the 9th element then.  But I
think we'd then basically would want to change all callers in Emacs to
include that parameter.

That's a safer option, though.

Either is fine with me.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no



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

* Re: Support for sub-second time in decoded time
  2019-07-29 14:43     ` Stefan Monnier
@ 2019-07-29 15:00       ` Lars Ingebrigtsen
  2019-07-29 17:50         ` Stefan Monnier
  0 siblings, 1 reply; 45+ messages in thread
From: Lars Ingebrigtsen @ 2019-07-29 15:00 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

>>> `encode-time` uses a fraction-representation (NOM . DENOM), which is
>>> probably easier to manipulate.
>>
>> You mean internally?
>
> No, as yet-another format.
>
>> It doesn't seem to be mentioned in the doc string,
>> but it's an entire essay now...
>
> It's here:
>
>     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 in ticks per second.  If FORM is ‘integer’, the time is

Hm, OK.  But I don't see how that relates to adding a new sub-second
field to the output value from `decode-time'.  :-)

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no



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

* encode-time vs decode-time
  2019-07-29 14:12   ` Lars Ingebrigtsen
  2019-07-29 14:43     ` Stefan Monnier
@ 2019-07-29 16:08     ` Stefan Monnier
  2019-07-30 10:32       ` Lars Ingebrigtsen
  1 sibling, 1 reply; 45+ messages in thread
From: Stefan Monnier @ 2019-07-29 16:08 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: emacs-devel

> I think the pair makes sense on the same level as
> {decode,encode}-coding-string does.  That is, encoded time is what Emacs
> uses internally for most things, and decoded time is pretty close to
> what you er find in nature.

I think the problem is one of presentation and vocabulary, indeed.

I think we should clarify:

- "time string", as returned by format-time-string and accepted
  by parse-time-string.

- "decoded time", i.e. a list of the form (SECOND MINUTE HOUR DAY MONTH YEAR IGNORED DST ZONE)
  I just pushed a patch which defines the `decoded-time` *type*, so we can
  refer to that precise name in docstrings.

- "time counted in seconds" for which we support several representations:
  a plain number, a list of integers (HIGH LOW USEC PSEC), a pair of
  integers (NUMERATOR . DENOMINATOR).

And part of the confusion for me is that `encode-time` not only encodes
time, but can also be used to convert between different representations
of "time counted in seconds" (in which case it's not the inverse of
decode-time).


        Stefan




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

* Re: Support for sub-second time in decoded time
  2019-07-29  9:48 Support for sub-second time in decoded time Lars Ingebrigtsen
  2019-07-29 14:03 ` Stefan Monnier
  2019-07-29 14:23 ` Support for sub-second time in decoded time Eli Zaretskii
@ 2019-07-29 16:46 ` Paul Eggert
  2019-07-30 11:43   ` Lars Ingebrigtsen
  2 siblings, 1 reply; 45+ messages in thread
From: Paul Eggert @ 2019-07-29 16:46 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: emacs-devel

On 7/29/19 4:48 AM, Lars Ingebrigtsen wrote:
> Currently, encoded time has support for picoseconds.  `decode-time' will
> throw away all sub-second time, which makes accurate round-tripping
> impossible.

No, it's quite possible. Say you want a resolution-R broken-down 
representation of the Lisp timestamp T. Then you can use (decode-time T) 
to get the broken-down time in seconds, and (mod (car (encode-time T R)) 
R) to get the subsecond part. Once you have that, you're off to the races.

Admittedly it is not that convenient. We could extend decode-time to 
accept an additional argument FORM that would let the caller specify the 
form of the returned value. decode-time could treat FORM much like 
encode-time does, and encode-time would be extended to grok the new 
forms so round-tripping would be simpler. This would be 
upward-compatible with the current behavior.

For example, under this proposal we'd have:

(setq R 1000000000)
(setq X (encode-time nil R))  =>  (1564418451413082782 . 1000000000)
(setq Y (decode-time X nil))  =>  ((51413082782 . 1000000000) 40 9 29 7 
2019 1 t -25200)
(equal X (encode-time Y R))  =>  t




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

* Re: Support for sub-second time in decoded time
  2019-07-29 15:00       ` Lars Ingebrigtsen
@ 2019-07-29 17:50         ` Stefan Monnier
  2019-07-30 11:33           ` Lars Ingebrigtsen
  0 siblings, 1 reply; 45+ messages in thread
From: Stefan Monnier @ 2019-07-29 17:50 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: emacs-devel

> Hm, OK.  But I don't see how that relates to adding a new sub-second
> field to the output value from `decode-time'.  :-)

I guess nothing, except to suggest to represent the resolution
explicitly (i.e. to avoid the ambiguity of (34 ... 5603) which could
be 34.5603 or 34.05603 ...).

        Stefan




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

* Re: encode-time vs decode-time
  2019-07-29 16:08     ` encode-time vs decode-time Stefan Monnier
@ 2019-07-30 10:32       ` Lars Ingebrigtsen
  2019-07-30 11:34         ` Andy Moreton
  0 siblings, 1 reply; 45+ messages in thread
From: Lars Ingebrigtsen @ 2019-07-30 10:32 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

> I think we should clarify:
>
> - "time string", as returned by format-time-string and accepted
>   by parse-time-string.
>
> - "decoded time", i.e. a list of the form (SECOND MINUTE HOUR DAY MONTH YEAR IGNORED DST ZONE)
>   I just pushed a patch which defines the `decoded-time` *type*, so we can
>   refer to that precise name in docstrings.

Makes sense.

> - "time counted in seconds" for which we support several representations:
>   a plain number, a list of integers (HIGH LOW USEC PSEC), a pair of
>   integers (NUMERATOR . DENOMINATOR).
>
> And part of the confusion for me is that `encode-time` not only encodes
> time, but can also be used to convert between different representations
> of "time counted in seconds" (in which case it's not the inverse of
> decode-time).

Yes, that's something that I think was added by Paul late last year, and
which I think was a mistake, because it makes things conceptually
confusing.

I think the stuff to convert between the various "time counted in
seconds" stuff should be a separate function, because it has nothing to
do with "decoded time".

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no



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

* Re: Support for sub-second time in decoded time
  2019-07-29 17:50         ` Stefan Monnier
@ 2019-07-30 11:33           ` Lars Ingebrigtsen
  0 siblings, 0 replies; 45+ messages in thread
From: Lars Ingebrigtsen @ 2019-07-30 11:33 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

>> Hm, OK.  But I don't see how that relates to adding a new sub-second
>> field to the output value from `decode-time'.  :-)
>
> I guess nothing, except to suggest to represent the resolution
> explicitly (i.e. to avoid the ambiguity of (34 ... 5603) which could
> be 34.5603 or 34.05603 ...).

Right.  That makes more sense than "the bit that would come after the
decimal point if this were floating point, which it isn't", which would
be rather awkward to work with indeed.  :-/

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no



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

* Re: encode-time vs decode-time
  2019-07-30 10:32       ` Lars Ingebrigtsen
@ 2019-07-30 11:34         ` Andy Moreton
  2019-07-30 11:37           ` Lars Ingebrigtsen
  0 siblings, 1 reply; 45+ messages in thread
From: Andy Moreton @ 2019-07-30 11:34 UTC (permalink / raw)
  To: emacs-devel

On Tue 30 Jul 2019, Lars Ingebrigtsen wrote:

> Stefan Monnier <monnier@iro.umontreal.ca> writes:
>
>> I think we should clarify:
>>
>> - "time string", as returned by format-time-string and accepted
>>   by parse-time-string.
>>
>> - "decoded time", i.e. a list of the form (SECOND MINUTE HOUR DAY MONTH YEAR IGNORED DST ZONE)
>>   I just pushed a patch which defines the `decoded-time` *type*, so we can
>>   refer to that precise name in docstrings.
>
> Makes sense.
>
>> - "time counted in seconds" for which we support several representations:
>>   a plain number, a list of integers (HIGH LOW USEC PSEC), a pair of
>>   integers (NUMERATOR . DENOMINATOR).
>>
>> And part of the confusion for me is that `encode-time` not only encodes
>> time, but can also be used to convert between different representations
>> of "time counted in seconds" (in which case it's not the inverse of
>> decode-time).
>
> Yes, that's something that I think was added by Paul late last year, and
> which I think was a mistake, because it makes things conceptually
> confusing.
>
> I think the stuff to convert between the various "time counted in
> seconds" stuff should be a separate function, because it has nothing to
> do with "decoded time".

It seems that the naming of these functions is unhelpful. They should
clearly distinguish between timestamps (absolute time/date) and
durations (relative intervals) and be named as such.

    AndyM




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

* Re: encode-time vs decode-time
  2019-07-30 11:34         ` Andy Moreton
@ 2019-07-30 11:37           ` Lars Ingebrigtsen
  2019-07-30 17:54             ` Paul Eggert
  0 siblings, 1 reply; 45+ messages in thread
From: Lars Ingebrigtsen @ 2019-07-30 11:37 UTC (permalink / raw)
  To: Andy Moreton; +Cc: emacs-devel

Andy Moreton <andrewjmoreton@gmail.com> writes:

> It seems that the naming of these functions is unhelpful. They should
> clearly distinguish between timestamps (absolute time/date) and
> durations (relative intervals) and be named as such.

Both encoded time and decoded time are what you call "absolute
time/date"; just represented differently.  (The "second" representation
uses 1970 as the starting point.)

In addition, a number of seconds can also be used as a duration, but
that's a different matter.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no



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

* Re: Support for sub-second time in decoded time
  2019-07-29 16:46 ` Paul Eggert
@ 2019-07-30 11:43   ` Lars Ingebrigtsen
  0 siblings, 0 replies; 45+ messages in thread
From: Lars Ingebrigtsen @ 2019-07-30 11:43 UTC (permalink / raw)
  To: Paul Eggert; +Cc: emacs-devel

Paul Eggert <eggert@cs.ucla.edu> writes:

> On 7/29/19 4:48 AM, Lars Ingebrigtsen wrote:
>> Currently, encoded time has support for picoseconds.  `decode-time' will
>> throw away all sub-second time, which makes accurate round-tripping
>> impossible.
>
> No, it's quite possible.

I meant "round-tripping via the decoded time structure".

> Admittedly it is not that convenient. We could extend decode-time to
> accept an additional argument FORM that would let the caller specify
> the form of the returned value. decode-time could treat FORM much like
> encode-time does, and encode-time would be extended to grok the new
> forms so round-tripping would be simpler. This would be
> upward-compatible with the current behavior.
>
> For example, under this proposal we'd have:
>
> (setq R 1000000000)
> (setq X (encode-time nil R))  =>  (1564418451413082782 . 1000000000)
> (setq Y (decode-time X nil))  =>  ((51413082782 . 1000000000) 40 9 29
> 7 2019 1 t -25200)
> (equal X (encode-time Y R))  =>  t

As I said in a previous message -- this is very confusing indeed, and I
think it should be reworked in a different way before Emacs 27 is
released and it's too late to get rid of this confusion.

But adding an optional parameter to `decode-time' to also make it
include sub-second time in the decoded time structure it returns (also
as previously discussed) may be the right thing to do.  However, I've
grepped through the sources now for usages of decoded time, and it kinda
looks to me like adding a ninth slot in decoded time structures would
probably not break anything.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no



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

* Re: encode-time vs decode-time
  2019-07-30 11:37           ` Lars Ingebrigtsen
@ 2019-07-30 17:54             ` Paul Eggert
  2019-07-30 22:50               ` Paul Eggert
  2019-07-31 19:03               ` Lars Ingebrigtsen
  0 siblings, 2 replies; 45+ messages in thread
From: Paul Eggert @ 2019-07-30 17:54 UTC (permalink / raw)
  To: Lars Ingebrigtsen, Andy Moreton; +Cc: emacs-devel

On 7/30/19 6:37 AM, Lars Ingebrigtsen wrote:
> a number of seconds can also be used as a duration, but
> that's a different matter.

The issues are not that independent. Just as a Lisp timestamp like 
1564500522 can be treated as absolute (2019-07-30 15:28:42 UTC) or 
relative (1564500522 seconds duration), a calendrical value like (42 28 
15 30 7 2019 2 nil 0) can also be treated as an absolute time (the same 
absolute time, in this example) or as a relative time (using calendrical 
arithmetic).

> adding an optional parameter to `decode-time' to also make it
> include sub-second time in the decoded time structure it returns (also
> as previously discussed) may be the right thing to do.  However, I've
> grepped through the sources now for usages of decoded time, and it kinda
> looks to me like adding a ninth slot in decoded time structures would
> probably not break anything.
Any ninth slot should be a numerator-denominator pair if it's nonzero, 
to avoid losing precision. Something like this:

(decode-time '(1564504076643563153 . 1000000000) t) =>

(56 27 16 30 7 2019 2 nil 0 (643563153 . 1000000000))

> this is very confusing indeed
To some extent the confusion is inherent: there are a lot of useful ways 
to represent and manipulate times, and people unfamiliar with the issues 
will find this complexity confusing. That being said, it would be good 
to simplify the Emacs API for time without significantly hurting 
functionality or compatibility.

So, how about this idea. First we go along the lines of your suggestion, 
and change decode-time to return a ninth slot with a 
numerator-denominator pair that preserves all the precision of its 
argument. Second, we change (encode-time TIME t) so that it also 
preserves all the precision of its argument. This will cause 
(encode-time (decode-time TIME) t) to return a value equivalent to TIME, 
which will simplify roundtripping.




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

* Re: encode-time vs decode-time
  2019-07-30 17:54             ` Paul Eggert
@ 2019-07-30 22:50               ` Paul Eggert
  2019-07-31 19:03               ` Lars Ingebrigtsen
  1 sibling, 0 replies; 45+ messages in thread
From: Paul Eggert @ 2019-07-30 22:50 UTC (permalink / raw)
  To: Lars Ingebrigtsen, Andy Moreton; +Cc: emacs-devel

On 7/30/19 12:54 PM, I wrote:
>
> So, how about this idea. First we go along the lines of your 
> suggestion, and change decode-time to return a ninth slot with a 
> numerator-denominator pair that preserves all the precision of its 
> argument. Second, we change (encode-time TIME t) so that it also 
> preserves all the precision of its argument. This will cause 
> (encode-time (decode-time TIME) t) to return a value equivalent to 
> TIME, which will simplify roundtripping.

Come to think of it, we would also need to change this obsolescent API 
for encode-time:

> 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.

Several places in the code use this obsolete API and do (apply 
#'encode-time FOO) where FOO is either the munged result of an earlier 
decode-time or is created from scratch. So, if we made the change as 
described above, we'd also need to change the obsolescent API to be 
something like this:

"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 between 7 and 9 
arguments the *last* argument is used as ZONE and if there are 10 
arguments the 9th is used as ZONE and the 10th as a fractional-second 
argument (TICKS . HZ) and in either case any other extra arguments are 
ignored, so that (apply #'encode-time (decode-time ...)) works. In this 
obsolete convention, the default  ZONE is nil and the default fractional 
second is zero."

Good thing that API is "obsolescent"....




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

* Re: encode-time vs decode-time
  2019-07-30 17:54             ` Paul Eggert
  2019-07-30 22:50               ` Paul Eggert
@ 2019-07-31 19:03               ` Lars Ingebrigtsen
  2019-07-31 19:31                 ` Stefan Monnier
  2019-08-06  1:48                 ` Paul Eggert
  1 sibling, 2 replies; 45+ messages in thread
From: Lars Ingebrigtsen @ 2019-07-31 19:03 UTC (permalink / raw)
  To: Paul Eggert; +Cc: Andy Moreton, emacs-devel

Paul Eggert <eggert@cs.ucla.edu> writes:

>> this is very confusing indeed
> To some extent the confusion is inherent: there are a lot of useful
> ways to represent and manipulate times, and people unfamiliar with the
> issues will find this complexity confusing. That being said, it would
> be good to simplify the Emacs API for time without significantly
> hurting functionality or compatibility.
>
> So, how about this idea. First we go along the lines of your
> suggestion, and change decode-time to return a ninth slot with a
> numerator-denominator pair that preserves all the precision of its
> argument. Second, we change (encode-time TIME t) so that it also
> preserves all the precision of its argument. This will cause
> (encode-time (decode-time TIME) t) to return a value equivalent to
> TIME, which will simplify roundtripping.

I think this sounds workable, but I think it would be even better to
introduce some new functions instead of overloading our poor encode-time
function.

When you did the major sanitation in this area last year (which was
overall a very good thing), you did stuff like 

-	(now (string-to-number (format-time-string "%s")))
+	(now (encode-time nil 'integer))

and

-	(now (float-time))
+	(now (encode-time nil 'integer))

and many similar transforms.  The new code is definitely better than the
old code, and it's not more cryptic than it used to be, really.

However, I think it would have been even better to introduce a new
function to enable people who are unfamiliar with how this all works to
just read the code and understand what it's doing.

So I think the things up above there should really be calls to a new
function `get-current-time', that returns the same thing as
`(encode-time nil 'integer)'.  It should default to `integer', but can
have an optional argument that says what form of "internal second time
format" to return (i.e., `list', `t').

And then, finally, I think there should be a function to convert between
these "internal second time" formats.  Like...  `convert-time-format',
which would be like `(encode-time time 'list)' (etc).

And then leave encode-time/decode-time a simple pair that would do
nothing but convert between "internal second time format" and "decoded
time format".

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no



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

* Re: encode-time vs decode-time
  2019-07-31 19:03               ` Lars Ingebrigtsen
@ 2019-07-31 19:31                 ` Stefan Monnier
  2019-08-06  1:48                 ` Paul Eggert
  1 sibling, 0 replies; 45+ messages in thread
From: Stefan Monnier @ 2019-07-31 19:31 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: Paul Eggert, Andy Moreton, emacs-devel

> So I think the things up above there should really be calls to a new
> function `get-current-time', that returns the same thing as
> `(encode-time nil 'integer)'.  It should default to `integer', but can
> have an optional argument that says what form of "internal second time
> format" to return (i.e., `list', `t').

I tend to agree.  Also I think we should try and standardize the
representation of "internal second time" (presumably using the
rational (NUM . DENOM) representation).  So I think `get-current-time`
should just always return this representation, and those who need
another will just have to call `convert-time-format` explicitly.


        Stefan




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

* Re: encode-time vs decode-time
  2019-07-31 19:03               ` Lars Ingebrigtsen
  2019-07-31 19:31                 ` Stefan Monnier
@ 2019-08-06  1:48                 ` Paul Eggert
  2019-08-06 14:21                   ` Eli Zaretskii
  2019-08-07 11:41                   ` Lars Ingebrigtsen
  1 sibling, 2 replies; 45+ messages in thread
From: Paul Eggert @ 2019-08-06  1:48 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: Andy Moreton, emacs-devel

Lars Ingebrigtsen wrote:
> I think there should be a function to convert between
> these "internal second time" formats.  Like...  `convert-time-format',
> which would be like `(encode-time time 'list)' (etc).
> 
> And then leave encode-time/decode-time a simple pair that would do
> nothing but convert between "internal second time format" and "decoded
> time format".

OK, after some cogitation I installed something along those lines into master. I 
called the new function 'time-convert' by analogy with the existing functions 
time-add etc. So now, as you suggested, encode-time has reverted to its old role 
of converting from decoded timestamps to Lisp timestamps and its API is now simpler.

I didn't follow your suggestion of adding another function that acts like 
current-time except it returns other Lisp timestamp formats, because 
(time-convert nil FORMAT) does that.

I didn't follow Stefan's suggestion of adding another function that acts like 
current-time except it returns the (TICKS . HZ) format, because current-time is 
already planned do exactly that after the next release (when we will default 
CURRENT_TIME_LIST to false) and it'll be simpler if we have one function rather 
than two that do the same thing.



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

* Re: encode-time vs decode-time
  2019-08-06  1:48                 ` Paul Eggert
@ 2019-08-06 14:21                   ` Eli Zaretskii
  2019-08-06 15:59                     ` Paul Eggert
  2019-08-07 11:41                   ` Lars Ingebrigtsen
  1 sibling, 1 reply; 45+ messages in thread
From: Eli Zaretskii @ 2019-08-06 14:21 UTC (permalink / raw)
  To: Paul Eggert; +Cc: larsi, andrewjmoreton, emacs-devel

> From: Paul Eggert <eggert@cs.ucla.edu>
> Date: Mon, 5 Aug 2019 18:48:21 -0700
> Cc: Andy Moreton <andrewjmoreton@gmail.com>, emacs-devel@gnu.org
> 
> OK, after some cogitation I installed something along those lines into master. I 
> called the new function 'time-convert' by analogy with the existing functions 
> time-add etc. So now, as you suggested, encode-time has reverted to its old role 
> of converting from decoded timestamps to Lisp timestamps and its API is now simpler.

Thanks, but judging by the changes in Lisp files all over, it sounds
like this is a backward-incompatible change?  That'd be unfortunate.



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

* Re: encode-time vs decode-time
  2019-08-06 14:21                   ` Eli Zaretskii
@ 2019-08-06 15:59                     ` Paul Eggert
  2019-08-06 18:23                       ` Eli Zaretskii
  0 siblings, 1 reply; 45+ messages in thread
From: Paul Eggert @ 2019-08-06 15:59 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, andrewjmoreton, emacs-devel

>> So now, as you suggested, encode-time has reverted to its old role 
>> of converting from decoded timestamps to Lisp timestamps and its API is now simpler.
> 
> Thanks, but judging by the changes in Lisp files all over, it sounds
> like this is a backward-incompatible change?

It's not as scary as it might first appear. The changes to the lisp/* files that 
work around backward-incompatibility issues (by replacing (encode-time TIME 
FORM) with (time-convert TIME FORM)) are needed only to fix Emacs internals code 
that had already been changed to rely on Emacs 27 encode-time. Since Emacs 26 
does not support (encode TIME FORM), users will not have to worry about making 
these kinds of changes to their own code.

They only significant backward-compatibility issue I see is in the 2nd patch 
("decode-time now returns subsec too"), which affects any user code that 
requires (= 9 (length (decode-time))). I originally proposed extending 
decode-time's API with a FORM option 
<https://lists.gnu.org/r/emacs-devel/2019-07/msg00750.html> that would cause 
decode-time to continue to behave as before unlless the given the new FORM 
argument; this would default to current behavior and so would avoid the 
backward-compatibility issue. However, Lars inspected uses of decode-time 
<https://lists.gnu.org/r/emacs-devel/2019-07/msg00772.html> and found that they 
invariably did something like (nth N (decode-time...)) or (apply #'encode-time 0 
0 0 (nthcdr 3 (decode-time))). These uses will continue to work, so Lars 
convinced me that his proposal to return a 10-element list was better. If you 
prefer the more-backward-compatible approach I could look into redoing the time 
code that way, though I expect it'll be a bit more hassle to document it and to 
support it internally.



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

* Re: encode-time vs decode-time
  2019-08-06 15:59                     ` Paul Eggert
@ 2019-08-06 18:23                       ` Eli Zaretskii
  2019-08-07  1:02                         ` Paul Eggert
  0 siblings, 1 reply; 45+ messages in thread
From: Eli Zaretskii @ 2019-08-06 18:23 UTC (permalink / raw)
  To: Paul Eggert; +Cc: larsi, andrewjmoreton, emacs-devel

> Cc: larsi@gnus.org, andrewjmoreton@gmail.com, emacs-devel@gnu.org
> From: Paul Eggert <eggert@cs.ucla.edu>
> Date: Tue, 6 Aug 2019 08:59:33 -0700
> 
> They only significant backward-compatibility issue I see is in the 2nd patch 
> ("decode-time now returns subsec too"), which affects any user code that 
> requires (= 9 (length (decode-time))). I originally proposed extending 
> decode-time's API with a FORM option 
> <https://lists.gnu.org/r/emacs-devel/2019-07/msg00750.html> that would cause 
> decode-time to continue to behave as before unlless the given the new FORM 
> argument; this would default to current behavior and so would avoid the 
> backward-compatibility issue. However, Lars inspected uses of decode-time 
> <https://lists.gnu.org/r/emacs-devel/2019-07/msg00772.html> and found that they 
> invariably did something like (nth N (decode-time...)) or (apply #'encode-time 0 
> 0 0 (nthcdr 3 (decode-time))).

Was this inspection done on Emacs' own code, or also outside Emacs?



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

* Re: encode-time vs decode-time
  2019-08-06 18:23                       ` Eli Zaretskii
@ 2019-08-07  1:02                         ` Paul Eggert
  2019-08-07  2:41                           ` Stefan Monnier
                                             ` (2 more replies)
  0 siblings, 3 replies; 45+ messages in thread
From: Paul Eggert @ 2019-08-07  1:02 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, andrewjmoreton, emacs-devel

Eli Zaretskii wrote:
> Was this inspection done on Emacs' own code, or also outside Emacs?

Lars didn't say. I assume he meant Emacs's own code.

Although almost all uses of decode-time will be unaffected by the change, 
there's little doubt that some user code somewhere will break because it 
(mistakenly) assumes that decode-time's result format will never be extended. If 
this is a real concern, we can go about the change in some other way.

One alternative would be to leave decode-time's API unchanged from Emacs 26 and 
put the new functionality into a new function, say "time-calendrical". While 
we're at it, we could call the data structure that the new function returns a 
"calendrical timestamp" instead of a "decoded timestamp", and rename the 
recently-added functions make-decoded-time, decoded-time-hour, decoded-time-year 
etc. to make-calendrical-time, calendrical-hour, calendrical-year, etc. This 
would reduce confusion, as it is harder to remember what a "decoded time" is 
than to remember what a "calendrical time" is, at least for me. Also, we could 
document that the calendrical data structure may change in future versions, and 
that programs should use the new functions rather than inspect the raw data 
structure.

Using the word "calendrical" in the new names would help avoid confusion with 
existing functions, which don't use that word in their names.



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

* Re: encode-time vs decode-time
  2019-08-07  1:02                         ` Paul Eggert
@ 2019-08-07  2:41                           ` Stefan Monnier
  2019-08-07 14:47                             ` Eli Zaretskii
  2019-08-17  6:47                             ` Paul Eggert
  2019-08-07 11:33                           ` Lars Ingebrigtsen
  2019-08-07 14:44                           ` Eli Zaretskii
  2 siblings, 2 replies; 45+ messages in thread
From: Stefan Monnier @ 2019-08-07  2:41 UTC (permalink / raw)
  To: Paul Eggert; +Cc: Eli Zaretskii, andrewjmoreton, larsi, emacs-devel

> Although almost all uses of decode-time will be unaffected by the change,
> there's little doubt that some user code somewhere will break because it
> (mistakenly) assumes that decode-time's result format will never be
> extended. If this is a real concern, we can go about the change in some
> other way.

Actually, while I agree that it's possible it will break something
somewhere, it's rather unlikely.


        Stefan




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

* Re: encode-time vs decode-time
  2019-08-07  1:02                         ` Paul Eggert
  2019-08-07  2:41                           ` Stefan Monnier
@ 2019-08-07 11:33                           ` Lars Ingebrigtsen
  2019-08-17  7:54                             ` Paul Eggert
  2019-08-07 14:44                           ` Eli Zaretskii
  2 siblings, 1 reply; 45+ messages in thread
From: Lars Ingebrigtsen @ 2019-08-07 11:33 UTC (permalink / raw)
  To: Paul Eggert; +Cc: Eli Zaretskii, andrewjmoreton, emacs-devel

Paul Eggert <eggert@cs.ucla.edu> writes:

> Eli Zaretskii wrote:
>> Was this inspection done on Emacs' own code, or also outside Emacs?
>
> Lars didn't say. I assume he meant Emacs's own code.

Yup.

> One alternative would be to leave decode-time's API unchanged from
> Emacs 26 and put the new functionality into a new function, say
> "time-calendrical". While we're at it, we could call the data
> structure that the new function returns a "calendrical timestamp"
> instead of a "decoded timestamp", and rename the recently-added
> functions make-decoded-time, decoded-time-hour, decoded-time-year
> etc. to make-calendrical-time, calendrical-hour, calendrical-year,
> etc.

I agree; calling these things encoded/decoded time isn't very clear
terminology.  "calendrical" is a mouthful, though.  And "calendar" would
imply that it belongs in the calendar package, perhaps...

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no



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

* Re: encode-time vs decode-time
  2019-08-06  1:48                 ` Paul Eggert
  2019-08-06 14:21                   ` Eli Zaretskii
@ 2019-08-07 11:41                   ` Lars Ingebrigtsen
  2019-08-17  9:25                     ` Paul Eggert
  1 sibling, 1 reply; 45+ messages in thread
From: Lars Ingebrigtsen @ 2019-08-07 11:41 UTC (permalink / raw)
  To: Paul Eggert; +Cc: Andy Moreton, emacs-devel

Paul Eggert <eggert@cs.ucla.edu> writes:

> OK, after some cogitation I installed something along those lines into
> master. I called the new function 'time-convert' by analogy with the
> existing functions time-add etc. So now, as you suggested, encode-time
> has reverted to its old role of converting from decoded timestamps to
> Lisp timestamps and its API is now simpler.

Great!  Thanks for doing this and adding the sub-second time to the
decoded time structure -- I'll be implementing the remaining bits of the
ISO 8601 standard in `iso8601-parse' (the ones that deal with fractional
seconds) so that we'll have a completely compliant parser.

> I didn't follow Stefan's suggestion of adding another function that
> acts like current-time except it returns the (TICKS . HZ) format,
> because current-time is already planned do exactly that after the next
> release (when we will default CURRENT_TIME_LIST to false) and it'll be
> simpler if we have one function rather than two that do the same
> thing.

However, I think this sounds overly ambitious.  I think it's likely that
there's tons of out-of-tree code that assumes that `current-time' always
returns a list of time values, because it's been that way since
basically forever.  It's been extended, but the first two elements have
always been a representation of seconds.

This is why I thought it would be good to introduce a new function, say
`get-current-time', that could have our new signature.  We'd then
deprecate `current-time' (but probably never actually remove it since
there's so much code out there in the wild that uses it) and rewrite all
calls in-tree to use this new function.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no



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

* Re: encode-time vs decode-time
  2019-08-07  1:02                         ` Paul Eggert
  2019-08-07  2:41                           ` Stefan Monnier
  2019-08-07 11:33                           ` Lars Ingebrigtsen
@ 2019-08-07 14:44                           ` Eli Zaretskii
  2 siblings, 0 replies; 45+ messages in thread
From: Eli Zaretskii @ 2019-08-07 14:44 UTC (permalink / raw)
  To: Paul Eggert; +Cc: larsi, andrewjmoreton, emacs-devel

> Cc: larsi@gnus.org, andrewjmoreton@gmail.com, emacs-devel@gnu.org
> From: Paul Eggert <eggert@cs.ucla.edu>
> Date: Tue, 6 Aug 2019 18:02:30 -0700
> 
> Although almost all uses of decode-time will be unaffected by the change, 
> there's little doubt that some user code somewhere will break because it 
> (mistakenly) assumes that decode-time's result format will never be extended. If 
> this is a real concern, we can go about the change in some other way.
> 
> One alternative would be to leave decode-time's API unchanged from Emacs 26 and 
> put the new functionality into a new function, say "time-calendrical". While 
> we're at it, we could call the data structure that the new function returns a 
> "calendrical timestamp" instead of a "decoded timestamp", and rename the 
> recently-added functions make-decoded-time, decoded-time-hour, decoded-time-year 
> etc. to make-calendrical-time, calendrical-hour, calendrical-year, etc. This 
> would reduce confusion, as it is harder to remember what a "decoded time" is 
> than to remember what a "calendrical time" is, at least for me. Also, we could 
> document that the calendrical data structure may change in future versions, and 
> that programs should use the new functions rather than inspect the raw data 
> structure.

Another alternative is to make the SECONDS member be a float, then we
could return the extra precision there.  Would this be a better way to
keep backward compatibility?



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

* Re: encode-time vs decode-time
  2019-08-07  2:41                           ` Stefan Monnier
@ 2019-08-07 14:47                             ` Eli Zaretskii
  2019-08-11 23:39                               ` Lars Ingebrigtsen
  2019-08-17  6:47                             ` Paul Eggert
  1 sibling, 1 reply; 45+ messages in thread
From: Eli Zaretskii @ 2019-08-07 14:47 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: larsi, eggert, andrewjmoreton, emacs-devel

> From: Stefan Monnier <monnier@iro.umontreal.ca>
> Cc: Eli Zaretskii <eliz@gnu.org>,  larsi@gnus.org,  andrewjmoreton@gmail.com,  emacs-devel@gnu.org
> Date: Tue, 06 Aug 2019 22:41:36 -0400
> 
> Actually, while I agree that it's possible it will break something
> somewhere, it's rather unlikely.

I think our record of making such predictions is rather poor.

And anyway, suppose someone comes up with complaints about this
change: what exactly do we say in response?  The reason for the change
is quite weak: it's basically an enhancement required by somewhat rare
niche use cases.  How can we justify breaking someone's code on such
weak grounds?



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

* Re: encode-time vs decode-time
  2019-08-07 14:47                             ` Eli Zaretskii
@ 2019-08-11 23:39                               ` Lars Ingebrigtsen
  0 siblings, 0 replies; 45+ messages in thread
From: Lars Ingebrigtsen @ 2019-08-11 23:39 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: eggert, andrewjmoreton, Stefan Monnier, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> Actually, while I agree that it's possible it will break something
>> somewhere, it's rather unlikely.
>
> I think our record of making such predictions is rather poor.
>
> And anyway, suppose someone comes up with complaints about this
> change: what exactly do we say in response?  The reason for the change
> is quite weak: it's basically an enhancement required by somewhat rare
> niche use cases.  How can we justify breaking someone's code on such
> weak grounds?

Having sub-second time resolution in calendar dates doesn't seem like a
minor thing to me.  I think if you say "now Emacs is ISO8601 compliant",
then it's all the justification anybody could possibly need.

I also think this is probably less likely to break anybody's code than
when Emacs changed the output from `current-time' from being a list of
two integers to four integers.

The only possible real world problem I can see being slightly likely is
somebody writing the output of `decode-time' to a file, and then reading
it back in again.  If the writer is a new Emacs, and the reader is an
old Emacs (that calls `encode-time' on the data), then things will
probably break.

But I don't know of anybody doing stuff like that.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no



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

* Re: encode-time vs decode-time
  2019-08-07  2:41                           ` Stefan Monnier
  2019-08-07 14:47                             ` Eli Zaretskii
@ 2019-08-17  6:47                             ` Paul Eggert
  1 sibling, 0 replies; 45+ messages in thread
From: Paul Eggert @ 2019-08-17  6:47 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Eli Zaretskii, andrewjmoreton, larsi, emacs-devel

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

>> there's little doubt that some user code somewhere will break because it
>> (mistakenly) assumes that decode-time's result format will never be
>> extended. If this is a real concern, we can go about the change in some
>> other way.
> 
> Actually, while I agree that it's possible it will break something
> somewhere, it's rather unlikely.

Although I initially thought the same way (that's why I installed the patch that 
altered decode-time), I may have been a bit hasty. I searched for "decode-time" 
in all public GitHub source code files with an .el extension, and looked just in 
the first page of results (10 hits) and found one hit that would have a problem:

https://github.com/tjim/nevermore/blob/5a3f29174b3a4b2b2e7a700a862f3b16a942687e/nm-dateparse.el

It contains several instances of code like this:

(defun nm-next-month (&optional dtime)
   "One month from now, or specified dtime"
   (pcase (or dtime (decode-time))
     (`(,SECONDS ,MINUTES ,HOUR ,DAY ,MONTH ,YEAR ,DOW ,DST ,ZONE)
      (nm-noon
       (if (eq MONTH 12)
           `(,SECONDS ,MINUTES ,HOUR ,DAY ,1 ,(1+ YEAR) ,DOW ,DST ,ZONE)
         `(,SECONDS ,MINUTES ,HOUR ,DAY ,(1+ MONTH) ,YEAR ,DOW ,DST ,ZONE))))))

which won't work if decode-time returns a 10-element list.

Although not a systematic study, this example suggests that we might want to be 
cautious about changing decode-time to return a 10-element list.

With that in mind, I installed the attached patch, which causes decode-time to 
behave at it does in Emacs 26 unless you pass it a new optional argument. And 
even with the new argument, it still returns a 9-element list so the above code 
will still work.

Eli Zaretskii wrote:

> Another alternative is to make the SECONDS member be a float, then we
> could return the extra precision there.

Floats suffer from rounding confusion, and won't suffice for 
attosecond-precision timestamps - admittedly not something one runs into every 
day, but they are needed for some physics applications. The experience that I've 
had with floating-point timestamps over the years has been negative, for the 
usual reasons. So the patch I installed uses the (TICKS . HZ) form that can 
handle attoseconds - indeed, it can even handle zeptoseconds, which were first 
needed by experimental physicists in 2016, according to Jason Daley 
<https://www.smithsonianmag.com/smart-news/physicists-record-smallest-slice-time-yet-180961085/>.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-More-compatible-subsecond-calendrical-timestamps.patch --]
[-- Type: text/x-patch; name="0001-More-compatible-subsecond-calendrical-timestamps.patch", Size: 60524 bytes --]

From 37257d6acadff17bd1e52cfa460950bcb684c5c3 Mon Sep 17 00:00:00 2001
From: Paul Eggert <eggert@cs.ucla.edu>
Date: Fri, 16 Aug 2019 22:09:04 -0700
Subject: [PATCH] More-compatible subsecond calendrical timestamps
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

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.
---
 doc/lispref/os.texi                    |  65 +++++----
 doc/misc/emacs-mime.texi               |   2 +-
 etc/NEWS                               |  22 ++-
 lisp/calendar/icalendar.el             |   2 +-
 lisp/calendar/iso8601.el               |  14 +-
 lisp/calendar/parse-time.el            |   8 +-
 lisp/calendar/time-date.el             |  75 ++++------
 lisp/net/soap-client.el                |   4 +-
 lisp/org/org.el                        |   4 +-
 lisp/simple.el                         |   8 +-
 src/timefns.c                          | 178 +++++++++++++----------
 test/lisp/calendar/icalendar-tests.el  |   8 +-
 test/lisp/calendar/iso8601-tests.el    | 190 ++++++++++++-------------
 test/lisp/calendar/parse-time-tests.el |  18 +--
 test/src/timefns-tests.el              |  46 +++---
 15 files changed, 337 insertions(+), 307 deletions(-)

diff --git a/doc/lispref/os.texi b/doc/lispref/os.texi
index 70ae39e6ab..49c07380c5 100644
--- a/doc/lispref/os.texi
+++ b/doc/lispref/os.texi
@@ -1478,23 +1478,23 @@ Time Conversion
 @end example
 @end defun
 
-@defun decode-time &optional time zone
+@defun decode-time &optional time zone form
 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 ten elements, as follows:
+The @var{form} argument controls the form of the returned
+@var{seconds} element, as described below.
+The return value is a list of nine elements, as follows:
 
 @example
-(@var{seconds} @var{minutes} @var{hour} @var{day} @var{month} @var{year}
- @var{dow} @var{dst} @var{utcoff} @var{subsec})
+(@var{seconds} @var{minutes} @var{hour} @var{day} @var{month} @var{year} @var{dow} @var{dst} @var{utcoff})
 @end example
 
 Here is what the elements mean:
 
 @table @var
 @item seconds
-The number of seconds past the minute, as an integer between 0 and 59.
-On some operating systems, this is 60 for leap seconds.
+The number of seconds past the minute, with form described below.
 @item minutes
 The number of minutes past the hour, as an integer between 0 and 59.
 @item hour
@@ -1514,22 +1514,33 @@ Time Conversion
 @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
 
+The @var{seconds} element is a Lisp timestamp that is nonnegative and
+less than 61; it is less than 60 except during positive leap seconds
+(assuming the operating system supports leap seconds).  If the
+optional @var{form} argument is @code{t}, @var{seconds} uses the same
+precision as @var{time}; if @var{form} is @code{integer},
+@var{seconds} is truncated to an integer.  For example, if @var{time}
+is the timestamp @code{(1566009571321 . 1000)}, which represents
+2019-08-17 02:39:31.321 UTC on typical systems that lack leap seconds,
+then @code{(decode-time @var{time} t t)} returns @code{((31321 . 1000)
+39 2 17 8 2019 6 nil 0)}, whereas @code{(decode-time @var{time} t
+'integer)} returns @code{(31 39 2 17 8 2019 6 nil 0)}.  If @var{form}
+is omitted or @code{nil}, it currently defaults to @code{integer} but
+this default may change in future Emacs releases, so callers requiring
+a particular form should specify @var{form}.
+
 @strong{Common Lisp Note:} Common Lisp has different meanings for
-@var{dow} and @var{utcoff}, and lacks @var{subsec}.
+@var{dow} and @var{utcoff}, and its @var{second} is an integer between
+0 and 59 inclusive.
 
 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},
-@code{decoded-time-zone} and @code{decoded-time-subsec}
-accessors can be used.
+@code{decoded-time-weekday}, @code{decoded-time-dst} and
+@code{decoded-time-zone} accessors can be used.
 
 For instance, to increase the year in a decoded time, you could say:
 
@@ -1551,7 +1562,7 @@ Time Conversion
 could say:
 
 @lisp
-(let ((time (decode-time))
+(let ((time (decode-time nil nil t))
       (delta (make-decoded-time :month 2)))
    (encode-time (decoded-time-add time delta)))
 @end lisp
@@ -1585,22 +1596,21 @@ Time Conversion
 
 Ordinarily the first argument is a list
 @code{(@var{second} @var{minute} @var{hour} @var{day} @var{month}
-@var{year} @var{ignored} @var{dst} @var{zone} @var{subsec})} that specifies a
+@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}.
 
 As an obsolescent calling convention, this function can be given six
-through ten arguments.  The first six arguments @var{second},
+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 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.
+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.  In this obsolescent
+convention, @var{zone} defaults to the current time zone rule
+(@pxref{Time Zone Rules}), and @var{dst} is treated as if it was
+@minus{}1.
 
 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
@@ -1615,9 +1625,8 @@ Time Conversion
 @end example
 
 You can perform simple date arithmetic by using out-of-range values for
-@var{seconds}, @var{minutes}, @var{hour}, @var{day}, @var{month}, and
-@var{subsec}; 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 the limits are exceeded while encoding the time, an error results.
diff --git a/doc/misc/emacs-mime.texi b/doc/misc/emacs-mime.texi
index c411bf3d68..eb829b0612 100644
--- a/doc/misc/emacs-mime.texi
+++ b/doc/misc/emacs-mime.texi
@@ -1535,7 +1535,7 @@ time-date
 
 @example
 (parse-time-string "Sat Sep 12 12:21:54 1998 +0200")
-@result{} (54 21 12 12 9 1998 6 -1 7200 0)
+@result{} (54 21 12 12 9 1998 6 -1 7200)
 
 (time-convert
   (date-to-time "Sat Sep 12 12:21:54 1998 +0200")
diff --git a/etc/NEWS b/etc/NEWS
index edce7b3e57..53408a871e 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -2116,10 +2116,20 @@ 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.
+*** Decoded (calendrical) timestamps now have subsecond resolution.
+This affects decode-time, which generates these timestamps, as well as
+functions like encode-time that accept them.  The subsecond info is
+present as a (TICKS . HZ) value in the seconds element of a decoded
+timestamp, and decode-time has a new optional FORM argument specifying
+the form of the seconds member.  For example, if X is the timestamp
+(1566009571321878186 . 1000000000), which represents 2019-08-17
+02:39:31.321878186 UTC, (decode-time X t t) returns ((31321878186
+. 1000000000) 39 2 17 8 2019 6 nil 0) instead of the traditional (31
+39 2 17 8 2019 6 nil 0) returned by plain (decode-time X t).  Although
+the default FORM is currently 'integer', which truncates the seconds
+to an integer and is the traditional behavior, this default may change
+in future Emacs versions, so callers requiring an integer should
+specify FORM explicitly.
 
 +++
 *** 'encode-time' supports a new API '(encode-time TIME)'.
@@ -2152,8 +2162,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', '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 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'
diff --git a/lisp/calendar/icalendar.el b/lisp/calendar/icalendar.el
index 84f579ad44..c2688705e3 100644
--- a/lisp/calendar/icalendar.el
+++ b/lisp/calendar/icalendar.el
@@ -644,7 +644,7 @@ icalendar--decode-isodatetime
         ;; create the decoded date-time
         ;; FIXME!?!
 	(let ((decoded-time (list second minute hour day month year
-				  nil -1 zone 0)))
+				  nil -1 zone)))
 	  (condition-case nil
 	      (decode-time (encode-time decoded-time))
 	    (error
diff --git a/lisp/calendar/iso8601.el b/lisp/calendar/iso8601.el
index 51f5dff909..30352c7e75 100644
--- a/lisp/calendar/iso8601.el
+++ b/lisp/calendar/iso8601.el
@@ -129,8 +129,7 @@ iso8601-parse
         (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-subsec date) (decoded-time-subsec time))))
+          (setf (decoded-time-second date) (decoded-time-second time))))
       ;; The time zone is optional.
       (when zone-string
         (setf (decoded-time-zone date)
@@ -237,8 +236,6 @@ iso8601-parse-time
           (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)))))))))
@@ -277,9 +274,7 @@ iso8601-parse-duration
                            :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)
-			   ;; FIXME: Support subsec.
-			   :subsec 0))
+                           :second (or (match-string 7 string) 0)))
    ;; PnW: Weeks.
    ((iso8601--match iso8601--duration-week-match string)
     (let ((weeks (string-to-number (match-string 1 string))))
@@ -341,7 +336,7 @@ iso8601--value
 
 (cl-defun iso8601--decoded-time (&key second minute hour
                                       day month year
-                                      dst zone subsec)
+                                      dst zone)
   (list (iso8601--value second)
         (iso8601--value minute)
         (iso8601--value hour)
@@ -350,8 +345,7 @@ iso8601--value
         (iso8601--value year)
         nil
         dst
-        zone
-	subsec))
+        zone))
 
 (defun iso8601--encode-time (time)
   "Like `encode-time', but fill in nil values in TIME."
diff --git a/lisp/calendar/parse-time.el b/lisp/calendar/parse-time.el
index 9af93b5b1e..b0b277db77 100644
--- a/lisp/calendar/parse-time.el
+++ b/lisp/calendar/parse-time.el
@@ -148,7 +148,7 @@ parse-time-rules
 
 ;;;###autoload
 (defun parse-time-string (string)
-  "Parse the time in STRING into (SEC MIN HOUR DAY MON YEAR DOW DST TZ SUBSEC).
+  "Parse the time in STRING into (SEC MIN HOUR DAY MON YEAR DOW DST TZ).
 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 @@ parse-time-string
 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 nil))
+  (let ((time (list nil nil nil nil nil nil nil -1 nil))
 	(temp (parse-time-tokenize (downcase string))))
     (while temp
       (let ((parse-time-elt (pop temp))
@@ -193,10 +193,6 @@ parse-time-string
 				       (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)
diff --git a/lisp/calendar/time-date.el b/lisp/calendar/time-date.el
index fa5e886869..f3d252f03c 100644
--- a/lisp/calendar/time-date.el
+++ b/lisp/calendar/time-date.el
@@ -420,26 +420,13 @@ decoded-time-add
 
     ;; Do the time part, which is pretty simple (except for leap
     ;; seconds, I guess).
-    (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)))
-
-    (cond
-     ((> seconds 0)
-      (decoded-time--alter-second time seconds t))
-     ((< seconds 0)
-      (decoded-time--alter-second time (abs seconds) nil)))
+    (setq seconds (time-add (+ (* (or (decoded-time-hour delta) 0) 3600)
+			       (* (or (decoded-time-minute delta) 0) 60)
+			       (or (decoded-time-zone delta) 0))
+			    (or (decoded-time-second delta) 0)))
 
+    (decoded-time--alter-second time seconds)
     time))
 
 (defun decoded-time--alter-month (time increase)
@@ -472,38 +459,31 @@ decoded-time--alter-day
             (date-days-in-month (decoded-time-year time)
                                 (decoded-time-month time))))))
 
-(defun decoded-time--alter-second (time seconds increase)
-  "Increase or decrease the time in TIME by SECONDS."
-  (let ((old (+ (* (or (decoded-time-hour time) 0) 3600)
-                (* (or (decoded-time-minute time) 0) 60)
-                (or (decoded-time-second time) 0))))
-
-    (if increase
-        (progn
-          (setq old (+ old seconds))
-          (setf (decoded-time-second time) (% old 60)
-                (decoded-time-minute time) (% (/ old 60) 60)
-                (decoded-time-hour time) (% (/ old 3600) 24))
-          ;; Hm...  DST...
-          (let ((days (/ old (* 60 60 24))))
-            (while (> days 0)
-              (decoded-time--alter-day time t)
-              (cl-decf days))))
-      (setq old (abs (- old seconds)))
-      (setf (decoded-time-second time) (% old 60)
-            (decoded-time-minute time) (% (/ old 60) 60)
-            (decoded-time-hour time) (% (/ old 3600) 24))
-      ;; Hm...  DST...
-      (let ((days (/ old (* 60 60 24))))
-        (while (> days 0)
-          (decoded-time--alter-day time nil)
-          (cl-decf days))))))
+(defun decoded-time--alter-second (time seconds)
+  "Increase the time in TIME by SECONDS."
+  (let* ((secsperday 86400)
+	 (old (time-add (+ (* 3600 (or (decoded-time-hour time) 0))
+			   (* 60 (or (decoded-time-minute time) 0)))
+			(or (decoded-time-second time) 0)))
+	 (new (time-add old seconds)))
+    ;; Hm...  DST...
+    (while (time-less-p new 0)
+      (decoded-time--alter-day time nil)
+      (setq new (time-add new secsperday)))
+    (while (not (time-less-p new secsperday))
+      (decoded-time--alter-day time t)
+      (setq new (time-subtract new secsperday)))
+    (let ((sec (time-convert new 'integer)))
+      (setf (decoded-time-second time) (time-add (% sec 60)
+						 (time-subtract new sec))
+	    (decoded-time-minute time) (% (/ sec 60) 60)
+	    (decoded-time-hour time) (/ sec 3600)))))
 
 (cl-defun make-decoded-time (&key second minute hour
                                   day month year
-                                  dst zone subsec)
+                                  dst zone)
   "Return a `decoded-time' structure with only the keywords given filled out."
-  (list second minute hour day month year nil dst zone subsec))
+  (list second minute hour day month year nil dst zone))
 
 (defun decoded-time-set-defaults (time &optional default-zone)
   "Set any nil values in `decoded-time' TIME to default values.
@@ -533,9 +513,6 @@ decoded-time-set-defaults
   (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)
diff --git a/lisp/net/soap-client.el b/lisp/net/soap-client.el
index eb08511171..7ce7d79c74 100644
--- a/lisp/net/soap-client.el
+++ b/lisp/net/soap-client.el
@@ -561,8 +561,8 @@ soap-decode-date-time
 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 SUBSEC (fractional
-seconds) field is omitted, the DOW (day-of-week) field
+`encode-time').  The differences are the SEC (seconds)
+field is always an integer, 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
diff --git a/lisp/org/org.el b/lisp/org/org.el
index 336c413c8c..ab29353ae8 100644
--- a/lisp/org/org.el
+++ b/lisp/org/org.el
@@ -17775,12 +17775,14 @@ org-parse-time-string
 	       (string-to-number (match-string 4 s))
 	       (string-to-number (match-string 3 s))
 	       (string-to-number (match-string 2 s))
-	       nil nil nil 0))
+	       nil nil nil))
 	((string-match "^<[^>]+>$" s)
 	 ;; FIXME: `decode-time' needs to be called with ZONE as its
 	 ;; second argument.  However, this requires at least Emacs
 	 ;; 25.1.  We can do it when we switch to this version as our
 	 ;; minimal requirement.
+	 ;; FIXME: decode-time needs to be called with t as its
+	 ;; third argument, but this requires at least Emacs 27.
 	 (decode-time (org-matcher-time s)))
 	(t (error "Not a standard Org time string: %s" s))))
 
diff --git a/lisp/simple.el b/lisp/simple.el
index cb938bb341..fdf7d893cd 100644
--- a/lisp/simple.el
+++ b/lisp/simple.el
@@ -9082,8 +9082,9 @@ capitalize-dwim
                (:copier nil)
                (:type list))
   (second nil :documentation "\
-This is an integer between 0 and 60 (inclusive).  (60 is a leap
-second, which only some operating systems support.)")
+This is an integer or a Lisp timestamp (TICKS . HZ) representing a nonnegative
+number of seconds less than 61.  (If not less than 60, it is a leap second,
+which only some operating systems support.)")
   (minute nil :documentation "This is an integer between 0 and 59 (inclusive).")
   (hour nil :documentation "This is an integer between 0 and 23 (inclusive).")
   (day nil :documentation "This is an integer between 1 and 31 (inclusive).")
@@ -9099,9 +9100,6 @@ capitalize-dwim
   (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.")
   )
 
 \f
diff --git a/src/timefns.c b/src/timefns.c
index 979550c843..16c39c8349 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, &lt, 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,
diff --git a/test/lisp/calendar/icalendar-tests.el b/test/lisp/calendar/icalendar-tests.el
index 060cd8c909..baea480404 100644
--- a/test/lisp/calendar/icalendar-tests.el
+++ b/test/lisp/calendar/icalendar-tests.el
@@ -477,18 +477,18 @@ icalendar-tests--trim
 
           ;; 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 0)
+          (should (equal '(0 0 10 1 1 2013 2 nil 7200)
                          (icalendar--decode-isodatetime "20130101T100000")))
           ;; 1 Aug 2013 10:00 (DST)
-          (should (equal '(0 0 10 1 8 2013 4 t 10800 0)
+          (should (equal '(0 0 10 1 8 2013 4 t 10800)
                          (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 0)
+          (should (equal '(0 0 1 1 1 2014 3 nil 7200)
                          (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 0)
+          (should (equal '(0 0 13 1 8 2013 4 t 10800)
                          (icalendar--decode-isodatetime "20130801T100000Z")))
 
           )
diff --git a/test/lisp/calendar/iso8601-tests.el b/test/lisp/calendar/iso8601-tests.el
index 3f1149c864..35c319ed03 100644
--- a/test/lisp/calendar/iso8601-tests.el
+++ b/test/lisp/calendar/iso8601-tests.el
@@ -24,65 +24,65 @@
 
 (ert-deftest test-iso8601-date-years ()
   (should (equal (iso8601-parse-date "1985")
-                 '(nil nil nil nil nil 1985 nil nil nil nil)))
+                 '(nil nil nil nil nil 1985 nil nil nil)))
   (should (equal (iso8601-parse-date "-0003")
-                 '(nil nil nil nil nil -4 nil nil nil nil)))
+                 '(nil nil nil nil nil -4 nil nil nil)))
   (should (equal (iso8601-parse-date "+1985")
-                 '(nil nil nil nil nil 1985 nil nil nil nil))))
+                 '(nil nil nil nil nil 1985 nil nil nil))))
 
 (ert-deftest test-iso8601-date-dates ()
   (should (equal (iso8601-parse-date "1985-03-14")
-                 '(nil nil nil 14 3 1985 nil nil nil nil)))
+                 '(nil nil nil 14 3 1985 nil nil nil)))
   (should (equal (iso8601-parse-date "19850314")
-                 '(nil nil nil 14 3 1985 nil nil nil nil)))
+                 '(nil nil nil 14 3 1985 nil nil nil)))
   (should (equal (iso8601-parse-date "1985-02")
-                 '(nil nil nil nil 2 1985 nil nil nil nil))))
+                 '(nil nil nil nil 2 1985 nil nil nil))))
 
 (ert-deftest test-iso8601-date-obsolete ()
   (should (equal (iso8601-parse-date "--02-01")
-                 '(nil nil nil 1 2 nil nil nil nil nil)))
+                 '(nil nil nil 1 2 nil nil nil nil)))
   (should (equal (iso8601-parse-date "--0201")
-                 '(nil nil nil 1 2 nil nil nil nil nil))))
+                 '(nil nil nil 1 2 nil nil nil nil))))
 
 (ert-deftest test-iso8601-date-weeks ()
   (should (equal (iso8601-parse-date "2008W39-6")
-                 '(nil nil nil 27 9 2008 nil nil nil nil)))
+                 '(nil nil nil 27 9 2008 nil nil nil)))
   (should (equal (iso8601-parse-date "2009W01-1")
-                 '(nil nil nil 29 12 2008 nil nil nil nil)))
+                 '(nil nil nil 29 12 2008 nil nil nil)))
   (should (equal (iso8601-parse-date "2009W53-7")
-                 '(nil nil nil 3 1 2010 nil nil nil nil))))
+                 '(nil nil nil 3 1 2010 nil nil nil))))
 
 (ert-deftest test-iso8601-date-ordinals ()
   (should (equal (iso8601-parse-date "1981-095")
-                 '(nil nil nil 5 4 1981 nil nil nil nil))))
+                 '(nil nil nil 5 4 1981 nil nil nil))))
 
 (ert-deftest test-iso8601-time ()
   (should (equal (iso8601-parse-time "13:47:30")
-                 '(30 47 13 nil nil nil nil nil nil 0)))
+                 '(30 47 13 nil nil nil nil nil nil)))
   (should (equal (iso8601-parse-time "134730")
-                 '(30 47 13 nil nil nil nil nil nil 0)))
+                 '(30 47 13 nil nil nil nil nil nil)))
   (should (equal (iso8601-parse-time "1347")
-                 '(0 47 13 nil nil nil nil nil nil 0))))
+                 '(0 47 13 nil nil nil nil nil nil))))
 
 (ert-deftest test-iso8601-combined ()
   (should (equal (iso8601-parse "2008-03-02T13:47:30")
-                 '(30 47 13 2 3 2008 nil nil nil 0)))
+                 '(30 47 13 2 3 2008 nil nil nil)))
   (should (equal (iso8601-parse "2008-03-02T13:47:30Z")
-                 '(30 47 13 2 3 2008 nil nil 0 0)))
+                 '(30 47 13 2 3 2008 nil nil 0)))
   (should (equal (iso8601-parse "2008-03-02T13:47:30+01:00")
-                 '(30 47 13 2 3 2008 nil nil 3600 0)))
+                 '(30 47 13 2 3 2008 nil nil 3600)))
   (should (equal (iso8601-parse "2008-03-02T13:47:30-01")
-                 '(30 47 13 2 3 2008 nil nil -3600 0))))
+                 '(30 47 13 2 3 2008 nil nil -3600))))
 
 (ert-deftest test-iso8601-duration ()
   (should (equal (iso8601-parse-duration "P3Y6M4DT12H30M5S")
-                 '(5 30 12 4 6 3 nil nil nil 0)))
+                 '(5 30 12 4 6 3 nil nil nil)))
   (should (equal (iso8601-parse-duration "P1M")
-                 '(0 0 0 0 1 0 nil nil nil 0)))
+                 '(0 0 0 0 1 0 nil nil nil)))
   (should (equal (iso8601-parse-duration "PT1M")
-                 '(0 1 0 0 0 0 nil nil nil 0)))
+                 '(0 1 0 0 0 0 nil nil nil)))
   (should (equal (iso8601-parse-duration "P0003-06-04T12:30:05")
-                 '(5 30 12 4 6 3 nil nil nil 0))))
+                 '(5 30 12 4 6 3 nil nil nil))))
 
 (ert-deftest test-iso8601-invalid ()
   (should-not (iso8601-valid-p " 2008-03-02T13:47:30-01"))
@@ -94,149 +94,149 @@
 (ert-deftest test-iso8601-intervals ()
   (should (equal
            (iso8601-parse-interval "2007-03-01T13:00:00Z/2008-05-11T15:30:00Z")
-           '((0 0 13 1 3 2007 nil nil 0 0)
-             (0 30 15 11 5 2008 nil nil 0 0)
+           '((0 0 13 1 3 2007 nil nil 0)
+             (0 30 15 11 5 2008 nil nil 0)
              ;; Hm...  can't really use decode-time for time differences...
-             (0 30 2 14 3 1971 0 nil 0 0))))
+             (0 30 2 14 3 1971 0 nil 0))))
   (should (equal (iso8601-parse-interval "2007-03-01T13:00:00Z/P1Y2M10DT2H30M")
-                 '((0 0 13 1 3 2007 nil nil 0 0)
-                   (0 30 15 11 5 2008 nil nil 0 0)
-                   (0 30 2 10 2 1 nil nil nil 0))))
+                 '((0 0 13 1 3 2007 nil nil 0)
+                   (0 30 15 11 5 2008 nil nil 0)
+                   (0 30 2 10 2 1 nil nil nil))))
   (should (equal (iso8601-parse-interval "P1Y2M10DT2H30M/2008-05-11T15:30:00Z")
-                 '((0 0 13 1 3 2007 nil nil 0 0)
-                   (0 30 15 11 5 2008 nil nil 0 0)
-                   (0 30 2 10 2 1 nil nil nil 0)))))
+                 '((0 0 13 1 3 2007 nil nil 0)
+                   (0 30 15 11 5 2008 nil nil 0)
+                   (0 30 2 10 2 1 nil nil nil)))))
 
 (ert-deftest standard-test-dates ()
   (should (equal (iso8601-parse-date "19850412")
-                 '(nil nil nil 12 4 1985 nil nil nil nil)))
+                 '(nil nil nil 12 4 1985 nil nil nil)))
   (should (equal (iso8601-parse-date "1985-04-12")
-                 '(nil nil nil 12 4 1985 nil nil nil nil)))
+                 '(nil nil nil 12 4 1985 nil nil nil)))
 
   (should (equal (iso8601-parse-date "1985102")
-                 '(nil nil nil 12 4 1985 nil nil nil nil)))
+                 '(nil nil nil 12 4 1985 nil nil nil)))
   (should (equal (iso8601-parse-date "1985-102")
-                 '(nil nil nil 12 4 1985 nil nil nil nil)))
+                 '(nil nil nil 12 4 1985 nil nil nil)))
 
   (should (equal (iso8601-parse-date "1985W155")
-                 '(nil nil nil 12 4 1985 nil nil nil nil)))
+                 '(nil nil nil 12 4 1985 nil nil nil)))
   (should (equal (iso8601-parse-date "1985-W15-5")
-                 '(nil nil nil 12 4 1985 nil nil nil nil)))
+                 '(nil nil nil 12 4 1985 nil nil nil)))
 
   (should (equal (iso8601-parse-date "1985W15")
-                 '(nil nil nil 7 4 1985 nil nil nil nil)))
+                 '(nil nil nil 7 4 1985 nil nil nil)))
   (should (equal (iso8601-parse-date "1985-W15")
-                 '(nil nil nil 7 4 1985 nil nil nil nil)))
+                 '(nil nil nil 7 4 1985 nil nil nil)))
 
   (should (equal (iso8601-parse-date "1985-04")
-                 '(nil nil nil nil 4 1985 nil nil nil nil)))
+                 '(nil nil nil nil 4 1985 nil nil nil)))
 
   (should (equal (iso8601-parse-date "1985")
-                 '(nil nil nil nil nil 1985 nil nil nil nil)))
+                 '(nil nil nil nil nil 1985 nil nil nil)))
 
   (should (equal (iso8601-parse-date "+1985-04-12")
-                 '(nil nil nil 12 4 1985 nil nil nil nil)))
+                 '(nil nil nil 12 4 1985 nil nil nil)))
   (should (equal (iso8601-parse-date "+19850412")
-                 '(nil nil nil 12 4 1985 nil nil nil nil))))
+                 '(nil nil nil 12 4 1985 nil nil nil))))
 
 (ert-deftest standard-test-time-of-day-local-time ()
   (should (equal (iso8601-parse-time "152746")
-                 '(46 27 15 nil nil nil nil nil nil 0)))
+                 '(46 27 15 nil nil nil nil nil nil)))
   (should (equal (iso8601-parse-time "15:27:46")
-                 '(46 27 15 nil nil nil nil nil nil 0)))
+                 '(46 27 15 nil nil nil nil nil nil)))
 
   (should (equal (iso8601-parse-time "1528")
-                 '(0 28 15 nil nil nil nil nil nil 0)))
+                 '(0 28 15 nil nil nil nil nil nil)))
   (should (equal (iso8601-parse-time "15:28")
-                 '(0 28 15 nil nil nil nil nil nil 0)))
+                 '(0 28 15 nil nil nil nil nil nil)))
 
   (should (equal (iso8601-parse-time "15")
-                 '(0 0 15 nil nil nil nil nil nil 0))))
+                 '(0 0 15 nil nil nil nil nil nil))))
 
 (ert-deftest standard-test-time-of-day-fractions ()
   ;; decoded-time doesn't support sub-second times.
   ;; (should (equal (iso8601-parse-time "152735,5")
-  ;;                '(46 27 15 nil nil nil nil nil nil (5 . 10))))
+  ;;                '(46 27 15 nil nil nil nil nil nil)))
   ;; (should (equal (iso8601-parse-time "15:27:35,5")
-  ;;                '(46 27 15 nil nil nil nil nil nil (5 . 10))))
+  ;;                '(46 27 15 nil nil nil nil nil nil)))
   )
 
 (ert-deftest standard-test-time-of-day-beginning-of-day ()
   (should (equal (iso8601-parse-time "000000")
-                 '(0 0 0 nil nil nil nil nil nil 0)))
+                 '(0 0 0 nil nil nil nil nil nil)))
   (should (equal (iso8601-parse-time "00:00:00")
-                 '(0 0 0 nil nil nil nil nil nil 0)))
+                 '(0 0 0 nil nil nil nil nil nil)))
 
   (should (equal (iso8601-parse-time "0000")
-                 '(0 0 0 nil nil nil nil nil nil 0)))
+                 '(0 0 0 nil nil nil nil nil nil)))
   (should (equal (iso8601-parse-time "00:00")
-                 '(0 0 0 nil nil nil nil nil nil 0))))
+                 '(0 0 0 nil nil nil nil nil nil))))
 
 (ert-deftest standard-test-time-of-day-utc ()
   (should (equal (iso8601-parse-time "232030Z")
-                 '(30 20 23 nil nil nil nil nil 0 0)))
+                 '(30 20 23 nil nil nil nil nil 0)))
   (should (equal (iso8601-parse-time "23:20:30Z")
-                 '(30 20 23 nil nil nil nil nil 0 0)))
+                 '(30 20 23 nil nil nil nil nil 0)))
 
   (should (equal (iso8601-parse-time "2320Z")
-                 '(0 20 23 nil nil nil nil nil 0 0)))
+                 '(0 20 23 nil nil nil nil nil 0)))
   (should (equal (iso8601-parse-time "23:20Z")
-                 '(0 20 23 nil nil nil nil nil 0 0)))
+                 '(0 20 23 nil nil nil nil nil 0)))
 
   (should (equal (iso8601-parse-time "23Z")
-                 '(0 0 23 nil nil nil nil nil 0 0))))
+                 '(0 0 23 nil nil nil nil nil 0))))
 
 
 (ert-deftest standard-test-time-of-day-zone ()
   (should (equal (iso8601-parse-time "152746+0100")
-                 '(46 27 15 nil nil nil nil nil 3600 0)))
+                 '(46 27 15 nil nil nil nil nil 3600)))
   (should (equal (iso8601-parse-time "15:27:46+0100")
-                 '(46 27 15 nil nil nil nil nil 3600 0)))
+                 '(46 27 15 nil nil nil nil nil 3600)))
 
   (should (equal (iso8601-parse-time "152746+01")
-                 '(46 27 15 nil nil nil nil nil 3600 0)))
+                 '(46 27 15 nil nil nil nil nil 3600)))
   (should (equal (iso8601-parse-time "15:27:46+01")
-                 '(46 27 15 nil nil nil nil nil 3600 0)))
+                 '(46 27 15 nil nil nil nil nil 3600)))
 
   (should (equal (iso8601-parse-time "152746-0500")
-                 '(46 27 15 nil nil nil nil nil -18000 0)))
+                 '(46 27 15 nil nil nil nil nil -18000)))
   (should (equal (iso8601-parse-time "15:27:46-0500")
-                 '(46 27 15 nil nil nil nil nil -18000 0)))
+                 '(46 27 15 nil nil nil nil nil -18000)))
 
   (should (equal (iso8601-parse-time "152746-05")
-                 '(46 27 15 nil nil nil nil nil -18000 0)))
+                 '(46 27 15 nil nil nil nil nil -18000)))
   (should (equal (iso8601-parse-time "15:27:46-05")
-                 '(46 27 15 nil nil nil nil nil -18000 0))))
+                 '(46 27 15 nil nil nil nil nil -18000))))
 
 (ert-deftest standard-test-date-and-time-of-day ()
   (should (equal (iso8601-parse "19850412T101530")
-                 '(30 15 10 12 4 1985 nil nil nil 0)))
+                 '(30 15 10 12 4 1985 nil nil nil)))
   (should (equal (iso8601-parse "1985-04-12T10:15:30")
-                 '(30 15 10 12 4 1985 nil nil nil 0)))
+                 '(30 15 10 12 4 1985 nil nil nil)))
 
   (should (equal (iso8601-parse "1985102T235030Z")
-                 '(30 50 23 12 4 1985 nil nil 0 0)))
+                 '(30 50 23 12 4 1985 nil nil 0)))
   (should (equal (iso8601-parse "1985-102T23:50:30Z")
-                 '(30 50 23 12 4 1985 nil nil 0 0)))
+                 '(30 50 23 12 4 1985 nil nil 0)))
 
   (should (equal (iso8601-parse "1985W155T235030")
-                 '(30 50 23 12 4 1985 nil nil nil 0)))
+                 '(30 50 23 12 4 1985 nil nil nil)))
   (should (equal (iso8601-parse "1985-W155T23:50:30")
-                 '(30 50 23 12 4 1985 nil nil nil 0))))
+                 '(30 50 23 12 4 1985 nil nil nil))))
 
 (ert-deftest standard-test-interval ()
   ;; A time interval starting at 20 minutes and 50 seconds past 23
   ;; hours on 12 April 1985 and ending at 30 minutes past 10 hours on
   ;; 25 June 1985.
   (should (equal (iso8601-parse-interval "19850412T232050Z/19850625T103000Z")
-                 '((50 20 23 12 4 1985 nil nil 0 0)
-                   (0 30 10 25 6 1985 nil nil 0 0)
-                   (10 9 11 15 3 1970 0 nil 0 0))))
+                 '((50 20 23 12 4 1985 nil nil 0)
+                   (0 30 10 25 6 1985 nil nil 0)
+                   (10 9 11 15 3 1970 0 nil 0))))
   (should (equal (iso8601-parse-interval
                   "1985-04-12T23:20:50Z/1985-06-25T10:30:00Z")
-                 '((50 20 23 12 4 1985 nil nil 0 0)
-                   (0 30 10 25 6 1985 nil nil 0 0)
-                   (10 9 11 15 3 1970 0 nil 0 0))))
+                 '((50 20 23 12 4 1985 nil nil 0)
+                   (0 30 10 25 6 1985 nil nil 0)
+                   (10 9 11 15 3 1970 0 nil 0))))
 
   ;; A time interval starting at 12 April 1985 and ending on 25 June
   ;; 1985.
@@ -251,41 +251,41 @@
   ;; A time interval of 2 years, 10 months, 15 days, 10 hours, 20
   ;; minutes and 30 seconds.
   (should (equal (iso8601-parse-duration "P2Y10M15DT10H20M30S")
-                 '(30 20 10 15 10 2 nil nil nil 0)))
+                 '(30 20 10 15 10 2 nil nil nil)))
 
   (should (equal (iso8601-parse-duration "P00021015T102030")
-                 '(30 20 10 15 10 2 nil nil nil 0)))
+                 '(30 20 10 15 10 2 nil nil nil)))
   (should (equal (iso8601-parse-duration "P0002-10-15T10:20:30")
-                 '(30 20 10 15 10 2 nil nil nil 0)))
+                 '(30 20 10 15 10 2 nil nil nil)))
 
   ;; A time interval of 1 year and 6 months.
   (should (equal (iso8601-parse-duration "P1Y6M")
-                 '(0 0 0 0 6 1 nil nil nil 0)))
+                 '(0 0 0 0 6 1 nil nil nil)))
   (should (equal (iso8601-parse-duration "P0001-06")
-                 '(nil nil nil nil 6 1 nil nil nil nil)))
+                 '(nil nil nil nil 6 1 nil nil nil)))
 
   ;; A time interval of seventy-two hours.
   (should (equal (iso8601-parse-duration "PT72H")
-                 '(0 0 72 0 0 0 nil nil nil 0)))
+                 '(0 0 72 0 0 0 nil nil nil)))
 
   ;; Defined by start and duration
   ;; A time interval of 1 year, 2 months, 15 days and 12 hours,
   ;; beginning on 12 April 1985 at 20 minutes past 23 hours.
   (should (equal (iso8601-parse-interval "19850412T232000/P1Y2M15DT12H")
-                 '((0 20 23 12 4 1985 nil nil nil 0)
-                   (0 20 11 28 6 1986 nil nil nil 0)
-                   (0 0 12 15 2 1 nil nil nil 0))))
+                 '((0 20 23 12 4 1985 nil nil nil)
+                   (0 20 11 28 6 1986 nil nil nil)
+                   (0 0 12 15 2 1 nil nil nil))))
   (should (equal (iso8601-parse-interval "1985-04-12T23:20:00/P1Y2M15DT12H")
-                 '((0 20 23 12 4 1985 nil nil nil 0)
-                   (0 20 11 28 6 1986 nil nil nil 0)
-                   (0 0 12 15 2 1 nil nil nil 0))))
+                 '((0 20 23 12 4 1985 nil nil nil)
+                   (0 20 11 28 6 1986 nil nil nil)
+                   (0 0 12 15 2 1 nil nil nil))))
 
   ;; Defined by duration and end
   ;; A time interval of 1 year, 2 months, 15 days and 12 hours, ending
   ;; on 12 April 1985 at 20 minutes past 23 hour.
   (should (equal (iso8601-parse-interval "P1Y2M15DT12H/19850412T232000")
-                 '((0 20 11 28 1 1984 nil nil nil 0)
-                   (0 20 23 12 4 1985 nil nil nil 0)
-                   (0 0 12 15 2 1 nil nil nil 0)))))
+                 '((0 20 11 28 1 1984 nil nil nil)
+                   (0 20 23 12 4 1985 nil nil nil)
+                   (0 0 12 15 2 1 nil nil nil)))))
 
 ;;; iso8601-tests.el ends here
diff --git a/test/lisp/calendar/parse-time-tests.el b/test/lisp/calendar/parse-time-tests.el
index 61a3838a52..7435620b71 100644
--- a/test/lisp/calendar/parse-time-tests.el
+++ b/test/lisp/calendar/parse-time-tests.el
@@ -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 0)))
+                 '(42 35 19 22 2 2016 1 -1 3600)))
   (should (equal (parse-time-string "22 Feb 2016 19:35:42 +0100")
-                 '(42 35 19 22 2 2016 nil -1 3600 0)))
+                 '(42 35 19 22 2 2016 nil -1 3600)))
   (should (equal (parse-time-string "22 Feb 2016 +0100")
-                 '(nil nil nil 22 2 2016 nil -1 3600 nil)))
+                 '(nil nil nil 22 2 2016 nil -1 3600)))
   (should (equal (parse-time-string "Mon, 22 Feb 16 19:35:42 +0100")
-                 '(42 35 19 22 2 2016 1 -1 3600 0)))
+                 '(42 35 19 22 2 2016 1 -1 3600)))
   (should (equal (parse-time-string "Mon, 22 February 2016 19:35:42 +0100")
-                 '(42 35 19 22 2 2016 1 -1 3600 0)))
+                 '(42 35 19 22 2 2016 1 -1 3600)))
   (should (equal (parse-time-string "Mon, 22 feb 2016 19:35:42 +0100")
-                 '(42 35 19 22 2 2016 1 -1 3600 0)))
+                 '(42 35 19 22 2 2016 1 -1 3600)))
   (should (equal (parse-time-string "Monday, 22 february 2016 19:35:42 +0100")
-                 '(42 35 19 22 2 2016 1 -1 3600 0)))
+                 '(42 35 19 22 2 2016 1 -1 3600)))
   (should (equal (parse-time-string "Monday, 22 february 2016 19:35:42 PST")
-                 '(42 35 19 22 2 2016 1 nil -28800 0)))
+                 '(42 35 19 22 2 2016 1 nil -28800)))
   (should (equal (parse-time-string "Friday, 21 Sep 2018 13:47:58 PDT")
-                 '(58 47 13 21 9 2018 5 t -25200 0)))
+                 '(58 47 13 21 9 2018 5 t -25200)))
   (should (equal (format-time-string
 		  "%Y-%m-%d %H:%M:%S"
 		  (parse-iso8601-time-string "1998-09-12T12:21:54-0200") t)
diff --git a/test/src/timefns-tests.el b/test/src/timefns-tests.el
index 362e7655a9..13ab7d83c3 100644
--- a/test/src/timefns-tests.el
+++ b/test/src/timefns-tests.el
@@ -40,25 +40,31 @@
 		    (7879679999900 . 100000)
 		    (78796799999999999999 . 1000000000000)))
       ;; UTC.
-     (let ((subsec (time-subtract (time-convert look t)
-				  (time-convert look 'integer))))
+     (let ((sec (time-add 59 (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)
-		     (list 59 59 23 30 6 1972 5 nil 0 subsec)))
+      (should (equal (decode-time look t 'integer)
+		     '(59 59 23 30 6 1972 5 nil 0)))
+      (should (equal (decode-time look t t)
+		     (list sec 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")
-		     (list 59 59 23 30 6 1972 5 nil 0 subsec)))
+      (should (equal (decode-time look "UTC0" 'integer)
+		     '(59 59 23 30 6 1972 5 nil 0)))
+      (should (equal (decode-time look "UTC0" t)
+		     (list sec 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"))
-		     (list 59 59 15 30 6 1972 5 nil -28800 subsec)))
+      (should (equal (decode-time look '(-28800 "PST") 'integer)
+		     '(59 59 15 30 6 1972 5 nil -28800)))
+      (should (equal (decode-time look '(-28800 "PST") t)
+		     (list sec 59 15 30 6 1972 5 nil -28800)))
       ;; Negative UTC offset, as a Lisp integer.
       (should (string-equal
 	       (format-time-string format look -28800)
@@ -67,14 +73,18 @@
 	       (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)
-		     (list 59 59 15 30 6 1972 5 nil -28800 subsec)))
+      (should (equal (decode-time look -28800 'integer)
+		     '(59 59 15 30 6 1972 5 nil -28800)))
+      (should (equal (decode-time look -28800 t)
+		     (list sec 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")
-		     (list 59 29 5 1 7 1972 6 nil 19800 subsec)))))))
+      (should (equal (decode-time look "IST-5:30" 'integer)
+		     '(59 29 5 1 7 1972 6 nil 19800)))
+      (should (equal (decode-time look "IST-5:30" t)
+		     (list sec 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
@@ -87,11 +97,13 @@
 			   (cons (1+ most-positive-fixnum) 1000000000000)
 			   (cons 1000000000000 (1+ most-positive-fixnum)))))
     (dolist (a time-values)
-      (let* ((d (ignore-errors (decode-time a t)))
+      (let* ((d (ignore-errors (decode-time a t t)))
+             (d-integer (ignore-errors (decode-time a t 'integer)))
 	     (e (if d (encode-time d)))
-	     (diff (float-time (time-subtract a e))))
-	(should (or (not d)
-		    (and (<= 0 diff) (< diff 1))))))))
+	     (e-integer (if d-integer (encode-time d-integer))))
+	(should (or (not d) (time-equal-p a e)))
+	(should (or (not d-integer) (time-equal-p (time-convert a 'integer)
+                                                  e-integer)))))))
 
 ;;; This should not dump core.
 (ert-deftest format-time-string-with-outlandish-zone ()
@@ -151,7 +163,7 @@ timefns-tests--have-leap-seconds
 (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 0))
+             (encode-time '(29 31 17 30 4 2019 2 t 7200))
              '(23752 27217))))
 
 (ert-deftest float-time-precision ()
-- 
2.17.1


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

* Re: encode-time vs decode-time
  2019-08-07 11:33                           ` Lars Ingebrigtsen
@ 2019-08-17  7:54                             ` Paul Eggert
  2019-08-17  8:16                               ` Eli Zaretskii
  2019-08-17 20:46                               ` Lars Ingebrigtsen
  0 siblings, 2 replies; 45+ messages in thread
From: Paul Eggert @ 2019-08-17  7:54 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: Eli Zaretskii, andrewjmoreton, emacs-devel

Lars Ingebrigtsen wrote:
> I agree; calling these things encoded/decoded time isn't very clear
> terminology.  "calendrical" is a mouthful, though.  And "calendar" would
> imply that it belongs in the calendar package, perhaps...

I used "calendrical" rather than "calendar" to try to avoid that implication 
(also, because os.texi already called these broken-down timestamps "calendrical 
data"). But perhaps "calendrical" isn't far enough away from "calendar".

The POSIX tradition is to call these timestamps "broken-down time", and that is 
what glibc calls them too. How about if we use that name instead? It would help 
to be more consistent with other GNU code.



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

* Re: encode-time vs decode-time
  2019-08-17  7:54                             ` Paul Eggert
@ 2019-08-17  8:16                               ` Eli Zaretskii
  2019-08-17  9:33                                 ` Paul Eggert
  2019-08-17 20:46                               ` Lars Ingebrigtsen
  1 sibling, 1 reply; 45+ messages in thread
From: Eli Zaretskii @ 2019-08-17  8:16 UTC (permalink / raw)
  To: Paul Eggert; +Cc: larsi, andrewjmoreton, emacs-devel

> From: Paul Eggert <eggert@cs.ucla.edu>
> Date: Sat, 17 Aug 2019 00:54:07 -0700
> Cc: Eli Zaretskii <eliz@gnu.org>, andrewjmoreton@gmail.com, emacs-devel@gnu.org
> 
> The POSIX tradition is to call these timestamps "broken-down time", and that is 
> what glibc calls them too. How about if we use that name instead? It would help 
> to be more consistent with other GNU code.

"Broken-down time" will replace "decoded time", I presume?  I think
I'm okay with that, but what will we use instead of "encoded time"
then?



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

* Re: encode-time vs decode-time
  2019-08-07 11:41                   ` Lars Ingebrigtsen
@ 2019-08-17  9:25                     ` Paul Eggert
  2019-08-17 20:51                       ` Lars Ingebrigtsen
  0 siblings, 1 reply; 45+ messages in thread
From: Paul Eggert @ 2019-08-17  9:25 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: Andy Moreton, emacs-devel

Lars Ingebrigtsen wrote:
> I thought it would be good to introduce a new function, say
> `get-current-time', that could have our new signature.  We'd then
> deprecate `current-time'

It's not just current-time; it's also encode-time, file-attributes, 
directory-files-and-attributes, visited-file-modtime, current-idle-time, 
process-attributes, get-internal-run-time, make-frame-visible, time-add, 
time-subtract, and no doubt other primitives written in C that make Lisp 
timestamps visible to user code, along with the many functions written in Lisp 
(e.g., time-since) that call these primitives and give Lisp timestamps to users. 
We'd have to come up with new names for all these functions, and all the 
user-visible variables these functions store into, and deprecate all the old 
names. And during this deprecation period, Emacs-defined and user-defined code 
would have to support both old- and new-format timestamps since code could 
generate either.

Of course it would be possible but it'll be easier for everyone concerned if we 
just bite the bullet at some point - not in the next version of course, but 
eventually, with an option to not-bite-the-bullet-quite-yet for people who need 
that option.



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

* Re: encode-time vs decode-time
  2019-08-17  8:16                               ` Eli Zaretskii
@ 2019-08-17  9:33                                 ` Paul Eggert
  0 siblings, 0 replies; 45+ messages in thread
From: Paul Eggert @ 2019-08-17  9:33 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: larsi, andrewjmoreton, emacs-devel

Eli Zaretskii wrote:

> "Broken-down time" will replace "decoded time", I presume?  I think
> I'm okay with that, but what will we use instead of "encoded time"
> then?

Currently the documentation uses "Lisp timestamp" for encoded timestamps that 
builtins can return: these are Lisp integers, integer pairs (TICKS . HZ) where 
HZ is positive, and the traditional four-integer lists (HI LO US PS).

The documentation uses the term "time value" for a brader class of timestamps 
that builtins accept. These are the Lisp timestamps, nil, floats, (HI LO US), 
and (HI LO).

I'm OK with sticking with this terminology, as it hasn't seemed to have been as 
confusing as "decoded time". As far as I can see, the documentation doesn't use 
the term "encoded time" which would indeed be confusing.



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

* Re: encode-time vs decode-time
  2019-08-17  7:54                             ` Paul Eggert
  2019-08-17  8:16                               ` Eli Zaretskii
@ 2019-08-17 20:46                               ` Lars Ingebrigtsen
  2019-08-17 20:56                                 ` Paul Eggert
  1 sibling, 1 reply; 45+ messages in thread
From: Lars Ingebrigtsen @ 2019-08-17 20:46 UTC (permalink / raw)
  To: Paul Eggert; +Cc: Eli Zaretskii, andrewjmoreton, emacs-devel

Paul Eggert <eggert@cs.ucla.edu> writes:

> The POSIX tradition is to call these timestamps "broken-down time",
> and that is what glibc calls them too. How about if we use that name
> instead? It would help to be more consistent with other GNU code.

"Broken-down time" sounds OK to me, although there may be some
confusion: Some may assume that it's a faulty time or something.

And when we get to the accessor names, it perhaps gets even more
potentially confusing:

(broken-down-seconds time)
(broken-down-zone time)

Hm...  Perhaps not ideal?

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no



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

* Re: encode-time vs decode-time
  2019-08-17  9:25                     ` Paul Eggert
@ 2019-08-17 20:51                       ` Lars Ingebrigtsen
  0 siblings, 0 replies; 45+ messages in thread
From: Lars Ingebrigtsen @ 2019-08-17 20:51 UTC (permalink / raw)
  To: Paul Eggert; +Cc: Andy Moreton, emacs-devel

Paul Eggert <eggert@cs.ucla.edu> writes:

> Lars Ingebrigtsen wrote:
>> I thought it would be good to introduce a new function, say
>> `get-current-time', that could have our new signature.  We'd then
>> deprecate `current-time'
>
> It's not just current-time; it's also encode-time, file-attributes,
> directory-files-and-attributes, visited-file-modtime,
> current-idle-time, process-attributes, get-internal-run-time,
> make-frame-visible, time-add, time-subtract, and no doubt other
> primitives written in C that make Lisp timestamps visible to user
> code, along with the many functions written in Lisp (e.g.,
> time-since) that call these primitives and give Lisp timestamps to
> users.

Yeah, that's true, so introducing a new function here wouldn't help.

But I think that having these functions return something that doesn't
have the second spec in car/cadr is going to break a lot of people's
out-of-tree code.  So my feeling is that your plan isn't feasible, and
we'll have to live with these functions returning encoded time on the
current form forever.

Which I think is OK, although your new format is more elegant.

-- 
(domestic pets only, the antidote for overdose, milk.)
   bloggy blog: http://lars.ingebrigtsen.no



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

* Re: encode-time vs decode-time
  2019-08-17 20:46                               ` Lars Ingebrigtsen
@ 2019-08-17 20:56                                 ` Paul Eggert
  2019-08-17 21:42                                   ` Stefan Monnier
  0 siblings, 1 reply; 45+ messages in thread
From: Paul Eggert @ 2019-08-17 20:56 UTC (permalink / raw)
  To: Lars Ingebrigtsen; +Cc: Eli Zaretskii, andrewjmoreton, emacs-devel

Lars Ingebrigtsen wrote:
> "Broken-down time" sounds OK to me, although there may be some
> confusion: Some may assume that it's a faulty time or something.

Yes, I'm not a big fan of "broken-down time" either. Its main advantages are (1) 
we can't think of a better name and (2) it's used elsewhere in GNU and POSIX.

> And when we get to the accessor names, it perhaps gets even more
> potentially confusing:
> 
> (broken-down-seconds time)
> (broken-down-zone time)
> 
> Hm...  Perhaps not ideal?

The POSIX identifier for broken-down time is 'tm', as in 'struct tm'. We could 
steal that identifier too, e.g., (tm-seconds time), (tm-zone time), etc. Again, 
not ideal, but perhaps better than (broken-down-seconds time). If the prefix 
'tm-' is too short, we could expand it to 'time-tm-' or something like that.



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

* Re: encode-time vs decode-time
  2019-08-17 20:56                                 ` Paul Eggert
@ 2019-08-17 21:42                                   ` Stefan Monnier
  2019-08-17 22:53                                     ` Paul Eggert
  0 siblings, 1 reply; 45+ messages in thread
From: Stefan Monnier @ 2019-08-17 21:42 UTC (permalink / raw)
  To: Paul Eggert; +Cc: Lars Ingebrigtsen, andrewjmoreton, Eli Zaretskii, emacs-devel

> The POSIX identifier for broken-down time is 'tm', as in 'struct tm'. We
> could steal that identifier too, e.g., (tm-seconds time), (tm-zone time),
> etc. Again, not ideal, but perhaps better than (broken-down-seconds
> time). If the prefix 'tm-' is too short, we could expand it to 'time-tm-' or
> something like that.

FWIW, I think neither "broken down time" nor "tm" are significantly less
bad than "decoded-time", so I don't think it's worth changing the term
we've been using.

I think part of the problem is that an "encoded time" object looked like
"a list of integers" and a "decoded time" object looked like "a list of
integers".  As we move the "decoded time" objects closer to actual
structs and the "encoded time" objects closer to plain (rational)
numbers, things should get more clear.

So maybe we should stop documenting "decoded time" objects as being of
the form (SEC MINUTE HOUR DAY MONTH YEAR DOW DST UTCOFF SUBSEC), but
instead say that it's a `decoded-time` object on which you can use
the `decoded-time-*` accessors (and provide a corresponding pcase pattern
to replace the `(,sec ,minute ...) one).


        Stefan




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

* Re: encode-time vs decode-time
  2019-08-17 21:42                                   ` Stefan Monnier
@ 2019-08-17 22:53                                     ` Paul Eggert
  2019-08-19 21:12                                       ` Stefan Monnier
  0 siblings, 1 reply; 45+ messages in thread
From: Paul Eggert @ 2019-08-17 22:53 UTC (permalink / raw)
  To: Stefan Monnier
  Cc: Lars Ingebrigtsen, andrewjmoreton, Eli Zaretskii, emacs-devel

Stefan Monnier wrote:
> I think neither "broken down time" nor "tm" are significantly less
> bad than "decoded-time", so I don't think it's worth changing the term
> we've been using.

I'm starting to come around to the same feeling.

> So maybe we should stop documenting "decoded time" objects as being of
> the form (SEC MINUTE HOUR DAY MONTH YEAR DOW DST UTCOFF SUBSEC), but
> instead say that it's a `decoded-time` object on which you can use
> the `decoded-time-*` accessors (and provide a corresponding pcase pattern
> to replace the `(,sec ,minute ...) one).

That sounds like a reasonable way to move forward.

I'm not a pcase expert. How would one go about doing the pcase stuff? That is, 
how does one provide a pattern so that code like (pcase time ((decoded-time 
:year y :month m) (some-expr-involving y & m))) can extract the year and month 
components Y and M from the broken-down time TIME that was created via 
(make-decoded-time :year y :month m)? Is there an example of doing this sort of 
thing somewhere?



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

* Re: encode-time vs decode-time
  2019-08-17 22:53                                     ` Paul Eggert
@ 2019-08-19 21:12                                       ` Stefan Monnier
  2019-08-21 10:55                                         ` Adam Porter
  0 siblings, 1 reply; 45+ messages in thread
From: Stefan Monnier @ 2019-08-19 21:12 UTC (permalink / raw)
  To: Paul Eggert; +Cc: Lars Ingebrigtsen, andrewjmoreton, Eli Zaretskii, emacs-devel

>> So maybe we should stop documenting "decoded time" objects as being of
>> the form (SEC MINUTE HOUR DAY MONTH YEAR DOW DST UTCOFF SUBSEC), but
>> instead say that it's a `decoded-time` object on which you can use
>> the `decoded-time-*` accessors (and provide a corresponding pcase pattern
>> to replace the `(,sec ,minute ...) one).
>
> That sounds like a reasonable way to move forward.
>
> I'm not a pcase expert. How would one go about doing the pcase stuff? That
> is, how does one provide a pattern so that code like (pcase time
> ((decoded-time :year y :month m) (some-expr-involving y & m))) can extract
> the year and month components Y and M from the broken-down time TIME that
> was created via (make-decoded-time :year y :month m)? Is there an example of
> doing this sort of thing somewhere?

With the cl-defstruct we use for decoded-time, we can already do:

    (pcase-let (((cl-struct decoded-time (second s) (minute m) hour day) time))
      ...)

so we could define

    (pcase-defmacro decoded-time (&rest fields)
      `(cl-struct decoded-time ,@fields))

OTOH it doesn't work in plain `pcase` currently because of a bug in the
`cl-struct` pcase pattern (it expands to code which does (cl-typep XX
'decoded-time) but the way `decoded-time` is defined makes it unusable
with `cl-typep`).


        Stefan




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

* Re: encode-time vs decode-time
  2019-08-19 21:12                                       ` Stefan Monnier
@ 2019-08-21 10:55                                         ` Adam Porter
  2019-08-21 20:20                                           ` Paul Eggert
  0 siblings, 1 reply; 45+ messages in thread
From: Adam Porter @ 2019-08-21 10:55 UTC (permalink / raw)
  To: emacs-devel

Forgive me for being late to this discussion.  After struggling with
using and manipulating date/time values in Emacs, I came up with this:

https://github.com/alphapapa/ts.el

It's probably not suitable for use in Emacs itself, but in my own
packages, it's greatly simplified parsing, manipulating, and accessing
timestamp values.

I hope that any changes to Emacs's time functions don't break it,
because it would require me to add compatibility code.  :)  But
enhancements that improve performance or usability would be great.




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

* Re: encode-time vs decode-time
  2019-08-21 10:55                                         ` Adam Porter
@ 2019-08-21 20:20                                           ` Paul Eggert
  2019-08-26 10:59                                             ` Adam Porter
  0 siblings, 1 reply; 45+ messages in thread
From: Paul Eggert @ 2019-08-21 20:20 UTC (permalink / raw)
  To: Adam Porter; +Cc: emacs-devel

Adam Porter wrote:
> Forgive me for being late to this discussion.  After struggling with
> using and manipulating date/time values in Emacs, I came up with this:
> 
> https://github.com/alphapapa/ts.el

I looked briefly at it, and don't see any compatibility issues - not that I 
understand all the code, which depends on packages I don't use.

The code's comments say that format-time-string is too slow. What performance 
issues did you run into? At any rate I think you'll find that this:

   (string-to-number (format-time-string "%Y" (ts-unix struct)))

is more efficient written this way:

   (nth 5 (decode-time (ts-unix struct)))

and I expect you can speed up the code further by caching the entire result of 
decode-time instead of calling format-time-string for each component.

Also, the timestamp functions in Emacs 27 should simplify ts.el, once you can 
assume Emacs 27. For example, in Emacs 27 you can do something like this:

   (decoded-time-add X (make-decoded-time :year 10))

to add 10 years to a broken-down timestamp X.

One more thing: ts.el's extensive use of float-time is fine for calendrical 
applications but has limited resolution (2**-22 s or about 2e-7 s for today's 
timestamps) and so would be problematic for apps requiring higher-resolution 
timestamps.



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

* Re: encode-time vs decode-time
  2019-08-21 20:20                                           ` Paul Eggert
@ 2019-08-26 10:59                                             ` Adam Porter
  2019-08-26 21:35                                               ` Paul Eggert
  0 siblings, 1 reply; 45+ messages in thread
From: Adam Porter @ 2019-08-26 10:59 UTC (permalink / raw)
  To: emacs-devel

Paul Eggert <eggert@cs.ucla.edu> writes:

Hi Paul,

> I looked briefly at it, and don't see any compatibility issues - not
> that I understand all the code, which depends on packages I don't use.

Thank you very much for looking at it.

> The code's comments say that format-time-string is too slow. What
> performance issues did you run into?

I didn't necessarily run into problems, but while doing some profiling,
I noticed that format-time-string was slower than I expected, so I tried
to implement some caching and avoid computing values unnecessarily.  You
can see some of my primitive test notes, e.g.:

https://github.com/alphapapa/ts.el/blob/master/notes.org#accessor-dispatch-vs-string-to-number-format-time-string

Some of those benchmarks may be out-of-date with respect to the current
code.

> At any rate I think you'll find
> that this:
>
>   (string-to-number (format-time-string "%Y" (ts-unix struct)))
>
> is more efficient written this way:
>
>   (nth 5 (decode-time (ts-unix struct)))

Thanks, I should have thought of that.

> and I expect you can speed up the code further by caching the entire
> result of decode-time instead of calling format-time-string for each
> component.

I do a form of that already in ts-fill by using format-time-string to
get all slot values and splitting the resulting string.  But that's a
good point, I might be able to use decode-time to improve it further.
I'll have to do some testing.  Thanks.

> Also, the timestamp functions in Emacs 27 should simplify ts.el, once
> you can assume Emacs 27. For example, in Emacs 27 you can do something
> like this:
>
>   (decoded-time-add X (make-decoded-time :year 10))
>
> to add 10 years to a broken-down timestamp X.

Thanks, I'll have to see if I can make use of those new functions.

> One more thing: ts.el's extensive use of float-time is fine for
> calendrical applications but has limited resolution (2**-22 s or about
> 2e-7 s for today's timestamps) and so would be problematic for apps
> requiring higher-resolution timestamps.

Right, thanks.  My use cases don't involve high-resolution timestamps,
so I decided to simplify things by only using Unix timestamp floats.
But there is a slot for internal time values, so if that were necessary,
the internal implementation could be changed to use them instead.

I'm curious, is Emacs fast enough or consistent enough to make use of
high-resolution timestamps anyway?  I guessed that, with GC pauses,
etc., it wouldn't be a suitable platform for such calculations in
real-time.  Although I guess if the timestamps were from outside Emacs,
one could operate on them anyway.

Grateful for your feedback!




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

* Re: encode-time vs decode-time
  2019-08-26 10:59                                             ` Adam Porter
@ 2019-08-26 21:35                                               ` Paul Eggert
  0 siblings, 0 replies; 45+ messages in thread
From: Paul Eggert @ 2019-08-26 21:35 UTC (permalink / raw)
  To: Adam Porter; +Cc: emacs-devel

Adam Porter wrote:
> I'm curious, is Emacs fast enough or consistent enough to make use of
> high-resolution timestamps anyway?  I guessed that, with GC pauses,
> etc., it wouldn't be a suitable platform for such calculations in
> real-time.  Although I guess if the timestamps were from outside Emacs,
> one could operate on them anyway.

You're right that calling (current-time) from Elisp won't give you nanosecond 
accuracy; on my platform, just calling that function takes a few hundred 
nanoseconds so some error is inherent. You're also right that Emacs isn't 
suitable for hard real-time applications.

As you mention, a common use for Emacs timestamps is to process times generated 
outside Emacs itself, e.g., file timestamps. On typical POSIXish platforms, file 
timestamps have nanosecond resolution and range from -2**63 to 2**63 seconds 
after the epoch, and Emacs can handle all of these timestamps though a few 
functions like decode-time will probably fail due to limitations of the 
underlying C libraries.



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

end of thread, other threads:[~2019-08-26 21:35 UTC | newest]

Thread overview: 45+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2019-07-29  9:48 Support for sub-second time in decoded time Lars Ingebrigtsen
2019-07-29 14:03 ` Stefan Monnier
2019-07-29 14:12   ` Lars Ingebrigtsen
2019-07-29 14:43     ` Stefan Monnier
2019-07-29 15:00       ` Lars Ingebrigtsen
2019-07-29 17:50         ` Stefan Monnier
2019-07-30 11:33           ` Lars Ingebrigtsen
2019-07-29 16:08     ` encode-time vs decode-time Stefan Monnier
2019-07-30 10:32       ` Lars Ingebrigtsen
2019-07-30 11:34         ` Andy Moreton
2019-07-30 11:37           ` Lars Ingebrigtsen
2019-07-30 17:54             ` Paul Eggert
2019-07-30 22:50               ` Paul Eggert
2019-07-31 19:03               ` Lars Ingebrigtsen
2019-07-31 19:31                 ` Stefan Monnier
2019-08-06  1:48                 ` Paul Eggert
2019-08-06 14:21                   ` Eli Zaretskii
2019-08-06 15:59                     ` Paul Eggert
2019-08-06 18:23                       ` Eli Zaretskii
2019-08-07  1:02                         ` Paul Eggert
2019-08-07  2:41                           ` Stefan Monnier
2019-08-07 14:47                             ` Eli Zaretskii
2019-08-11 23:39                               ` Lars Ingebrigtsen
2019-08-17  6:47                             ` Paul Eggert
2019-08-07 11:33                           ` Lars Ingebrigtsen
2019-08-17  7:54                             ` Paul Eggert
2019-08-17  8:16                               ` Eli Zaretskii
2019-08-17  9:33                                 ` Paul Eggert
2019-08-17 20:46                               ` Lars Ingebrigtsen
2019-08-17 20:56                                 ` Paul Eggert
2019-08-17 21:42                                   ` Stefan Monnier
2019-08-17 22:53                                     ` Paul Eggert
2019-08-19 21:12                                       ` Stefan Monnier
2019-08-21 10:55                                         ` Adam Porter
2019-08-21 20:20                                           ` Paul Eggert
2019-08-26 10:59                                             ` Adam Porter
2019-08-26 21:35                                               ` Paul Eggert
2019-08-07 14:44                           ` Eli Zaretskii
2019-08-07 11:41                   ` Lars Ingebrigtsen
2019-08-17  9:25                     ` Paul Eggert
2019-08-17 20:51                       ` Lars Ingebrigtsen
2019-07-29 14:23 ` Support for sub-second time in decoded time Eli Zaretskii
2019-07-29 14:59   ` Lars Ingebrigtsen
2019-07-29 16:46 ` Paul Eggert
2019-07-30 11:43   ` Lars Ingebrigtsen

Code repositories for project(s) associated with this external index

	https://git.savannah.gnu.org/cgit/emacs.git
	https://git.savannah.gnu.org/cgit/emacs/org-mode.git

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.