Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Integrate Intl.DurationFormat into Temporal #3088

Merged
merged 6 commits into from
Feb 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion polyfill/lib/duration.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
} from './primordials.mjs';
import { assert } from './assert.mjs';
import * as ES from './ecmascript.mjs';
import { ModifiedIntlDurationFormatPrototypeFormat } from './intl.mjs';
import { MakeIntrinsicClass } from './intrinsicclass.mjs';
import {
YEARS,
Expand Down Expand Up @@ -377,7 +378,8 @@ export class Duration {
toLocaleString(locales = undefined, options = undefined) {
if (!ES.IsTemporalDuration(this)) throw new TypeErrorCtor('invalid receiver');
if (typeof IntlDurationFormat === 'function') {
return new IntlDurationFormat(locales, options).format(this);
const formatter = new IntlDurationFormat(locales, options);
return ES.Call(ModifiedIntlDurationFormatPrototypeFormat, formatter, [this]);
}
warn('Temporal.Duration.prototype.toLocaleString() requires Intl.DurationFormat.');
return ES.TemporalDurationToString(this, 'auto');
Expand Down
46 changes: 46 additions & 0 deletions polyfill/lib/intl.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,10 @@ import {
IntlDateTimeFormatPrototypeFormatRangeToParts,
IntlDateTimeFormatPrototypeFormatToParts,
IntlDateTimeFormatPrototypeResolvedOptions,
IntlDurationFormatPrototype,
IntlDurationFormatPrototypeFormat,
IntlDurationFormatPrototypeFormatToParts,
IntlDurationFormatPrototypeResolvedOptions,
ObjectAssign,
ObjectCreate,
ObjectDefineProperties,
Expand All @@ -25,14 +29,24 @@ import * as ES from './ecmascript.mjs';
import { MakeIntrinsicClass } from './intrinsicclass.mjs';
import {
CreateSlots,
DAYS,
GetSlot,
HasSlot,
EPOCHNANOSECONDS,
HOURS,
ISO_DATE,
ISO_DATE_TIME,
MICROSECONDS,
MILLISECONDS,
MINUTES,
MONTHS,
NANOSECONDS,
ORIGINAL,
SECONDS,
SetSlot,
TIME,
WEEKS,
YEARS,
CALENDAR
} from './slots.mjs';

Expand Down Expand Up @@ -632,3 +646,35 @@ function extractOverrides(temporalObj, main) {

return {};
}

function temporalDurationToCompatibilityRecord(duration) {
const record = ObjectCreate(null);
record.years = GetSlot(duration, YEARS);
record.months = GetSlot(duration, MONTHS);
record.weeks = GetSlot(duration, WEEKS);
record.days = GetSlot(duration, DAYS);
record.hours = GetSlot(duration, HOURS);
record.minutes = GetSlot(duration, MINUTES);
record.seconds = GetSlot(duration, SECONDS);
record.milliseconds = GetSlot(duration, MILLISECONDS);
record.microseconds = GetSlot(duration, MICROSECONDS);
record.nanoseconds = GetSlot(duration, NANOSECONDS);
return record;
}

export function ModifiedIntlDurationFormatPrototypeFormat(durationLike) {
ES.Call(IntlDurationFormatPrototypeResolvedOptions, this); // brand check
const duration = ES.ToTemporalDuration(durationLike);
const record = temporalDurationToCompatibilityRecord(duration);
return ES.Call(IntlDurationFormatPrototypeFormat, this, [record]);
}

if (IntlDurationFormatPrototype) {
IntlDurationFormatPrototype.format = ModifiedIntlDurationFormatPrototypeFormat;
IntlDurationFormatPrototype.formatToParts = function formatToParts(durationLike) {
ES.Call(IntlDurationFormatPrototypeResolvedOptions, this); // brand check
const duration = ES.ToTemporalDuration(durationLike);
const record = temporalDurationToCompatibilityRecord(duration);
return ES.Call(IntlDurationFormatPrototypeFormatToParts, this, [record]);
};
}
6 changes: 6 additions & 0 deletions polyfill/lib/primordials.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ export const {
formatToParts: IntlDateTimeFormatPrototypeFormatToParts,
resolvedOptions: IntlDateTimeFormatPrototypeResolvedOptions
} = IntlDateTimeFormat?.prototype || ObjectCreate(null);
export const IntlDurationFormatPrototype = IntlDurationFormat?.prototype ?? ObjectCreate(null);
export const {
format: IntlDurationFormatPrototypeFormat,
formatToParts: IntlDurationFormatPrototypeFormatToParts,
resolvedOptions: IntlDurationFormatPrototypeResolvedOptions
} = IntlDurationFormatPrototype;
export const { stringify: JSONStringify } = JSON;
export const {
prototype: { entries: MapPrototypeEntries, get: MapPrototypeGet, has: MapPrototypeHas, set: MapPrototypeSet }
Expand Down
1 change: 1 addition & 0 deletions spec.html
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
contributors: Maggie Pint, Matt Johnson, Brian Terlson, Daniel Ehrenberg, Philipp Dunkel, Sasha Pierson, Ujjwal Sharma, Philip Chimento, Justin Grant
markEffects: true
</pre>
<emu-biblio href="spec/biblio.json"></emu-biblio>

<emu-intro id="sec-temporal-intro">
<h1>Introduction</h1>
Expand Down
17 changes: 0 additions & 17 deletions spec/abstractops.html
Original file line number Diff line number Diff line change
Expand Up @@ -1769,23 +1769,6 @@ <h1>
</emu-alg>
</emu-clause>

<emu-clause id="sec-tointegerifintegral" type="abstract operation">
<h1>
ToIntegerIfIntegral (
_argument_: an ECMAScript language value,
): either a normal completion containing an integer or a throw completion
</h1>
<dl class="header">
<dt>description</dt>
<dd>It converts _argument_ to an integer representing its Number value, or throws a *RangeError* when that value is not <emu-xref href="#integral-number">integral</emu-xref>.</dd>
</dl>
<emu-alg>
1. Let _number_ be ? ToNumber(_argument_).
1. If _number_ is not an integral Number, throw a *RangeError* exception.
1. Return ℝ(_number_).
</emu-alg>
</emu-clause>

<emu-clause id="sec-temporal-tomonthcode" type="abstract operation">
<h1>
ToMonthCode (
Expand Down
37 changes: 37 additions & 0 deletions spec/biblio.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[
{
"location": "https://tc39.es/ecma402/",
"entries": [
{
"type": "op",
"aoid": "FormatNumericHours",
"id": "sec-formatnumerichours"
},
{
"type": "op",
"aoid": "FormatNumericMinutes",
"id": "sec-formatnumericminutes"
},
{
"type": "op",
"aoid": "FormatNumericSeconds",
"id": "sec-formatnumericseconds"
},
{
"type": "op",
"aoid": "NextUnitFractional",
"id": "sec-nextunitfractional"
},
{
"type": "op",
"aoid": "ListFormatParts",
"id": "sec-listformatparts"
},
{
"type": "clause",
"number": "Table 21",
"id": "table-partition-duration-format-pattern"
}
]
}
]
12 changes: 6 additions & 6 deletions spec/duration.html
Original file line number Diff line number Diff line change
Expand Up @@ -1096,10 +1096,10 @@ <h1>
</emu-alg>
</emu-clause>

<emu-clause id="sec-temporal-durationsign" type="abstract operation">
<emu-clause id="sec-durationsign" type="abstract operation">
<h1>
DurationSign (
_duration_: a Temporal.Duration,
_duration_: a <del>Duration Record</del><ins>Temporal.Duration</ins>,
): -1, 0, or 1
</h1>
<dl class="header">
Expand Down Expand Up @@ -1149,7 +1149,7 @@ <h1>
</emu-alg>
</emu-clause>

<emu-clause id="sec-temporal-isvalidduration" type="abstract operation">
<emu-clause id="sec-isvalidduration" type="abstract operation">
<h1>
IsValidDuration (
_years_: an integer,
Expand All @@ -1166,7 +1166,7 @@ <h1>
</h1>
<dl class="header">
<dt>description</dt>
<dd>It returns *true* if its arguments form valid input from which to construct a `Temporal.Duration`, and *false* otherwise.</dd>
<dd>It returns *true* if its arguments form valid input from which to construct a <del>Duration Record</del><ins>Temporal.Duration</ins>, and *false* otherwise.</dd>
</dl>
<emu-alg>
1. Let _sign_ be 0.
Expand All @@ -1181,9 +1181,9 @@ <h1>
1. If abs(_years_) ≥ 2<sup>32</sup>, return *false*.
1. If abs(_months_) ≥ 2<sup>32</sup>, return *false*.
1. If abs(_weeks_) ≥ 2<sup>32</sup>, return *false*.
1. Let _totalFractionalSeconds_ be _days_ × 86,400 + _hours_ × 3600 + _minutes_ × 60 + _seconds_ + ℝ(𝔽(_milliseconds_)) × 10<sup>-3</sup> + ℝ(𝔽(_microseconds_)) × 10<sup>-6</sup> + ℝ(𝔽(_nanoseconds_)) × 10<sup>-9</sup>.
1. Let _normalizedSeconds_ be _days_ × 86,400 + _hours_ × 3600 + _minutes_ × 60 + _seconds_ + ℝ(𝔽(_milliseconds_)) × 10<sup>-3</sup> + ℝ(𝔽(_microseconds_)) × 10<sup>-6</sup> + ℝ(𝔽(_nanoseconds_)) × 10<sup>-9</sup>.
1. NOTE: The above step cannot be implemented directly using floating-point arithmetic. Multiplying by 10<sup>-3</sup>, 10<sup>-6</sup>, and 10<sup>-9</sup> respectively may be imprecise when _milliseconds_, _microseconds_, or _nanoseconds_ is an unsafe integer. This multiplication can be implemented in C++ with an implementation of `std::remquo()` with sufficient bits in the quotient. String manipulation will also give an exact result, since the multiplication is by a power of 10.
1. If abs(_totalFractionalSeconds_) ≥ 2<sup>53</sup>, return *false*.
1. If abs(_normalizedSeconds_) ≥ 2<sup>53</sup>, return *false*.
1. Return *true*.
</emu-alg>
</emu-clause>
Expand Down
Loading