fix: format date
This commit is contained in:
parent
fb071ff650
commit
0703704307
4 changed files with 104 additions and 6 deletions
|
|
@ -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 = {
|
export const EventInMarch: Story = {
|
||||||
args: {
|
args: {
|
||||||
date: '2024-03-24T15:00:00+01:00',
|
date: '2024-03-24T15:00:00+01:00',
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { useEffect, useMemo, useState } from 'react'
|
||||||
import styles from "./styles.module.scss"
|
import styles from "./styles.module.scss"
|
||||||
import classNames from 'classnames'
|
import classNames from 'classnames'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
import { formatEventDate } from '@/utils/formatEventDate'
|
||||||
|
|
||||||
export type EventRowProps = {
|
export type EventRowProps = {
|
||||||
/** datetime 8601 format */
|
/** datetime 8601 format */
|
||||||
|
|
@ -41,8 +42,6 @@ const shortMonth = (date: string) => {
|
||||||
return months[month - 1];
|
return months[month - 1];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export const EventRow = ({date, end, title, location, cancelled, href, color = "base", showDate = true}: EventRowProps) => {
|
export const EventRow = ({date, end, title, location, cancelled, href, color = "base", showDate = true}: EventRowProps) => {
|
||||||
const day = useMemo(() => date.substring(8, 10), [date]);
|
const day = useMemo(() => date.substring(8, 10), [date]);
|
||||||
const dateObj = useMemo(() => new Date(date), [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 })}>
|
<span className={classNames({ [styles.cancelled]: cancelled })}>
|
||||||
{ showDate &&
|
{ showDate &&
|
||||||
<>
|
<>
|
||||||
{dateObj.toLocaleDateString("de-DE", { weekday: "long" })}
|
{formatEventDate(dateObj, endObj, dayFormat)}
|
||||||
{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" })}</> : "" }
|
|
||||||
Uhr
|
|
||||||
<br />
|
<br />
|
||||||
</>
|
</>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
52
src/utils/formatEventDate.test.ts
Normal file
52
src/utils/formatEventDate.test.ts
Normal 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`)
|
||||||
|
})
|
||||||
|
})
|
||||||
39
src/utils/formatEventDate.ts
Normal file
39
src/utils/formatEventDate.ts
Normal 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`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue