Files
tolaria/src/utils/dateStringParts.ts
2026-05-07 19:52:14 +02:00

74 lines
2.4 KiB
TypeScript

export interface DateParts {
year: number
month: number
day: number
}
export function parseDashDateParts(value: string): DateParts | null {
const [datePart, timePart] = value.split('T', 2)
if (timePart !== undefined && !isClockPrefix(timePart)) return null
const parts = datePart.split('-')
if (parts.length !== 3) return null
if (!matchesLength(parts[0], 4)) return null
if (!matchesLength(parts[1], 2)) return null
if (!matchesLength(parts[2], 2)) return null
if (!parts.every(isDigits)) return null
return validDateParts({
year: Number(parts[0]),
month: Number(parts[1]),
day: Number(parts[2]),
})
}
export function parseSlashDateParts(value: string): DateParts | null {
return parseDateTuple(value, '/', [undefined, undefined, [2, 4]])
}
export function dateFromParts(parts: DateParts): Date {
return new Date(parts.year, parts.month - 1, parts.day)
}
function parseDateTuple(
value: string,
separator: string,
lengths: readonly [number | undefined, number | undefined, number | readonly number[]],
): DateParts | null {
const parts = value.split(separator)
if (parts.length !== 3) return null
if (!matchesLength(parts[0], lengths[0])) return null
if (!matchesLength(parts[1], lengths[1])) return null
if (!matchesLength(parts[2], lengths[2])) return null
if (!parts.every(isDigits)) return null
return validDateParts({
year: Number(parts[2].length === 2 ? `20${parts[2]}` : parts[2]),
month: Number(parts[0]),
day: Number(parts[1]),
})
}
function matchesLength(value: string, length: number | readonly number[] | undefined): boolean {
if (length === undefined) return value.length > 0
if (typeof length === 'number') return value.length === length
return length.includes(value.length)
}
function isDigits(value: string): boolean {
return value.length > 0 && [...value].every((char) => char >= '0' && char <= '9')
}
function isClockPrefix(value: string): boolean {
const parts = value.split(':')
return (parts.length === 2 || parts.length === 3)
&& matchesLength(parts[0], 2)
&& matchesLength(parts[1], 2)
&& (parts.length === 2 || matchesLength(parts[2], 2))
&& parts.every(isDigits)
}
function validDateParts(parts: DateParts): DateParts | null {
const date = dateFromParts(parts)
return date.getFullYear() === parts.year && date.getMonth() === parts.month - 1 && date.getDate() === parts.day
? parts
: null
}