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

ISO 8601 calendar #638

Merged
merged 14 commits into from
Jun 12, 2020
Merged

ISO 8601 calendar #638

merged 14 commits into from
Jun 12, 2020

Conversation

ptomato
Copy link
Collaborator

@ptomato ptomato commented Jun 1, 2020

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:

  • Check if calendar.dateTimeFromFields can be removed
  • Add the in-progress Japanese calendar, if it's possible to get it into shape in time
  • Add tests for creating a custom calendar inheriting from Temporal.Calendar
  • Add tests for creating a custom calendar not inheriting from Temporal.Calendar

@ptomato ptomato added this to the Stable API milestone Jun 1, 2020
@ptomato ptomato marked this pull request as draft June 1, 2020 19:55
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.
Copy link
Collaborator

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?

Copy link
Collaborator Author

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.

docs/calendar.md Outdated Show resolved Hide resolved
docs/calendar.md Outdated Show resolved Hide resolved
docs/calendar.md Outdated Show resolved Hide resolved
@sffc
Copy link
Collaborator

sffc commented Jun 2, 2020

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.

@ptomato
Copy link
Collaborator Author

ptomato commented Jun 3, 2020

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
Copy link

codecov bot commented Jun 5, 2020

Codecov Report

Merging #638 into main will decrease coverage by 1.55%.
The diff coverage is 90.77%.

Impacted file tree graph

@@            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              
Flag Coverage Δ
#test262 52.82% <53.83%> (-1.02%) ⬇️
#tests 89.35% <86.21%> (-1.85%) ⬇️
Impacted Files Coverage Δ
polyfill/lib/calendar.mjs 65.01% <78.74%> (+37.74%) ⬆️
polyfill/lib/datetime.mjs 93.55% <91.31%> (-2.12%) ⬇️
polyfill/lib/date.mjs 93.43% <92.39%> (-0.46%) ⬇️
polyfill/lib/intl.mjs 98.73% <92.85%> (-1.27%) ⬇️
polyfill/lib/ecmascript.mjs 95.62% <94.85%> (-1.97%) ⬇️
polyfill/lib/yearmonth.mjs 97.81% <97.80%> (+0.38%) ⬆️
polyfill/lib/monthday.mjs 97.85% <98.03%> (-1.29%) ⬇️
polyfill/lib/absolute.mjs 100.00% <100.00%> (ø)
polyfill/lib/now.mjs 100.00% <100.00%> (ø)
polyfill/lib/regex.mjs 100.00% <100.00%> (ø)
... and 5 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 81a4b67...915ae53. Read the comment docs.

@ptomato ptomato marked this pull request as ready for review June 5, 2020 00:24
@ptomato ptomato marked this pull request as draft June 5, 2020 23:16
@ptomato ptomato mentioned this pull request Jun 5, 2020
@ptomato ptomato marked this pull request as ready for review June 9, 2020 00:30
Copy link
Collaborator

@sffc sffc left a 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.

polyfill/index.d.ts Outdated Show resolved Hide resolved

export class Iso8601 extends Calendar {
constructor(id = 'iso8601') {
// Needs to be subclassable, that's why the ID is a default argument
Copy link
Collaborator

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.

Copy link
Collaborator Author

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).

polyfill/lib/calendar.mjs Show resolved Hide resolved
polyfill/lib/calendar.mjs Outdated Show resolved Hide resolved
}
year(date) {
if (!ES.IsTemporalCalendar(this)) throw new TypeError('invalid receiver');
return GetSlot(date, ISO_YEAR);
Copy link
Collaborator

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.

Copy link
Collaborator Author

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?

Copy link
Collaborator

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?

Copy link
Collaborator Author

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.

Copy link
Collaborator

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.

Copy link
Collaborator

@sffc sffc left a 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.

polyfill/lib/date.mjs Show resolved Hide resolved
polyfill/lib/datetime.mjs Show resolved Hide resolved
polyfill/lib/datetime.mjs Show resolved Hide resolved
polyfill/lib/datetime.mjs Show resolved Hide resolved
polyfill/lib/datetime.mjs Outdated Show resolved Hide resolved
polyfill/lib/datetime.mjs Outdated Show resolved Hide resolved
polyfill/lib/ecmascript.mjs Show resolved Hide resolved
polyfill/lib/monthday.mjs Outdated Show resolved Hide resolved
polyfill/lib/monthday.mjs Outdated Show resolved Hide resolved
Copy link
Collaborator

@sffc sffc left a 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.

polyfill/lib/regex.mjs Show resolved Hide resolved
polyfill/lib/yearmonth.mjs Outdated Show resolved Hide resolved
polyfill/lib/yearmonth.mjs Outdated Show resolved Hide resolved
polyfill/lib/yearmonth.mjs Show resolved Hide resolved
polyfill/lib/yearmonth.mjs Outdated Show resolved Hide resolved

export class Iso8601 extends Calendar {
constructor(id = 'iso8601') {
// Needs to be subclassable, that's why the ID is a default argument
Copy link
Member

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?

Copy link
Member

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.

Copy link
Collaborator Author

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.

Copy link
Member

@jasonwilliams jasonwilliams Jun 11, 2020

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 have ISO8601Base, 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?

Copy link
Member

@jasonwilliams jasonwilliams Jun 11, 2020

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

Copy link
Member

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)

Copy link
Collaborator Author

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.

ptomato added 3 commits June 10, 2020 14:30
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.
ptomato added 11 commits June 10, 2020 14:42
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.
The exact form of this annotation is under discussion in #293, but this
adds the form of it that previously had support in the champions group:
[c=identifier] appended to the ISO string.

See: #293
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.
@ptomato
Copy link
Collaborator Author

ptomato commented Jun 10, 2020

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 gregory and japanese calendars to the end of this pull request but if anyone has concerns about them then I'd rather merge this without them than delay shipping the polyfill any longer.

@jasonwilliams
Copy link
Member

I don't see any other issues on top of what @sffc has pointed out

Copy link
Collaborator

@sffc sffc left a 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!

polyfill/lib/ecmascript.mjs Show resolved Hide resolved
@ptomato
Copy link
Collaborator Author

ptomato commented Jun 11, 2020

I plan to merge this first thing tomorrow and then cut a release.

@ptomato ptomato merged commit a64fb62 into main Jun 12, 2020
@ptomato ptomato deleted the iso-calendar branch June 12, 2020 19:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants