Skip to content

Commit c6e5fc4

Browse files
petebacondarwinatscott
authored andcommitted
fix(common): format day-periods that cross midnight (#36611)
When formatting a time with the `b` or `B` format codes, the rendered string was not correctly handling day periods that spanned midnight. Instead the logic was falling back to the default case of `AM`. Now the logic has been updated so that it matches times that are within a day period that spans midnight, so it will now render the correct output, such as `at night` in the case of English. Applications that are using either `formatDate()` or `DatePipe` and any of the `b` or `B` format codes will be affected by this change. Fixes #36566 PR Close #36611
1 parent 074266b commit c6e5fc4

File tree

2 files changed

+45
-13
lines changed

2 files changed

+45
-13
lines changed

packages/common/src/i18n/format_date.ts

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -277,26 +277,40 @@ function getDateTranslation(
277277
if (extended) {
278278
const rules = getLocaleExtraDayPeriodRules(locale);
279279
const dayPeriods = getLocaleExtraDayPeriods(locale, form, width);
280-
let result;
281-
rules.forEach((rule: Time|[Time, Time], index: number) => {
280+
const index = rules.findIndex(rule => {
282281
if (Array.isArray(rule)) {
283282
// morning, afternoon, evening, night
284-
const {hours: hoursFrom, minutes: minutesFrom} = rule[0];
285-
const {hours: hoursTo, minutes: minutesTo} = rule[1];
286-
if (currentHours >= hoursFrom && currentMinutes >= minutesFrom &&
287-
(currentHours < hoursTo ||
288-
(currentHours === hoursTo && currentMinutes < minutesTo))) {
289-
result = dayPeriods[index];
283+
const [from, to] = rule;
284+
const afterFrom = currentHours >= from.hours && currentMinutes >= from.minutes;
285+
const beforeTo =
286+
(currentHours < to.hours ||
287+
(currentHours === to.hours && currentMinutes < to.minutes));
288+
// We must account for normal rules that span a period during the day (e.g. 6am-9am)
289+
// where `from` is less (earlier) than `to`. But also rules that span midnight (e.g.
290+
// 10pm - 5am) where `from` is greater (later!) than `to`.
291+
//
292+
// In the first case the current time must be BOTH after `from` AND before `to`
293+
// (e.g. 8am is after 6am AND before 10am).
294+
//
295+
// In the second case the current time must be EITHER after `from` OR before `to`
296+
// (e.g. 4am is before 5am but not after 10pm; and 11pm is not before 5am but it is
297+
// after 10pm).
298+
if (from.hours < to.hours) {
299+
if (afterFrom && beforeTo) {
300+
return true;
301+
}
302+
} else if (afterFrom || beforeTo) {
303+
return true;
290304
}
291305
} else { // noon or midnight
292-
const {hours, minutes} = rule;
293-
if (hours === currentHours && minutes === currentMinutes) {
294-
result = dayPeriods[index];
306+
if (rule.hours === currentHours && rule.minutes === currentMinutes) {
307+
return true;
295308
}
296309
}
310+
return false;
297311
});
298-
if (result) {
299-
return result;
312+
if (index !== -1) {
313+
return dayPeriods[index];
300314
}
301315
}
302316
// if no rules for the day periods, we use am/pm by default

packages/common/test/i18n/format_date_spec.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,13 +202,31 @@ describe('Format date', () => {
202202
BBBBB: 'mi',
203203
};
204204

205+
const midnightCrossingPeriods: any = {
206+
b: 'night',
207+
bb: 'night',
208+
bbb: 'night',
209+
bbbb: 'night',
210+
bbbbb: 'night',
211+
B: 'at night',
212+
BB: 'at night',
213+
BBB: 'at night',
214+
BBBB: 'at night',
215+
BBBBB: 'at night',
216+
};
217+
205218
Object.keys(dateFixtures).forEach((pattern: string) => {
206219
expectDateFormatAs(date, pattern, dateFixtures[pattern]);
207220
});
208221

209222
Object.keys(isoStringWithoutTimeFixtures).forEach((pattern: string) => {
210223
expectDateFormatAs(isoStringWithoutTime, pattern, isoStringWithoutTimeFixtures[pattern]);
211224
});
225+
226+
const nightTime = new Date(2015, 5, 15, 2, 3, 1, 550);
227+
Object.keys(midnightCrossingPeriods).forEach(pattern => {
228+
expectDateFormatAs(nightTime, pattern, midnightCrossingPeriods[pattern]);
229+
});
212230
});
213231

214232
it('should format with timezones', () => {

0 commit comments

Comments
 (0)