mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 14:06:27 +00:00
bf0507ddb5
Google writes a range that stays in one half of the day with the meridiem on the closing time only, "1:00 – 3:00 PM" for one to three in the afternoon. The 24-hour conversion only saw times carrying their own AM/PM, so the opener stayed "1:00" beside a converted "15:00". The closing meridiem is handed back to an opener that has none before converting; a range spelled on both sides, the 12h display and lines without a meridiem are untouched. Closes #2412
81 lines
3.7 KiB
TypeScript
81 lines
3.7 KiB
TypeScript
/**
|
||
* Formatting for a provider's weekday opening-hours lines.
|
||
*
|
||
* These arrive as display text — "Monday: 11:30 AM – 11:00 PM" from Google in
|
||
* whatever language was requested, "Monday: 09:00-18:00" from the OSM tag
|
||
* parser. They cannot be computed from (that is what `placeOpenState` and the
|
||
* structured periods are for), only rendered. Lifted out of PlaceInspector so
|
||
* the add-place detail column shows hours the same way the inspector does
|
||
* rather than growing a second, subtly different implementation.
|
||
*/
|
||
|
||
/** Rewrites a line between 12h and 24h clocks, leaving it alone when it already fits. */
|
||
export function convertHoursLine(line: string, timeFormat: string): string {
|
||
if (!line) return ''
|
||
const hasAmPm = /\d{1,2}:\d{2}\s*(AM|PM)/i.test(line)
|
||
|
||
if (timeFormat === '12h' && !hasAmPm) {
|
||
// 24h → 12h: "10:00" → "10:00 AM", "21:00" → "9:00 PM", "Uhr" entfernen.
|
||
// split/trimEnd rather than /\s*Uhr/g: that pattern walks the rest of the line
|
||
// from every space it passes, so a line with no "Uhr" in it costs a pass per
|
||
// space. Cutting on the word and trimming the piece in front of it is the
|
||
// same edit, once.
|
||
const parts = line.split('Uhr')
|
||
const withoutUhr = parts.map((part, i) => (i < parts.length - 1 ? part.trimEnd() : part)).join('')
|
||
return withoutUhr.replace(/(\d{1,2}):(\d{2})/g, (match, h, m) => {
|
||
const hour = Number.parseInt(h)
|
||
if (Number.isNaN(hour)) return match
|
||
const period = hour >= 12 ? 'PM' : 'AM'
|
||
const h12 = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour
|
||
return `${h12}:${m} ${period}`
|
||
})
|
||
}
|
||
if (timeFormat !== '12h' && hasAmPm) {
|
||
// Google spells a range that stays in one half of the day with the meridiem on
|
||
// the closing time only: "1:00 – 3:00 PM" is one to three in the afternoon. The
|
||
// conversion below only sees times that carry their own AM/PM, so the opener
|
||
// stayed "1:00" next to a "15:00" (#2412). Hand the closing meridiem back to an
|
||
// opener that has none. It fires only where nothing but the dash sits between
|
||
// the two times, so "11:00 AM – 1:00 PM" keeps both halves as written.
|
||
const paired = line.replace(
|
||
/(\d{1,2}:\d{2})(\s*[–—-]\s*)(\d{1,2}:\d{2}\s*)(AM|PM)\b/gi,
|
||
(_, open, sep, close, p) => `${open} ${p}${sep}${close}${p}`,
|
||
)
|
||
// 12h → 24h: "10:00 AM" → "10:00", "9:00 PM" → "21:00"
|
||
return paired.replace(/(\d{1,2}):(\d{2})\s*(AM|PM)/gi, (_, h, m, p) => {
|
||
let hour = Number.parseInt(h)
|
||
if (p.toUpperCase() === 'PM' && hour !== 12) hour += 12
|
||
if (p.toUpperCase() === 'AM' && hour === 12) hour = 0
|
||
return `${String(hour).padStart(2, '0')}:${m}`
|
||
})
|
||
}
|
||
return line
|
||
}
|
||
|
||
/**
|
||
* Splits "Monday: 09:00-18:00" into its day and its times.
|
||
*
|
||
* On the FIRST separator only, because the times contain colons of their own.
|
||
* The fullwidth colon is in there because that is what Google returns for
|
||
* Chinese and Japanese, and splitting on the ASCII one alone left those lines
|
||
* unsplit and the whole string in the day column.
|
||
*
|
||
* A line with no separator at all comes back with an empty day, which callers
|
||
* render across the full width rather than squeezing into a two-column row.
|
||
*/
|
||
export function splitHoursLine(line: string): [day: string, times: string] {
|
||
const at = line.search(/[::]/)
|
||
if (at < 0) return ['', line.trim()]
|
||
return [line.slice(0, at).trim(), line.slice(at + 1).trim()]
|
||
}
|
||
|
||
/**
|
||
* Whether a line says nothing about that day.
|
||
*
|
||
* `parseOpeningHours` writes "Monday: ?" for a day the OSM tag did not cover.
|
||
* Showing a bare question mark reads like a bug, so callers render a dash.
|
||
*/
|
||
export function isUnknownHoursLine(times: string): boolean {
|
||
return times.trim() === '?'
|
||
}
|