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

Editorial: Refactor handling of numeric-like styles, support differing hours/minutes vs minutes/seconds separators #188

Merged
merged 2 commits into from May 20, 2024

Conversation

ben-allen
Copy link
Collaborator

@ben-allen ben-allen commented Feb 22, 2024

  • Refactors handling of "numeric"-like styles
  • Add additional slots to handle 2-digit hours in locales that use them in all cases
  • Add additional slots to handle differing hours/minutes separators in some locales

Fixes #161

Copy link
Member

@ryzokuken ryzokuken left a comment

Choose a reason for hiding this comment

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

Massive improvement, kudos! A bunch of comments inline to make this PR perfect :D

spec.emu Outdated
<h1>
NextUnitFractional (
_durationFormat_: a DurationFormat Object,
_duration_: a Duration Record,
Copy link
Member

Choose a reason for hiding this comment

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

duration is never used in this AO, you can drop the argument.

spec.emu Outdated
@@ -1,4 +1,5 @@
<!doctype html>
1. Perform SetFractionalDigitOptions(_durationFormat_, _nfOpts_).
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
1. Perform SetFractionalDigitOptions(_durationFormat_, _nfOpts_).

spec.emu Outdated
Comment on lines 237 to 239
1. If _unit_ is *"minutes"* or *"seconds"*, then
1. Set _style_ to *"2-digit"*.
1. If _unit_ is *"hours"* and _durationFormat_.[[TwoDigitHours]] is *true*, then
1. Set _style_ to *"2-digit"*.
Copy link
Member

Choose a reason for hiding this comment

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

nit: it could be merged with the condition above

Suggested change
1. If _unit_ is *"minutes"* or *"seconds"*, then
1. Set _style_ to *"2-digit"*.
1. If _unit_ is *"hours"* and _durationFormat_.[[TwoDigitHours]] is *true*, then
1. Set _style_ to *"2-digit"*.
1. If _unit_ is *"minutes"* or *"seconds"* or _unit_ is *"hours"* and _durationFormat_.[[TwoDigitHours]] is *true*, then
1. Set _style_ to *"2-digit"*.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

My thought is to leave this one as-is, because stringing together four conditionals on one line will likely be annoying for implementers

@@ -495,18 +697,27 @@ contributors: Ujjwal Sharma, Younies Mahmoud
1. Let _r_ be ResolveLocale(%DurationFormat%.[[AvailableLocales]], _requestedLocales_, _opt_, %DurationFormat%.[[RelevantExtensionKeys]], %DurationFormat%.[[LocaleData]]).
1. Let _locale_ be r.[[locale]].
1. Set _durationFormat_.[[Locale]] to _locale_.
1. Set _durationFormat_.[[DataLocale]] to _r_.[[dataLocale]].
1. Let _dataLocale_ be _durationFormat_.[[DataLocale]].
1. Let _dataLocaleData_ be _durationFormat_.[[LocaleData]].[[&lt;_dataLocale_&gt;]].
Copy link
Member

Choose a reason for hiding this comment

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

This doesn't look right?

Copy link
Member

Choose a reason for hiding this comment

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

Also you no longer need dataLocaleData IIUC, you can directly extract the value of digitalFormat and set the values in the instance from that.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

going to leave this one for now -- I'm working on a PR to update DurationFormat to reflect changes made in tc39/ecma402#849

spec.emu Outdated

<emu-clause id="sec-listformatparts" type="abstract operation">
<h1>ListFormatParts (
_nfResult_: a List of Records,
Copy link
Member

Choose a reason for hiding this comment

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

I'd be in favor of generalizing this AO by renaming things from nfResultFoo which is specific to our use case (we only list format the results of NF) to something more general.

spec.emu Outdated
Comment on lines 558 to 566
1. If _durationFormat_.[[FractionalDigits]] is *undefined*, then
1. Let _maximumFractionDigits_ be *9*<sub>𝔽</sub>.
1. Let _minimumFractionDigits_ be *+0*<sub>𝔽</sub>.
1. Else,
1. Let _maximumFractionDigits_ be _durationFormat_.[[FractionalDigits]].
1. Let _minimumFractionDigits_ be _durationFormat_.[[FractionalDigits]].
1. Perform ! CreateDataPropertyOrThrow(_nfOpts_, *"maximumFractionDigits"*, _maximumFractionDigits_).
1. Perform ! CreateDataPropertyOrThrow(_nfOpts_, *"minimumFractionDigits"*, _minimumFractionDigits_).
1. Perform ! CreateDataPropertyOrThrow(_nfOpts_, *"roundingMode"*, *"trunc"*).
Copy link
Member

Choose a reason for hiding this comment

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

Didn't you move this into an AO? Idk how it ended up back in.

Copy link
Collaborator Author

@ben-allen ben-allen Mar 5, 2024

Choose a reason for hiding this comment

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

So here's my thoughts on this: I suspect implementers might be annoyed by having the properties on nfOpts set in multiple AOs, since after a cursory glance I didn't find anything that does that in either 402 or Temporal.

@FrankYFTang @gibson042

spec.emu Outdated
1. Let _secondsValue_ be _duration_.[[Seconds]].
1. Let _fractionalDigits_ be AddFractionalDigits(_durationFormat_, _duration).
1. If _fractionalDigits_ is not 0, then
1. If _durationFormat_.[[FractionalDigits]] is *undefined*, then
Copy link
Member

Choose a reason for hiding this comment

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

Replace with SetFractionalDigitOptions

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

See above

Copy link
Member

Choose a reason for hiding this comment

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

fwiw I don't think it's particularly confusing for implementers if some options are set in an AO as long as it results in cleaner/DRY-er spec text... perhaps implementers can give their thoughts on the subject.

spec.emu Outdated
1. If _unit_ is *"seconds"*, *"milliseconds"*, or *"microseconds"*, then
1. If NextUnitFractional(_durationFormat_, _duration_, _unit_) is *true*, then
1. Set _value_ to _value_ + AddFractionalDigits(_durationFormat_, _duration_).
1. If _durationFormat_.[[FractionalDigits]] is *undefined*, then
Copy link
Member

Choose a reason for hiding this comment

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

Replace with SetFractionalDigitOptions

@FrankYFTang FrankYFTang self-assigned this Feb 22, 2024
@ben-allen ben-allen changed the title New feb refactor Editorial: Refactor handling of numeric-like styles, support differing hours/minutes vs minutes/seconds separators Feb 22, 2024
@ben-allen
Copy link
Collaborator Author

Supersedes #186

@sffc
Copy link
Collaborator

sffc commented Feb 23, 2024

spec.emu Outdated
@@ -189,6 +190,7 @@ contributors: Ujjwal Sharma, Younies Mahmoud
<emu-clause id="sec-getdurationunitoptions" type="abstract operation">
<h1>
GetDurationUnitOptions (
_durationFormat_: a DurationFormat Object,
Copy link
Collaborator

Choose a reason for hiding this comment

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

Why do we need to pass in a DurationFormat Object here? I see you only use durationFormat.[[TwoDigitHours]] inside this AO. Why not just pass in that value only ?

spec.emu Outdated
PartitionDurationFormatPattern (
_durationFormat_: a DurationFormat,
SetFractionalDigitOptions (
_durationFormat_: a DurationFormat Object,
Copy link
Collaborator

Choose a reason for hiding this comment

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

The only thing you use internally is durationFormat.[[FractionalDigits]], why not just pass in that value and avoid passing in durationFormat ?

spec.emu Outdated
<emu-clause id="sec-formathours" type="abstract operation">
<h1>
FormatHours (
_durationFormat_: a DurationFormat Object,
Copy link
Collaborator

Choose a reason for hiding this comment

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

how about we only pass in durationFormat.[[Locale]] and durationFormat.[[HoursStyle]] but not passing in durationFormat?

@ben-allen
Copy link
Collaborator Author

Incorporated feedback, made a few additional changes:

  • renamed isFirstUnitDisplayed and isSignDisplayed aliases to signDisplayed throughout
  • Renamed FormatHours, FormatMinutes, FormatSeconds to FormatNumericHours, FormatNumericMinutes, FormatNumericSeconds to clarify usage
  • Removed SetFractionalDigitOptions

Justification for removing SetFractionalDigitOptions: Talked it over with @ptomato, who observed that it could be confusing to add nfOpts properties in multiple AOs due to how setting properties can trigger setters in user code. Options as I see it:

  1. Leave as in this version of PR, with the steps for setting fractional digit options repeated in two places.
  2. Restore SetFractionalDigitOptions, since we're passing *null* to OrdinaryObjectCreate when creating nfOpts objects and so, if I understand it correctly, the poisoned object problem won't actually occur. However: creating properties on objects across multiple AOs is not currently something that happens in either 402 or Temporal
  3. Rewrite SetFractionalDigitOptions to return a Record which is then used by the caller to create nfOpts properties. Note: this doesn't actually save that many repeated steps.

requesting re-review! @ryzokuken @FrankYFTang

@ben-allen
Copy link
Collaborator Author

Requesting re-review! @FrankYFTang @ryzokuken

Copy link
Member

@ryzokuken ryzokuken left a comment

Choose a reason for hiding this comment

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

Overall LGTM, the improvements to the spec are quite evident by the amount of time it took me to review it in entirety this time... Either that or there's something with my coffee :P

Anyway, I have made a flurry of comments that should all further clean up the text. All of this is very much editorial and fills me with hope that by the end of this exercise we'd have a pretty looking and readable spec.

@@ -234,6 +235,8 @@ contributors: Ujjwal Sharma, Younies Mahmoud
1. Throw a *RangeError* exception.
1. If _unit_ is *"minutes"* or *"seconds"*, then
1. Set _style_ to *"2-digit"*.
1. If _unit_ is *"hours"* and _twoDigitHours_ is *true*, then
Copy link
Member

Choose a reason for hiding this comment

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

I tried to think about this for a while but I don't feel like I can approve this. We need to think of another approach because overriding the hours style like this at the end can be extremely surprising to the caller.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

We've had the previous lines about forcing minutes and seconds to "2-digit" when preceded by a "numeric" or "2-digit" style in the spec for quite some time, and I view this as analogous. I'm working on the assumption -- which may be incorrect -- that in the locales that always use a two-digit representation of hours, displaying a one-digit hour is analogous to using the wrong separator.

spec.emu Outdated
Comment on lines 288 to 293
1. If _unit_ is *"seconds"*, then
1. If _durationFormat_.[[MillisecondsStyle]] is *"fractional"*, return *true*.
1. If _unit_ is *"milliseconds"*, then
1. If _durationFormat_.[[MicrosecondsStyle]] is *"fractional"*, return *true*.
1. If _unit_ is *"microseconds"*, then
1. If _durationFormat_.[[NanosecondsStyle]] is *"fractional"*, return *true*.
Copy link
Member

Choose a reason for hiding this comment

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

editorial nit but it'll be cleaner/shorter if you merge the conditions, atleast pair them up.

spec.emu Outdated
</emu-alg>
</emu-clause>

<emu-clause id="sec-formathours" type="abstract operation">
Copy link
Member

Choose a reason for hiding this comment

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

id is "incorrect" is there a strong reason to exclude the word numeric from all these AOs?

spec.emu Outdated
1. Let _nf_ be ! Construct(%NumberFormat%, &laquo; _durationFormat_.[[Locale]] &raquo;, _nfOpts_).
1. Let _hoursParts_ be ! PartitionNumberPattern(_nf_, _hoursValue_).
1. For each Record { [[Type]], [[Value]] } _part_ of _hoursParts_, do
1. Return the Record { [[Type]]: _part_.[[Type]], [[Value]]: _part_.[[Value]], [[Unit]]: *"hour"* }.
Copy link
Member

Choose a reason for hiding this comment

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

This is incorrect, you can't return in a loop like this. Construct a second list of records and return the whole list.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

oof, can't believe I left that in

spec.emu Outdated
</h1>
<dl class="header">
<dt>description</dt>
<dd>It creates a List of Records containing the formatted parts of the hours unit of _duration_ when the requested style is *"numeric"* or *"2-digit"* according to the effective locale and the formatting options of _durationFormat_.</dd>
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
<dd>It creates a List of Records containing the formatted parts of the hours unit of _duration_ when the requested style is *"numeric"* or *"2-digit"* according to the effective locale and the formatting options of _durationFormat_.</dd>
<dd>It creates a List of Records containing the formatted parts of the minutes unit of _duration_ when the requested style is *"numeric"* or *"2-digit"* according to the effective locale and the formatting options of _durationFormat_.</dd>

1. Let _numericUnitFound_ be *false*.
1. While _numericUnitFound_ is *false*, repeat for each row in <emu-xref href="#table-partition-duration-format-pattern"></emu-xref> in table order, except the header row:
1. Let _value_ be the value of _duration_'s field whose name is the Value Field of the current row.
1. Let _style_ be the value of _durationFormat_'s internal slot whose name is the Style Slot value of the current row.
Copy link
Member

Choose a reason for hiding this comment

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

it's inconsistent with the last line, either say "value" in both or neither. Since it says "value" in the next line too, I guess you should just include it in the previous line.

1. Let _maximumFractionDigits_ be *9*<sub>𝔽</sub>.
1. Let _minimumFractionDigits_ be *+0*<sub>𝔽</sub>.
Copy link
Member

Choose a reason for hiding this comment

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

I think these are the defaults anyway if we keep the values undefined?

Copy link
Collaborator Author

@ben-allen ben-allen Apr 23, 2024

Choose a reason for hiding this comment

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

I don't see where those values could get set -- isn't [[FractionDigits]] assigned a value returned from GetNumberOption with *undefined* as the default?

1. Append the Record { [[Type]]: _part_.[[Type]], [[Value]]: _part_[[Value]], [[Unit]]: _numberFormatUnit_ } to _list_.
1. Append _list_ to _result_.
1. Return ListFormatParts(_durationFormat_, _result_).
</emu-alg>
Copy link
Member

Choose a reason for hiding this comment

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

Great! PDFP (if I can call it that) has become so much better!

1. Set _durationFormat_.[[DataLocale]] to _r_.[[dataLocale]].
1. Let _dataLocale_ be _durationFormat_.[[DataLocale]].
1. Let _dataLocaleData_ be _durationFormat_.[[LocaleData]].[[&lt;_dataLocale_&gt;]].
1. Let _digitalFormat_ be _dataLocaleData_.[[DigitalFormat]].
Copy link
Member

Choose a reason for hiding this comment

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

Basically this is the value you actually need, the few lines right before this one can be collapsed into this.

Copy link
Collaborator Author

@ben-allen ben-allen Apr 24, 2024

Choose a reason for hiding this comment

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

I've thought pretty hard about this one. Collapsing these lines together doesn't match rest-of-402 style, as I understand it. See:

  • steps 18 and 19 of Intl.DisplayNames constructor:

      18. Let _resolvedLocaleData_ be _r_.[[LocaleData]].
      19. Let _types_ be _resolvedLocaleData_.[[types]].
    

with resolvedLocaleData never used after the [[types]] access.

  • Steps 14 and 15 of Intl.ListFormat constructor, resolvedLocaleData likewise not used after resolvedLocaleData.[[<type>]] access:

      14. Let _resolvedLocaleData_ be _r_.[[LocaleData]].
      15. Let _dataLocaleTypes_ be _resolvedLocaleData_.[[&lt;_type_&gt;]].
    
  • First two steps of GetNumberFormatPattern AO in Intl.NumberFormat

      1. Let _resolvedLocaleData_ be _numberFormat_.[[LocaleData]].
      2. Let _patterns_ be _resolvedLocaleData_.[[patterns]].
    

(among others)

Additionally, the pattern "Let alias be obj.[[X]].[[Y]].[[Z]]" is very rare in 402 algorithms; it shows up in one place in Intl.DateTimeFormat and nowhere else (https://tc39.es/ecma402/#sec-date-time-style-format)

Four-deep accesses (as would happen here if these were collapsed together) occur not at all in rest-of-402

Comment on lines +678 to +682
1. Let _twoDigitHours_ be _digitalFormat_.[[TwoDigitHours]].
1. Set _durationFormat_.[[TwoDigitHours]] to _twoDigitHours_.
1. Let _hoursMinutesSeparator_ be _digitalFormat_.[[HoursMinutesSeparator]].
1. Set _durationFormat_.[[HoursMinutesSeparator]] to _hoursMinutesSeparator_.
1. Let _minutesSecondsSeparator_ be _digitalFormat_.[[MinutesSecondsSeparator]].
1. Set _durationFormat_.[[MinutesSecondsSeparator]] to _minutesSecondsSeparator_.
Copy link
Member

Choose a reason for hiding this comment

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

No need to set three variables, all of these lines can be collapsed.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Two-deep accesses are pretty rare across rest-of-402 -- only really shows up in Intl.DateTimeFormat spec. My leaning is toward leaving this one this way.

@ben-allen ben-allen force-pushed the new-feb-refactor branch 2 times, most recently from 88029c1 to c6025ef Compare April 24, 2024 23:03
@ben-allen
Copy link
Collaborator Author

ben-allen commented Apr 24, 2024

I've pushed a major update reflecting feedback from @ryzokuken and incorporating some additional bugfixes.

@ben-allen
Copy link
Collaborator Author

New revision ready for review! Much more solid thanks to @ryzokuken's feedback, but could still use more eyes. @gibson042 @FrankYFTang @sffc @anba

@sffc
Copy link
Collaborator

sffc commented May 3, 2024

Copy link
Collaborator

@sffc sffc left a comment

Choose a reason for hiding this comment

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

I reviewed this in enough detail to say that I believe it covers the edge cases we had identified and overall makes the spec much easier to reason about and implement. Please merge :)

* Separates out handling for `"numeric"` and `"2-digit"` styles,
  referred to as "digital styles" in the rest of this commit message, in
  `FormatNumericUnits`, `FormatNumericHours`, `FormatNumericMinutes`,
  `FormatNumericSeconds`, and `NextUnitFractional` AOs

* New AO `ListFormatParts` for portion of
  `PartitionDurationFormatPattern` concerned with ListFormatting the
  NumberFormatted parts

* Bug fix: Incorporate better support for certain locales:

- locales like `"af"` that when using digital styles always use 2-digit
  hours, i.e. "09:00" rather than "9:00".  Added [[HoursDigit]] slot.

- locales like `"fr-CA"` that use a separator between minutes and
  seconds that differs from the separator between hours and minutes, as
  in `"fr-CA"`'s use of 'h' and "min" as separators: 8 h 59 min 59 for 8
  hours, 59 minutes, 59 seconds. Added [[HoursMinutesSeparator]] and
  [[MinutesSecondsSeparator]] slots.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Digital duration format should probably be more lenient with data
4 participants