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);
+}