-
Notifications
You must be signed in to change notification settings - Fork 149
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
Safer handling of negative years in non-ISO calendars #1231
Comments
I love the idea of having a "signed year", I call it "algebraic year", meaning that you can easily make computations with it. As far as I am aware, all calendar can define such a year enumeration, using signed integers from a well defined and fixed origin. A number of calendars count years before the origin as negative integers, the origin being year zero. So does the modern Indian calendar (not surprising from the people that introduced zero to mankind), so do by default calendars for which the origin is "the world's creation", like the Hebrew calendar and also the Ethiopic ("Amete amet" means "from the world's creation"). As for the "default era", this is a "subjective" issue. The default era is the era we are presently in. Three years ago, the default era for the Japanese was Heisei, but now it is Reiwa. There is a pending proposal on this issue, your comments are most welcome at https://github.com/Louis-Aime/proposal-intl-eradisplay. It could an interesting idea to suggest that each calendar defines an "origin era", the era of the "signed year" 1. For a number of calendars, this era is the first one, e.g. IMHO the "signed year" or "algebraic year", defined by the calendar's author, would fulfil the needs. |
Hi @Louis-Aime - @sffc pointed me to your proposal and from a quick review it looks good to me. Note that the use cases of Temporal are subtly different from the use cases of Intl's formatting APIs (and hence your For Temporal, dynamically choosing the era based on the current date seems more problematic
Yep, this is essentially what I'm recommending: each calendar defines one era whose For Ethiopic, both the "start of creation" era and the modern era seem to have pros and cons as the anchor era. Given that there's an For Japanese, the choice seems more ambiguous, especially because (unlike Ethiopic) there's going to be another Japanese era within a few more decades. If there's already a logical anchor era that scholars use, then we can use that one, but it seems reasonable to just pick the current (Reiwa) era, or perhaps Shōwa which was active at the UNIX epoch date? Regardless, if only the Japanese calendar has this ambiguity, I don't think that one case is enough to avoid offering a signed year field on all calendars. Another interesting aspect of Japanese eras is that they don't start on year boundaries. For example: the current Reiwa era began on
Agreed. What do you think about my proposal above to use Temporal's |
Hello @justingrant , I think we have the same point of view about the issues. Here is what I belief. Temporal documentation should clearly state to calendar's author whether the The first option means clearly: In the second option, Personally, I am in favour of option 2, because one expectation of the calendar's user is to easily convert from ISO 8601 to any calendar and the reverse, and to get the results without using DateTimeFormat.
Morevover, as a calendar's author, I would be glad to add other characteristics. I personally added "week characteristics" like In short I am in favour of having |
This is very well stated! I agree.
Agreed also.
For the "display-oriented" option, there is an existing standard RFC 7529 that defines a simplified combination of month and type by using a string that's either an integer (normal month) or an integer with an "L" suffix (e.g. "5L" for Adar I, or "7L" for a leap month added after the 7th chinese month) to denote an unusual month. We may want to add a leading zero for better comparability if the standard is OK with it. Anyway, I'd be inclined to follow this standard in Temporal too for a BTW I'm OK with having
AFAIK, either option could enable this use case. Here's how I was assuming this case could work with option 1. What's missing? // ISO => Calendar
const isoDate = Temporal.PlainDate.from('2022-05-01[c=hebrew]');
const {
year, // 5782
month, // 6 (ordinal index of Adar I in this year)
monthCode, // "5L"
monthType, // "leap"
regularMonth, // 6 ("normal month" that this month is related to)
day // 29
} = calendarDate;
// Calendar => ISO (the developer has several choices depending on which data they have)
date = Temporal.PlainDate.from({ year: 5782, month: 6, day: 29 });
date = Temporal.PlainDate.from({ year: 5782, monthCode: '5L', day: 29 });
date = Temporal.PlainDate.from({ year: 5782, regularMonth: 6, monthType: 'leap', day: 29 }); BTW, here's the reasons why I prefer Option 1:
Note that the last two point above may be in tension. For example, we could choose to use an opaque string value for |
Another idea for Japanese: January 1 of the Gregorian year 1 could be used as the "anchor epoch". In other words, the signed year for Japanese would always match the Gregorian year. Given that Japan already uses the Gregorian calendar for months and days, this might be a logical choice to avoid having to pick a particular era as the zero-date. Given that the signed year is never going to satisfy all Japanese developers, then at least picking a familiar epoch seems reasonable and would probably make coding easier with that calendar. |
I feel like we have discussed most arguments. Now the Temporal design team can take reasonable decisions. I think everyone should stick to existing standards like RFC 7529 unless there are solid arguments for not doing so.
This works indeed. The small difference I suggested is that developers and users may be "ambiguous", that is, they could use the same word, e.g.
This works also: |
About the Japanese calendar:
I have two remarks:
|
Meeting 2020-12-17: we don't have consensus on this one. Not yet at least! More discussion needed. A quick note about invariants: if we made
I'd expect that the biggest benefit would be lowering the bar for developers to write trans-calendar code, even if they don't intend to write trans-calendar code. Having more trans-calendar code will improve the reliability of JS apps for end-users who prefer non-ISO calendars. It'd also ensure that developers who who are using era-dependent calendars will have a wider range of 3rd-party libraries that they can successfully use. In the meeting, we also discussed downsides of making this change:
|
A Japanese colleague of mine suggested that if we need to set an epoch for the Japanese calendar, Meiji 1 is a good choice (Gregorian year 1868), since it is the first era in Modern Japan. To me, having an algebraic year (a monotonically increasing integer with both positive and negative values, rooted at an epoch) seems convenient to supplement the calendar year (a year number in the current era), although it wasn't clear if we have consensus on that point. If we did decide to have both an algebraic year and a calendar year, it's not clear which one should be used as the "default": we could have Also, here's a little table comparing this discussion with #1203:
We should think about being consistent with regard to whether we make the arithmetic property or the calendar property the "default" for both years and months. |
Agreed.
For developers who intend to write trans-calendar code, IMHO either option would be fine, because those developers already have to think about eras in their app, already have to pair But for developers who don't intend to write trans-calendar code (or who don't even know about other calendars) making the signed year the default would seem to make it much more likely that their code will work across calendars because more ISO business logic can be used as-is. As noted above, this isn't just for apps, it's for libraries too. If it's easier to write trans-calendar code in libraries, then more libraries will work trans-calendar and developers who are working in an era-dependent calendar will have more libraries they can use.
I'd suggest |
So, pending further research (and I do expect further research to be coming), I see three choices emerging:
In either case, all calendars should accept all properties. For simplicity, we should consider defining an era named "iso8601" to be the only era in the ISO calendar, which can be filled in automatically if not present. For example: // Option 1 equivalents (with iCal month):
Temporal.Date.from({ era: "iso8601", year: 2020, month: "01", day: 1 });
Temporal.Date.from({ year: 2020, month: "01", day: 1 }); // era optional in single-era calendars
Temporal.Date.from({ year: 2020, month: 1, day: 1 }); // month can be coerced to a string
Temporal.Date.from({ year: 2020, monthNumber: 1, day: 1 });
Temporal.Date.from({ yearsSinceEpoch: 2020, monthNumber: 1, day: 1 });
// Option 2 equivalents (with monthType):
Temporal.Date.from({ era: "iso8601", year: 2020, month: 1, monthType: "standard", day: 1 });
Temporal.Date.from({ year: 2020, month: 1, monthType: "standard", day: 1 }); // calendar makes era optional
Temporal.Date.from({ year: 2020, month: 1, day: 1 }); // calendar makes monthType optional
Temporal.Date.from({ year: 2020, monthNumber: 1, day: 1 });
Temporal.Date.from({ yearsSinceEpoch: 2020, monthNumber: 1, day: 1 });
// Option 3 equivalents (with monthCode):
Temporal.Date.from({ year: 2020, month: 1, day: 1 });
Temporal.Date.from({ year: 2020, monthCode: "01", day: 1 });
Temporal.Date.from({ eraYear: 2020, monthCode: "01", day: 1 });
Temporal.Date.from({ era: "iso8601", eraYear: 2020, monthCode: "01", day: 1 }); I won't yet put a stake in the ground on which option I prefer, except to say that I think we should choose the one that makes it easiest to write trans-calendar code by default. A separate but related question is how we want to deal with the data
|
These code samples are great! Thanks @sffc for the comparison.
I agree, with a caveat: "we should choose the one that makes it easiest to write trans-calendar code by default, as long as it doesn't make it harder to write single-calendar and (especially) ISO-calendar code." Would you be OK with that caveat? For example, we could choose to make
I'd apply the same goal here too: whichever makes it easiest to write trans-calendar code. IMHO, this means that fields have the same (or as similar as possible) names, number, and types across calendars. The current model where some calendars add fields and some don't seems sup-optimal for cross-calendar compatibility. Also, ideally Taking these two criteria together, I think it means that the ideal
It may be reasonable to require that |
I prefer it without the caveat, and there lies the source of many of our polite disagreements. 😉
Possibly. We should evaluate if we can maintain that invariant while being general enough to support all built-in and userland calendars.
Not sure about how much value to put here. People are not intended to generally be calling
That sounds like a reasonable change within the context of option 3. |
As a "user" of Temporal for defining calendars, I definitely think that:
If these properties are required, I would also specify that in any Temporal expression like Observe that with I would also prohibe any "wild calculations" using |
I'm not convinced that this meets the high bar of adding new API at this point in the proposal, especially after our discussion today. I would very strongly prefer to defer this to a Temporal v2. |
Is this a new API? Or is it only guidance for calendar authors, including authors of built-in calendars? e.g.:
The value of making I'm also concerned about the security implications of The reverse proposal--where |
I like this proposal. I do feel like flipping it to be I will point out that we already have isoYear. Then again, the whole point of this is for people who may have missed something in the docs, so we can't guarantee they'll use isoYear for this 😄 |
For context, the reasons for the proposal in this issue are:
With those goals in mind:
AFAICT, none of the goals above are satisfied unless If we made a non-
Nope, because BTW, because of the behavior of the BC era, if this proposal isn't adopted then I'd hope that usage of |
If it's the case that this is not new API, and it's only guidance for calendar authors, then can we agree to keep the status quo in the polyfill, and close this discussion here, moving it to the https://github.com/tc39/ecma402 repo? I'm very concerned that continuing to discuss on speculative tickets about what the meaning of the If it is the case that it's new API, because we change the meaning of the |
Consensus from 2021-01-19 meeting:
|
The state of this after #1319 is that:
|
The slight changes to the polyfill implementation of the Japanese calendar in #1319 use the ISO year as the signed year; I'm wary of this because it seems culturally insensitive at best. For more context, #526. However, since this is only used on output and rejected on input, it is less bad than it could be, and importantly it's not in the spec - just the polyfill. An improvement would be to select a culturally relevant, non-Gregorian anchor value for the signed year, and to fix the Japanese calendar implementation in the polyfill to recognize inter-ISO-year era+eraYear transitions, but that is also beyond the scope of #1319. |
We should not reject
Take a look at #526 (comment) which are notes from a Japanese colleague of @sffc about the |
Got it. The changes just merged do not specify that anywhere, so let's follow that up in #526 and fix the polyfill for that issue. |
While building non-ISO calendar prototypes, one problem I keep running into is the complexity of era-dependent year numbering, specifically because it introduces the possibility of years counting backwards. For formatting, "12 B.C." is desirable, but for calculation use cases (aka the main reason Temporal exists!) these era-based years add a lot of problems. For example:
.add
-ing one day untildate.year > originalDate.year
. For BC years, that's an infinite loop.So far, the best solution I can come up with is:
eraYear
oryearInEra
). Either property could be used to represent the year inwith
andfrom
(but not both together).An alternative to (1) above would be to do what's recommended in the eraDisplay proposal which is to set the default era to the current time. In Temporal use cases, this seems like it'd introduce quite a bit of complexity, perf, and maybe security issues. Not sure dynamic era selection is worth it, esp. since the vast majority of non-ISO calendars wouldn't be affected. (Japanese would, though.) For formatting, dynamic era selection seems appropriate. But for calculation and back-end use (aka Temporal) I'm not sure it's worth it.
The text was updated successfully, but these errors were encountered: