From 9f57bbf4fb11108313fe7f5efb3b03112ca7783b Mon Sep 17 00:00:00 2001 From: Maurice Date: Thu, 17 Sep 2026 14:53:58 +0200 Subject: [PATCH] fix(dawarich): the stay times follow the clock the traveller set Every time Dawarich shows was cut straight out of the timestamp and printed, so on a 12-hour setting the app said "2:05 PM" everywhere and "14:05" here. Both ends of a range go through the same formatter the rest of the app uses now, and the argument is required rather than optional so the next caller answers the question instead of inheriting the default. That covers the rows in the panel, on the phone and in the planner, and the summary line a journal day carries, which reads its span through the very same function so the fold and the stays under it cannot disagree about the clock. The accept dialog was already right: it fills a CustomTimePicker, which knows the setting, from the 24-hour value it keeps internally. --- .../Dawarich/DawarichSuggestionsPanel.tsx | 6 +++++- .../Dawarich/dawarichSuggestionModel.test.ts | 16 +++++++++++++--- .../Dawarich/dawarichSuggestionModel.ts | 12 +++++++++--- .../components/Journey/JourneyDayDawarich.tsx | 11 ++++++----- 4 files changed, 33 insertions(+), 12 deletions(-) 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 (