-
Notifications
You must be signed in to change notification settings - Fork 156
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
ISO 8601 calendar #638
ISO 8601 calendar #638
Conversation
docs/calendar.md
Outdated
However, the ISO 8601 calendar is not the only calendar in common use in the world. | ||
Some places use another calendar system as the main calendar, or have a separate calendar system as a commonly-used civil or religious calendar. | ||
|
||
> **NOTE:** The Temporal polyfill currently only includes the ISO 8601 calendar, but the Temporal proposal will eventually provide all the calendars supported by Intl. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you ship the Japanese calendar in the polyfill since I think you already have it mostly done?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought we had mentioned not doing that and instead encouraging others to implement Intl calendars in order to exercise the API?
I can add it, but I think it might need a bit more work than I will be able to complete this week if we still want to keep to our deadline of "within the next few days". On the other hand, it would certainly be nice to have an actual non-ISO calendar that we could use in the docs examples, so that we can make the docs examples actually executable. I'll see how far I get.
Looks quite good! I left some comments. I started looking through the code but my laptop was about to die so I thought it was better to send what I had. |
Thanks for the review, very helpful. There are still some to-do items on this branch that I won't get to today, but it's getting closer. |
Codecov Report
@@ Coverage Diff @@
## main #638 +/- ##
==========================================
- Coverage 95.99% 94.43% -1.56%
==========================================
Files 17 17
Lines 4040 4760 +720
Branches 663 749 +86
==========================================
+ Hits 3878 4495 +617
- Misses 159 262 +103
Partials 3 3
Continue to review full report at Codecov.
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at more of the code.
|
||
export class Iso8601 extends Calendar { | ||
constructor(id = 'iso8601') { | ||
// Needs to be subclassable, that's why the ID is a default argument |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure what to think here. On the one hand, new Iso8601()
shouldn't need an ID argument. On the other hand, it seems like it should be required for subclasses to specify their ID.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see a good alternative here if we want to store the ID in an internal slot of the calendar. That can only be done in super()
, not in userland. To solve this we could drop the internal slot (just require an .id
property on calendars), or disallow subclassing, or have ISO8601Base
, IslamicBase
, etc. calendars that you extend (but then we'd have to expose them to userland calendars as part of the API).
} | ||
year(date) { | ||
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver'); | ||
return GetSlot(date, ISO_YEAR); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder if we should make an observable access to all three ISO slots, since other non-ISO calendar systems may need to access all three slots.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They can use date.getISOCalendarFields()
, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe we should read date.getISOCalendarFields()
here as well, then, to reduce differences between calendars?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I might have misunderstood your original remark, but I'm not sure I understand what purpose that would serve. Compared with the old, pre-calendar situation, reading the internal slot directly doesn't introduce any observable accesses other than that of invoking the calendar getter itself. Calling the method means the spec will mandate that an observable call to getISOCalendarFields()
is interposed every time you evaluate date.year
.
The call to getISOCalendarFields()
is unavoidable in userland calendars because they can't read slots, but all of the built-in calendars can at least just read the slots.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, and I was saying that maybe the built in calendars should also have an observable call to make it consistent with custom or polyfilled calendars. But I don't feel strongly; I was just making an observation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got through a few more files.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Phew, done with the library code. I'll mostly trust that the tests are correct.
|
||
export class Iso8601 extends Calendar { | ||
constructor(id = 'iso8601') { | ||
// Needs to be subclassable, that's why the ID is a default argument |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason we can't have an .id
property on the class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(you'd want it to be an accessor on the prototype that returned the ID string from an internal slot) this seems pretty important for Calendar objects to have.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See my reply to Shane's question above (#638 (comment)), if we want the ID in an internal slot then we do need to chain up in order to give userland subclasses a way to set that internal slot.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry that was a response to this
I don't see a good alternative here if we want to store the ID in an internal slot of the calendar. That can only be done in
super()
, not in userland. To solve this we could drop the internal slot (just require an.id
property on calendars), or disallow subclassing, or haveISO8601Base
,IslamicBase
, etc. calendars that you extend (but then we'd have to expose them to userland calendars as part of the API).
Whats the pros and cons of just doing this:
To solve this we could drop the internal slot (just require an
.id
property on calendars)
Would an .id
property not be useful for userland code anyway?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(you'd want it to be an accessor on the prototype that returned the ID string from an internal slot) this seems pretty important for Calendar objects to have.
Hey @ljharb could you elaborate on this a bit more?
If the user is setting the id
on their own subclass why does this need to be the case? (Why does it need to be an internal_slot over a property)
Thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They can override whatever they want to on their subclass - but the ID seems like a pretty intrinsic and immutable characteristic of a calendar to me, and those things generally aren’t data properties.
Certainly, like all of our builtin classes, subclasses should/must use super()
to get all the appropriate internal slots, and I’d expect the ID to be provided to the base constructor (to store it in the internal slot)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IIUC no change is necessary then? We have an .id
property, but the ID is stored in an internal slot of the calendar, which is achieved by calling super(id)
in the constructor of your calendar. The only ugliness is this default argument on the ISO8601 calendar's constructor which I think is acceptable, at least until there is a better idea.
This is a test for a regression that could occur when splitting date and time arithmetic in Temporal.DateTime.
In order for calendars to work properly, we need to split the Regulate operations into two parts: one to reject or constrain individual values (such as days == 32), which is calendar-sensitive, and one to reject or constrain the entire range of the type (such as -271821-04-18), which is only calendar-sensitive for Date and YearMonth, not for DateTime.
This adds an 'iso8601' calendar and changes the polyfill code for Temporal.Date, Temporal.DateTime, Temporal.YearMonth, and Temporal.MonthDay so that they gain a CALENDAR slot, and delegate all of their calendar-sensitive operations to the object in the CALENDAR slot. Among the changes made to those types: - Constructors gain a calendar argument of type Temporal.Calendar. - Types gain a 'calendar' property of type Temporal.Calendar. - from() accepts a 'calendar' string or Temporal.Calendar. - Temporal.Date and Temporal.DateTime accept a calendar in with(). - Temporal.MonthDay & Temporal.YearMonth throw on a calendar in with(). - The calendar is returned in getFields() (see #641). - equals() and compare() take the calendar into account (see #625). This includes documentation, but leaves out spec changes and all but the necessary test changes for the time being.
Since MonthDay.withYear(year) is not guaranteed to be sufficient to convert to a Temporal.Date if the calendar uses eras as well as years. Instead of picking the arbitrary day 1 or year 2004 to convert, use the refISODay or refISOYear, respectively.
For Intl.DateTimeFormat and toLocaleString(), the Temporal object's calendar should take precedence if it is non-ISO (which here we define as not 'iso8601' or 'gregory'), and otherwise the locale's calendar should take precedence. For ranges, we throw if two Temporal objects are given with differing calendars that are both non-ISO. See: #262
Projects a date or date/time from one calendar into another. Spec for these methods is not written yet.
This implements "option 1" for the default calendar, which is defaulting to the full ISO 8601 calendar everywhere. We add optional calendar parameters to Temporal.Absolute.prototype.inTimeZone, Temporal.now.date, Temporal.now.dateTime, and Temporal.TimeZone.prototype.getDateTimeFor. See: #292.
In order to allow subclasses to provide their own calendar identifier, even when subclassing ISO8601Calendar rather than Temporal.Calendar directly, we need to make sure the identifier can be passed when chaining up. Adds tests to verify that subclassing Temporal.Calendar in userland works and that a simple object implementing the interface behaves correctly.
The other half of the custom time zones and calendars protocol discussed in #300 was allowing calendars and time zones to be plain objects with the appropriate methods on them. This adds a test verifying that it's possible to use a plain object with calendar methods as a time zone, and makes the changes needed to allow that.
This field is always undefined in the ISO calendar, but is used in several other calendars.
We always need to pass all fields for each type into the calendar methods, in case calendars require more fields. This moves these field accesses into the abstract operations ToTemporalDateRecord, ToTemporalDateTimeRecord, ToTemporalMonthDayRecord, ToTemporalTimeRecord, and ToTemporalYearMonthRecord, since it is otherwise easy to forget a field or forget that they have to be accessed in alphabetical order.
This is identical to the ISO8601 calendar, except for ISO week numbering rules, which we do not currently use in Temporal. See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/Locale/calendar
This is a hacky, untested implementation of the Japanese calendar, intended as a guide for how to implement calendars. It only supports the most recent five eras, and may contain many bugs.
I believe I've addressed the comments now, except for a few that I think should belong in a separate discussion. What's new: I've also tacked on the hacky |
I don't see any other issues on top of what @sffc has pointed out |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's hard to tell what changed from my previous review to now, but I trust that you responded to my feedback appropriately, so let's get this in. Thanks!
I plan to merge this first thing tomorrow and then cut a release. |
This adds a built-in ISO 8601 calendar and delegates all calendar-sensitive operations to it.
This should be the last PR that needs to be merged before we can release the polyfill. It's ready for review and can be merged as-is, but here is a list of things I still intend to do, either in this pull request or a follow up:
calendar.dateTimeFromFields
can be removedTemporal.Calendar
Temporal.Calendar