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', () => {