Skip to content
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

Most of methods of Temporal.PlainDate requires a fresh new Calendar instance for each Temporal.PlainDate #1808

Closed
Constellation opened this issue Sep 8, 2021 · 103 comments
Assignees
Labels
normative Would be a normative change to the proposal spec-text Specification text involved
Milestone

Comments

@Constellation
Copy link
Member

Constellation commented Sep 8, 2021

The current spec creates a fresh instance for each Temporal.PlainDate. Since most of fields of Temporal.PlainDate touches calendars, Temporal.PlainDate almost always requires two objects per instance.

This fresh object is meaningful only when the user adds adhoc method to these created calendar instances after creating Temporal.PlainDate. But the use of custom calendar can be cleanly achieved by passing an optional calendar parameter to the Temporal.PlainDate constructor.

So, how about removing necessity of creating a calendar object for known calendars for performance and memory saving?

@ptomato
Copy link
Collaborator

ptomato commented Sep 15, 2021

My thinking on this topic so far has been that it will be mostly unnecessary to create calendar objects. If the following three conditions hold, then you won't need to create the object:

  1. Temporal.Calendar.prototype is untouched
  2. The calendar is a built-in calendar (I'm guessing iso8601 and the built-in human calendars will account for 99.9% of usage)
  3. The .calendar property of the Temporal object isn't accessed

It's annoying that the implementation would have to keep checking that condition 1 holds every time you would have to call a calendar method. But otherwise I expect that in the vast majority of applications, these 3 conditions will hold, and implementations can elide the calendar objects while still maintaining the same semantics as if they were created.

Do you have any ideas already in mind for how to remove the necessity of creating calendar objects?

Related: #1432; #1588; thread starting at #1294 (comment) for an exploration of a different way that it could work and the weirdnesses that that brings in.

@gibson042
Copy link
Collaborator

gibson042 commented Nov 10, 2021

I don't think implementation optimizations are the right perspective from which to approach this issue. The question is instead whether or not the following kinds of action-at-a-distance (and possibly covert communication) should be supported.

const instance = Temporal.PlainDate.from("2021-11-10");
Temporal.Calendar.from("iso8601").year = () => 0;
instance.year; // => 0

@ptomato
Copy link
Collaborator

ptomato commented Nov 18, 2021

Here's my summary of what we discussed in the meeting of 2021-11-18.

  • We consider that the action-at-a-distance demonstrated by @gibson042 above would be a problem, if it were possible. (With the status quo, it is not possible.) So any change must maintain that this is disallowed. We estimate that this opinion is probably widely shared by others in committee.
  • According to @Constellation the optimization that I proposed above is acceptable, but we all agree that it would be desirable to make a change that would make this simpler to optimize, if such a change exists and the tradeoffs are acceptable.
  • Freezing all built-in calendar instances would make it possible to share them between PlainDate etc. instances and reduce the overhead of creating a new one each time, but it would still be difficult to optimize because user code can still mess with Temporal.Calendar.prototype.
  • Freezing Temporal.Calendar.prototype as well as the built-in calendars would solve that problem but would disrupt monkey-patching, which is a use case that we think should be preserved.
  • For implementors it's important to be able to easily determine "this calendar is built-in and unmodified" so they know whether it would be unobservable to optimize by skipping calling the calendar methods and just using the built-in calendar logic directly.
  • @Constellation suggests two possibilities, which we think are worth investigating, although we may still conclude to keep the status quo:
    1. built-in calendar instances are frozen, shared between Temporal objects, and have own-property methods on them that shadow the ones on Temporal.Calendar.prototype.
    2. built-in calendar instances are frozen, shared between Temporal objects, and inherit from a (let's call it) BuiltInCalendar class which itself inherits from Temporal.Calendar, and BuiltInCalendar's prototype is frozen.
  • I can do this investigation eventually, but I won't have time in the short term, so any help is welcome.

@ptomato ptomato self-assigned this Apr 11, 2022
@ptomato
Copy link
Collaborator

ptomato commented Apr 12, 2022

I have done some initial investigation around this and will continue by figuring out how to express these changes in the spec text.

(1) Built-in calendar objects have own-property methods: https:/tc39/proposal-temporal/compare/frozen-builtin-calendar-1?expand=1
(2) Built-in calendar objects inherit from BuiltinCalendar: https:/tc39/proposal-temporal/compare/frozen-builtin-calendar-2?expand=1

In both branches you can see some tests that illustrate what this change would mean for Calendar objects. You can view the branches commit-by-commit to see what would change between the two approaches.

A few things to note:

  • This only covers Calendar objects, of course we'd want to extend it to TimeZone objects as well.
  • I'm assuming that "the constructor should always return a new object" is an invariant we wouldn't want to break. That has some possibly weird consequences: Calendar.from returns a frozen, shared, built-in calendar, and new Calendar doesn't. However, constructors being for low-level advanced use has plenty of precedent elsewhere in Temporal.
  • In approach (2), it's unclear to me whether BuiltinCalendar should be exposed as a property on the Temporal object (although omitting it wouldn't prevent you from reaching it from user code of course) and what should happen if you new it.

Please let me know if you have any opinions about this.

@ptomato
Copy link
Collaborator

ptomato commented Sep 10, 2022

I haven't had any comments in the meantime, so I intend to proceed with approach (1) after the September plenary.

@anba
Copy link
Contributor

anba commented Sep 12, 2022

  1. In Discoverability of wrapper prototypes proposal-iterator-helpers#173, there was a requirement that all intrinsics should be reachable from the global object. Is this a non-issue here, because the built-in calendars and all their own properties are frozen?

  2. The approach to add all built-in Calendar methods as own properties seems to cover this comment:

For implementors it's important to be able to easily determine "this calendar is built-in and unmodified" so they know whether it would be unobservable to optimize by skipping calling the calendar methods and just using the built-in calendar logic directly.

It's probably worth mentioning that this will make it more easier to optimise Calendar/TimeZone methods in the interpreter, but will probably require special handing in the JITs. For example if timeZone.toString() is called with different built-in TimeZone objects, the JIT compiler will see distinct toString functions.

  1. In https:/tc39/proposal-temporal/compare/frozen-builtin-calendar-1?expand=1, every copied intrinsic is (essentially) a bound function (except for the "id" getter). So this is now valid: let {year} = Temporal.Calendar.from('iso8601'); year(2022);. I'm not sure how I feel about adding bound functions, because it means let {year} = calendar; year(2022); only works as long as only built-in Calendar object are used. This could lead to confusing errors when user-defined calendars are used.

I'm still not convinced that we need this optimisation. Especially copying every built-in method seems questionable to me. Checking if a built-in Calendar uses the built-in Calendar methods shouldn't be too hard to implement in engines and fairly fast. (Should be fast because there are only memory loads and comparisons.) Engines typically know how to implement this sequence of operations:

if (ClassOf(calendar) is not Temporal.Calendar) bail;

// If built-in calendars aren't frozen, check if any own properties are
// defined which may shadow built-in Calendar.prototype methods.
// (This is a cheap check, but can lead to deoptimisations when unrelated properties
// are added.)
if (HasOwnProperties(calendar)) bail;

// If built-in calendars aren't frozen, check we still use the initial prototype.
if (GetPrototypeOf(calendar) != Temporal.Calendar.prototype) bail;

// Either bail or handle updating the shape of Temporal.Calendar.prototype.
if (ShapeOf(Temporal.Calendar.prototype) != InitialShape(Temporal.Calendar.prototype)) bail;

// If the shape is still optimisable, we know where the Calendar method named "method" is
// stored. Get the current value and compare it against the expected value.
if (GetValue(Temporal.Calendar.prototype, Slot("method")) != InitialValue("method")) bail;

@ptomato
Copy link
Collaborator

ptomato commented Sep 12, 2022

Thanks for the comments!

  1. In Discoverability of wrapper prototypes proposal-iterator-helpers#173, there was a requirement that all intrinsics should be reachable from the global object. Is this a non-issue here, because the built-in calendars and all their own properties are frozen?

I intend to take this proposal to the SES meeting and discuss it there before discussing it in TC39. My understanding is that the way that adding new unreachable intrinsincs would "break existing code" is that the code expects to be able to reach them by walking the properties of the global object in order to freeze them. So, it seems like if they were already frozen that wouldn't apply.

I considered whether we could make them reachable by walking the properties, which seems doable for calendars (and you could test for support of a particular calendar with Temporal.Calendar.DISCORDIAN !== undefined; I'm not sure if that's an advantage or a disadvantage) but it doesn't seem practical for time zones.

  1. The approach to add all built-in Calendar methods as own properties seems to cover this comment:

For implementors it's important to be able to easily determine "this calendar is built-in and unmodified" so they know whether it would be unobservable to optimize by skipping calling the calendar methods and just using the built-in calendar logic directly.

It's probably worth mentioning that this will make it more easier to optimise Calendar/TimeZone methods in the interpreter, but will probably require special handing in the JITs. For example if timeZone.toString() is called with different built-in TimeZone objects, the JIT compiler will see distinct toString functions.

I'm glad you raised this. How big of a problem is it? I don't have a sense of whether it'd be a minor annoyance for JIT implementors or something that would break all sorts of assumptions.

  1. In https:/tc39/proposal-temporal/compare/frozen-builtin-calendar-1?expand=1, every copied intrinsic is (essentially) a bound function (except for the "id" getter). So this is now valid: let {year} = Temporal.Calendar.from('iso8601'); year(2022);. I'm not sure how I feel about adding bound functions, because it means let {year} = calendar; year(2022); only works as long as only built-in Calendar object are used. This could lead to confusing errors when user-defined calendars are used.

That sounds like a good reason to go for approach 2 rather than 1, if there are no larger disadvantages.

Although in approach 2 let {year} = calendar; would give you a different year method depending on whether calendar was a built-in or user calendar. I'm not sure whether that's a concern.

I'm still not convinced that we need this optimisation. Especially copying every built-in method seems questionable to me. Checking if a built-in Calendar uses the built-in Calendar methods shouldn't be too hard to implement in engines and fairly fast. (Should be fast because there are only memory loads and comparisons.) Engines typically know how to implement this sequence of operations:

This is helpful too. This is a more detailed version of my hand-wavy assumption in #1808 (comment) that engines could optimize this. All other things being equal, I'd be happy not to make a change here. Maybe @Constellation and @FrankYFTang could weigh in about these checks?

@anba
Copy link
Contributor

anba commented Sep 15, 2022

How big of a problem is it? I don't have a sense of whether it'd be a minor annoyance for JIT implementors or something that would break all sorts of assumptions.

It kind of depends on the exact specification. For example it's more complicated if every built-in Calendar/TimeZone method is a bound function. It should be less complicated if the methods are the same function object for all built-in Calendar/TimeZone objects. That means if Temporal.Calendar.from("iso8601").year === Temporal.Calendar.from("gregory").year. It's also less complicated when approach 2 is used. (This is from my SpiderMonkey POV, not sure if other engines have better support for polymorphic calls through bound functions.)

Although in approach 2 let {year} = calendar; would give you a different year method depending on whether calendar was a built-in or user calendar.

That's also the case for approach 1, isn't it?

With approach 1:

var iso8601 = Temporal.Calendar.from("iso8601");
var gregorian = Temporal.Calendar.from("gregory");

assert.notSameValue(iso8601.year, gregorian.year);
assert.notSameValue(iso8601.year, Temporal.Calendar.prototype.year);
assert.notSameValue(gregorian.year, Temporal.Calendar.prototype.year);

With approach 2:

var iso8601 = Temporal.Calendar.from("iso8601");
var gregorian = Temporal.Calendar.from("gregory");

assert.sameValue(iso8601.year, gregorian.year);
assert.notSameValue(iso8601.year, Temporal.Calendar.prototype.year);
assert.notSameValue(gregorian.year, Temporal.Calendar.prototype.year);

webkit-early-warning-system pushed a commit to rkirsling/WebKit that referenced this issue Sep 16, 2022
https://bugs.webkit.org/show_bug.cgi?id=245282

Reviewed by Yusuke Suzuki.

This patch implements the following Temporal API methods:
  - PlainDate.prototype.with
  - PlainDateTime.prototype.{with, withPlainDate, withPlainTime, round}

Note: `with` will likely need significant refactoring in the future. At present, the spec demands bafflingly inefficient
behavior, observably calling out to Calendar multiple times and creating multiple intermediate JSObjects, even in the
default case where the user is not making conscious use of Calendar at all. This is a showstopper, but one which Yusuke
already reported in tc39/proposal-temporal#1808.

* JSTests/stress/temporal-plaindate.js:
* JSTests/stress/temporal-plaindatetime.js:
* JSTests/test262/config.yaml:
* JSTests/test262/expectations.yaml:
* Source/JavaScriptCore/runtime/TemporalCalendar.cpp:
(JSC::TemporalCalendar::from):
(JSC::TemporalCalendar::isoDateFromFields): Split into two.
* Source/JavaScriptCore/runtime/TemporalCalendar.h:
* Source/JavaScriptCore/runtime/TemporalObject.cpp:
(JSC::rejectObjectWithCalendarOrTimeZone): Added.
* Source/JavaScriptCore/runtime/TemporalObject.h:
* Source/JavaScriptCore/runtime/TemporalPlainDate.cpp:
(JSC::TemporalPlainDate::toPartialDate): Added.
(JSC::TemporalPlainDate::with): Added.
* Source/JavaScriptCore/runtime/TemporalPlainDate.h:
* Source/JavaScriptCore/runtime/TemporalPlainDatePrototype.cpp:
* Source/JavaScriptCore/runtime/TemporalPlainDateTime.cpp:
(JSC::TemporalPlainDateTime::with): Added.
(JSC::TemporalPlainDateTime::round): Added.
* Source/JavaScriptCore/runtime/TemporalPlainDateTime.h:
* Source/JavaScriptCore/runtime/TemporalPlainDateTimePrototype.cpp:
* Source/JavaScriptCore/runtime/TemporalPlainTime.cpp:
(JSC::TemporalPlainTime::roundTime):
(JSC::TemporalPlainTime::toTemporalTimeRecord):
(JSC::TemporalPlainTime::toPartialTime):
(JSC::TemporalPlainTime::with const):
(JSC::toPartialTime): Made public static.
* Source/JavaScriptCore/runtime/TemporalPlainTime.h:

Canonical link: https://commits.webkit.org/254565@main
@littledan
Copy link
Member

I think this issue is an important one to address, since the optimization cited earlier is very fragile: It needs to be turned off both whenever there are any changes to the Timezone/Calendar prototypes, and whenever the calendar accessor is explicitly used. ES6 introduced the need for a number of such fragile optimizations, and this lead to performance cliffs when expectations are violated, which are ultimately not a great experience for everyone all around.

At the same time, I'd be cautious about using frozen classes to enable singleton instances. The most natural way to freeze classes would be to make Temporal.Calendar and Temporal.TimeZone deeply frozen, but this prevents important polyfilling use cases, as discussed above. The alternatives, based on a parallel class hierarchy or many own methods, have a couple downsides (1) they add a bunch of complexity and differ from the normal pattern of built-in classes (2) as @anba pointed out, they introduce built-in values which aren't reachable from the global object just through property access and not method calls.

To avoid these problems, I'd suggest a different design. The following is based on significant discussion with @gibson042 @ptomato and @pipobscure who helped me refine it.

The data model of calendars and timezones, and occur as internal slots in dates and times and such, changes from "always an object with methods for operations" to "either an object or a string". If a string is provided as the argument to a date/time constructor, or if the Now/iso APIs are used, or the default iso calendar from certain date APIs appears, then rather than creating a Temporal.Calendar or Temporal.TimeZone instance, the built-in calendar/timezone is represented directly as the string that was going to be passed into that constructor.

For example, the .calendar accessor may return either a string or an object, directly reflecting the data model. You can always use Temporal.Calendar.from, either on the date or the .calendar, to get something that you can then call calendar methods on. Each time you do Temporal.Calendar.from(aDateWithISOCalendar), you'll get a new ISO calendar instance, even though it's the same date, since it's recreating something new from the "gregory" string.

When an operation is done on a calendar or timezone, the logic changes from "call the appropriate method and use the result" to "if this is a string, directly apply the correct built-in algorithm; otherwise, call the right method on the calendar/timezone object".

In the end, the observable behavior is extremely similar to if we used frozen classes for singleton objects for built-in timezones and calendars, but without all the complexity around the object model of those instances, and substituting that for the complexity of the logic to switch between the two cases at method call sites in internal algorithms. Frozen classes and the string-based system are similar in that there is no risk of a "performance cliff" from optimizations that may turn off if you get them confused.

What do you think?

@sffc
Copy link
Collaborator

sffc commented Oct 12, 2022

I like the direction of using a primitive to represent built-in calendars and time zones.

At what point do we check that we support the calendar or time zone ID? Do we verify eagerly, or do we wait until we actually need it to perform an operation?

Is the string calendar/time zone ID the correct primitive, or should we consider e.g. symbols as the primitive?

@justingrant
Copy link
Collaborator

The proposed solution from @littledan is intriguing. I see its advantages, especially for implementers. Is this "string or object" pattern used in other built-in objects, either for method return values or object properties? (Other than types like Set whose entire raison d'être is storing and returning arbitrary userland values.)

I admittedly don't understand the downsides of the frozen object alternative. @littledan, I heard from @ptomato that you have some insight about challenges (technical as well as more opinionated concerns from committee members) about freezing. Could you share those here?

I also see some significant challenges from the perspective of app developers and userland libraries... challenges which would go away if a frozen-object solution were adopted. I'll follow up with another post soon to explain the problems I see for app developers, but in parallel I'd also like to understand the problems with frozen objects, which might outweigh the problems I'm seeing for "string or object" properties.

@justingrant
Copy link
Collaborator

I also see some significant challenges from the perspective of app developers and userland libraries... challenges which would go away if a frozen-object solution were adopted.

Here's a few concerns around the "string or object" solution:

TypeScript

One problem is library code:

// library.ts
export function nowInMyTimeZone(timeZone?: string) {
 return Temporal.Now.zonedDateTimeISO(timeZone); 
}

// app.ts
function doSomething(zdt: Temporal.ZonedDateTime) {
  const now = nowInMyTimeZone(zdt.timeZone); // ERROR!
  ...
}

Library authors (including people in your own company) will probably assume that TZ is always a string, which puts callers of that code in a bind. They can lie to TS and claim it's a string, knowing that it will probably be OK. Or they can try to get the library author to change their signature, which can often be hard or impossible depending on the maintainer.

This could also happen with the current API. Nothing stops library authors from being more restrictive. But I suspect that having the properties be strings almost all the time will make it more likely.

There's also an easy-to-fix but annoying ergonomic issue, where type casts will be needed in a lot of places.

let tz = 'America/Los_Angeles';
if (someCondition) tz = zdt.timeZone; // ERROR!  

The core problem is that even if a developer's code has no custom timezones/calendars, TypeScript doesn't know this so type casts are required. And if the developer adds type casts, then the code becomes brittle if, later, custom timezones/calendars are added.

Challenges with ===

If developers assume (as most probably will) that .timeZone / .calendar is always a string, then it's obvious and ergonomic to write code like this:

if (timeZone === 'UTC') { ... }

This code will almost always work, but it will break if timeZone is an object like Temporal.TimeZone.from('UTC'). This can make code, esp. library code, vulnerable to subtle bugs.

Challenges with ===, cont'd: React Hooks

Some libraries use === (or similar APIs like Object.is) to make decisions about whether a value has changed. The most obvious example is how some React APIs depend on fixed object identity of the strings, which then breaks for custom TZ/Calendars.

For example, React's useCallback API accepts a dependencies array parameter. React uses those dependencies to decide if a component needs to be re-rendered.

With the current Temporal API, developers using useCallback will know that they need to always call .id or .toString() before dropping a timezone into a dependency array. With the new API, they'll probably just drop the .timeZone value as-is. If the timezone is custom, or if the code has been passed a built-in timezone object instead of a string, then it'd cause React to mistakenly assume that the dependencies have changed every time.

Conditional Logic

I suspect that most developers will just assume that timezones and calendars will always be strings, because that's what almost all code will return for the vast majority of programs where there's no custom timezones or calendars.

But conscientious developers (including many library developers but sadly not all of them!) will know that these values can sometimes be objects. Defending against this rare case will require conditional code wherever timezone methods are called, wherever timezone/calendars are tested for equality, and probably a few other cases I'm not thinking of.

Ideally we could avoid the additional complexity (and code size and...) of conditional code.

@ljharb
Copy link
Member

ljharb commented Oct 27, 2022

Given that there’s TS syntax to extract the type of the first argument from zonedDateTimeISO, and the DT types would probably export this as a type for convenience, if a dev only accepts a string they just have a bug. I don’t think we need to be concerned with this case in TS when designing JS.

@littledan
Copy link
Member

Apologies for my delayed response here. It will take me more time to consider how significant the downsides of object-or-string are, but here's my reasoning for avoiding frozen objects:

If we make a frozen class, we have two choices, both of which violate constraints that we've discussed in this thread:

  • Expose it via a property (of a property of a property...) of the global object: Exposed APIs should usually be monkey-patchable (per @ljharb, and I agree), but this is frozen, so it violates that goal.
  • Don't expose it as a property (...) of the global object: This creates many built-in objects and functions which are not reachable from the global object. Per SES, we cannot make more of these, or we would need an extremely good reason to do so. It also wouldn't absolve us of creating a new Calendar/Timezone identity each time, since all object identities need to be reachable from the global object. (As we've been discussing, maybe we could expose all calendars as properties, since the set of calendars is somewhat small and stable, but this just wouldn't work for timezones.)

I'm also motivated by a possibly-weaker rationale for the object-or-string version: I see it as having a smaller "surface area". Now, this is a very debatable concept--exposing strings is its own surface area--but making this whole parallel class, which then implementations are supposed to realize they can elide, seems to me like a lot of "stuff" when there's a lighter-weight option sitting right there.

I think JS is based on pervasive "union types", so it wouldn't be out of character to use one here. I do see your point that APIs that expect a timezone object should now also accept a string, if we adopt this change.

@justingrant
Copy link
Collaborator

justingrant commented Oct 27, 2022

@littledan I think JS is based on pervasive "union types", so it wouldn't be out of character to use one here.

My understanding is that unions (aka permissive typing) are used extensively in parameters to methods and constructors, as well as in coercion. In other words, as input.

But my understanding is that the language generally canonicalizes input into well-known types on output generated by the language. Is this assumption correct?

Are there any other cases in the language today where the return type of a method or property getter is a union type? (Other than the obvious exceptions of Array, Set, etc. who's entire purpose is to expose user data which could be a union.)

@justingrant
Copy link
Collaborator

justingrant commented Oct 27, 2022

@littledan If we make a frozen class

Dumb question: why do classes need to be frozen? Why not just freeze the instances but not the class?

@ljharb
Copy link
Member

ljharb commented Oct 27, 2022

The prototype would still need to be frozen, otherwise the instance methods wouldn't be safe to use.

@littledan
Copy link
Member

If the methods are frozen, then the built-in implementation can be used rather than dispatching out to JS all the time. The string version meets this goal. Freezing the instances doesn't achieve the elimination of state, since the big issue is that we shouldn't have any "hidden" identities embedded in methods like Temporal.TimeZone.from, per SES. (e.g., Identity + WeakMap ==> state)

@justingrant
Copy link
Collaborator

The prototype would still need to be frozen, otherwise the instance methods wouldn't be safe to use.

@ljharb is SES-compatible that what you meant by "safe to use"? Or is there a different problem you were referring to?

If the former, @gibson042 noted in today's meeting that because all the methods on the TZ/Cal prototypes would be reachable from the global object, they could be locked down by SES even if shared singleton TZ/Cal instances were used. Or did we miss something?

@littledan Freezing the instances doesn't achieve the elimination of state, since the big issue is that we shouldn't have any "hidden" identities embedded in methods like Temporal.TimeZone.from, per SES. (e.g., Identity + WeakMap ==> state)

Would it be possible to explain this case a bit more? What's the specific attack you have in mind? Also, @gibson042 how does Dan's point here relate to your conclusion in today's meeting that shared singleton frozen objects seemed to be SES-compatible?

@littledan If the methods are frozen, then the built-in implementation can be used rather than dispatching out to JS all the time.

Does frozen instances achieve this goal too?

ptomato added a commit that referenced this issue Apr 7, 2023
The toJSON method should return the value in the internal slot, instead of
making an observable call to toString.

See: #1808
ptomato added a commit that referenced this issue Apr 7, 2023
Refactor so that ZonedDateTime's [[TimeZone]] internal slot can now store
either a string (builtin time zone) or an object (custom time zone).

The .timeZone getter is replaced with a .timeZoneId getter that always
returns a string, and a .getTimeZone() method that always returns a new
object if the slot stored a string.

The operations ToTemporalTimeZoneIdentifier and ToTemporalTimeZoneObject
convert a [[TimeZone]] slot value to one or the other.

See: #1808
ptomato added a commit that referenced this issue Apr 7, 2023
When creating Temporal objects with a builtin time zone, we want them to
be created with a string in the internal slot, not a TimeZone instance.
(For example, when parsing an IANA time zone name out of a string or using
UTC as a default.)

See: #1808
ptomato added a commit that referenced this issue Apr 7, 2023
Previously algorithms such as TimeZoneEquals used ToString to get the
identifier of a time zone object. Instead, use
ToTemporalTimeZoneIdentifier which observably gets the .id property of a
custom time zone instead of getting and calling the .toString property.

See: #1808
ptomato added a commit that referenced this issue Apr 7, 2023
The toJSON method should return the value in the internal slot, instead of
making an observable call to toString.

See: #1808
ptomato added a commit that referenced this issue Apr 7, 2023
Temporal.Now.timeZone() is replaced by Temporal.Now.timeZoneId() which
always returns a time zone identifier string, rather than a TimeZone
instance.

See: #1808
ptomato added a commit that referenced this issue Apr 7, 2023
PlainTime now has no calendar. Removes the getter and the internal slot.
Calendar annotations are now ignored in ParseTemporalTimeString.

RejectObjectWithCalendarOrTimeZone still rejects PlainTime, so it is
renamed to RejectTemporalLikeObject.

ToTemporalCalendar rejects PlainTime explicitly instead of looking for its
`calendar` property or treating it as a plain object calendar.

See: #1808
Closes: #1588
ptomato added a commit that referenced this issue Apr 7, 2023
Refactor so that Temporal objects' [[Calendar]] internal slot can now
store either a string (builtin calendar) or an object (custom calendar).

The .calendar getter is replaced with a .calendarId getter that always
returns a string, and a .getCalendar() method that always returns a new
object if the slot stored a string.

The operations ToTemporalCalendarIdentifier and ToTemporalCalendarObject
convert a [[Calendar]] slot value to one or the other.

See: #1808
ptomato added a commit that referenced this issue Apr 7, 2023
When creating Temporal objects with a builtin ISO 8601 calendar, we want
them to be created with a string in the internal slot, not a Calendar
instance. (For example, when parsing a calendar name out of a string or
using ISO 8601 as a default.) Therefore we don't need the
GetISO8601Calendar and GetBuiltinCalendar AOs anymore.

See: #1808
ptomato added a commit that referenced this issue Apr 7, 2023
Previously algorithms such as CalendarEquals used ToString to get the
identifier of a calendar object. Instead, use ToTemporalCalendarIdentifier
which observably gets the .id property of a custom calendar instead of
getting and calling the .toString property.

See: #1808
ptomato added a commit that referenced this issue Apr 7, 2023
The toJSON method should return the value in the internal slot, instead of
making an observable call to toString.

See: #1808
ptomato added a commit that referenced this issue Apr 7, 2023
Refactor so that ZonedDateTime's [[TimeZone]] internal slot can now store
either a string (builtin time zone) or an object (custom time zone).

The .timeZone getter is replaced with a .timeZoneId getter that always
returns a string, and a .getTimeZone() method that always returns a new
object if the slot stored a string.

The operations ToTemporalTimeZoneIdentifier and ToTemporalTimeZoneObject
convert a [[TimeZone]] slot value to one or the other.

See: #1808
ptomato added a commit that referenced this issue Apr 7, 2023
When creating Temporal objects with a builtin time zone, we want them to
be created with a string in the internal slot, not a TimeZone instance.
(For example, when parsing an IANA time zone name out of a string or using
UTC as a default.)

See: #1808
ptomato added a commit that referenced this issue Apr 7, 2023
Previously algorithms such as TimeZoneEquals used ToString to get the
identifier of a time zone object. Instead, use
ToTemporalTimeZoneIdentifier which observably gets the .id property of a
custom time zone instead of getting and calling the .toString property.

See: #1808
ptomato added a commit that referenced this issue Apr 7, 2023
The toJSON method should return the value in the internal slot, instead of
making an observable call to toString.

See: #1808
@ptomato
Copy link
Collaborator

ptomato commented Apr 7, 2023

Closed by #2482.

justingrant pushed a commit that referenced this issue Apr 20, 2023
Temporal.Now.timeZone() is replaced by Temporal.Now.timeZoneId() which
always returns a time zone identifier string, rather than a TimeZone
instance.

See: #1808

UPSTREAM_COMMIT=d67db4a7731d13f9e6e2b4be4f6d10732756c06e
justingrant pushed a commit that referenced this issue Apr 20, 2023
PlainTime now has no calendar. Removes the getter and the internal slot.
Calendar annotations are now ignored in ParseTemporalTimeString.

RejectObjectWithCalendarOrTimeZone still rejects PlainTime, so it is
renamed to RejectTemporalLikeObject.

ToTemporalCalendar rejects PlainTime explicitly instead of looking for its
`calendar` property or treating it as a plain object calendar.

See: #1808
Closes: #1588

UPSTREAM_COMMIT=61fbb401df7463e435b8e6f932de123fd5bc0c2d
justingrant pushed a commit that referenced this issue Apr 20, 2023
Refactor so that Temporal objects' [[Calendar]] internal slot can now
store either a string (builtin calendar) or an object (custom calendar).

The .calendar getter is replaced with a .calendarId getter that always
returns a string, and a .getCalendar() method that always returns a new
object if the slot stored a string.

The operations ToTemporalCalendarIdentifier and ToTemporalCalendarObject
convert a [[Calendar]] slot value to one or the other.

See: #1808

UPSTREAM_COMMIT=7cf55b9bea1f19ee5a911934e17a37a9bbf0a4c7
justingrant pushed a commit that referenced this issue Apr 20, 2023
When creating Temporal objects with a builtin ISO 8601 calendar, we want
them to be created with a string in the internal slot, not a Calendar
instance. (For example, when parsing a calendar name out of a string or
using ISO 8601 as a default.) Therefore we don't need the
GetISO8601Calendar and GetBuiltinCalendar AOs anymore.

See: #1808

UPSTREAM_COMMIT=52f3159ba4d03343e1cfdbcee1720b03e28753b8
justingrant pushed a commit that referenced this issue Apr 20, 2023
Previously algorithms such as CalendarEquals used ToString to get the
identifier of a calendar object. Instead, use ToTemporalCalendarIdentifier
which observably gets the .id property of a custom calendar instead of
getting and calling the .toString property.

See: #1808

UPSTREAM_COMMIT=5e0e87061bfb563a2b8a0c740da5995d8ed850be
justingrant pushed a commit that referenced this issue Apr 20, 2023
The toJSON method should return the value in the internal slot, instead of
making an observable call to toString.

See: #1808

UPSTREAM_COMMIT=9199c05fdd58ddf45a03211396762e57da4fe6bc
justingrant pushed a commit that referenced this issue Apr 20, 2023
Refactor so that ZonedDateTime's [[TimeZone]] internal slot can now store
either a string (builtin time zone) or an object (custom time zone).

The .timeZone getter is replaced with a .timeZoneId getter that always
returns a string, and a .getTimeZone() method that always returns a new
object if the slot stored a string.

The operations ToTemporalTimeZoneIdentifier and ToTemporalTimeZoneObject
convert a [[TimeZone]] slot value to one or the other.

See: #1808

UPSTREAM_COMMIT=c56a90c814b4632b87da80be26f4775b0fae1751
justingrant pushed a commit that referenced this issue Apr 20, 2023
When creating Temporal objects with a builtin time zone, we want them to
be created with a string in the internal slot, not a TimeZone instance.
(For example, when parsing an IANA time zone name out of a string or using
UTC as a default.)

See: #1808

UPSTREAM_COMMIT=973981239d033fe29bbcaa753bccfcd688e654c3
justingrant pushed a commit that referenced this issue Apr 20, 2023
Previously algorithms such as TimeZoneEquals used ToString to get the
identifier of a time zone object. Instead, use
ToTemporalTimeZoneIdentifier which observably gets the .id property of a
custom time zone instead of getting and calling the .toString property.

See: #1808

UPSTREAM_COMMIT=08ae76a9030e7592825317e4da4444f7686a2dd2
justingrant pushed a commit that referenced this issue Apr 20, 2023
The toJSON method should return the value in the internal slot, instead of
making an observable call to toString.

See: #1808

UPSTREAM_COMMIT=51bb26b9392b90be6a7341a8af2332559639107e
@justingrant
Copy link
Collaborator

Really closed by #2482. :-)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
normative Would be a normative change to the proposal spec-text Specification text involved
Projects
None yet
Development

No branches or pull requests

9 participants