-
Notifications
You must be signed in to change notification settings - Fork 155
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
How to handle this code? Temporal.TimeZone.from({timeZone: 'Asia/Tokyo'}) #925
Comments
The thing missing from the above analysis is that a custom time zone or calendar can be indistinguishable from a property bag (e.g., The currently specified behaviour is for Temporal.TimeZone.from() to return its argument unchanged if it is an object, and everything else is converted to a string and looked up to see if it matches a known ID. I would be fine having Temporal.TimeZone.from() and Temporal.Calendar.from() take only strings. That would make it easier for people to monkeypatch them to make their own calendars globally available, which is currently a bit tricky to get right. But on the other hand, it's just as much of a design wart as the current situation. I don't think there are any good answers to this if we want to keep plain objects as custom calendars and time zones. |
@ptomato i would expect |
Could we require custom time zones and calendars to provide some kind of tag or brand to indicate what they are? This is a very advanced use case, so asking custom calendar/timezone authors to jump through one more hoop seems reasonable. Then any random property bag that lacked the tag could throw. |
Alternatively, won't custom calendars and/or timezones have at least one required method? If yes, we could test for that method and throw if it's missing. |
@justingrant i'd also expect TimeZone objects to have an internal slot that can be used to brand-check a TimeZone object, without needing to duck-type. |
It was decided in #300 that custom time zones and calendars don't have to be subclasses, they can be plain objects (and therefore calling Temporal.TimeZone.from() on them can't return an object with the TimeZone internal slot). |
@ptomato why can't it? It's totally fine that anything accepting a timezone also can accept a plain object, but why wouldn't the explicit method that's designed to create a TimeZone object (the way Array.from works, for example) be able to still adapt it to a TimeZone instance? Specifically, |
FWIW, one usage of I don't have a strong opinion about whether custom time zones should be derived classes or plain objects, but having such a "normalize a timezone-line input" function is quite useful, not just for Temporal but also for userland code that wants to accept timezone input parameters with the same range of formats that Temporal accepts. |
I agree it's useful; I feel pretty strongly that it needs to return an instance of a builtin type. |
If we can find out a way to make that work, that's fine, but what do you think (cc @Ms2ger who is working on making this consistent) |
For example, it could return a Temporal.TimeZone object that has the appropriate id, but has an own property |
How about ... class NotBuiltinTimeZone {
#offset;
constructor(offset) {
this.#offset = offset;
}
toString() {
return `not-builtin-${this.#offset}`;
}
getOffsetNanosecondsFor() {
return this.#offset;
}
}
Temporal.TimeZone.from(new NotBuiltinTimeZone(3600e9)) How should that get transformed into a builtin Temporal.TimeZone? There's a way to make it work by having some sort of wrapper Temporal.TimeZone alongside the offset and named variations, but what's the benefit of adding this sort of Frankenstein object to the language? I don't see the justification for a blanket rule of "the return value of X.from() must always be an instance of X". It's a good rule in general, but it conflicts with the equally good rule that non-subclass TimeZone objects can be used everywhere that subclass TimeZone objects can be. |
That’s a fair point, and a great justification for simply not supporting this use case. Users can pass a string, or a plain object, or a proper TimeZone. Why would anyone need to construct such a class and also be unable to |
I agree with what you say about a consistent subclassing story. I wonder if we shouldn't just remove the "plain object" kinds of time zones and calendars, and require subclassing. I'm not sure what the motivation was for allowing plain objects in the first place, since that was before my time. It's true we do allow for example plain
(by "plain object" here you mean "object whose |
Yes, i mean like conceptually "an options bag" or "a bag of fields", as opposed to a serialized value (string) or an actual proper TimeZone object with internal state (proper subclass). |
I admit I've never understood the importance of a plain-object custom timezone or calendar. I'd be in favor of dropping that requirement, but first I'd suggest that we try to figure out (from folks with a longer history than us) why this requirement got here in the first place. Philip, can we discuss at tomorrow's champions meeting? |
Is this in the agenda? |
Meeting 2020-10-15:
TimeZone.from({timeZone: {timeZone: 'Asia/Tokyo'}});
Calendar.from({calendar: {calendar: 'japanese'}});
(Clarifications below added 2022-01-20)
bag = { year: 2020, month: 1, day: 1, timeZone: 'Asia/Tokyo', calendar: 'japanese' };
zdt = Temporal.ZonedDateTime.from(bag);
instant = Temporal.Instant.from('2020-01-01T00:00Z');
plainDate = Temporal.PlainDate.from('2020-01-01');
plainTime = Temporal.PlainTime.from('00:00');
plainTime.toZonedDateTime({ timeZone: bag, plainDate });
instant.toZonedDateTime({ timeZone: 'Europe/London', calendar: bag});
instant.toZonedDateTime({ timeZone: bag, calendar: 'japanese' });
instant.toString({ timeZone: bag });
// the code below will currently throw, but if #2005 is approved then it won't throw
plainDate.toLocaleString('ja-JP', { timeZone: bag, calendar: bag });
new DateTimeFormat('ja-JP', { timeZone: bag, calendar: bag }); |
There's another problem with plain-object (non-subclassed) custom time zones: it's currently optional to implement many of TimeZone's methods. That's problematic if the custom timezone is stored on a ZonedDateTime instance because users who access the instance's Let's discuss over in #810 (comment) |
These are kept distinct from plain-object custom calendars by the absence of a 'calendar' property. This way, any Temporal type that carries a calendar, as well as any property bag of that type, can be passed to Temporal.Calendar.from(). See: #925
These are kept distinct from plain-object custom time zones by the absence of a 'timeZone' property. This way, any Temporal type that carries a time zone (currently only ZonedDateTime), as well as any ZonedDateTime property bag, can be passed to Temporal.TimeZone.from(). See: #925
These are kept distinct from plain-object custom calendars by the absence of a 'calendar' property. This way, any Temporal type that carries a calendar, as well as any property bag of that type, can be passed to Temporal.Calendar.from(). See: #925
These are kept distinct from plain-object custom time zones by the absence of a 'timeZone' property. This way, any Temporal type that carries a time zone (currently only ZonedDateTime), as well as any ZonedDateTime property bag, can be passed to Temporal.TimeZone.from(). See: #925
I'm a bit confused by this issue. Do we really need to support this use case? I would prefer that we just interpret strings as time zones, not options bags. We don't have any other meaningful things that we want to put in this options bag, do we? I think type systems are the appropriate thing to prevent this late error from problems with duck typing; I don't think we need to make this work. |
About #925 (comment) and #925 (comment) , I guess the way these could be unified is to say, Temporal.TimeZone.from always outputs a Temporal.TimeZone instance, but there's a separate cast algorithm which leaves things be if they are neither a string nor have a Temporal.TimeZone internal slot (with the presumption that it will meet the protocol). I'm not a big fan of the conclusions in #925 (comment) . I apologize for not participating in this discussion earlier. As I wrote above, I don't think these options bags should be a goal. Not working to detect the options bags would simplify points 1, 2, and 3. Point 4 was already settled in #300, I thought, but I'm baffled by the "cross-realm" point--everything about internal slots already is cross-realm, so what is this referring to? |
These are kept distinct from plain-object custom calendars by the absence of a 'calendar' property. This way, any Temporal type that carries a calendar, as well as any property bag of that type, can be passed to Temporal.Calendar.from(). See: #925
These are kept distinct from plain-object custom time zones by the absence of a 'timeZone' property. This way, any Temporal type that carries a time zone (currently only ZonedDateTime), as well as any ZonedDateTime property bag, can be passed to Temporal.TimeZone.from(). See: #925
@littledan I had forgotten about the comments above that I never answered. Are you OK with the status quo in https://tc39.es/proposal-temporal/#sec-temporal.timezone.from ? |
If passing a Temporal.ZonedDateTime (that is, a Temporal object with a [[TimeZone]] internal slot) where a Temporal.TimeZone is expected, then read that slot instead of doing a Get on the "timeZone" property. The Get operation was also not consistently done throughout the existing spec text, so fix that as well. This means the TimeZoneFrom operation becomes redundant, so remove it. Additionally, the check for the "timeZone" property on plain objects was not implemented according to the consensus in #925 (comment) and this change fixes that as well. See: #1428
If passing a Temporal.ZonedDateTime (that is, a Temporal object with a [[TimeZone]] internal slot) where a Temporal.TimeZone is expected, then read that slot instead of doing a Get on the "timeZone" property. The Get operation was also not consistently done throughout the existing spec text, so fix that as well. This means the TimeZoneFrom operation becomes redundant, so remove it. Additionally, the check for the "timeZone" property on plain objects was not implemented according to the consensus in #925 (comment) and this change fixes that as well. See: #1428
If passing a Temporal.ZonedDateTime (that is, a Temporal object with a [[TimeZone]] internal slot) where a Temporal.TimeZone is expected, then read that slot instead of doing a Get on the "timeZone" property. The Get operation was also not consistently done throughout the existing spec text, so fix that as well. This means the TimeZoneFrom operation becomes redundant, so remove it. Additionally, the check for the "timeZone" property on plain objects was not implemented according to the consensus in #925 (comment) and this change fixes that as well. See: #1428
If passing a Temporal.ZonedDateTime (that is, a Temporal object with a [[TimeZone]] internal slot) where a Temporal.TimeZone is expected, then read that slot instead of doing a Get on the "timeZone" property. The Get operation was also not consistently done throughout the existing spec text, so fix that as well. This means the TimeZoneFrom operation becomes redundant, so remove it. Additionally, the check for the "timeZone" property on plain objects was not implemented according to the consensus in #925 (comment) and this change fixes that as well. See: #1428
If passing a Temporal.ZonedDateTime (that is, a Temporal object with a [[TimeZone]] internal slot) where a Temporal.TimeZone is expected, then read that slot instead of doing a Get on the "timeZone" property. The Get operation was also not consistently done throughout the existing spec text, so fix that as well. This means the TimeZoneFrom operation becomes redundant, so remove it. Additionally, the check for the "timeZone" property on plain objects was not implemented according to the consensus in #925 (comment) and this change fixes that as well. See: #1428
If passing a Temporal.ZonedDateTime (that is, a Temporal object with a [[TimeZone]] internal slot) where a Temporal.TimeZone is expected, then read that slot instead of doing a Get on the "timeZone" property. The Get operation was also not consistently done throughout the existing spec text, so fix that as well. This means the TimeZoneFrom operation becomes redundant, so remove it. Additionally, the check for the "timeZone" property on plain objects was not implemented according to the consensus in tc39/proposal-temporal#925 (comment) and this change fixes that as well. See: #1428
Given our new pattern (#889) for conversion methods where multiple args go into a property bag, we can expect that at least some users will use this pattern in places where we currently don't expect it. Like this:
The current behavior of this code is bad. It returns without an exception but will fail downstream with a confusing error if you try to use the result for anything, e.g.
So it's a polyfill bug that needs fixing.
That said, in our decisions around #889 and #592, we agreed that all places in Temporal that currently accept a Temporal instance would be retrofitted to also support a string or a property bag.
But we didn't discuss how to handle places in Temporal where a string and Temporal instance is currently accepted but not a property bag. For example:
TimeZone.from
andCalendar.from
. This is essentially the inverse case of methods likeplus
that currently accept a property bag or an instance but not a string.My proposal would be to support a property bag input for those places too, so there's one consistent pattern used everywhere, that anywhere you can pass a Temporal object (including TimeZone and Calendar), then a string and a property bag should be accepted. I'm not saying we should promote this syntax, only that some users will probably assume it works and I don't see a downside from having consistency everywhere.
It also mirrors what we're discussing for handling the advice we give to developers about how to handle ISO strings, e.g.
I'd argue that for Calendar and TimeZone, we should accept two variants of the property bag: one for
id
and one for what we call the field ingetFields()
results from other types. Examples:If the plan above doesn't have consensus, then my alternate proposal would be that passing a property bag to those methods should throw.
The text was updated successfully, but these errors were encountered: