-
Notifications
You must be signed in to change notification settings - Fork 153
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Recursive calendar
or timeZone
property not rejected if property value is a Temporal object
#2104
Comments
Your polyfill trace checks out with how I interpret the spec text:
I wonder if it was maybe unintended that falling off the end of Step 1 of ToTemporalCalendar should go on to Step 2 where ToString is called on the object? Maybe there's a missing throw there? There are a couple of cases I could think of where this would make a difference: Temporal.PlainDate.from({
year: 2020, month: 1, day: 1, calendar: {
calendar: {
calendar: 'gregory',
toString() { return 'gregory'; },
},
},
});
// returns 2020-01-01[u-ca=gregory], with the built-in 'gregory' calendar,
// despite being a plain object with 3x nested calendar property
customCal = { toString() { return "not-builtin"; } };
date = new Temporal.PlainDate(2020, 1, 1, customCal);
Temporal.PlainDate.from({ year: 2020, month: 1, day: 1, calendar: { calendar: date } });
// throws RangeError "invalid calendar identifier 'not-builtin'"
// despite being the same invocation that succeeds in the example above |
This issue is complicated enough that it may be best to walk through in real time. My eyes glaze over when I try to understand all the various choose-your-own-adventure paths here. Should we cover this at a future meeting, or perhaps in a 1:1 call? |
Yes, let's talk over this in a 1:1 call and try to come up with a recommendation for the champions group, that we can decide quickly yes/no in the next meeting. |
@ptomato and I paired and came up with a solution: at the end of Step 1, instead of falling through, we'd require the However, we're not sure it's worth making this obscure change in Stage 3. Let's discuss.
|
We had some discussion on this at the 2022-05-12 champions meeting but didn't come to a conclusion, partly because it's difficult to understand what exactly is going on in the edge case. As for Justin's and my recommendation to change nothing, at least @gibson042 considers this worth fixing. We'll continue discussing this next time. |
Meeting, 2022-07-07: We will make the above fix that Justin outlined; given that we are proposing several other similarly subtle changes from implementor experience. |
Nested properties like e.g. { calendar: { calendar: plainDate } } should not be accepted, since we already disallow nested calendar properties in property bags elsewhere. Closes: #2104
I've found that the proposed fix is inconsistent: const bag = { year: 2022, month: 7, day: 11, calendar: "2016-12-31T23:59[u-ca=gregory]" };
Temporal.PlainDate.from(bag)
// returns a PlainDate with 'gregory' built-in calendar
Temporal.Calendar.from(bag)
// throws RangeError (expected: 'gregory' built-in calendar?) It seems to me that this is not intended, because it violates the principle that a property bag with the appropriate properties should be able to stand in for a Temporal object (and I'm not sure what to do with this. We may have to special-case |
Honestly, I find the success of If Calendar.from were designed like e.g. PlainDate.from, then there would be only be one code path for extracting a Calendar instance from the [[Calendar]] slot of a Temporal type instance or a non-Temporal-type-instance object's "calendar" property (the above-mentioned equivalence) and a mutually exclusive code path for serializing the input to a string and interpreting the result as a calendar type (NB: not for extracting a calendar type from input representing other data along with a calendar). The first code path would not accept the name of a calendar in the "calendar" property any more than PlainDate.from would accept a PlainDate instance in the "day" property of plain object input— I would like to see that model adopted, in which there is no deep processing and a property bag is instead subject to the same expectations as a Temporal type instance—specifically, that any "calendar" property value must be an object and not undergo further transformation. Would it break any relevant use cases? And (I hope) obviously, TimeZone and Calendar should behave similarly with respect to both |
@ptomato - does the original proposed fix in this comment have the same issue? @gibson042 - there are a some use cases where it's helpful to have function parseDateTimeOfInstantString(s) {
return Temporal.Instant.from(s).toZonedDateTimeISO(s).toPlainDateTime();
}
parseDateTimeOfInstantString('2020-01-01T12:34Z');
// => 2020-01-01T12:34:00
parseDateTimeOfInstantString('2020-01-01T12:34+02:00');
// => 2020-01-01T12:34:00 Another case: do two ISO strings share the same calendar, without needing to build different versions of this function for every Temporal type? (and while being able to compare strings of different types too) function hasSameCalendar (s1, s2) {
return Calendar.from(s1).id === Calendar.from(s2).id;
} I suspect there are more cases like this: admittedly not super-popular ones, but useful enough that I'd want to see some specific gain we get from removing this capability that outweighs the loss of cases like these. |
Yes, the issue arose with that fix. |
@justingrant Thank you for the examples. I appreciate the cleverness and convenience, but I've got to be honest—they both look like kludges to me, difficult to comprehend and probably rather brittle. The right way to handle them would probably be a generic multi-type parser of the sort that has been kicked down the road, and I'm personally uncomfortable with the workaround. |
It sounds like we need to withdraw this one from presenting in next week's meeting, as it's not clear-cut like we initially thought. |
In the 2022-10-27 Temporal champions meeting, we discussed this and didn't reach any agreement. However, if we go with the builtin-calendars-as-strings resolution of #1808, this issue will change substantially and might no longer need to be solved. So, the discussion is deferred until #1808 is resolved. |
Champions meeting, 2023-01-19: We have a resolution of #1808. It doesn't require a change to this issue, so the issue still needs to be solved. Therefore this goes back on the agenda next week. One proposal is to go with what Richard suggested, which I'll prepare a draft PR for. That PR may need to be staged on top of the solution to #1808. I just verified that the proposed solution for #1808 changes the example (4) above to throw a TypeError. I need to investigate whether this is an unavoidable consequence or an unintended side effect that can be removed. Regardless, we agreed in the meeting that we'd proceed with #1808 whether or not it affected the edge cases listed in this issue. |
Discussed in the champions meeting of 2023-01-26, although it took a while to work out the details. This explanation is partly a rehash of my comment on #2483. I'll be describing the situation with time zones but the same resolution was made for calendars, without loss of generality. Currently, the following kinds of input are accepted in ToTemporalTimeZone:
The builtin-time-zones-as-strings change discussed in #1808 (and implemented in PR #2482), makes it necessary to reexamine this list, because now ZonedDateTime's [[TimeZone]] internal slot may contain either a string (for fast, builtin time zone behaviour where engines can elide creating any objects) or an object (for custom time zone behaviour where the engine has to call methods on the object.) We don't want to create a situation where user code can easily and silently drop from the fast builtin time zone path into the slow custom time zone path. Note that the types of input given in (1), (3), and (4) are semantically time zones themselves, while the conversions in (2), (5), (6), and (7) are extracting a time zone from another object that is semantically not a time zone. We're conceptually splitting these cases into abstract operations ToTimeZone and ExtractTimeZone. (No guarantee that the spec text will actually use these names) We are dropping (7) altogether. It's now not worth the confusion about whether it produces a ZonedDateTime with a slow-path or fast-path time zone. Without (7), you always know: strings are fast builtin time zones, ZonedDateTime instances preserve whatever the instance already had, and other objects are slow custom time zones. For (2), even though this is, strictly speaking, extracting a time zone rather than accepting a time zone object, we'll continue to support this case in ToTimeZone so that it's easier and more natural for user code to stay on the fast path if it is already on it. Otherwise, you would have to use For (3), we'll do HasProperty checks on the required properties from the protocol, and throw a TypeError if the object doesn't implement the protocol. These checks are only observable by Proxy objects, so if the object isn't a Proxy and doesn't have one in its prototype chain, implementations are free to elide these checks. Interpretation of property bags that include a // builtin time zones:
Temporal.ZonedDateTime.from({ ...ymd, timeZone: 'Asia/Tokyo' })
Temporal.ZonedDateTime.from({ ...ymd, timeZone: '-07:00' })
Temporal.ZonedDateTime.from({ ...ymd, timeZone: Temporal.Now.zonedDateTimeISO() })
// custom time zones:
Temporal.ZonedDateTime.from({ ...ymd, timeZone: Temporal.TimeZone.from(...) })
Temporal.ZonedDateTime.from({ ...ymd, timeZone: { getOffsetNanosecondsFor(...) {...}, ... } }) The following are not good input for Temporal.ZonedDateTime.from({ ...ymd, timeZone: '2023-01-26T00[Asia/Tokyo]' })
Temporal.ZonedDateTime.from({ ...ymd, timeZone: {} }) We additionally considered allowing these property bags to alternately have a Interpretation of input passed where a time zone is expected, such as // builtin time zones:
instant.toZonedDateTimeISO('Asia/Tokyo')
instant.toZonedDateTimeISO('-07:00')
instant.toZonedDateTimeISO('2023-01-26T00Z')
instant.toZonedDateTimeISO('2023-01-26T00-07:00')
instant.toZonedDateTimeISO('2023-01-26T00[Asia/Tokyo]')
instant.toZonedDateTimeISO(Temporal.Now.zonedDateTimeISO())
// custom time zones:
instant.toZonedDateTimeISO(Temporal.Now.zonedDateTimeISO().getTimeZone())
instant.toZonedDateTimeISO({ getOffsetNanosecondsFor(...) {...}, ... }) Concretely, for the examples given in the OP of this thread: date = Temporal.PlainDate.from('2020-01-01');
/* 1 */ Temporal.PlainDate.from({ year: 2020, month: 1, day: 1, calendar: 'iso8601' });
// no exception, iso8601 calendar (unchanged)
/* 2 */ Temporal.PlainDate.from({ year: 2020, month: 1, day: 1, calendar: date });
// no exception, iso8601 calendar (unchanged)
/* 3 */ Temporal.PlainDate.from({ year: 2020, month: 1, day: 1, calendar: { calendar: 'iso8601' } });
// Before: no exception, iso8601 calendar
// After: TypeError, object doesn't implement the calendar protocol
/* 4 */ Temporal.PlainDate.from({ year: 2020, month: 1, day: 1, calendar: { calendar: date } });
// Before: no exception, iso8601 calendar (but the lack of an exception was the unexpected behaviour that caused this issue to be opened in the first place)
// After: TypeError, object doesn't implement the calendar protocol
/* 5 */ Temporal.PlainDate.from({ year: 2020, month: 1, day: 1, calendar: { calendar: { calendar: date } } });
// Before: RangeError (`[object Object]` is not parseable as an ISO string)
// After: TypeError, object doesn't implement the calendar protocol #2485 is updated as per these conclusions. |
Previously, "nested" time zone property bags were unwrapped up to one level. That is, this object: { timeZone: { // ...Temporal.TimeZone methods } } would not be considered to implement the TimeZone protocol, but would have its timeZone property used instead, if it were passed to an API that required a TimeZone protocol object. These nested property bags are no longer supported. Discussion: tc39/proposal-temporal#2104 (comment) Corresponding normative PR: tc39/proposal-temporal#2485
Checking whether an object implements the Calendar protocol is now done by means of HasProperty operations for each of the required methods unless the object already has the Calendar brand. Discussion: #2104 (comment) See: #2104
Previously, "nested" time zone property bags were unwrapped up to one level. That is, this object: { timeZone: { // ...Temporal.TimeZone methods } } would not be considered to implement the TimeZone protocol, but would have its timeZone property used instead, if it were passed to an API that required a TimeZone protocol object. These nested property bags are no longer supported. Discussion: #2104 (comment) See: #2104
Previously, "nested" calendar property bags were unwrapped up to one level. That is, this object: { calendar: { // ...Temporal.Calendar methods } } would not be considered to implement the Calendar protocol, but would have its calendar property used instead, if it were passed to an API that required a Calendar protocol object. These nested property bags are no longer supported. Discussion: #2104 (comment) See: #2104
Checking whether an object implements the TimeZone protocol is now done by means of HasProperty operations for each of the required methods unless the object already has the TimeZone brand. Discussion: #2104 (comment) See: #2104
Checking whether an object implements the Calendar protocol is now done by means of HasProperty operations for each of the required methods unless the object already has the Calendar brand. Discussion: #2104 (comment) See: #2104
Previously, "nested" time zone property bags were unwrapped up to one level. That is, this object: { timeZone: { // ...Temporal.TimeZone methods } } would not be considered to implement the TimeZone protocol, but would have its timeZone property used instead, if it were passed to an API that required a TimeZone protocol object. These nested property bags are no longer supported. Discussion: #2104 (comment) See: #2104 UPSTREAM_COMMIT=1b097998da0bc4bd2a98aaf3427ecca4d707f7ec
Previously, "nested" calendar property bags were unwrapped up to one level. That is, this object: { calendar: { // ...Temporal.Calendar methods } } would not be considered to implement the Calendar protocol, but would have its calendar property used instead, if it were passed to an API that required a Calendar protocol object. These nested property bags are no longer supported. Discussion: #2104 (comment) See: #2104 UPSTREAM_COMMIT=f431988a6eb4b02c056af820aa7f3c0d0d973d22
Checking whether an object implements the TimeZone protocol is now done by means of HasProperty operations for each of the required methods unless the object already has the TimeZone brand. Discussion: #2104 (comment) See: #2104 UPSTREAM_COMMIT=9852b94aaeee917e6822013afd89a3cc21294350
Checking whether an object implements the Calendar protocol is now done by means of HasProperty operations for each of the required methods unless the object already has the Calendar brand. Discussion: #2104 (comment) See: #2104 UPSTREAM_COMMIT=0ee7c6e2fc0f4b61fb01a4187fef949a3c10c57d
Previously, "nested" time zone property bags were unwrapped up to one level. That is, this object: { timeZone: { // ...Temporal.TimeZone methods } } would not be considered to implement the TimeZone protocol, but would have its timeZone property used instead, if it were passed to an API that required a TimeZone protocol object. These nested property bags are no longer supported. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=1b097998da0bc4bd2a98aaf3427ecca4d707f7ec
Previously, "nested" calendar property bags were unwrapped up to one level. That is, this object: { calendar: { // ...Temporal.Calendar methods } } would not be considered to implement the Calendar protocol, but would have its calendar property used instead, if it were passed to an API that required a Calendar protocol object. These nested property bags are no longer supported. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=f431988a6eb4b02c056af820aa7f3c0d0d973d22
Checking whether an object implements the TimeZone protocol is now done by means of HasProperty operations for each of the required methods unless the object already has the TimeZone brand. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=9852b94aaeee917e6822013afd89a3cc21294350
Checking whether an object implements the Calendar protocol is now done by means of HasProperty operations for each of the required methods unless the object already has the Calendar brand. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=0ee7c6e2fc0f4b61fb01a4187fef949a3c10c57d
Previously, "nested" time zone property bags were unwrapped up to one level. That is, this object: { timeZone: { // ...Temporal.TimeZone methods } } would not be considered to implement the TimeZone protocol, but would have its timeZone property used instead, if it were passed to an API that required a TimeZone protocol object. These nested property bags are no longer supported. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=1b097998da0bc4bd2a98aaf3427ecca4d707f7ec
Previously, "nested" calendar property bags were unwrapped up to one level. That is, this object: { calendar: { // ...Temporal.Calendar methods } } would not be considered to implement the Calendar protocol, but would have its calendar property used instead, if it were passed to an API that required a Calendar protocol object. These nested property bags are no longer supported. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=f431988a6eb4b02c056af820aa7f3c0d0d973d22
Checking whether an object implements the TimeZone protocol is now done by means of HasProperty operations for each of the required methods unless the object already has the TimeZone brand. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=9852b94aaeee917e6822013afd89a3cc21294350
Checking whether an object implements the Calendar protocol is now done by means of HasProperty operations for each of the required methods unless the object already has the Calendar brand. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=0ee7c6e2fc0f4b61fb01a4187fef949a3c10c57d
Previously, "nested" time zone property bags were unwrapped up to one level. That is, this object: { timeZone: { // ...Temporal.TimeZone methods } } would not be considered to implement the TimeZone protocol, but would have its timeZone property used instead, if it were passed to an API that required a TimeZone protocol object. These nested property bags are no longer supported. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=1b097998da0bc4bd2a98aaf3427ecca4d707f7ec
Previously, "nested" calendar property bags were unwrapped up to one level. That is, this object: { calendar: { // ...Temporal.Calendar methods } } would not be considered to implement the Calendar protocol, but would have its calendar property used instead, if it were passed to an API that required a Calendar protocol object. These nested property bags are no longer supported. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=f431988a6eb4b02c056af820aa7f3c0d0d973d22
Checking whether an object implements the TimeZone protocol is now done by means of HasProperty operations for each of the required methods unless the object already has the TimeZone brand. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=9852b94aaeee917e6822013afd89a3cc21294350
Checking whether an object implements the Calendar protocol is now done by means of HasProperty operations for each of the required methods unless the object already has the Calendar brand. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=0ee7c6e2fc0f4b61fb01a4187fef949a3c10c57d
Previously, "nested" time zone property bags were unwrapped up to one level. That is, this object: { timeZone: { // ...Temporal.TimeZone methods } } would not be considered to implement the TimeZone protocol, but would have its timeZone property used instead, if it were passed to an API that required a TimeZone protocol object. These nested property bags are no longer supported. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=1b097998da0bc4bd2a98aaf3427ecca4d707f7ec
Previously, "nested" calendar property bags were unwrapped up to one level. That is, this object: { calendar: { // ...Temporal.Calendar methods } } would not be considered to implement the Calendar protocol, but would have its calendar property used instead, if it were passed to an API that required a Calendar protocol object. These nested property bags are no longer supported. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=f431988a6eb4b02c056af820aa7f3c0d0d973d22
Checking whether an object implements the TimeZone protocol is now done by means of HasProperty operations for each of the required methods unless the object already has the TimeZone brand. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=9852b94aaeee917e6822013afd89a3cc21294350
Checking whether an object implements the Calendar protocol is now done by means of HasProperty operations for each of the required methods unless the object already has the Calendar brand. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=0ee7c6e2fc0f4b61fb01a4187fef949a3c10c57d
Previously, "nested" time zone property bags were unwrapped up to one level. That is, this object: { timeZone: { // ...Temporal.TimeZone methods } } would not be considered to implement the TimeZone protocol, but would have its timeZone property used instead, if it were passed to an API that required a TimeZone protocol object. These nested property bags are no longer supported. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=1b097998da0bc4bd2a98aaf3427ecca4d707f7ec
Previously, "nested" calendar property bags were unwrapped up to one level. That is, this object: { calendar: { // ...Temporal.Calendar methods } } would not be considered to implement the Calendar protocol, but would have its calendar property used instead, if it were passed to an API that required a Calendar protocol object. These nested property bags are no longer supported. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=f431988a6eb4b02c056af820aa7f3c0d0d973d22
Checking whether an object implements the TimeZone protocol is now done by means of HasProperty operations for each of the required methods unless the object already has the TimeZone brand. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=9852b94aaeee917e6822013afd89a3cc21294350
Checking whether an object implements the Calendar protocol is now done by means of HasProperty operations for each of the required methods unless the object already has the Calendar brand. Discussion: tc39/proposal-temporal#2104 (comment) See: #2104 UPSTREAM_COMMIT=0ee7c6e2fc0f4b61fb01a4187fef949a3c10c57d
According to #925 (comment):
and
I found an interesting case where the no-recursion limit may not hold true: if there's a 2-level recursive property bag where a non-TimeZone/non-Calendar Temporal instance is used as the second-level
calendar
ortimeZone
property value. Repro:Tracing through the polyfill code, what's happening in the unexpected case above is that the nested
calendar
property is not aCalendar
so it's assumed to be either a user-generated calendar or a string. Then it's coerced to a string:"2020-01-01"
. Finally, that string is passed toToTemporalCalendar
which doesn't see a calendar annotation in that string so returns the ISO calendar.As I remember, the reason recursion was prohibited was to prevent infinite loops. An infinite loop can't happen here. But the current behavior does feel at least somewhat contrary to our "no recursion" rule, because
date
isn't a calendar... instead the calendar is insidedate
which is one more level of recursion.Is this OK?
I'm inclined to say that the current behavior is not exactly as intended, but it doesn't seem to be harmful and we're in Stage 3, so we can just leave it as-is. But I wanted to check with the other champions in case I'm missing something.
The text was updated successfully, but these errors were encountered: