Skip to content

Conversation

@ptomato
Copy link
Contributor

@ptomato ptomato commented Apr 3, 2025

Taken from Richard's suggestion in #4428 (review)

Copy link
Member

@gibson042 gibson042 left a comment

Choose a reason for hiding this comment

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

LGTM modulo variable declarations.

const long = new Intl.DateTimeFormat('en', { dayPeriod: 'long' });

function assertParts(parts, expected, message) {
function assertParts(parts, message) {
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 by no means blocking, but I wonder if a harness file should define some formatToParts assertion helpers:

function assertFormatPart(part, type, value, label) {
  var suffix = label ? ', ' + label : '';
  assert.sameValue(part.type, type, 'part type' + suffix);
  if (value === undefined) return;
  assert.sameValue(part.value, value, 'part value' + suffix);
}

function assertFormatParts(parts, expectations, label) {
  var suffix = label ? ', ' + label : '';
  assert.sameValue(parts.length, expectations.length, 'parts count' + suffix);
  for (var i = 0; i < parts.length; i++) {
    assertFormatPart(parts[i], expectations[i][0], expectations[i][1],
      suffix && (suffix + ' part ' + i));
  }
}

for use in files like this:

for (var h = 0; h < 24; h++) {
  var parts = long.formatToParts(inputs[h]);
  assertFormatParts(parts, [['dayPeriod']], 'hour ' + h + ' long dayPeriod');
  
  var partsNumeric = longNumeric.formatToParts(inputs[h]);
  assertFormatParts(partsNumeric,
    [['hour', String((h % 12) || 12)], ['literal'], ['dayPeriod']],
    'numeric hour ' + h + ' must precede long dayPeriod');

  counts[i]++;
  prevDayPeriod = dayPeriod;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This seems interesting. I'd wait to see what kinds of format parts assertions we need once we get the tests into a better state. Want to open an issue with this suggestion?

Copy link
Member

Choose a reason for hiding this comment

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

@anba
Copy link
Contributor

anba commented Apr 4, 2025

Two issues:

  1. The updated tests are now too loose. For example it allows 1 noon, 2 in the afternoon, 3 in the evening, 4 at night, and the rest in the morning.
  2. The transition checks seem more complicated than necessary.

How about something like this instead?

// Definitions for inputs, expectedDayPeriods, and formatters elided.

const dayPeriods = inputs.map(date => long.format(date));

// Ensure all day periods are valid.
assert(dayPeriods.every(d => expectedDayPeriods.includes(d)));

// Known day periods for specific hours.
assert.sameValue(dayPeriods[0], "at night");
assert.sameValue(dayPeriods[6], "in the morning");
assert.sameValue(dayPeriods[12], "noon");
assert.sameValue(dayPeriods[15], "in the afternoon");
assert.sameValue(dayPeriods[18], "in the evening");
assert.sameValue(dayPeriods[22], "at night");

// Noon happens exactly once per day.
assert.sameValue(dayPeriods.filter(d => d === "noon").length, 1);

// Ensure all transitions are valid.
for (let h = 1; h < dayPeriods.length; h++) {
  if (dayPeriods[h] !== dayPeriods[h - 1]) {
    let currentIndex = expectedDayPeriods.indexOf(dayPeriods[h]);
    let previousIndex = expectedDayPeriods.indexOf(dayPeriods[h - 1]);

    // The previous-index plus one matches the current index, possibly by wrapping around.
    assert.sameValue(
      currentIndex,
      (previousIndex + 1) % expectedDayPeriods.length
    );
  }
}

for (let h = 0; h < inputs.length; h++) {
  assert.sameValue(
    longNumeric.format(inputs[h]),
    // Hour "00" is represented as "12".
    ((h % 12) || 12) + " " + dayPeriods[h],
    "numeric hour must precede dayPeriod"
  );
}

@gibson042
Copy link
Member

The updated tests are now too loose. For example it allows 1 noon, 2 in the afternoon, 3 in the evening, 4 at night, and the rest in the morning.

I think I would consider that acceptable. It's not our place to define the association of hours and day periods; that is the purview of upstream projects like CLDR.

// Known day periods for specific hours.
assert.sameValue(dayPeriods[0], "at night");
assert.sameValue(dayPeriods[6], "in the morning");
assert.sameValue(dayPeriods[12], "noon");
assert.sameValue(dayPeriods[15], "in the afternoon");
assert.sameValue(dayPeriods[18], "in the evening");
assert.sameValue(dayPeriods[22], "at night");

I'd rather not include these kinds of assertions, but I'm not strongly opposed. However, I definitely wouldn't bake in an assumption that hour 0 is necessarily "at night".

The transition checks seem more complicated than necessary.

I think it's a worthwhile tradeoff for reducing the maintenance burden for reacting to upstream changes (e.g., introduction of "midnight", expansion of "noon" to more than one hour, a new term that splits "in the morning"). Basically, I want to not touch these files at all in response to likely CLDR changes, and touch only one line each even for surprising ones.

But simplifying the checks while upholding those goals seems good. I like the idea of asserting that all day periods are represented and then iterating over those rather than over hours.

@ptomato
Copy link
Contributor Author

ptomato commented Apr 5, 2025

Yeah, agreed with Richard — the general direction we have been discussing is that tests for locale-defined results become looser. Otherwise we are just testing the specifics of the underlying version of ICU. Currently test262 is only runnable for engines with a specific version of ICU (and if you are using any other i18n library, forget it) and that's the situation I'm trying to chip away at here 😄

I'll try to simplify the checks though.

@ptomato ptomato force-pushed the dayPeriod-cldr-robustness branch from 65bc027 to 809477d Compare April 5, 2025 01:52
@ptomato ptomato requested a review from gibson042 April 5, 2025 01:56
@anba
Copy link
Contributor

anba commented Apr 5, 2025

Removing too many checks makes these tests rather useless, I'm almost more in favour of just removing them altogether instead of allowing nonsensical results. The latest set of changes made this even worse, it's now allowed for implementations to return:

0 -> 0 noon
1 -> 1 in the afternoon
2 -> 2 in the evening
3 -> 3 at night
4 -> 4 in the morning
5 -> 5 noon
6 -> 6 in the afternoon
7 -> 7 in the evening
8 -> 8 at night
9 -> 9 in the morning
10 -> 10 noon
...

@gibson042
Copy link
Member

Updated suggestion incorporating @anba's approach:

// Each expected dayPeriod value must be contiguous, and
// b) represented in sequence.
var expectedDayPeriods = [
  'in the morning',
  'noon',
  'in the afternoon',
  'in the evening',
  'at night'
];

// Cover all 24 hours of a single day.
var inputs = [];
for (var h = 0; h < 24; h++) {
  inputs.push(new Date(2017, 11, 12,  h, 0, 0, 0));
}

// Verify complete and exclusive representation.
var formatter = new Intl.DateTimeFormat('en', {
  dayPeriod: 'long'
});
var observedDayPeriods = [];
var unexpectedDayPeriods = [];
for (var h = 0; h < 24; h++) {
  var dayPeriod = formatter.format(inputs[h]);
  observedDayPeriods.push(dayPeriod);
  if (expectedDayPeriods.indexOf(dayPeriod) === -1) {
    unexpectedDayPeriods.push(dayPeriod);
  }
}
var unusedDayPeriods = expectedDayPeriods.filter(function (dayPeriod) {
  return observedDayPeriods.indexOf(dayPeriod) === -1;
});
assert.compareArray(unexpectedDayPeriods, [],
  'unexpected dayPeriods: ' + unexpectedDayPeriods.join());
assert.compareArray(unusedDayPeriods, [],
  'unused dayPeriods: ' + unusedDayPeriods.join());

// Verify ordering, accounting for the possibility of one value spanning day transitions.
var transitionCount = 0;
for (var h = 0; h < 24; h++) {
  var dayPeriod = observedDayPeriods[h];
  var prevDayPeriod = observedDayPeriods.at(h - 1);
  if (dayPeriod === prevDayPeriod) continue;
  transitionCount++;
  var i = expectedDayPeriods.indexOf(dayPeriod);
  assert.sameValue(prevDayPeriod, expectedDayPeriods.at(i - 1),
    dayPeriod + ' must be preceded by ' + prevDayPeriod);
}
assert.sameValue(transitionCount, expectedDayPeriods.length,
  'dayPeriods must be contiguous');

var numericFormatter = new Intl.DateTimeFormat('en', {
  dayPeriod: 'long',
  hour: 'numeric'
});
for (var h = 0; h < 24; h++) {
  assert.sameValue(
    numericFormatter.format(inputs[h]),
    // Hour "00" is represented as "12".
    ((h % 12) || 12) + ' ' + observedDayPeriods[h],
    'numeric hour must precede dayPeriod'
  );
}

@ptomato ptomato force-pushed the dayPeriod-cldr-robustness branch from 809477d to 7af9fa3 Compare April 7, 2025 19:45
@ptomato
Copy link
Contributor Author

ptomato commented Apr 7, 2025

Thanks, I've adopted that suggestion (but with a workaround instead of using Array.prototype.at) and updated the PR.

const long = new Intl.DateTimeFormat('en', { dayPeriod: 'long' });

function assertParts(parts, expected, message) {
function assertParts(parts, message) {
Copy link
Member

Choose a reason for hiding this comment

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

Taken from Richard's suggestion in
tc39#4428 (review)

Co-Authored-By: André Bargull <[email protected]>
Co-Authored-By: Philip Chimento <[email protected]>
@ptomato ptomato force-pushed the dayPeriod-cldr-robustness branch from 7af9fa3 to 3279248 Compare April 11, 2025 01:21
@ptomato ptomato merged commit afc3d1b into tc39:main Apr 11, 2025
11 checks passed
@ptomato ptomato deleted the dayPeriod-cldr-robustness branch April 11, 2025 01:24
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.

3 participants