tc39 / proposal-temporal

Provides standard objects and functions for working with dates and times.
https://tc39.es/proposal-temporal/docs/
Other
3.35k stars 153 forks source link

Extended ISO string representation if time zone is an offset (not an IANA name) #703

Closed justingrant closed 4 years ago

justingrant commented 4 years ago

From #700 open issues:

What extended ISO string representation should be used when the time zone is an offset, not an IANA name? Should the offset be in brackets?

Example:

Temporal.LocalDateTime.from({ absolute: '2020-03-08T09:30Z', timeZone: '+07:00' }).toString();
// => "2020-03-08T16:30+07:00[+07:00]"
// but should it be this? => "2020-03-08T16:30+07:00"

The argument to repeat the offset in brackets is to prevent LocalDateTime from parsing ISO strings that lack a time zone identifier, in order to prevent implicitly treating a timezone-less value as if it had a time zone. The whole idea behind LocalDateTime is that time zones should never be implicit (with now.localDateTime as the only exception) because implicitly assuming a time zone is the path to timezone issues and DST bugs like we get with legacy Date.

But what's the argument on the other side to avoid emitting the duplicated offset in brackets? And if we did that, then should LocalDateTime still forbid parsing of bracket-less ISO strings?

sffc commented 4 years ago

I think I like the omit array of strings with 3 values: timeZone, offset, and calendar. If you want to omit only one, you don't need the [].

ptomato commented 4 years ago

Meeting, Sept. 17: we are agreed that this should be possible but not on the exact form the option should take. @mj1856 and @pipobscure proposed flipping it around to match the behaviour of Intl.DateTimeFormat. Could either of you elaborate so that we have something concrete to discuss?

justingrant commented 4 years ago

Following up from 2020-10-01 meeting :

  1. toString accepts an options bag with properties that map to components that should or shouldn't be displayed in the output.

    • 1.1 Options are usually string-valued. All options support include 'show'' and 'hide' strings. Some options accept other values too. Some options accept numbers too.
    • 1.2 Future Temporal releases can add options for more components, or can add choices to individual options.
  2. In V1, components will be:

    • 2.1 timeZone: 'show' | 'hide' - show or hide the bracketed time zone in the output. Default = show.
    • 2.2 calendar: 'show' | 'showNonISO' | 'hide' - show or hide the calendar in the output. Default = 'showNonISO'.. This could be useful to force showing the calendar (even for ISO) as well as force hiding it.
    • 2.3 timeZoneOffsetString: 'show' | 'hide' - show or hide the offset in the output. Default = 'show'.
    • 2.4 fractionalSeconds: 'show' | 'hide' | 0 | 3 | 6 | 9 - 'hide' is the same as 0. 'show' means to omit trailing zeroes. Default = 'show'. TO DISCUSS: Any need for 1,2,4,5,7,8?
    • 2.5 TO DISCUSS: Any more needed in V1, e..g ordinal day?
sffc commented 4 years ago

timeZone: 'show' | 'hide'

I was thinking "always" and false might be good choices here because it allows for a falsy test on whether or not the field is present. This is similar to the approach I've proposed for useGrouping in NumberFormat V3. A value of true maps to the default value.

calendar: 'show' | 'showNonISO' | 'hide'

What do you think about "always" and "auto", similar to the signDisplay options?

timeZoneOffsetString

Is there a reason you didn't make it timeZoneOffset?

fractionalSeconds

You could name it fractionalSecondDigits, like in NumberFormat, and the options could be "auto", false, and the integers.

justingrant commented 4 years ago

Proposal approved from meeting 2020-10-02:

  1. toString accepts an options bag with properties that map to components that should or shouldn't be displayed in the output.

    • 1.1 Options are usually string-valued. All options support include 'auto' and false strings. 'false' means "don't show". Some options accept other values too. Some options accept numbers too.
    • 1.2 Future Temporal releases can add options for more components, or can add choices to individual options.
  2. In V1, components will be:

    • 2.1 timeZone: 'auto' | false - show or hide the bracketed time zone in the output. Default = 'auto'.
    • 2.2 calendar: 'always' | 'auto' | false - show or hide the calendar in the output. Default = 'auto'. This could be useful to force showing the calendar (even for ISO) as well as force hiding it.
    • 2.3 offset: 'auto' | false - show or hide the offset in the output. Default = 'auto'.
    • 2.3.1 Note that if we decide to extend the ISO standard to allow sub-minute offsets, then we'd need to add options to allow constraining the offset to minutes or larger.
    • 2.4 fractionalSecondDigits: 'auto' | false | 'second' | 'millisecond' | 'microsecond' | 'nanosecond' | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 . Default = 'auto'.
    • 2.4.1 false is the same as 0.
    • 2.4.2 'auto' means to omit trailing zeroes. Seconds are always displayed.
    • 2.4.3 The unit names (second | millisecond | microsecond | nanosecond) all round with 'nearest'.
    • 2.4.4 The numeric versions should truncate.
gibson042 commented 4 years ago
  • 2.4.3 The unit names (second | millisecond | microsecond | nanosecond) all round with 'nearest'.
  • 2.4.4 The numeric versions should truncate.

This makes me uncomfortable, because there's basically no way to build intuition around it. I would propose instead adding an explicit roundingMode and defaulting it to either 'trunc' or 'nearest' (with a weak preference for 'trunc' to avoid surprises in which the default behavior can flip any element, e.g. representing "2019-12-31T11:59:59.800" as "2020-01-01T00:00:00").

ptomato commented 4 years ago

I would also be fine with just not having rounding (it can be added later) since we also have a round() method already.

gibson042 commented 4 years ago

True, but removing rounding would also mean renaming fractionalSecondDigits to something like minimumFractionalSecondDigits (since a value like "2019-12-31T11:59:59.800" cannot be accurately represented with a precision of seconds). On balance, I think it's better to let the option specify exact precision (which is then necessarily subject to a choice of rounding mode, whether or not practitioners are allowed to override it).

ptomato commented 4 years ago

Sorry, I should have worded that more carefully, I meant I'd be fine with always truncating to the requested number of digits, since you can pick one of the other rounding modes by calling round() before toString().

gibson042 commented 4 years ago

If Temporal is going to expose rounding in arithmetic methods (supporting e.g. x.plus("PT1H", {smallestUnit: "second"}) as shorthand for x.plus("PT1H").round({smallestUnit: "second"})), then I think it should also do so in serialization.

justingrant commented 4 years ago

Proposal is updated below from meeting 2020-10-09.

@sffc - we opted to remove false because we were concerned about the ambiguity of true and also we found 'never' used elsewhere in Intl's signDisplay option for exactly this purpose. Feel free to follow up if you disagree.

  1. toString accepts an options bag with properties that map to components that should or shouldn't be displayed in the output, as well as options that control how individual components are displayed.

    • 1.1 Options are usually string-valued, although some options accept numbers too.
    • 1.2 Future Temporal releases can add options for more components, or can add choices to individual options.
  2. In V1, components will be:

    • 2.1 timeZoneName: 'auto' | 'never' - show or hide the bracketed time zone in the output. (BTW, we chose "never" because it's already used in Intl NumberFormat's signDisplay option)
    • 2.1.1 Default = 'auto'.
    • 2.2 calendar: 'always' | 'auto' | 'never' - show or hide the calendar in the output.
    • 2.2.1 Default = 'auto'.
    • 2.2.2 This could be useful to force showing the calendar (even for ISO) as well as force hiding it.
    • 2.3 offset: 'auto' | 'never' - show or hide the offset in the output. Default = 'auto'.
    • 2.3.1 Note that if we decide to extend the ISO standard to allow sub-minute offsets, then we'd need to add options to allow constraining the offset to minutes or larger.
    • 2.4 smallestUnit - Don't emit any unit smaller than this one.
    • 2.4.1 Limited to the units that are 'minutes' or smaller because minutes are required to be a valid ISO string but smaller units are optional.
    • 2.4.2 If omitted, the default behavior is that seconds and larger units will always be displayed, and if there are nonzero fractional seconds units then they will be displayed too unless overridden by fractionalSecondDigits.
    • 2.5 roundingMode - controls rounding, works same as round()
    • 2.5.1 Default is 'truncate'
    • 2.6 roundingIncrement will not be supported because it's not core to the output use case.
    • 2.7 fractionalSecondDigits: control the number of fractional digits output for the seconds unit.
    • 2.7.1 Choices are: 'auto' | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9.
    • 2.7.2 Default depends on smallestUnit, but can be overridden
    • 2.7.2.1 'minutes': n/a (no digits are shown, so fractionalSecondDigits is ignored)
    • 2.7.2.2 'seconds': 0
    • 2.7.2.3 'milliseconds': 3
    • 2.7.2.4 'microseconds': 6
    • 2.7.2.5 'nanoseconds': 9
    • 2.7.2.6 undefined: 'auto'
    • 2.7.3 'auto' means to show seconds and any fractional seconds but to omit trailing zeroes in fractional seconds.
ptomato commented 4 years ago

Technically in 2.4.2 there is no default, because of 2.7.2.6. Otherwise :+1:, this looks ready for implementation.

justingrant commented 4 years ago

Technically in 2.4.2 there is no default, because of 2.7.2.6. Otherwise ๐Ÿ‘, this looks ready for implementation.

OK I updated the text which should cure the ambiguity. Now fractionalSecondDigits always depends on smallestUnit which has a new default as follows:

  • 2.4.2 If omitted, the default is the smallest nonzero unit in the Duration, or 'seconds', whichever is the smaller unit.
justingrant commented 4 years ago

Technically in 2.4.2 there is no default, because of 2.7.2.6. Otherwise ๐Ÿ‘, this looks ready for implementation.

OK I updated the text which should cure the ambiguity. Now fractionalSecondDigits always depends on smallestUnit which has a new default as follows:

  • 2.4.2 If omitted, the default is the smallest nonzero unit in the Duration, or 'seconds', whichever is the smaller unit.

Actually, my revision above wasn't accurate. I replaced with this one:

  • 2.4.2 If omitted, the default behavior is that seconds and larger units will always be displayed, and if there are nonzero fractional seconds units then they will be displayed too unless overridden by fractionalSecondDigits.
ptomato commented 4 years ago

I've taken the liberty of renaming timeZone to timeZoneName since otherwise the meaning conflicts with that of the timeZone option to be added in #741. This distinction between timeZone and timeZoneName is also exactly the distinction that Intl.DateTimeFormat makes, so I think it works out well.

justingrant commented 4 years ago

Sounds good. I edited https://github.com/tc39/proposal-temporal/issues/703#issuecomment-706314122 accordingly.