From cf7275c9c32fd88f060649233852206a9d0da9b1 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Thu, 15 Oct 2026 04:05:55 +0200 Subject: [PATCH] fix: write calendar dates in the order of the app language --- src/components/calendar/EventModal.tsx | 5 +++-- .../__tests__/calendar-date-format.test.ts | 21 +++++++++++++++++++ src/lib/date-format.ts | 19 ++++++++++++++++- 3 files changed, 42 insertions(+), 3 deletions(-) create mode 100644 src/lib/__tests__/calendar-date-format.test.ts diff --git a/src/components/calendar/EventModal.tsx b/src/components/calendar/EventModal.tsx index 8c22a022..7bdd45e5 100644 --- a/src/components/calendar/EventModal.tsx +++ b/src/components/calendar/EventModal.tsx @@ -47,6 +47,7 @@ import { editorTimesFromEvent, eventTimeFieldsToSave } from '../../lib/event-edi import { useSettingsStore } from '../../stores/settings-store'; import { canCreateEventsIn } from '../../lib/calendar-editability'; import { useCalendarLocale } from '../../lib/calendar-locale'; +import { formatCalendarDate } from '../../lib/date-format'; import { alertsToReminders, remindersToAlerts, @@ -530,7 +531,7 @@ export function EventModal({ style={styles.dateBtn} onPress={() => setShowStartDate(true)} > - {format(start, 'EEE, MMM d', { locale })} + {formatCalendarDate(start, locale.code, 'weekdayMonthDay')} {!allDay && ( setShowEndDate(true)} > - {format(end, 'EEE, MMM d', { locale })} + {formatCalendarDate(end, locale.code, 'weekdayMonthDay')} {!allDay && ( { + it('puts the day before the month in German', () => { + const s = formatCalendarDate(oct15, 'de', 'weekdayMonthDay'); + expect(s).toMatch(/15\. Okt/); + expect(s).not.toMatch(/Okt\.? 15/); + }); + + it('keeps the US order in English', () => { + expect(formatCalendarDate(oct15, 'en-US', 'weekdayMonthDay')).toBe('Thu, Oct 15'); + expect(formatCalendarDate(oct15, 'en', 'monthDayYear')).toBe('Oct 15, 2026'); + }); + + it('names the weekday in full with the year for an all-day date', () => { + expect(formatCalendarDate(oct15, 'de', 'longWeekdayMonthDayYear')).toMatch(/^Donnerstag, 15\. Okt\.? 2026$/); + }); +}); diff --git a/src/lib/date-format.ts b/src/lib/date-format.ts index 51bd439d..95e52604 100644 --- a/src/lib/date-format.ts +++ b/src/lib/date-format.ts @@ -17,7 +17,8 @@ import type { DateFormat, TimeFormat } from '../stores/settings-store'; type Translate = (key: string, fallback?: string, params?: Record) => string; type FormatName = - | 'time12' | 'time24' | 'full12' | 'full24' | 'weekday' | 'date' | 'monthDay' | 'monthDayYear'; + | 'time12' | 'time24' | 'full12' | 'full24' | 'weekday' | 'date' | 'monthDay' | 'monthDayYear' + | 'weekdayMonthDay' | 'longWeekdayMonthDayYear'; const FORMATS: Record = { time12: { hour: '2-digit', minute: '2-digit', hour12: true }, @@ -28,6 +29,8 @@ const FORMATS: Record = { date: { year: 'numeric', month: '2-digit', day: '2-digit' }, monthDay: { month: 'short', day: 'numeric' }, monthDayYear: { month: 'short', day: 'numeric', year: 'numeric' }, + weekdayMonthDay: { weekday: 'short', month: 'short', day: 'numeric' }, + longWeekdayMonthDayYear: { weekday: 'long', month: 'short', day: 'numeric', year: 'numeric' }, }; // Building an Intl formatter is the expensive part of formatting a date (on @@ -124,3 +127,17 @@ export function formatListDate( return formatWith(d, intlLocale, 'date'); } + +export type CalendarDateStyle = 'weekdayMonthDay' | 'longWeekdayMonthDayYear' | 'monthDay' | 'monthDayYear'; + +/** + * A calendar date in the order and words of `locale` ("Thu, Oct 15", + * "Do., 15. Okt."). The calendar used fixed US patterns such as + * 'EEE, MMM d', which put German and most other languages in the wrong order. + * `locale` is a language tag such as a date-fns locale's `code`. + */ +export function formatCalendarDate(date: Date, locale: string | undefined, style: CalendarDateStyle): string { + syncFormatterZone(new Date()); + const tag = !locale || locale === 'en' ? 'en-US' : locale; + return formatWith(date, tag, style); +}