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

Improve Intl.DurationFormat tests #3903

Merged
merged 5 commits into from
Aug 28, 2023
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
211 changes: 211 additions & 0 deletions harness/testIntl.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ defines:
- getDateTimeComponents
- getDateTimeComponentValues
- isCanonicalizedStructurallyValidTimeZoneName
- partitionDurationFormatPattern
- formatDurationFormatPattern
---*/
/**
*/
Expand Down Expand Up @@ -2502,3 +2504,212 @@ function isCanonicalizedStructurallyValidTimeZoneName(timeZone) {
}
return zoneNamePattern.test(timeZone);
}


/**
* @description Simplified PartitionDurationFormatPattern implementation which
* only supports the "en" locale.
* @param {Object} duration the duration record
* @param {String} style the duration format style
* @result {Array} an array with formatted duration parts
*/

function partitionDurationFormatPattern(duration, style = "short") {
const units = [
"years",
"months",
"weeks",
"days",
"hours",
"minutes",
"seconds",
"milliseconds",
"microseconds",
"nanoseconds",
];

function durationToFractionalSeconds(duration) {
let {
seconds = 0,
milliseconds = 0,
microseconds = 0,
nanoseconds = 0,
} = duration;

// Directly return seconds when no sub-seconds are present.
if (milliseconds === 0 && microseconds === 0 && nanoseconds === 0) {
return seconds;
}

// Otherwise compute the overall amount of nanoseconds using BigInt to avoid
// loss of precision.
let ns_sec = BigInt(seconds) * 1_000_000_000n;
let ns_ms = BigInt(milliseconds) * 1_000_000n;
let ns_us = BigInt(microseconds) * 1_000n;
let ns = ns_sec + ns_ms + ns_us + BigInt(nanoseconds);

// Split the nanoseconds amount into seconds and sub-seconds.
let q = ns / 1_000_000_000n;
let r = ns % 1_000_000_000n;

// Pad sub-seconds, without any leading negative sign, to nine digits.
if (r < 0) {
r = -r;
}
r = String(r).padStart(9, "0");

// Return seconds with fractional part as a decimal string.
return `${q}.${r}`;
}

// Only "en" is supported.
const locale = "en";
const numberingSystem = "latn";
const timeSeparator = ":";

let result = [];
let separated = false;

for (let unit of units) {
// Absent units default to zero.
let value = duration[unit] ?? 0;

let display = "auto";
if (style === "digital") {
// Always display numeric units per GetDurationUnitOptions.
if (unit === "hours" || unit === "minutes" || unit === "seconds") {
display = "always";
}

// Numeric seconds and sub-seconds are combined into a single value.
if (unit === "seconds") {
value = durationToFractionalSeconds(duration);
}
}

// "auto" display omits zero units.
if (value !== 0 || display !== "auto") {
// Map the DurationFormat style to a NumberFormat style.
let unitStyle = style;
if (style === "digital") {
if (unit === "hours") {
unitStyle = "numeric";
} else if (unit === "minutes" || unit === "seconds") {
unitStyle = "2-digit";
} else {
unitStyle = "short";
}
}

// NumberFormat requires singular unit names.
let numberFormatUnit = unit.slice(0, -1);

// Compute the matching NumberFormat options.
let nfOpts;
if (unitStyle !== "numeric" && unitStyle !== "2-digit") {
// The value is formatted as a standalone unit.
nfOpts = {
numberingSystem,
style: "unit",
unit: numberFormatUnit,
unitDisplay: unitStyle,
};
} else {
let roundingMode = undefined;
let minimumFractionDigits = undefined;
let maximumFractionDigits = undefined;

// Numeric seconds include any sub-seconds.
if (style === "digital" && unit === "seconds") {
roundingMode = "trunc";
minimumFractionDigits = 0;
maximumFractionDigits = 9;
}

// The value is formatted as a numeric unit.
nfOpts = {
numberingSystem,
minimumIntegerDigits: (unitStyle === "2-digit" ? 2 : 1),
roundingMode,
minimumFractionDigits,
maximumFractionDigits,
};
}

let nf = new Intl.NumberFormat(locale, nfOpts);
let formatted = nf.formatToParts(value);

// Add |numberFormatUnit| to the formatted number.
let list = [];
for (let {value, type} of formatted) {
list.push({type, value, unit: numberFormatUnit});
}

if (!separated) {
// Prepend the separator before the next numeric unit.
if (unitStyle === "2-digit" || unitStyle === "numeric") {
separated = true;
}

// Append the formatted number to |result|.
result.push(list);
} else {
let last = result[result.length - 1];

// Prepend the time separator before the formatted number.
last.push({
type: "literal",
value: timeSeparator,
});

// Concatenate |last| and the formatted number.
last.push(...list);
}
} else {
separated = false;
}

// No further units possible after "seconds" when style is "digital".
if (style === "digital" && unit === "seconds") {
break;
}
}

let lf = new Intl.ListFormat(locale, {
type: "unit",
style: (style !== "digital" ? style : "short"),
});

// Collect all formatted units into a list of strings.
let strings = [];
for (let parts of result) {
let string = "";
for (let {value} of parts) {
string += value;
}
strings.push(string);
}

// Format the list of strings and compute the overall result.
let flattened = [];
for (let {type, value} of lf.formatToParts(strings)) {
if (type === "element") {
flattened.push(...result.shift());
} else {
flattened.push({type, value});
}
}
return flattened;
}


/**
* @description Return the formatted string from partitionDurationFormatPattern.
* @param {Object} duration the duration record
* @param {String} style the duration format style
* @result {String} a string containing the formatted duration
*/

function formatDurationFormatPattern(duration, style) {
return partitionDurationFormatPattern(duration, style).reduce((acc, e) => acc + e.value, "");
}
26 changes: 0 additions & 26 deletions test/intl402/DurationFormat/prototype/format/basic-format-en.js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright (C) 2023 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-Intl.DurationFormat.prototype.format
description: >
Test format method with negative duration and default style
locale: [en-US]
includes: [testIntl.js]
features: [Intl.DurationFormat]
---*/

const duration = {
years: -1,
months: -2,
weeks: -3,
days: -3,
hours: -4,
minutes: -5,
seconds: -6,
milliseconds: -7,
microseconds: -8,
nanoseconds: -9,
};

const expected = formatDurationFormatPattern(duration);

const df = new Intl.DurationFormat("en");
assert.sameValue(
df.format(duration),
expected,
`DurationFormat format output using default style option`
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (C) 2023 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-Intl.DurationFormat.prototype.format
description: >
Test format method with negative duration and "short" style
locale: [en-US]
includes: [testIntl.js]
features: [Intl.DurationFormat]
---*/

const style = "short";

const duration = {
years: -1,
months: -2,
weeks: -3,
days: -3,
hours: -4,
minutes: -5,
seconds: -6,
milliseconds: -7,
microseconds: -8,
nanoseconds: -9,
};

const expected = formatDurationFormatPattern(duration, style);

const df = new Intl.DurationFormat("en", {style});
assert.sameValue(
df.format(duration),
expected,
`DurationFormat format output using ${style} style option`
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (C) 2023 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-Intl.DurationFormat.prototype.format
description: >
Test format method with negative duration and "digital" style
locale: [en-US]
includes: [testIntl.js]
features: [Intl.DurationFormat]
---*/

const style = "digital";

const duration = {
years: -1,
months: -2,
weeks: -3,
days: -3,
hours: -4,
minutes: -5,
seconds: -6,
milliseconds: -7,
microseconds: -8,
nanoseconds: -9,
};

const expected = formatDurationFormatPattern(duration, style);

const df = new Intl.DurationFormat("en", {style});
assert.sameValue(
df.format(duration),
expected,
`DurationFormat format output using ${style} style option`
);
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright (C) 2023 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
esid: sec-Intl.DurationFormat.prototype.format
description: >
Test format method with negative duration and "long" style
locale: [en-US]
includes: [testIntl.js]
features: [Intl.DurationFormat]
---*/

const style = "long";

const duration = {
years: -1,
months: -2,
weeks: -3,
days: -3,
hours: -4,
minutes: -5,
seconds: -6,
milliseconds: -7,
microseconds: -8,
nanoseconds: -9,
};

const expected = formatDurationFormatPattern(duration, style);

const df = new Intl.DurationFormat("en", {style});
assert.sameValue(
df.format(duration),
expected,
`DurationFormat format output using ${style} style option`
);
Loading