Proposal-temporal: What about the default calendar?

Created on 8 Dec 2019  Â·  33Comments  Â·  Source: tc39/proposal-temporal

Calendar support has landed in Temporal. Now we need to decide on the default calendar.

Option 1 is for the API to always assume the ISO (Gregorian) calendar. The programmer can opt-in to an alternative calendar system, but the API doesn't nudge them to do the right thing when a non-Gregorian calendar should actually be used.

However, it is bad i18n practice to assume that dates should be represented in the Gregorian calendar by default.

I have presented several ways that we could improve the API to nudge developers to do the right thing when it comes to non-Gregorian calendars:

  1. Require the programmer to explicitly specify the calendar.
  2. Default to a partial ISO calendar (detailed in the explainer).
  3. Default to Intl.defaultCalendar (a new symbol), or ISO if that field doesn't exist.
  4. Add ZonedAbsolute, a new type between Absolute (timestamp) and DateTime (calendar-dependent wall clock time).
  5. Split factory methods into ISO and non-ISO variants.
  6. Use Intl.defaultCalendar in Temporal.now, and explicit elsewhere. See https://github.com/tc39/proposal-temporal/issues/292#issuecomment-687551317

More details on these six options is documented in the explainer doc: https://github.com/tc39/proposal-temporal/blob/main/docs/calendar-draft.md#default-calendar

calendar documentation has-consensus polyfill spec-text

Most helpful comment

@sffc requested specific feedback on each of the 6 options, considered individually, so here's mine. I'd like to preface it by saying that I do see disadvantages in each of the options, so to me the question is picking the one with the least serious disadvantage.

  1. Full ISO: I agree that this is a potential source of bugs as detailed in calendar-draft.md. However, I believe there ought to be some other way to reduce the likelihood of those kinds of bugs, even if we choose this option. I believe that should be carefully considered, but I don't believe it's an insurmountable problem, and the fact that those mistakes may be _possible_ to commit is not a disadvantage that I weight very highly.

  2. Explicit calendar: For the reason I commented elsewhere, I feel strongly that the calendar doesn't need to be explicit in Temporal.X.from(fields), so I would consider this option only for now and inTimeZone. So, concretely, we are talking about removing default values from arguments in the following four API signatures:

    • Temporal.now.dateTime(timeZone = Temporal.now.timeZone(), calendar = 'iso8601') → Temporal.now.dateTime(calendar, timeZone = Temporal.now.timeZone())
    • Temporal.now.date(timeZone = Temporal.now.timeZone(), calendar = 'iso8601') → Temporal.now.date(calendar, timeZone = Temporal.now.timeZone())
    • timeZone.getDateTimeFor(absolute, calendar = 'iso8601') → timeZone.getDateTimeFor(absolute, calendar)
    • absolute.inTimeZone(timeZone, calendar = 'iso8601') → absolute.inTimeZone(timeZone, calendar)

    I don't prefer this, because I think in practice the vast majority of programmers will be using ISO anyway, so having to write 'iso8601' explicitly will be a minor annoyance. At worst, it will confuse JavaScript beginners because it's a required parameter that they won't understand. (I'm assuming that inexperienced JavaScript programmers are barely going to understand the Temporal types to begin with, let alone non-ISO calendars.) I weight that argument very highly.

    I think in practice, tutorials and StackOverflow will tell people "oh, you have to pass 'iso8601' for that parameter, it's too complicated to explain why" and so any benefit to making it explicit will be lost. Specifically, for that reason I don't agree that this forces a decision point on the programmer.

  3. Partial ISO: I see this option as adding a flag to Temporal.Date and Temporal.DateTime saying "is this object broken Y/N" and the way you flip the flag is using a method named withCalendar(). If you write a function that takes a Temporal.Date or Temporal.DateTime, you have no way to know whether the object has its broken-flag set or not, so in practice your function will have to defensively call withCalendar('iso8601') on it anyway if you need to do one of the operations not supported by partial ISO. This violates predictability, which I weight highest of all, and for that reason this is a non-starter for me.

  4. Locale-defined default (whether via Intl.defaultCalendar or something else): I think this option does prevent the bugs detailed in calendar-draft.md, but it makes your arithmetic operations have potentially different results depending on what locale you're in. This violates predictability, making this one also a non-starter for me.

  5. Extra types: This one I haven't been able to fully study the advantages and disadvantages yet. But, I think just the fact that it's been several days and I haven't had time to wrap my head around it, is an indication that it's too complicated. If inexperienced JavaScript programmers are barely going to understand the existing Temporal types, then adding two more types is not going to improve things.

    What I do understand is that we'd need a new Date-type and DateTime-type without a calendar, to contrast Temporal.Date and Temporal.DateTime with a calendar. I think this will confuse inexperienced JavaScript programmers, and the advice on StackOverflow will be "oh, you have to do withCalendar('iso8601') to be able to subtract," and that advice will be applied indiscriminately.

  6. Separate API: Again assuming that the ISO calendar can be the default in Temporal.X.from(fields), we are talking concretely about doing option 2, removing the default value from some calendar arguments that would otherwise default to ISO, while also adding new methods for the convenience of programmers who only want the ISO calendar:

    • add Temporal.now.isoDateTime(timeZone = Temporal.now.timeZone())
    • add Temporal.now.isoDate(timeZone = Temporal.now.timeZone())
    • add timeZone.getISODateTimeFor(absolute)
    • add absolute.inTimeZoneISO(timeZone)

    Since the vast majority of programmers will want ISO, I think this prioritizes the wrong use case. It will also confuse inexperienced JavaScript programmers. Reading the docs, or seeing what pops up in their IDEs, they will think "Do I want inTimeZone or inTimeZoneISO?" and code in the wild will end up with mixtures of the two.

    I'd have less of an objection to this option if we kept the existing methods as they are, and instead added differently-named methods, that mention "calendar" in the name, which require a calendar. That way, the obvious way would be to call e.g. Temporal.now.date() and absolute.inTimeZone(). Programmers would see Temporal.now.dateWithCalendar() and absolute.inTimeZoneWithCalendar() in the docs or in their IDEs, but the thought process would be clearer, since if they don't need calendars then they also don't need the calendar-named methods.

Here's approximately how I'd weight the various criteria that the proposals are validated by:

  • API consistency & predictability
  • Impact on Temporal call sites

    • _(which I interpret more like "confusion caused for developers who don't know what calendars are")_

  • Impact on interoperability
  • Impact on i18n correctness
  • Logistics

    • _(which I assume means "how difficult to get from here to there in the proposal-temporal repo", and if that's correct, then I'd weight this at basically zero)_

I can make a pull request to calendar-draft.md showing how I'd change the emoji faces in the table if anyone wants that, but hopefully this illustrates my opinion.

All 33 comments

I want to suggest that, rather than a broad survey like @codehag created for the pipeline operator, we focus on some targeted outreach to developers who work with various non-ISO calendars and locales. This may be more in the form of interviews rather than a survey, since this information is pretty detailed. I met one such developer, and wrote about his feedback in https://github.com/tc39/proposal-temporal/issues/268#issuecomment-570670212 (sorry for posting on the wrong issue); my overall impression is that any of 1-4 would be acceptable for his needs, which span both Gregorian and Hijri (Islamic) calendars.

Sorry i totally missed this issue. Happy to help where I can though.

Yes I agree with @littledan in addition I'd ask people about their circumstance. I believe knowing whether respondents regularly deal with differing calendars will be a key input.

See also my PR in #309

We've been thinking about this for a while. Now that we have an extensive cookbook, and the polyfill for non-Gregorian calendars is coming along, I think we're coming to the understanding of the design space where we could really come to a decision, based on consulting with more people.

I've updated calendar-draft.md in #590 with relevant cookbook changes and some new material in the default calendar explainer, including a closer look at the different factory methods used for constructing Temporal types.

@sffc requested specific feedback on each of the 6 options, considered individually, so here's mine. I'd like to preface it by saying that I do see disadvantages in each of the options, so to me the question is picking the one with the least serious disadvantage.

  1. Full ISO: I agree that this is a potential source of bugs as detailed in calendar-draft.md. However, I believe there ought to be some other way to reduce the likelihood of those kinds of bugs, even if we choose this option. I believe that should be carefully considered, but I don't believe it's an insurmountable problem, and the fact that those mistakes may be _possible_ to commit is not a disadvantage that I weight very highly.

  2. Explicit calendar: For the reason I commented elsewhere, I feel strongly that the calendar doesn't need to be explicit in Temporal.X.from(fields), so I would consider this option only for now and inTimeZone. So, concretely, we are talking about removing default values from arguments in the following four API signatures:

    • Temporal.now.dateTime(timeZone = Temporal.now.timeZone(), calendar = 'iso8601') → Temporal.now.dateTime(calendar, timeZone = Temporal.now.timeZone())
    • Temporal.now.date(timeZone = Temporal.now.timeZone(), calendar = 'iso8601') → Temporal.now.date(calendar, timeZone = Temporal.now.timeZone())
    • timeZone.getDateTimeFor(absolute, calendar = 'iso8601') → timeZone.getDateTimeFor(absolute, calendar)
    • absolute.inTimeZone(timeZone, calendar = 'iso8601') → absolute.inTimeZone(timeZone, calendar)

    I don't prefer this, because I think in practice the vast majority of programmers will be using ISO anyway, so having to write 'iso8601' explicitly will be a minor annoyance. At worst, it will confuse JavaScript beginners because it's a required parameter that they won't understand. (I'm assuming that inexperienced JavaScript programmers are barely going to understand the Temporal types to begin with, let alone non-ISO calendars.) I weight that argument very highly.

    I think in practice, tutorials and StackOverflow will tell people "oh, you have to pass 'iso8601' for that parameter, it's too complicated to explain why" and so any benefit to making it explicit will be lost. Specifically, for that reason I don't agree that this forces a decision point on the programmer.

  3. Partial ISO: I see this option as adding a flag to Temporal.Date and Temporal.DateTime saying "is this object broken Y/N" and the way you flip the flag is using a method named withCalendar(). If you write a function that takes a Temporal.Date or Temporal.DateTime, you have no way to know whether the object has its broken-flag set or not, so in practice your function will have to defensively call withCalendar('iso8601') on it anyway if you need to do one of the operations not supported by partial ISO. This violates predictability, which I weight highest of all, and for that reason this is a non-starter for me.

  4. Locale-defined default (whether via Intl.defaultCalendar or something else): I think this option does prevent the bugs detailed in calendar-draft.md, but it makes your arithmetic operations have potentially different results depending on what locale you're in. This violates predictability, making this one also a non-starter for me.

  5. Extra types: This one I haven't been able to fully study the advantages and disadvantages yet. But, I think just the fact that it's been several days and I haven't had time to wrap my head around it, is an indication that it's too complicated. If inexperienced JavaScript programmers are barely going to understand the existing Temporal types, then adding two more types is not going to improve things.

    What I do understand is that we'd need a new Date-type and DateTime-type without a calendar, to contrast Temporal.Date and Temporal.DateTime with a calendar. I think this will confuse inexperienced JavaScript programmers, and the advice on StackOverflow will be "oh, you have to do withCalendar('iso8601') to be able to subtract," and that advice will be applied indiscriminately.

  6. Separate API: Again assuming that the ISO calendar can be the default in Temporal.X.from(fields), we are talking concretely about doing option 2, removing the default value from some calendar arguments that would otherwise default to ISO, while also adding new methods for the convenience of programmers who only want the ISO calendar:

    • add Temporal.now.isoDateTime(timeZone = Temporal.now.timeZone())
    • add Temporal.now.isoDate(timeZone = Temporal.now.timeZone())
    • add timeZone.getISODateTimeFor(absolute)
    • add absolute.inTimeZoneISO(timeZone)

    Since the vast majority of programmers will want ISO, I think this prioritizes the wrong use case. It will also confuse inexperienced JavaScript programmers. Reading the docs, or seeing what pops up in their IDEs, they will think "Do I want inTimeZone or inTimeZoneISO?" and code in the wild will end up with mixtures of the two.

    I'd have less of an objection to this option if we kept the existing methods as they are, and instead added differently-named methods, that mention "calendar" in the name, which require a calendar. That way, the obvious way would be to call e.g. Temporal.now.date() and absolute.inTimeZone(). Programmers would see Temporal.now.dateWithCalendar() and absolute.inTimeZoneWithCalendar() in the docs or in their IDEs, but the thought process would be clearer, since if they don't need calendars then they also don't need the calendar-named methods.

Here's approximately how I'd weight the various criteria that the proposals are validated by:

  • API consistency & predictability
  • Impact on Temporal call sites

    • _(which I interpret more like "confusion caused for developers who don't know what calendars are")_

  • Impact on interoperability
  • Impact on i18n correctness
  • Logistics

    • _(which I assume means "how difficult to get from here to there in the proposal-temporal repo", and if that's correct, then I'd weight this at basically zero)_

I can make a pull request to calendar-draft.md showing how I'd change the emoji faces in the table if anyone wants that, but hopefully this illustrates my opinion.

I agree with all the points that @ptomato mentioned above, and his weighing of them. I want to mention a few more disadvantages of option 4, which I've mentioned in some calls but don't think I properly wrote down anywhere:

  • Determining the default calendar: My understanding is that many regions of the world have a default calendar which is really more of an individual preference. CLDR sometimes lists, for these regions, that the default calendar is ISO, and other times another calendar, but the ground truth is more subtle. If the default calendar is chosen based on the region, we may not meet user expectations; application-level logic would lead to better results. (I don't really know how close we'll get to users' real calendar preferences if we pass up the "right" default calendar through navigator.locales; maybe that would be enough?) Application-level calendar determination would be best implemented on top of a simpler, more predictable base.
  • Testing: Since most of the world uses an ISO calendar default, most testing would be executed in environments with an ISO calendar. This leads to the risk of code being shipped which works in most of the world and is broken when the "wrong" default calendar is selected by the environment.
  • Separation of concerns: Without the inheritance of the default calendar from the environment, Temporal would operate consistently the same across different contexts/environments, modulo ECMA-402's implementation-defined-ness of calendars. I'd prefer to expose the user agent's default calendar through a separate navigator.locales API or similar, and ask developers to thread it through if it makes sense. Often, there will be application-level preferences which would override it. Also, not all environments have a meaningful user-agent-level default (e.g., servers). (Yes, Intl has a pervasive concept of a default locale; my claim is, that was an unfortunate design and we shouldn't extend it more broadly.)

Across options 2, 3, 5 and 6, there is a common API design principle that, if we ask developers to explicitly invoke the "iso" calendar in one way or another, then that will lead to a decision point from them. I share @ptomato 's skepticism about what this would mean in practice--I think the meaning of this would remain confusing to many programmers, and wouldn't constitute a choice point in practice. However, this is just a hypothesis, which we might investigate by talking to more programmers, distributing sample implementations/documentation for API alternatives, etc.

Among options 2, 3, 5 and 6, I prefer 6--I think it has the lowest syntactic overhead in practice, and it still meets the goal of "providing an explicit choice point", if that's taken to be a goal. The alternate naming that @ptomato proposes in option 6 seems interesting to me--this alternative option 6 would still create a situation where, in the documentation listing, tab completion for methods, etc, you see an option where there's an explicit calendar parameter, so this may still lead developers to invoke the right calendar-specific APIs. Option 6 seems to me like, at worst, a very minor source of slight ugliness compared to Option 1.

Overall, I prefer option 1, but the only option I am very strongly concerned about is option 4.

I think the high-order bit for defaults is (as @littledan notes above) that calendar defaults are nuanced and can affect application logic more than other localization domains. By contrast, a "default locale" is simpler, e.g. Japanese user only wants to see Japanese text or number formats. In that case, a developer's job is mostly to pass the right locale parameter to the right APIs, but otherwise their application logic could be almost identical across locales.

I don't think calendars will ever work like that. In markets using non-ISO calendars, both developers and end-users must navigate the complexity of local and/or ISO calendars used depending on the user (e.g. young vs. old users) or the app (e.g. government websites vs. ecommerce). A "default calendar" at the user _or_ app level may not really exist. It could be a toggle, like @littledan's Jordanian friend describes. Or could even be 2 calendars side-by-side in the same UI.

Also, I think it's important to think of defaults in context of which users will need them:

  • A) Developers writing software that must work in every calendar. This is a small group, mostly at huge companies like Google/Facebook/Microsoft that build developer platforms and/or consumer-facing software for a global market, or developers who contribute to a relatively small set of globalized OSS projects. Defaults are probably irrelevant for this sophisticated group. Instead, they'll want flexible APIs that don't preclude the weird things they need (e.g. custom calendars, sophisticated math/conversion, etc.) but won't care as much about defaults. Also, it should be easy, or at least easier, to build multi-calendar test automation.
  • B) Developers writing software for local markets in countries that use non-ISO calendars. This complexity means that non-ISO developers also work in ISO, so I'm not sure these developers would be upset by an ISO default. They'll probably care more about having APIs that are easy to learn (few concepts, few types), easy to use (e.g. don't require passing the same calendar parameter in a chain of method calls), and predictable in a context where flipping between ISO and other calendars is common.
  • C) Everyone else. Probably 95%+ of JS developers. The most conscientious of these will use appropriate best practices if we provide clear documentation, e.g. to use Intl instead of toString() for formatting. Many won't even do that. These developers rely heavily on defaults and won't understand non-ISO calendars so asking them to choose 'iso' every time won't be helpful. This group will appreciate docs with common use cases like "display today's date in X calendar" so that conscientious developers who get bug reports from non-ISO markets can easily modify their code to fix those bugs. I think "it just works" date math in non-ISO calendars is too much to expect from this group, even with perfect APIs, because there's underlying domain knowledge they won't have e.g. year*12+month !== totalMonths, or "leap months".

With those customer profiles in mind, here's some specific suggestions:

  • Default to ISO (@sffc's Option 1) but have an easy opt-in for non-ISO calendars. I think this default would best meet the needs of all three groups.
  • Make ISO calendar APIs shorter and more easily discoverable than the non-ISO variants, so Group C won't accidentally end up using the "wrong" variant. The other groups already know about calendar complexity so will know that they need to look harder to find what they need.
  • Use the word "calendar" (not "iso"!) in class/method/field names, because "ISO" already has a different meaning for many mainstream JS devs: "_that weird string format that ends in Z and has the wrong time that I don't really understand but I know it's used to store in databases or send to external APIs_". Trying to add another meaning will confuse many developers.
  • Try to avoid "method soup" where there's a "withCalendar" variant of every method call. Alternatives could be optional calendar parameters, nested types in a separate namespace (e.g. Temporal.WithCalendar.CalendarDateTime), or some other way to avoid polluting method autocomplete in the IDE.
  • Ideally, persist calendar on Temporal objects so you wouldn't have to keep telling Temporal what calendar something is across multiple chained method calls.
  • In a perfect world, calendar would also persist on types too. Not sure this is possible to implement with static properties, but if it is then programmer-defined aliases would probably be usable, like this:
const WesternTemporal = Temporal;
const HijriTemporal = Temporal.withCalendar('hijri');
const HijriDateTime = HijriTemporal.DateTime;

Overall I agree with @littledan's and @ptomato's prioritization so I won't rehash the pro/con arguments for each option, other than to be careful not to make an already-complex API even more complicated for the vast majority of developers who only use ISO.

Meeting, May 28: We'll initially ship the polyfill with option 1 (default is full ISO) with the understanding that it is revisited for Stage 3 based on feedback. It would be helpful to think about how we can most effectively gather that feedback (talk to individual developers using these calendars?) and what sort of feedback would change our minds vs. what sort of feedback would not.

(Moving to Stage 3 milestone since option 1, like 2 or 6, doesn't require writing any significant extra code, I'll just implement it that way in the calendar branch)

First, it is great to have support for non-Gregorian calendar systems; that is an important feature for many cultures.

However, I do have concerns about having the calendar support default to Gregorian. I'm afraid that that is repeating what happened with other areas, such as locales. We've had unpleasant (and costly) experiences in internationalization where programmers have to go out of their way to do the right thing. It is very easy for a programmer to not realize, for example, that numbers are formatted very differently for different locales, when the default formatting API for them doesn't need a locale.

When the programmer needs to supply a locale, then that at least raises the question for them as to how to get it, and makes it more likely than they will use the locale of the user, rather than simply default to (say) English.

Option #1 feels quite similar to that case, and I'd urge the group to take a second look at some of the options where the calendar choice needs to be made. None of them feel that onerous: although I do have some preferences among them, but that is less important than the fundamental issue of whether to require a calendar choice or use a default.

Thanks to @sffc's friendly but persistent feedback, I've been giving this topic a lot of thought. Here's a short writeup of my understanding of the problem and my current opinion about a solution.

TL;DR - I think we should require a calendar in toLocaleString, but default to ISO when creating objects. This means that using non-ISO calendars for business logic would require an opt-in if the calendar isn't already attached to the instance by whoever created it.

AFAIK, there are two logical places where calendars could be required:
1) When formatting an ISO-calendar date via toLocaleString() methods of Date, DateTime, LocalDateTime, MonthDay, and YearMonth.
2) When creating new Temporal object instances, which primarily happens via .from() or less-common APIs like Time.toDateTime.

Did I miss any others?

Requiring a calendar-enabled locale in toLocaleString() methods of Temporal types seems reasonable to consider.

I'd assume that the calendar could come from one of three places:

  • Explicitly set on the Temporal object instance
  • Provided as an options parameter to toLocaleString()
  • A default user calendar in environments where a default calendar could be reliably determined (do any of these environments exist today? My understanding from @littledan was that browsers' detection of default calendar was notoriously unreliable.)

toLocaleString() would throw if none of the three ways above provided a calendar.

Here's a few reasons why I think requiring a calendar for toLocaleString() could be OK:

  • If my server-side Node app is emitting human-readable text back to a client, the developer already has to figure out the user's locale. Picking a calendar seems like it should just be part of picking the locale to use.
  • toLocaleString is a localization API, dammit! If there's a place that developers expect to do extra work to enable a globalized result, that's it.
  • Familiarity. Same patterns that developers already use for number formatting, pluralization, collation, etc.
  • Can be successfully done by developers who don't understand how non-ISO calendars work.
  • Minimal impact to ergonomics and developer productivity because most apps do relatively little formatting; most use of date/time data is in business logic.

toLocaleString() works like other localization APIs that we know are successful. Let's follow the same path.

However, I'm not sold on requiring a calendar when creating Temporal objects.

This is the status quo: Temporal objects that don't opt in to a specific calendar would default to ISO-valued properties for year, month, day, and era.

Use cases that only display date/time data fetched from somewhere else (e.g. a date picker, or a database) and don't change that data before sending it to toLocaleString() would get the localized results in the desired calendar, because toLocaleString() would require a calendar.

Developers who refuse to use toLocaleString (e.g. ${date.month}/${date.day}/${date.year}) will show ISO values for month/day/year to users. This is the same problem as lazy developers writing code like £${price} instead of using an Intl currency API. IMHO we can only fix this problem via docs and evangelism, not APIs.

Here's the part that @sffc won't like: if developers want to create or manipulate date/time data using non-ISO-calendar-aware business logic (e.g. add 1 Hebrew month, or set date to the first of the Islamic year) then the developer will need to explicitly set the calendar, either when constructing the object or after construction. Non-ISO developers won't have to do this step.

The main reason I think this is the only viable option is because I don't believe that most developers will be successful with non-ISO-calendar business logic unless they already understand non-ISO calendars or are motivated to learn-- and these developers are the same people who will be OK to add a non-default calendar parameter! I outlined these concerns in more detail here: https://github.com/tc39/proposal-temporal/issues/292#issuecomment-633736952. Happy to discuss further.

Thanks for the reply @justingrant, but unfortunately I don't think it moves us closer to a solution.

toLocaleString takes a locale parameter, and the locale carries a calendar (for example, fa-IR implicitly resolves to the persian calendar; en-US implicitly resolves to the gregory calendar; and en-US-u-ca-hebrew explicitly resolves to the hebrew calendar). We are already discussing in #262 about whether the locale's calendar or Temporal's calendar should win when formatting. It is therefore unnecessary for toLocaleString to take a calendar parameter.

Furthermore, this is actively detrimental to good i18n, because we should in general rely on the locale as the source for the calendar. On the client, the locale is environment-determined, and in TC39-TG2, we are actively working on having it explicitly reflect the user's preferred calendar system based on browser/OS settings (see, e.g., https://github.com/tc39/ecma402/issues/68). @littledan is closely involved in those discussions. On the server, the locale should be determined from the request (either a ?lang= URL parameter or the Accept-Language header as a fallback), and that locale should be used whenever you use toLocaleString anywhere in your code. Again, although Accept-Language does not yet carry an explicit calendar system, we're working on a solution (https://github.com/tc39/ecma402/issues/416).

Here's the part that @sffc won't like: if developers want to create or manipulate date/time data using non-ISO-calendar-aware business logic (e.g. add 1 Hebrew month, or set date to the first of the Islamic year) then the developer will need to explicitly set the calendar, either when constructing the object or after construction. Non-ISO developers won't have to do this step.

Right, that's the crux. Not only is this an extra step for non-Gregorian programmers, but more importantly, the API won't give the programmer a clear signal of "hey! you need to specify a calendar argument here!" It requires careful coding to make sure that you find everywhere in your code where you need to specify the calendar argument.

The main reason I think this is the only viable option is because I don't believe that most developers will be successful with non-ISO-calendar business logic unless they already understand non-ISO calendars or are motivated to learn-- and these developers are the same people who will be OK to add a non-default calendar parameter! I outlined these concerns in more detail here: https://github.com/tc39/proposal-temporal/issues/292#issuecomment-633736952. Happy to discuss further.

You're bringing me over to your point of view on calendars being an explicit choice for programmers. But, I don't see why this means that calendar should be defaulted. Having calendars be required helps these programmers by showing them everywhere they need to put their calendar argument.

I wanted to add Option 7 based on discussions from today.

As I've previously noted, there are really only two functions affected by an implicit calendar argument: Temporal.now.xxx(), and Temporal.Absolute.prototype.toLocalDateTime().

Option 7 is to make Temporal.now.xxx() use the environment's calendar (from Intl), like it already does with the time zone. Temporal.Absolute.prototype.toLocalDateTime() would require both the time zone and the calendar, since those are the two fields added when transitioning from Temporal.Absolute to Temporal.LocalDateTime.

Option 7 is therefore a hybrid between Option 4 (applied to Temporal.now) and Option 2 (applied to Temporal.Absolute.prototype.toLocalDateTime).

Option 7 looks promising.

There's one more problematic method:Time. prototype.toDateTime. This problem goes away if Time gets a calendar as @pipobscure has assured we should do for reasons unrelated to calendars in #522.

Time. prototype.toDate

There is Time.prototype.toDateTime, which takes a Temporal.Date, which has a calendar slot. There is no Time.prototype.toDate.

Oops yes. Typo. Fixed.

Right, except that Time.prototype.toDateTime is not problematic, even without #522, because it can use the calendar from its required Temporal.Date argument.

Ahhh, you're right. You always have a date to combine with. Never mind.

there are really only two functions affected by an implicit calendar argument: Temporal.now.xxx(), and Temporal.Absolute.prototype.toLocalDateTime().

What about Absolute.prototype.toDateTime ?

What about Absolute.prototype.toDateTime ?

If we still have such a function, then yes, that one would be affected. We don't really need that function, though, since you can go to LocalDateTime first and then down to DateTime. We should probably remove that function.

@justingrant asked me to give more examples of where i18n-related functionality was used outside of the context of formatting.

I just came across one in my coding today: charsets when reading files from the filesystem. Node.js and Python now expect you to pass the character encoding when reading a file from disk. Often, you just hard-code "utf-8". However, I can think of a couple times where this has really saved me: when my file wasn't UTF-8, I knew exactly where I needed to fix it in code.

Formatting is a big one, and you mention charsets (diminishing but still
necessary). There's also parsing, comparison (collation, searching,...),
transforms (lowercasing, transliteration,..), choice of units per
locale+scope (currency, measures, ...), etc.

On Mon, Sep 7, 2020, 13:38 Shane F. Carr notifications@github.com wrote:

@justingrant https://github.com/justingrant asked me to give more
examples of where i18n-related functionality was used outside of the
context of formatting.

I just came across one in my coding today: charsets when reading files
from the filesystem. Node.js and Python now expect you to pass the
character encoding when reading a file from disk. Often, you just hard-code
"utf-8". However, I can think of a couple times where this has really saved
me: when my file wasn't UTF-8, I knew exactly where I needed to fix it
in code.

—
You are receiving this because you commented.
Reply to this email directly, view it on GitHub
https://github.com/tc39/proposal-temporal/issues/292#issuecomment-688503445,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/ACJLEMHZGEQ66HMJFTTUWLLSEVAFXANCNFSM4JYACW2A
.

@macchiati Can you say more about your preferences among the non-1 options?

I'll weigh in later (not feeling well now).

Mark

On Mon, Sep 7, 2020 at 9:19 PM Daniel Ehrenberg notifications@github.com
wrote:

@macchiati https://github.com/macchiati Can you say more about your
preferences among the non-1 options?

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/tc39/proposal-temporal/issues/292#issuecomment-688608718,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/ACJLEMA6MV7YRBNC3HCMRZ3SEWWDZANCNFSM4JYACW2A
.

I'm a bit concerned about this option 7, as I am with 4, because I don't think we're going to be able to get a correct default calendar in a reliable way.

In general, my understanding is that communicating users' calendar preference is a lot less mature than communicating users' default timezone around computers in general, with a common pattern of higher layers ending up maintaining preferences. I'm not really sure if browsers will be able to get an accurate enough view of which calendar they should choose, even if we add web APIs to communicate this to the client and HTTP headers for the server. It would definitely be a good data source, and I support these efforts, but I'm not sure if having it "just work" will be feasible (e.g., given how many people and UIs juggle multiple calendar systems at once).

In JavaScript server environments, I don't see how a default calendar would work: there would be no way to change the default calendar within a particular scope, since JS doesn't support any kind of dynamic scoping that you'd need to make that work. The default calendar of the surrounding environment may show through, rather than that of the client. So, if you try to evaluate the same JS code on the server and client, you'll often get mismatching results, which could potentially lead to bugs.

In browsers, the default calendar would currently be chosen based on just the region, which we know is inaccurate. Even if we move it to OS preferences, I believe that this would not always be set as the user would like--this is why some applications have user-level preferences for calendars.

I believe having a global default locale was a design mistake in ECMA-402, which we shouldn't reproduce in Temporal. Instead, it would be better to read the locale from navigator.languages thread these things through explicitly. We've been grandfathering in the default locale in in that one area (Intl constructors), but shouldn't extend the pattern everywhere. Defaults lead to unreliable behavior, difficulty testing/reproducing results. They're "too cute", working just enough for easier examples, sacrificing correctness in the harder cases.

For those reasons, I would strongly prefer that we use an explicit calendar for those cases or default to the Gregorian calendar, and I would disprefer having a locale-sensitive default calendar used when no calendar is provided. (I have a slight preference for default-ISO over locale-sensitive, but that's more a matter of taste than the more objective concerns I listed above.)

I'm a bit concerned about this option 7, as I am with 4, because I don't think we're going to be able to get a correct default calendar in a reliable way.

In general, my understanding is that communicating users' calendar preference is a lot less mature than communicating users' default timezone around computers in general, with a common pattern of higher layers ending up maintaining preferences.

My hope and intention is that the environment calendar will evolve to become more reliable over time. For example, the User Preferences proposal makes the user's OS preference available to the web platform (via navigator.locales, which Temporal.now would use).

I'm not really sure if browsers will be able to get an accurate enough view of which calendar they should choose, even if we add web APIs to communicate this to the client and HTTP headers for the server. It would definitely be _a_ good data source, and I support these efforts, but I'm not sure if having it "just work" will be feasible (e.g., given how many people and UIs juggle multiple calendar systems at once).

Agreed that the data source isn't perfect. However, it should have a higher precision than "just assume Gregorian".

In JavaScript server environments, I don't see how a default calendar would work: there would be no way to change the default calendar within a particular scope, since JS doesn't support any kind of dynamic scoping that you'd need to make that work. The default calendar of the surrounding environment may show through, rather than that of the client. So, if you try to evaluate the same JS code on the server and client, you'll often get mismatching results, which could potentially lead to bugs

In browsers, the default calendar would currently be chosen based on just the region, which we know is inaccurate. Even if we move it to OS preferences, I believe that this would not always be set as the user would like--this is why some applications have user-level preferences for calendars.

No matter which option we choose, server-side and client-side applications can provide the calendar via an explicit argument, which will be better than any default.

I would be open to consider allowing Temporal.now.calendar() to be overriden by user code as a way to set the default calendar for other Temporal.now functions to use. Main issue: #873

Option 2, explicit everywhere, is the only one that encourages best server-side practices.

I believe having a global default locale was a design mistake in ECMA-402, which we shouldn't reproduce in Temporal. Instead, it would be better to read the locale from navigator.languages thread these things through explicitly. We've been grandfathering in the default locale in in that one area (Intl constructors), but shouldn't extend the pattern everywhere. Defaults lead to unreliable behavior, difficulty testing/reproducing results. They're "too cute", working just enough for easier examples, sacrificing correctness in the harder cases.

I generally agree, which is why Option 2 remains my first choice.

For those reasons, I would strongly prefer that we use an explicit calendar for those cases or default to the Gregorian calendar, and I would disprefer having a locale-sensitive default calendar used when no calendar is provided. (I have a slight preference for default-ISO over locale-sensitive, but that's more a matter of taste than the more objective concerns I listed above.)

I agree with your arguments that any default calendar is bad in principle. However, if we had to pick one for the purposes of Temporal.now (and I want to emphasize that this is the only case we are debating), your arguments don't lead to the conclusion that default-ISO is better than locale-sensitive.

I agree with Shane.

Ideal for avoiding potential problems is to pass in explicit locale,
timezone, calendar, etc parameters always instead of defaulting (Option 2).
And in a server environment that is especially the case.

But if the API is to have defaults, Option 7 is far better than Option 1,
since it is much more likely to get the right answer. Having Temporal.now
default to a locale-sensitive choice of calendar won't make things any
worse than defaulting to Gregorian. If the user's choice of calendar is
available, then the user would get it. If it isn't available, then the user
gets the 'ultimate default': Gregorian.

Mark

On Tue, Sep 8, 2020 at 2:52 PM Shane F. Carr notifications@github.com
wrote:

I'm a bit concerned about this option 7, as I am with 4, because I don't
think we're going to be able to get a correct default calendar in a
reliable way.

In general, my understanding is that communicating users' calendar
preference is a lot less mature than communicating users' default timezone
around computers in general, with a common pattern of higher layers ending
up maintaining preferences.

My hope and intention is that the environment calendar will evolve to
become more reliable over time. For example, the User Preferences proposal
makes the user's OS preference available to the web platform (via
navigator.locales, which Temporal.now would use).

I'm not really sure if browsers will be able to get an accurate enough
view of which calendar they should choose, even if we add web APIs to
communicate this to the client and HTTP headers for the server. It would
definitely be a good data source, and I support these efforts, but I'm
not sure if having it "just work" will be feasible (e.g., given how many
people and UIs juggle multiple calendar systems at once).

Agreed that the data source isn't perfect. However, it should have a
higher precision than "just assume Gregorian".

In JavaScript server environments, I don't see how a default calendar
would work: there would be no way to change the default calendar within a
particular scope, since JS doesn't support any kind of dynamic scoping that
you'd need to make that work. The default calendar of the surrounding
environment may show through, rather than that of the client. So, if you
try to evaluate the same JS code on the server and client, you'll often get
mismatching results, which could potentially lead to bugs

In browsers, the default calendar would currently be chosen based on just
the region, which we know is inaccurate. Even if we move it to OS
preferences, I believe that this would not always be set as the user would
like--this is why some applications have user-level preferences for
calendars.

No matter which option we choose, server-side and client-side
applications can provide the calendar via an explicit argument, which will
be better than any default.

I would be open to consider allowing Temporal.now.calendar() to be
overriden by user code as a way to set the default calendar for other
Temporal.now functions to use. Main issue: #873
https://github.com/tc39/proposal-temporal/issues/873

Option 2, explicit everywhere, is the only one that encourages best
server-side practices.

I believe having a global default locale was a design mistake in ECMA-402,
which we shouldn't reproduce in Temporal. Instead, it would be better to
read the locale from navigator.languages thread these things through
explicitly. We've been grandfathering in the default locale in in that one
area (Intl constructors), but shouldn't extend the pattern everywhere.
Defaults lead to unreliable behavior, difficulty testing/reproducing
results. They're "too cute", working just enough for easier examples,
sacrificing correctness in the harder cases.

I generally agree, which is why Option 2 remains my first choice.

For those reasons, I would strongly prefer that we use an explicit
calendar for those cases or default to the Gregorian calendar, and I would
disprefer having a locale-sensitive default calendar used when no calendar
is provided. (I have a slight preference for default-ISO over
locale-sensitive, but that's more a matter of taste than the more objective
concerns I listed above.)

I agree with your arguments that any default calendar is bad in
principle. However, if we had to pick one for the purposes of Temporal.now
(and I want to emphasize that this is the only case we are debating), your
arguments don't lead to the conclusion that default-ISO is better than
locale-sensitive.

—
You are receiving this because you were mentioned.
Reply to this email directly, view it on GitHub
https://github.com/tc39/proposal-temporal/issues/292#issuecomment-689156439,
or unsubscribe
https://github.com/notifications/unsubscribe-auth/ACJLEMFYA6IE5TA467QJRRDSE2RQPANCNFSM4JYACW2A
.

Some ideas stemming from discussions in last Friday's meeting:

First, we decided to add (see #625) an equalsISO method on the Temporal.Date type to mirror the existing getISOFields method. Could this XxxISO pattern be useful in other calendar-related places? For example, should there be a Temporal.nowISO object which is optimized for servers or other environments where an ISO default is desired, leaving Temporal.now to pick up the environment's calendar?

Second, in Friday's meeting we discussed using an ESLint rule to catch cases where users specify string or object literals without specifying a calendar. The more I thought about this approach, the more I liked it, because ESLint rules are highly configurable. The developer could dial up or dial down the "strictness" of the rule, e.g. whether to require the a calendar to be explicitly specified for object bags vs. strings vs. now() vs. toLocalDateTime/toDateTime.

If we had such a lint rule, @sffc how would you feel about Absolute.prototype.toLocalDateTime accepting an optional calendar (defaulting to ISO) instead of a required calendar? I'm asking because the pattern that we both like in #889 gets a lot more ergonomic if the calendar were optional in that method.

// 2 required parameters, so object bag required
absolute.toLocalDateTime({timeZone: 'America/Los_Angeles', calendar: 'iso8601'});
// optional calendar, so can provide only a string
absolute.toLocalDateTime('America/Los_Angeles');

First, we decided to add (see #625) an equalsISO method on the Temporal.Date type to mirror the existing getISOFields method. Could this XxxISO pattern be useful in other calendar-related places? For example, should there be a Temporal.nowISO object which is optimized for servers or other environments where an ISO default is desired, leaving Temporal.now to pick up the environment's calendar?

Works for me.

Second, in Friday's meeting we discussed using an ESLint rule to catch cases where users specify string or object literals without specifying a calendar. The more I thought about this approach, the more I liked it, because ESLint rules are highly configurable. The developer could dial up or dial down the "strictness" of the rule, e.g. whether to require the a calendar to be explicitly specified for object bags vs. strings vs. now() vs. toLocalDateTime/toDateTime.

If we had such a lint rule, @sffc how would you feel about Absolute.prototype.toLocalDateTime accepting an optional calendar (defaulting to ISO) instead of a required calendar? I'm asking because the pattern that we both like in #889 gets a lot more ergonomic if the calendar were optional in that method.

I would rather not rely solely on ESLint rules to enforce best i18n practices. The comment I made about strings is intended as a general comment about strings in code. My hope is that as more platforms adopt our convention for expressing calendars in strings, issues involving strings will diminish over time.

I also acknowledge the brevity argument, since it is the number 1 complaint about Temporal overall. However, I do think that this particular case warrants the extra code. It's now the one and only place where we're still talking about making the calendar a required option, and I think it will go great lengths to help developers understand this crucial difference between Absolute and the rest of Temporal.

I would likely be happy with toISOLocalDateTime("America/Chicago") coexisting as a briefer alternative to toLocalDateTime({ timeZone: "America/Chicago", calendar: "iso8601" }).

Okay. Here is my latest proposal, based on a synthesis of suggestions from @justingrant, @macchiati, and @pipobscure. Thanks everyone for hanging with me over the last several months. I truly hope that this solution makes everyone happy.

Instant → LocalDateTime

Since this is the only transition that adds a calendar field to the Temporal data model, it is the one and only transition that needs to get a calendar from somewhere. My proposal is to have two methods:

  1. Temporal.Instant.prototype.toLocalDateTime({ timeZone, calendar }) takes an option bag with two required fields (timeZone and calendar).
  2. Temporal.Instant.prototype.toLocalDateTimeISO(timeZone) takes only one option, the timeZone (which may be specified in the option bag as per #889).

Temporal.now

This is a situation where the current time and time zone already come from the environment, so it makes intuitive sense that the calendar system also come from the environment. However, @littledan feels strongly that the ecosystem for user preferences hasn't evolved far enough yet to make the environment calendar high quality. I therefore propose two separate pieces:

  1. Add new functions with the "iso" suffix that return objects with the ISO-8601 calendar system:

    • Temporal.now.dateISO()

    • Temporal.now.dateTimeISO()

    • Temporal.now.localDateTimeISO()

    • Temporal.now.timeISO()

  2. Either remove the existing functions in Temporal.now that take an implicit calendar (Daniel's preference if I understand correctly), or change them to use the environment's calendar. If we remove them, we could always add them in the future when the user preferences ecosystem is more fully developed.

    • Temporal.now.date()

    • Temporal.now.dateTime()

    • Temporal.now.localDateTime()

    • Temporal.now.time()

Temporal.*.from(string)

As discussed in #293, I am engaging the Calsify group in IETF to discuss an extension to RFC 3339 that adds support for IANA time zone identifiers as well as calendars. As more of the ecosystem adopts this convention, strings are more and more likely to contain the correct calendar information when that information is pertinent.

Therefore, I do not feel it is warranted to have a separate calendar argument in this method.

Temporal.*.from(fields)

@ptomato astutely observed that at the point when the field object is created, the calendar system was already considered. For example, if a developer wrote { year: 2020, month: 9, day: 18 }, they clearly intended that to be a date in the ISO-8601 calendar, even though the calendar is not a slot (if this was not their intention, the developer should find out quickly because the date will be wrong). Likewise, { year: 5780, /* ... */, calendar: "hebrew" } accurately expresses the author's intent that the fields are expressed in the Hebrew calendar.

Therefore, I do not feel it is warranted to have a separate calendar argument in this method.

2020-09-18: Consensus on https://github.com/tc39/proposal-temporal/issues/292#issuecomment-694670080, with the following amendment: keep Temporal.now.date() and friends, and they take an explicit calendar argument.

// Current date in the ISO calendar
Temporal.now.dateISO();
Temporal.now.date({ calendar: "iso8601" });
Temporal.now.date("iso8601");  // shortcut according to #889

// Current date in the Hebrew calendar
Temporal.now.date({ calendar: "hebrew" });
Temporal.now.date("hebrew");  // shortcut

// Current date in the environment calendar
Temporal.now.date({ calendar: new Intl.DateTimeFormat().resolvedOptions().calendar });
Temporal.now.date(new Intl.DateTimeFormat().resolvedOptions().calendar);  // shortcut

// Throws an exception: calendar must be provided
Temporal.now.date()
Was this page helpful?
0 / 5 - 0 ratings