Proposal-temporal: Semantics of type conversion methods with calendars

Created on 31 Mar 2020  路  9Comments  路  Source: tc39/proposal-temporal

This includes methods such as:

  • Temporal.Date.prototype.with
  • Temporal.Date.prototype.withTime
  • Temporal.Date.prototype.getYearMonth
  • Temporal.Date.prototype.getMonthDay
  • Temporal.DateTime.prototype.getDate
  • and any other methods that create new Temporal objects based on a previous one

I'd like to propose the following semantics for these methods: they take the enumerable fields (calendar-specific fields, not ISO fields), make any modifications necessary, and then pass the fields to the correct calendar factory method, such as dateTimeFromFields().

For example, the implementation of .with() could be something like:

Temporal.Date.prototype.with = function(overrides) {
  const newFields = Object.assign({}, this.getFields(), overrides);
  return this.calendar.dateFromFields(newFields);
}

And .getYearMonth():

Temporal.Date.prototype.getYearMonth = function() {
  return this.calendar.yearMonthFromFields(this.getFields());
}
calendar has-consensus

Most helpful comment

I do prefer the name withYear, I find it more intuitive than toDate (I can visualize tacking the year onto the MonthDay in my head) and it's in line with methods in other types (withTime, withDay, withDate, withCalendar) .

All 9 comments

Action from Apr 16 meeting: @sffc to add this to calendar-draft.md.

I am looking into this while prototyping calendar support and I had almost convinced myself that it wasn't necessary to go through the calendar after all, but I realized that user parameters such as in yearMonth.withDay(15) are in the YearMonth's calendar, not in ISO. And methods without user parameters such as getYearMonth and getMonthDay still need to get a calendar-dependent refIsoYear or refIsoDay. So it might be good to explicitly make a note of that in calendar-draft.md when adding this.

Somewhat related, the agreed-upon solution may turn out to be necessary but not sufficient. What about this?

const monthDay = Temporal.MonthDay({ month: 4, day: 1, calendar: japanese });
const date = monthDay.withYear(18);  // <-- I want ISO year 2006, how to specify Heisei era?

Hmm. Great point. Maybe we should make a method

Temporal.MonthDay.prototype.toDate = function(newFields) {
  const fields = Object.assign({}, newFields, this.getFields());
  return this.calendar.dateFromFields(fields);
}

And then you would call it like this

const monthDay = Temporal.MonthDay({ month: 4, day: 1, calendar: japanese });
const date = monthDay.toDate({ year: 18, era: "heisei" });

And it's up to the calendar to reject dates that don't have sufficient information.

This would replace the withYear() method. In the ISO calendar, you would just do,

// Old:
monthDay.withYear(2020);

// New:
monthDay.toDate({ year: 2020 });

I'm not a fan of making the API more verbose for applications that don't use calendars, I'd rather err on the other side of keeping the concise API for the more common use case. Maybe we could make withYear() throw for calendars that don't support it? Then the correct way to go from MonthDay to Date in the Japanese calendar would be this:

const date = Temporal.Date.from({ ...monthDay.getFields(), year: 18, era: 'heisei' });

Or, we could make the interpretation of the argument to withYear() up to the calendar, and the Japanese calendar could choose to interpret it as an ISO year, or require it to be a { year, era } object instead of a number. But I like that idea less.

I don't find changing withYear(yyyy) to toDate({ year: yyyy }) to be less ergonomic. Actually, even apart from calendar concerns, I personally find it to be more consistent with the idea of year being a field in the option bag.

I'm not in favor of functions that work when the calendar is ISO and don't work when it's not. That's bad for the usability of the API in i18n-centric applications.

I have qualified positive sentiment toward your section option, though I would want the semantics to be that withYear(n) is sugar for withYear({ year: n }), and you could choose the second form if you want consistency with non-ISO calendars. Basically, withYear would be what I proposed as toDate, but with the extra step of messaging a number argument.

Put like that, I'm fine with the second option too.

What do you think of using the proposed withYear() semantics, but calling the function toDate()?

let monthDay = MonthDay.from({ calendar: "iso", month: 4, day: 23 });
monthDay.toDate({ year: 2020 });  // OK
monthDay.toDate(2020);  // OK

monthDay = MonthDay.from({ calendar: "japanese", month: 4, day: 23 });
monthDay.toDate({ year: 2020 });  // Missing era; calendar can decide what to do
monthDay.toDate(2020);  // equivalent to above
monthDay.toDate({ year: 2, era: "reiwa" });  // OK

I do prefer the name withYear, I find it more intuitive than toDate (I can visualize tacking the year onto the MonthDay in my head) and it's in line with methods in other types (withTime, withDay, withDate, withCalendar) .

Okay. Let's move forward with withYear() then with the semantics proposed above.

Was this page helpful?
0 / 5 - 0 ratings