diff --git a/src/components/FeaturePanel/renderers/openingHours/complex.ts b/src/components/FeaturePanel/renderers/openingHours/complex.ts index 42d2d913..fa289024 100644 --- a/src/components/FeaturePanel/renderers/openingHours/complex.ts +++ b/src/components/FeaturePanel/renderers/openingHours/complex.ts @@ -1,8 +1,9 @@ import OpeningHours from 'opening_hours'; -import { splitDateRangeAtMidnight } from './utils'; +import { DateRange, isMidnight, splitDateRangeAtMidnight } from './utils'; import { Address, SimpleOpeningHoursTable } from './types'; import { LonLat } from '../../../../services/types'; -import { intl } from '../../../../services/intl'; +import { intl, t } from '../../../../services/intl'; +import { addDays, isAfter, isEqual, set } from 'date-fns'; type Weekday = keyof SimpleOpeningHoursTable; const WEEKDAYS: Weekday[] = ['su', 'mo', 'tu', 'we', 'th', 'fr', 'sa', 'ph']; @@ -19,6 +20,14 @@ const weekdayMappings: Record = { const fmtDate = (d: Date) => d.toLocaleTimeString(intl.lang, { hour: 'numeric', minute: 'numeric' }); +const fmtDateRange = ([start, end]: DateRange) => { + if (isMidnight(start) && isMidnight(end)) { + return t('opening_hours.all_day'); + } + + return `${fmtDate(start)}-${isMidnight(end) ? t('opening_hours.midnight') : fmtDate(end)}`; +}; + export type Status = 'opens-soon' | 'closes-soon' | 'opened' | 'closed'; const getStatus = (opensInMins: number, closesInMins: number): Status => { @@ -56,7 +65,16 @@ export const parseComplexOpeningHours = ( const intervals = oh.getOpenIntervals(today, oneWeekLater); const splittedIntervals = intervals.flatMap(([openingDate, endDate]) => - splitDateRangeAtMidnight([openingDate, endDate]), + splitDateRangeAtMidnight([openingDate, endDate], (d1, d2) => { + const splitPoint = set(addDays(new Date(d1), 1), { + hours: 5, + minutes: 0, + seconds: 0, + milliseconds: 0, + }); + + return isEqual(d2, splitPoint) || isAfter(d2, splitPoint); + }), ); const grouped = WEEKDAYS.map((w) => { @@ -70,9 +88,7 @@ export const parseComplexOpeningHours = ( const daysTable = Object.fromEntries( grouped.map((entry) => { - const strings = entry[1].map( - ([from, due]) => `${fmtDate(from)}-${fmtDate(due)}`, - ); + const strings = entry[1].map(fmtDateRange); return [entry[0], strings] as const; }), diff --git a/src/components/FeaturePanel/renderers/openingHours/utils.ts b/src/components/FeaturePanel/renderers/openingHours/utils.ts index 4d97cc12..05de0708 100644 --- a/src/components/FeaturePanel/renderers/openingHours/utils.ts +++ b/src/components/FeaturePanel/renderers/openingHours/utils.ts @@ -1,14 +1,20 @@ -type DateRange = [Date, Date]; +export type DateRange = [Date, Date]; -export function splitDateRangeAtMidnight([ - startDate, - endDate, -]: DateRange): DateRange[] { - const isSameDay = (date1: Date, date2: Date) => - date1.getFullYear() === date2.getFullYear() && - date1.getMonth() === date2.getMonth() && - date1.getDate() === date2.getDate(); +const isSameDay = (date1: Date, date2: Date) => + date1.getFullYear() === date2.getFullYear() && + date1.getMonth() === date2.getMonth() && + date1.getDate() === date2.getDate(); +export const isMidnight = (date: Date) => + date.getHours() === 0 && + date.getMinutes() === 0 && + date.getSeconds() === 0 && + date.getMilliseconds() === 0; + +export function splitDateRangeAtMidnight( + [startDate, endDate]: DateRange, + shouldSplit = (d1: Date, d2: Date) => !isSameDay(d1, d2), +): DateRange[] { const midnight = new Date(startDate); midnight.setHours(0, 0, 0, 0); midnight.setDate(midnight.getDate() + 1); @@ -17,12 +23,12 @@ export function splitDateRangeAtMidnight([ return []; } - if (isSameDay(startDate, endDate)) { + if (!shouldSplit(startDate, endDate)) { return [[startDate, endDate]]; } return [ [startDate, midnight], - ...splitDateRangeAtMidnight([midnight, endDate]), + ...splitDateRangeAtMidnight([midnight, endDate], shouldSplit), ]; } diff --git a/src/locales/de.js b/src/locales/de.js index 89e09270..af689eb4 100644 --- a/src/locales/de.js +++ b/src/locales/de.js @@ -67,6 +67,8 @@ export default { 'featurepanel.inline_edit_title': 'Bearbeiten', 'featurepanel.objects_around': 'Orte in der Nähe', + 'opening_hours.all_day': '24 Stunden', + 'opening_hours.midnight': '24', 'opening_hours.open': 'Geöffnet: __todayTime__', 'opening_hours.now_closed_but_today': 'Geschlossen, heute: __todayTime__', 'opening_hours.today_closed': 'Heute geschlossen', diff --git a/src/locales/vocabulary.js b/src/locales/vocabulary.js index c60d008a..a37ff853 100644 --- a/src/locales/vocabulary.js +++ b/src/locales/vocabulary.js @@ -116,6 +116,8 @@ export default { 'featurepanel.more_in_openplaceguide': 'More information on __instanceName__', 'featurepanel.climbing_restriction': 'Climbing restriction', + 'opening_hours.all_day': '24 hours', + 'opening_hours.midnight': '12 PM', 'opening_hours.open': 'Open: __todayTime__', 'opening_hours.now_closed_but_today': 'Closed now - Open __todayTime__', 'opening_hours.today_closed': 'Closed today',