diff --git a/client/src/components/Dawarich/DawarichSuggestionsPanel.tsx b/client/src/components/Dawarich/DawarichSuggestionsPanel.tsx index 8c15ce977..622f556f5 100644 --- a/client/src/components/Dawarich/DawarichSuggestionsPanel.tsx +++ b/client/src/components/Dawarich/DawarichSuggestionsPanel.tsx @@ -4,6 +4,7 @@ import type { DawarichAccept, DawarichSuggestion, DawarichSuggestionTarget } fro import { useTranslation } from '../../i18n' import { useDawarichSuggestions } from '../../hooks/useDawarichSuggestions' import { useTripStore } from '../../store/tripStore' +import { useSettingsStore } from '../../store/settingsStore' import { useIsPhone } from '../../mobile/useIsPhone' import { relativeTime } from '../../utils/relativeTime' import { Tooltip } from '../shared/Tooltip' @@ -309,6 +310,9 @@ export function SuggestionRow({ onRestore?: () => void }): React.ReactElement { const { t, locale } = useTranslation() + // The traveller's own clock: these times used to come straight out of the timestamp, so + // a 12-hour setting got 24-hour times here and nowhere else. + const is12h = useSettingsStore(s => s.settings.time_format) === '12h' return (
@@ -355,7 +359,7 @@ export function SuggestionRow({ {showDate && ( {formatDayHeading(suggestion.localDate, locale)} )} - {timeRange(suggestion.startedAt, suggestion.endedAt)} + {timeRange(suggestion.startedAt, suggestion.endedAt, is12h)} {formatDuration(suggestion.durationMinutes, t)} {suggestion.state === 'accepted' && ( diff --git a/client/src/components/Dawarich/dawarichSuggestionModel.test.ts b/client/src/components/Dawarich/dawarichSuggestionModel.test.ts index 3eb967e1a..4e49da102 100644 --- a/client/src/components/Dawarich/dawarichSuggestionModel.test.ts +++ b/client/src/components/Dawarich/dawarichSuggestionModel.test.ts @@ -105,12 +105,22 @@ describe('clockOf / timeRange', () => { }) it('FE-DAWARICH-SUGMODEL-006: a range when both ends read, the arrival alone when the end does not', () => { - expect(timeRange('2026-09-10T10:15:00+02:00', '2026-09-10T12:40:00+02:00')).toBe('10:15 – 12:40') - expect(timeRange('2026-09-10T10:15:00+02:00', 'nonsense')).toBe('10:15') + expect(timeRange('2026-09-10T10:15:00+02:00', '2026-09-10T12:40:00+02:00', false)).toBe('10:15 – 12:40') + expect(timeRange('2026-09-10T10:15:00+02:00', 'nonsense', false)).toBe('10:15') }) it('FE-DAWARICH-SUGMODEL-007: no arrival, no range', () => { - expect(timeRange('nonsense', '2026-09-10T12:40:00+02:00')).toBe('') + expect(timeRange('nonsense', '2026-09-10T12:40:00+02:00', false)).toBe('') + }) + + it('FE-DAWARICH-SUGMODEL-012: a twelve-hour clock gets twelve-hour times, both ends of the range', () => { + // These were cut out of the timestamp and printed as they stood, so a traveller on a + // 12-hour clock read every Dawarich time in 24-hour while the rest of the app obeyed + // the setting. + expect(timeRange('2026-09-10T10:15:00+02:00', '2026-09-10T12:40:00+02:00', true)).toBe('10:15 AM – 12:40 PM') + expect(timeRange('2026-09-10T14:05:00+02:00', '2026-09-10T23:00:00+02:00', true)).toBe('2:05 PM – 11:00 PM') + // Midnight is 12 AM, not 0 AM, and noon is 12 PM. + expect(timeRange('2026-09-10T00:30:00+02:00', '2026-09-10T12:00:00+02:00', true)).toBe('12:30 AM – 12:00 PM') }) }) diff --git a/client/src/components/Dawarich/dawarichSuggestionModel.ts b/client/src/components/Dawarich/dawarichSuggestionModel.ts index eb53eec7f..dfe530969 100644 --- a/client/src/components/Dawarich/dawarichSuggestionModel.ts +++ b/client/src/components/Dawarich/dawarichSuggestionModel.ts @@ -1,5 +1,6 @@ import type { DawarichSuggestion } from '@trek/shared' import { dayColor } from '../Map/dawarichTrail' +import { formatClockTime } from '../../utils/formatters' /** * Pure shaping for the suggestions panel — no React, so the grouping and the @@ -54,10 +55,15 @@ export function clockOf(iso: string): string { * * An en dash with hair spaces rather than a hyphen: this is a range, and it sits * next to times often enough that the difference is visible. + * + * `is12h` is not optional on purpose. These clocks were cut straight out of the + * timestamp and printed, so a traveller on a 12-hour clock read every Dawarich time in + * 24-hour while the rest of the app obeyed the setting. A required argument makes the + * next caller answer the question rather than inherit the bug. */ -export function timeRange(startedAt: string, endedAt: string): string { - const from = clockOf(startedAt) - const to = clockOf(endedAt) +export function timeRange(startedAt: string, endedAt: string, is12h: boolean): string { + const from = formatClockTime(clockOf(startedAt), is12h) + const to = formatClockTime(clockOf(endedAt), is12h) if (!from) return '' return to ? `${from} – ${to}` : from } diff --git a/client/src/components/Journey/JourneyDayDawarich.tsx b/client/src/components/Journey/JourneyDayDawarich.tsx index ed5fae2ac..821d20b49 100644 --- a/client/src/components/Journey/JourneyDayDawarich.tsx +++ b/client/src/components/Journey/JourneyDayDawarich.tsx @@ -4,7 +4,8 @@ import type { DawarichSuggestion, DawarichSuggestionTarget } from '@trek/shared' import { useTranslation } from '../../i18n' import DawarichIcon from '../shared/DawarichIcon' import { SuggestionRow } from '../Dawarich/DawarichSuggestionsPanel' -import { clockOf } from '../Dawarich/dawarichSuggestionModel' +import { timeRange } from '../Dawarich/dawarichSuggestionModel' +import { useSettingsStore } from '../../store/settingsStore' /** * The stays Dawarich recorded on ONE day of the journal, folded into that day. @@ -36,15 +37,15 @@ export default function JourneyDayDawarich({ onDismiss: (suggestion: DawarichSuggestion) => void }): React.ReactElement | null { const { t } = useTranslation() + const is12h = useSettingsStore(s => s.settings.time_format) === '12h' const [open, setOpen] = useState(false) if (suggestions.length === 0) return null // From the first arrival to the last departure: what part of the day this covers, in - // one line, so the row says something even while it is shut. - const from = clockOf(suggestions[0]!.startedAt) - const to = clockOf(suggestions[suggestions.length - 1]!.endedAt) - const span = from && to ? `${from} – ${to}` : from + // one line, so the row says something even while it is shut. Through the same formatter + // the rows use, so the summary and the stays under it cannot disagree about the clock. + const span = timeRange(suggestions[0]!.startedAt, suggestions[suggestions.length - 1]!.endedAt, is12h) return (