You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

295 lines
7.2 KiB

/**
* Date Formatting Utilities
*
* Provides utilities for formatting dates in German locale and converting
* between different date formats for display and database storage.
*/
/**
* Format a date to German format (DD.MM.YYYY)
*
* @param date - Date to format (Date object, ISO string, or timestamp)
* @returns Formatted date string (e.g., "24.12.2024")
*
* @example
* formatDateGerman(new Date('2024-12-24'))
* // => "24.12.2024"
*
* formatDateGerman('2024-12-24T10:30:00Z')
* // => "24.12.2024"
*/
export function formatDateGerman(date: Date | string | number): string {
const dateObj = typeof date === 'string' || typeof date === 'number' ? new Date(date) : date
if (isNaN(dateObj.getTime())) {
return ''
}
return dateObj.toLocaleDateString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
})
}
/**
* Format a date to German long format (e.g., "24. Dezember 2024")
*
* @param date - Date to format
* @returns Formatted date string with month name
*
* @example
* formatDateGermanLong(new Date('2024-12-24'))
* // => "24. Dezember 2024"
*/
export function formatDateGermanLong(date: Date | string | number): string {
const dateObj = typeof date === 'string' || typeof date === 'number' ? new Date(date) : date
if (isNaN(dateObj.getTime())) {
return ''
}
return dateObj.toLocaleDateString('de-DE', {
day: 'numeric',
month: 'long',
year: 'numeric',
})
}
/**
* Format a date with time to German format (DD.MM.YYYY HH:MM)
*
* @param date - Date to format
* @returns Formatted datetime string
*
* @example
* formatDateTimeGerman(new Date('2024-12-24T15:30:00Z'))
* // => "24.12.2024 15:30"
*/
export function formatDateTimeGerman(date: Date | string | number): string {
const dateObj = typeof date === 'string' || typeof date === 'number' ? new Date(date) : date
if (isNaN(dateObj.getTime())) {
return ''
}
return dateObj.toLocaleString('de-DE', {
day: '2-digit',
month: '2-digit',
year: 'numeric',
hour: '2-digit',
minute: '2-digit',
})
}
/**
* Parse a date input string (DD.MM.YYYY or YYYY-MM-DD) to ISO format (YYYY-MM-DD)
* Suitable for database storage and HTML date inputs
*
* @param dateString - Date string in DD.MM.YYYY or YYYY-MM-DD format
* @returns ISO date string (YYYY-MM-DD) or null if invalid
*
* @example
* parseDateToISO('24.12.2024')
* // => "2024-12-24"
*
* parseDateToISO('2024-12-24')
* // => "2024-12-24"
*
* parseDateToISO('invalid')
* // => null
*/
export function parseDateToISO(dateString: string): string | null {
if (!dateString || dateString.trim() === '') {
return null
}
// Check if already in ISO format (YYYY-MM-DD)
if (/^\d{4}-\d{2}-\d{2}$/.test(dateString)) {
const date = new Date(dateString)
return isNaN(date.getTime()) ? null : dateString
}
// Parse German format (DD.MM.YYYY)
const germanMatch = dateString.match(/^(\d{1,2})\.(\d{1,2})\.(\d{4})$/)
if (germanMatch) {
const [, day, month, year] = germanMatch
const paddedDay = day.padStart(2, '0')
const paddedMonth = month.padStart(2, '0')
const isoDate = `${year}-${paddedMonth}-${paddedDay}`
// Validate the date
const date = new Date(isoDate)
if (isNaN(date.getTime())) {
return null
}
return isoDate
}
return null
}
/**
* Convert ISO date string (YYYY-MM-DD) to German format (DD.MM.YYYY)
*
* @param isoDate - ISO date string (YYYY-MM-DD)
* @returns German date string (DD.MM.YYYY) or empty string if invalid
*
* @example
* isoToGermanDate('2024-12-24')
* // => "24.12.2024"
*/
export function isoToGermanDate(isoDate: string): string {
if (!isoDate || !/^\d{4}-\d{2}-\d{2}$/.test(isoDate)) {
return ''
}
const [year, month, day] = isoDate.split('-')
return `${day}.${month}.${year}`
}
/**
* Validate if a date of birth indicates a person is at least 18 years old
*
* @param dateOfBirth - Date of birth (Date object, ISO string, or German format)
* @returns true if person is 18 or older
*
* @example
* isAgeAtLeast18(new Date('2000-01-01'))
* // => true (as of 2024)
*
* isAgeAtLeast18('01.01.2010')
* // => false (as of 2024)
*/
export function isAgeAtLeast18(dateOfBirth: Date | string): boolean {
let dateObj: Date
if (typeof dateOfBirth === 'string') {
// Try parsing German format first
const isoDate = parseDateToISO(dateOfBirth)
if (isoDate) {
dateObj = new Date(isoDate)
} else {
dateObj = new Date(dateOfBirth)
}
} else {
dateObj = dateOfBirth
}
if (isNaN(dateObj.getTime())) {
return false
}
const today = new Date()
const age = today.getFullYear() - dateObj.getFullYear()
const monthDiff = today.getMonth() - dateObj.getMonth()
const dayDiff = today.getDate() - dateObj.getDate()
// Check if birthday has occurred this year
if (monthDiff < 0 || (monthDiff === 0 && dayDiff < 0)) {
return age - 1 >= 18
}
return age >= 18
}
/**
* Format a relative date (e.g., "vor 2 Tagen", "gerade eben")
*
* @param date - Date to format
* @returns Relative time string in German
*
* @example
* formatRelativeDate(new Date(Date.now() - 1000 * 60 * 5))
* // => "vor 5 Minuten"
*/
export function formatRelativeDate(date: Date | string | number): string {
const dateObj = typeof date === 'string' || typeof date === 'number' ? new Date(date) : date
if (isNaN(dateObj.getTime())) {
return ''
}
const now = new Date()
const diffMs = now.getTime() - dateObj.getTime()
const diffSecs = Math.floor(diffMs / 1000)
const diffMins = Math.floor(diffSecs / 60)
const diffHours = Math.floor(diffMins / 60)
const diffDays = Math.floor(diffHours / 24)
if (diffSecs < 60) {
return 'gerade eben'
}
if (diffMins < 60) {
return `vor ${diffMins} ${diffMins === 1 ? 'Minute' : 'Minuten'}`
}
if (diffHours < 24) {
return `vor ${diffHours} ${diffHours === 1 ? 'Stunde' : 'Stunden'}`
}
if (diffDays < 7) {
return `vor ${diffDays} ${diffDays === 1 ? 'Tag' : 'Tagen'}`
}
if (diffDays < 30) {
const weeks = Math.floor(diffDays / 7)
return `vor ${weeks} ${weeks === 1 ? 'Woche' : 'Wochen'}`
}
if (diffDays < 365) {
const months = Math.floor(diffDays / 30)
return `vor ${months} ${months === 1 ? 'Monat' : 'Monaten'}`
}
const years = Math.floor(diffDays / 365)
return `vor ${years} ${years === 1 ? 'Jahr' : 'Jahren'}`
}
/**
* Get today's date in ISO format (YYYY-MM-DD)
*
* @returns Today's date in ISO format
*
* @example
* getTodayISO()
* // => "2024-12-24"
*/
export function getTodayISO(): string {
const today = new Date()
return today.toISOString().split('T')[0]
}
/**
* Validate if a date is in the future
*
* @param date - Date to validate
* @returns true if date is in the future
*/
export function isDateInFuture(date: Date | string): boolean {
const dateObj = typeof date === 'string' ? new Date(date) : date
if (isNaN(dateObj.getTime())) {
return false
}
return dateObj.getTime() > Date.now()
}
/**
* Validate if a date is in the past
*
* @param date - Date to validate
* @returns true if date is in the past
*/
export function isDateInPast(date: Date | string): boolean {
const dateObj = typeof date === 'string' ? new Date(date) : date
if (isNaN(dateObj.getTime())) {
return false
}
return dateObj.getTime() < Date.now()
}