From c6e5fc4fbe2af24b02e24fd04071282bad7bf85c Mon Sep 17 00:00:00 2001 From: Pete Bacon Darwin Date: Mon, 13 Apr 2020 21:06:40 +0100 Subject: [PATCH] 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 --- packages/common/src/i18n/format_date.ts | 40 +++++++++++++------ packages/common/test/i18n/format_date_spec.ts | 18 +++++++++ 2 files changed, 45 insertions(+), 13 deletions(-) diff --git a/packages/common/src/i18n/format_date.ts b/packages/common/src/i18n/format_date.ts index 06ad7ab760..02609c3e2a 100644 --- a/packages/common/src/i18n/format_date.ts +++ b/packages/common/src/i18n/format_date.ts @@ -277,26 +277,40 @@ function getDateTranslation( if (extended) { const rules = getLocaleExtraDayPeriodRules(locale); const dayPeriods = getLocaleExtraDayPeriods(locale, form, width); - let result; - rules.forEach((rule: Time|[Time, Time], index: number) => { + const index = rules.findIndex(rule => { if (Array.isArray(rule)) { // morning, afternoon, evening, night - const {hours: hoursFrom, minutes: minutesFrom} = rule[0]; - const {hours: hoursTo, minutes: minutesTo} = rule[1]; - if (currentHours >= hoursFrom && currentMinutes >= minutesFrom && - (currentHours < hoursTo || - (currentHours === hoursTo && currentMinutes < minutesTo))) { - result = dayPeriods[index]; + const [from, to] = rule; + const afterFrom = currentHours >= from.hours && currentMinutes >= from.minutes; + const beforeTo = + (currentHours < to.hours || + (currentHours === to.hours && currentMinutes < to.minutes)); + // We must account for normal rules that span a period during the day (e.g. 6am-9am) + // where `from` is less (earlier) than `to`. But also rules that span midnight (e.g. + // 10pm - 5am) where `from` is greater (later!) than `to`. + // + // In the first case the current time must be BOTH after `from` AND before `to` + // (e.g. 8am is after 6am AND before 10am). + // + // In the second case the current time must be EITHER after `from` OR before `to` + // (e.g. 4am is before 5am but not after 10pm; and 11pm is not before 5am but it is + // after 10pm). + if (from.hours < to.hours) { + if (afterFrom && beforeTo) { + return true; + } + } else if (afterFrom || beforeTo) { + return true; } } else { // noon or midnight - const {hours, minutes} = rule; - if (hours === currentHours && minutes === currentMinutes) { - result = dayPeriods[index]; + if (rule.hours === currentHours && rule.minutes === currentMinutes) { + return true; } } + return false; }); - if (result) { - return result; + if (index !== -1) { + return dayPeriods[index]; } } // if no rules for the day periods, we use am/pm by default diff --git a/packages/common/test/i18n/format_date_spec.ts b/packages/common/test/i18n/format_date_spec.ts index cd1b6dfcae..b43ac860a7 100644 --- a/packages/common/test/i18n/format_date_spec.ts +++ b/packages/common/test/i18n/format_date_spec.ts @@ -202,6 +202,19 @@ describe('Format date', () => { BBBBB: 'mi', }; + const midnightCrossingPeriods: any = { + b: 'night', + bb: 'night', + bbb: 'night', + bbbb: 'night', + bbbbb: 'night', + B: 'at night', + BB: 'at night', + BBB: 'at night', + BBBB: 'at night', + BBBBB: 'at night', + }; + Object.keys(dateFixtures).forEach((pattern: string) => { expectDateFormatAs(date, pattern, dateFixtures[pattern]); }); @@ -209,6 +222,11 @@ describe('Format date', () => { Object.keys(isoStringWithoutTimeFixtures).forEach((pattern: string) => { expectDateFormatAs(isoStringWithoutTime, pattern, isoStringWithoutTimeFixtures[pattern]); }); + + const nightTime = new Date(2015, 5, 15, 2, 3, 1, 550); + Object.keys(midnightCrossingPeriods).forEach(pattern => { + expectDateFormatAs(nightTime, pattern, midnightCrossingPeriods[pattern]); + }); }); it('should format with timezones', () => {