fix: format date

This commit is contained in:
Benno Tielen 2026-07-16 09:22:46 +02:00
parent fb071ff650
commit 0703704307
4 changed files with 104 additions and 6 deletions

View file

@ -29,6 +29,17 @@ export const EventWithEnd: Story = {
},
}
export const MultiDayEvent: Story = {
args: {
date: '2026-07-24T18:00:00+02:00',
end: '2026-07-26T14:00:00+02:00',
title: 'Gemeindewochenende',
href: 'https://www.link_to_event.com',
location: "St. Clara",
cancelled: false,
},
}
export const EventInMarch: Story = {
args: {
date: '2024-03-24T15:00:00+01:00',

View file

@ -4,6 +4,7 @@ import { useEffect, useMemo, useState } from 'react'
import styles from "./styles.module.scss"
import classNames from 'classnames'
import Link from 'next/link'
import { formatEventDate } from '@/utils/formatEventDate'
export type EventRowProps = {
/** datetime 8601 format */
@ -41,8 +42,6 @@ const shortMonth = (date: string) => {
return months[month - 1];
}
export const EventRow = ({date, end, title, location, cancelled, href, color = "base", showDate = true}: EventRowProps) => {
const day = useMemo(() => date.substring(8, 10), [date]);
const dateObj = useMemo(() => new Date(date), [date]);
@ -80,10 +79,7 @@ export const EventRow = ({date, end, title, location, cancelled, href, color = "
<span className={classNames({ [styles.cancelled]: cancelled })}>
{ showDate &&
<>
{dateObj.toLocaleDateString("de-DE", { weekday: "long" })}
{dayFormat === "long" && " " + dateObj.toLocaleDateString("de-DE", { dateStyle: "short" })}, {dateObj.toLocaleTimeString("de-DE", { timeStyle: "short", timeZone: "Europe/Berlin" })}
{ endObj ? <> - {endObj.toLocaleTimeString("de-DE", { timeStyle: "short", timeZone: "Europe/Berlin" })}</> : "" }
&nbsp;Uhr
{formatEventDate(dateObj, endObj, dayFormat)}
<br />
</>
}

View file

@ -0,0 +1,52 @@
import { describe, expect, test } from 'vitest'
import { formatEventDate } from './formatEventDate'
// Fri 24.07.2026 18:00 Berlin time
const start = new Date('2026-07-24T18:00:00+02:00')
const endSameDay = new Date('2026-07-24T20:00:00+02:00')
const endNextDay = new Date('2026-07-25T01:00:00+02:00')
const endSunday = new Date('2026-07-26T14:00:00+02:00')
describe('format "long"', () => {
test('start only', () => {
expect(formatEventDate(start)).toBe(`Freitag 24.07.26, 18:00 Uhr`)
})
test('same-day end renders as time range', () => {
expect(formatEventDate(start, endSameDay)).toBe(`Freitag 24.07.26, 18:00 - 20:00 Uhr`)
})
test('multi-day end renders both dates', () => {
expect(formatEventDate(start, endSunday)).toBe(
`Fr. 24.07. 18:00 Uhr - So. 26.07. 14:00 Uhr`,
)
})
test('end after midnight counts as multi-day', () => {
expect(formatEventDate(start, endNextDay)).toBe(
`Fr. 24.07. 18:00 Uhr - Sa. 25.07. 01:00 Uhr`,
)
})
})
describe('format "short"', () => {
test('start only', () => {
expect(formatEventDate(start, undefined, 'short')).toBe(`Freitag, 18:00 Uhr`)
})
test('same-day end renders as time range without date', () => {
expect(formatEventDate(start, endSameDay, 'short')).toBe(`Freitag, 18:00 - 20:00 Uhr`)
})
test('multi-day end renders compact weekday range', () => {
expect(formatEventDate(start, endSunday, 'short')).toBe(`Fr 18:00 - So 14:00 Uhr`)
})
})
describe('timezone handling', () => {
test('calendar day is compared in Europe/Berlin, not UTC', () => {
const lateStart = new Date('2026-07-24T22:30:00Z')
const earlyEnd = new Date('2026-07-25T01:00:00Z')
expect(formatEventDate(lateStart, earlyEnd)).toBe(`Samstag 25.07.26, 00:30 - 03:00 Uhr`)
})
})

View file

@ -0,0 +1,39 @@
/**
* Format an event's date and time range in a user-friendly way.
*
* format "long" (default):
* - same day: "Freitag 24.07.26, 18:00 - 20:00 Uhr"
* - multi-day: "Fr. 24.07. 18:00 Uhr - So. 26.07. 14:00 Uhr"
*
* format "short" (narrow screens):
* - same day: "Freitag, 18:00 - 20:00 Uhr"
* - multi-day: "Fr 18:00 - So 14:00 Uhr"
*/
export const formatEventDate = (start: Date, end?: Date, format: "long" | "short" = "long"): string => {
const time = (d: Date) => d.toLocaleTimeString("de-DE", { timeStyle: "short", timeZone: "Europe/Berlin" })
const calendarDay = (d: Date) => d.toLocaleDateString("de-DE", { timeZone: "Europe/Berlin" })
const weekday = (d: Date, length: "long" | "short") =>
d.toLocaleDateString("de-DE", { weekday: length, timeZone: "Europe/Berlin" })
const isMultiDay = end && calendarDay(start) !== calendarDay(end)
switch (format) {
case "short": {
if (end && isMultiDay) {
return `${weekday(start, "short")} ${time(start)} - ${weekday(end, "short")} ${time(end)} Uhr`
}
const endTime = end ? ` - ${time(end)}` : ""
return `${weekday(start, "long")}, ${time(start)}${endTime} Uhr`
}
case "long": {
if (end && isMultiDay) {
// "Fr. 24.07." — assembled manually because Intl puts a comma between weekday and date
const dayDate = (d: Date) =>
`${weekday(d, "short")}. ${d.toLocaleDateString("de-DE", { day: "2-digit", month: "2-digit", timeZone: "Europe/Berlin" })}`
return `${dayDate(start)} ${time(start)} Uhr - ${dayDate(end)} ${time(end)} Uhr`
}
const endTime = end ? ` - ${time(end)}` : ""
return `${weekday(start, "long")} ${start.toLocaleDateString("de-DE", { dateStyle: "short", timeZone: "Europe/Berlin" })}, ${time(start)}${endTime} Uhr`
}
}
}