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

Normative: Add dayPeriod option #346

Merged
merged 5 commits into from
Jan 5, 2021
Merged

Normative: Add dayPeriod option #346

merged 5 commits into from
Jan 5, 2021

Conversation

FrankYFTang
Copy link
Contributor

No description provided.

@FrankYFTang FrankYFTang added c: datetime Component: dates, times, timezones s: discuss Status: TG2 must discuss to move forward Small Smaller change solvable in a Pull Request enhancement labels May 20, 2019
@FrankYFTang
Copy link
Contributor Author

Format Day Period

var dtf = new Intl.DateTimeFormat("en", {hour: "numeric", dayPeriod: "short"});
dtf.format(new Date("2019-05-20T07:00:00"));  // "7 in the morning"
dtf.formatToParts(new Date("2019-05-20T07:00:00"));
// [{type: "hour", value: "7"},
//  {type: "literal", value: " "}, 
//  {type: "dayPeriod", value: "in the morning"}]
dtf.format(new Date("2019-05-20T11:59:00"));  // "11 in the morning"
dtf.format(new Date("2019-05-20T12:00:00"));  // "12 noon"
dtf.format(new Date("2019-05-20T13:00:00"));  // "1 in the afternoon"
dtf.format(new Date("2019-05-20T18:00:00"));  // "6 in the evening"
dtf.format(new Date("2019-05-20T22:00:00"));  // "10 at night"
dtf = new Intl.DateTimeFormat("zh-Hant", {hour: "numeric", dayPeriod: "short"});
dtf.format(new Date("2019-05-20T07:00:00"));  // "清晨7時"
dtf.formatToParts(new Date("2019-05-20T07:00:00"));
// [{type: "dayPeriod", value: "清晨"}, 
//  {type: "hour", value: "7"},
//  {type: "literal", value: "時"}]
dtf.format(new Date("2019-05-20T11:59:00"));  // "上午11時"
dtf.format(new Date("2019-05-20T12:00:00"));  // "中午12時"
dtf.format(new Date("2019-05-20T13:00:00"));  // "下午1時"
dtf.format(new Date("2019-05-20T18:00:00"));  // "下午6時"
dtf.format(new Date("2019-05-20T22:00:00"));  // "晚上10時"

@FrankYFTang
Copy link
Contributor Author

@FrankYFTang
Copy link
Contributor Author

To address #343

Copy link
Member

@leobalter leobalter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I wonder if we could fit a note to the specs telling what a dayPeriod means as the examples in this PR are super useful.

@FrankYFTang
Copy link
Contributor Author

FrankYFTang commented May 24, 2019

LGTM, I wonder if we could fit a note to the specs telling what a dayPeriod means as the examples in this PR are super useful.

The calculation is quite complex since for each locale, AFTER decide the locale hour by considering TimeZone and inDST,

  1. there are different set of dayPeriod and
  2. the mapping from hour:minute to dayPeriod are also different and
  3. the text to represent such dayPeriod are also different

In ICU, this information came from CLDR, 1) and 2) are covered in
https://cs.chromium.org/chromium/src/third_party/icu/source/data/misc/dayPeriods.txt

For example:

dayPeriods:table(nofallback){
    locales{
...
        en{"set2"}
...
        zh{"set51"}

and then later

        set2{
            afternoon1{
                before{"18:00"}
                from{"12:00"}
            }
            evening1{
                before{"21:00"}
                from{"18:00"}
            }
            midnight{
                at{"00:00"}
            }
            morning1{
                before{"12:00"}
                from{"06:00"}
            }
            night1{
                before{"06:00"}
                from{"21:00"}
            }
            noon{
                at{"12:00"}
            }
        }
...
        set51{
            afternoon1{
                before{"13:00"}
                from{"12:00"}
            }
            afternoon2{
                before{"19:00"}
                from{"13:00"}
            }
            evening1{
                before{"24:00"}
                from{"19:00"}
            }
            midnight{
                at{"00:00"}
            }
            morning1{
                before{"08:00"}
                from{"05:00"}
            }
            morning2{
                before{"12:00"}
                from{"08:00"}
            }
            night1{
                before{"05:00"}
                from{"00:00"}
            }
        }

and for 3) of en: in https://cs.chromium.org/chromium/src/third_party/icu/source/data/locales/en.txt

            dayPeriod{
                format{
                    abbreviated{
                        afternoon1{"in the afternoon"}
                        evening1{"in the evening"}
                        midnight{"midnight"}
                        morning1{"in the morning"}
                        night1{"at night"}
                        noon{"noon"}
                    }

and for 3) of zh-Hant: in https://cs.chromium.org/chromium/src/third_party/icu/source/data/locales/zh_Hant.txt

            dayPeriod{
                format{
                    abbreviated{
                        afternoon1{"中午"}
                        afternoon2{"下午"}
                        evening1{"晚上"}
                        midnight{"午夜"}
                        morning1{"清晨"}
                        morning2{"上午"}
                        night1{"凌晨"}
                    }

@littledan
Copy link
Member

littledan commented May 27, 2019

Is it expected that you can use any combination of dayPeriod with hourCycle, or does dayPeriod imply a locale-dependent "informal" hourCycle?

@littledan
Copy link
Member

I'm also wondering how "dayPeriod" should relate to the "ampm" field in formatToParts.

@FrankYFTang
Copy link
Contributor Author

I'm also wondering how "dayPeriod" should relate to the "ampm" field in formatToParts.

The ampm field is not an option in the options to be controlled by the caller to output or not, but rather a field that decided by the pattern of the locale to output depending on the hourcycle. Therefore, it should depending the pattern in the locale data to decide, not in the algorithm of ECMA402 to decide.
I search the pattern, "so far" none of the pattern will output ampm field while the dayPeriod is requested, but that does not mean we should put text in the spec to prohibit such combination ever happen in the pattern.

@FrankYFTang
Copy link
Contributor Author

Is it expected that you can use any combination of dayPeriod with hourCycle, or does dayPeriod imply a locale-dependent "informal" hourCycle?

yes, it should be use any combination of dayPeriod with hourCycle.

@FrankYFTang
Copy link
Contributor Author

We have discussed this PR in Jun 5 2019 TC39 and decided this is small enough and align with pre-existing API already and therefore does not need to form a staged proposal but do require consensus with in ECMA402 committee + tests in test262 + 1 js engine implementation and no concern from other js implementation to land into ECMC402 as stage 4.

@jungshik
Copy link

jungshik commented Jun 7, 2019

Has any thought been given to B vs b (cf h vs H and k)?

@FrankYFTang
Copy link
Contributor Author

Has any thought been given to B vs b (cf h vs H and k)?

Currently, the spec only support 'B', not 'b'- Neither do we currently support requesting of 'a'
ref: http://cldr.unicode.org/translation/date-time-patterns

If we consider adding support for requesting 'b', we should consider together with how we support 'a'.

@FrankYFTang
Copy link
Contributor Author

FrankYFTang commented Jun 11, 2019

Initial set of tests in tc39/test262#2194
V8 is now having this behind a flag --harmony_intl_dateformat_day_period

@leobalter leobalter added the has consensus Has consensus from TC39-TG2 label Jun 13, 2019
@leobalter
Copy link
Member

This achieved consensus from TC39 and Intl meetings in June 2019

@sffc sffc added the s: in progress Status: the issue has an active proposal label Sep 20, 2019
@FrankYFTang
Copy link
Contributor Author

FrankYFTang commented Oct 16, 2019

Maybe #346 (comment) is just an issue which requires good documentation? That means the current behaviour can be left as is and we just need to make sure the documentation is clear enough so that users don't trip up?

More importantly:
The implementation of this PR is now blocked by the following two ICU bugs:

And https://unicode-org.atlassian.net/browse/CLDR-13184 maybe also worth looking into.

The CLDR bug report has the following test case:

js> new Intl.DateTimeFormat("en", {minute: "numeric", dayPeriod: "short"}).format(new Date("2019-01-01T12:00:00"))
"0 ├AM/PM: noon┤"

Apart from the "├ ┤" issue, the descriptive name for the day period seems a bit questionable to me. Hmm, I guess CLDR added support for more fine grained day periods when support for am/pm was already present and then simply reused the descriptive name for am/pm and applied it for all day periods?

It's even worse for other locales, for example the German data uses the descriptive name "Tageshälfte" (literally: "day half"), which makes no sense when applied to the day period "nachmittags" (noon), because there are only two halves in a day and not seven (or actually six, because ICU currently doesn't support "midnight").

js> new Intl.DateTimeFormat("de", {minute: "numeric", dayPeriod: "short"}).format(new Date("2019-01-01T12:00:00"))
"0 ├Tageshälfte: nachmittags┤"

Day period name in en.xml: https://github.com/unicode-org/cldr/blob/6204abf3e7936ef6663e528b90d585c24e594041/common/main/en.xml#L3148
Day period name in de.xml: https://github.com/unicode-org/cldr/blob/6204abf3e7936ef6663e528b90d585c24e594041/common/main/de.xml#L3160-L3162

@anba - thanks for your comments.
Just want to be clear. My understanding is all the issues you commented above are about the implementation inside ICU but not the specification change itself. Is it correct?
Do you have suggestion for me to change the text inside this PR?

@sffc
Copy link
Contributor

sffc commented Dec 10, 2019

One spec text question:

The spec currently codifies a format as having two subtypes: [[pattern]] and [[pattern12]]:

If the record has an hour field, it must also have a pattern12 field, whose value is a String value that, in addition to the substrings of the pattern field, contains a substring "{ampm}".

If the user requests a certain width for dayPeriod, how does that influence the type that is chosen? For example, does dayPeriod: "short" imply hour12: true?

@bkardell
Copy link

if you specify a non-zero value for fractional second digits, and only that, you also get the '10 at night ├F14: 10┤ kind of behavior, but if you specify both seconds and fractional seconds - only seconds are shown? In v8 for me...

> new Intl.DateTimeFormat("en", {hour: "numeric", dayPeriod: "short", fractionalSecondDigits: 2 }).format(Date.UTC(2012, 11,20,3,0,20,100))
// '10 at night ├F14: 10┤'

> new Intl.DateTimeFormat("en", {hour: "numeric", dayPeriod: "short", second: 'numeric', fractionalSecondDigits: 2 }).format(Date.UTC(2012, 11,20,3,0,20,100))
// '10 at night (second: 20)'

Does that seem strange to anyone else? As an author I found that a little surprising..

@bkardell
Copy link

LGTM, I wonder if we could fit a note to the specs telling what a dayPeriod means as the examples in this PR are super useful.

[snip] The calculation is quite complex since for each locale, AFTER decide the locale hour by considering TimeZone and inDST,.......

@FrankYFTang I could misunderstand but I don't think this actually answers @leobalter's question. I could be wrong, but I had a similar q which was: Could we add some short prose or example that describes some (basic) idea or example "what is a dayPeriod" and "what do these possible values mean" (or a pointer to where they come from if that is better)?

@leobalter
Copy link
Member

Could we add some short prose or example that describes some (basic) idea or example "what is a dayPeriod" and "what do these possible values mean" (or a pointer to where they come from if that is better)?

That's what I meant, thanks @bkardell!

I liked the examples in the PR and I'd like to transfer some of the reasoning of dayPeriod to the ECMA-402 text.

@littledan
Copy link
Member

It seems to me like these underlying data issues should probably be fixed before we consider this PR "done"; I don't know if it makes sense to ship this to web developers while it gives such strange output.

I agree with others in this thread that it'd be helpful to have more motivating examples documented. (I'm not sure where in applications you want to use the longer English form, for example, or if that's more included out of completeness/consistency.)

@sffc
Copy link
Contributor

sffc commented Jan 14, 2020

The resolution to #394 will guide the behavior we want to specify for the above edge cases in this PR.

Copy link
Contributor

@anba anba left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://tc39.es/ecma402/#table-datetimeformat-tolocaltime-record needs to be updated. Not sure how easy that's going to be, because most other fields in that table are tied to ECMA-262 abstract operations, but dayPeriod doesn't have any matching operation we can use here.

@@ -514,6 +519,9 @@ <h1>Internal slots</h1>
<li>month, day</li>
<li>hour, minute, second</li>
<li>hour, minute</li>
<li>dayPeriod, hour</li>
<li>dayPeriod, hour, minute, second</li>
<li>dayPeriod, hour, minute</li>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a good idea what's the most reasonable minimum subset for day-period display should be. So someone with more background in internalisation/localisation is probably a better reviewer for this specific part.

@@ -514,6 +519,9 @@ <h1>Internal slots</h1>
<li>month, day</li>
<li>hour, minute, second</li>
<li>hour, minute</li>
<li>dayPeriod, hour</li>
<li>dayPeriod, hour, minute, second</li>
<li>dayPeriod, hour, minute</li>
</ul>
Each of the records must also have a pattern field, whose value is a String value that contains for each of the date and time format component fields of the record a substring starting with `"{"`, followed by the name of the field, followed by `"}"`. If the record has an hour field, it must also have a pattern12 field, whose value is a String value that, in addition to the substrings of the pattern field, contains a substring `"{ampm}"`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This part seems problematic, doesn't it? The current spec requires a pattern12 field with an "{ampm}" substring for any record which has an hour field. But I don't see how this should work with day-period, because then the record must contain day-period and an am/pm marker, which doesn't really make sense, does it?

For example I currently get in SpiderMonkey with the day-period patch enabled:

js> new Intl.DateTimeFormat("en", {hour:"numeric", dayPeriod:"long"}).format()
"1 in the afternoon"

But the current spec text kind of implies the output should rather be:

js> new Intl.DateTimeFormat("en", {hour:"numeric", dayPeriod:"long"}).format()
"1 PM in the afternoon"

And https://unicode.org/reports/tr35/tr35-dates.html#availableFormats_appendItems also seems to disallow using "B" and "a" in a single skeleton:

Only one field of each type is allowed; that is, "Hh" is not valid.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch; I agree we should fix this. Do you have an idea for wording which would capture that same constraint as CLDR skeletons have?

@anba
Copy link
Contributor

anba commented Feb 27, 2020

https://tc39.es/ecma402/#table-datetimeformat-tolocaltime-record needs to be updated. Not sure how easy that's going to be, because most other fields in that table are tied to ECMA-262 abstract operations, but dayPeriod doesn't have any matching operation we can use here.

Day period should probably just not be mentioned at all in ToLocalTime, but instead be computed manually in PartitionDateTimePattern before step 10.c (which is by the way missing an "Else" in the current spec).

@anba
Copy link
Contributor

anba commented Feb 27, 2020

Also: Do we worry about users having to explicitly set hour12: true resp. hourCycle: "h12" or "h11" to get a useful result?

@littledan
Copy link
Member

Is this PR blocking on landing for #394 to land first? If so, that seems a little unfair, since that issue affects basically everything in Intl.DateTimeFormat.

@littledan
Copy link
Member

littledan commented May 2, 2020

@anba I like your suggestion in #346 (comment) . Would you be interested in writing a patch to add to this PR which does that?

For #346 (comment) , could you explain what you mean (e.g., give a case where the result would be unexpectedly not useful)?

@anba
Copy link
Contributor

anba commented May 6, 2020

For #346 (comment) , could you explain what you mean (e.g., give a case where the result would be unexpectedly not useful)?

For locales defaulting to a 24-hour-cycle, dayPeriod is currently effectively ignored (, unless present in a standalone context):

for (let locale of ["en", "de", "fr", "es"]) {
    console.log(`locale = ${locale}`);
    for (let hour12 of [undefined, true, false]) {
        console.log(`  hour12 = ${hour12}:`);
        let dtf = new Intl.DateTimeFormat(locale, {hour12, hour: "numeric", dayPeriod: "long", timeZone: "UTC"});
        for (let date of [new Date("2020-01-01T06:00Z"), new Date("2020-01-01T12:00Z"), new Date("2020-01-01T18:00Z"), new Date("2020-01-01T24:00Z")]) {
            console.log(`    ${dtf.format(date)}`);
        }
    }
}

Prints in Firefox Nightly:

locale = en
  hour12 = undefined:
    6 in the morning
    12 noon
    6 in the evening
    12 at night
  hour12 = true:
    6 in the morning
    12 noon
    6 in the evening
    12 at night
  hour12 = false:
    06
    12
    18
    00

locale = es
  hour12 = undefined:
    6
    12
    18
    0
  hour12 = true:
    6 de la mañana
    12 del mediodía
    6 de la tarde
    12 de la madrugada
  hour12 = false:
    6
    12
    18
    0

@Constellation
Copy link
Member

Note that JSC dayPeriod support is landed behind the flag.
https://trac.webkit.org/changeset/266323/webkit

@leobalter
Copy link
Member

@FrankYFTang @sffc please help me recapping this one, is anything pending here before merging this PR?

Copy link
Member

@leobalter leobalter left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I applied minor editorial fixes and this is ready to go

@leobalter leobalter merged commit 0e40c1e into master Jan 5, 2021
@leobalter leobalter deleted the FrankYFTang-patch-2 branch January 5, 2021 23:57
anba added a commit to anba/ecma402 that referenced this pull request Feb 25, 2021
The current wording requires to output strings like "12 pm noon", because "ampm"
is always required in pattern12 strings. So accept at least one of "ampm" or
"dayPeriod". See also the review comments in tc39#346.

Note:
- "ampm" and "dayPeriod" are probably not both present in a pattern, but UTS 35
  doesn't seem to forbid this.
- Added the same change to [[styles]], even though no CLDR locale currently use
  both "ampm" and "dayPeriod". (There are some special cases, though. Cf. "my"
  locale.)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
c: datetime Component: dates, times, timezones enhancement has consensus Has consensus from TC39-TG2 needs tests s: in progress Status: the issue has an active proposal Small Smaller change solvable in a Pull Request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

9 participants