130 lines
3.7 KiB
TypeScript
130 lines
3.7 KiB
TypeScript
import { de } from "date-fns/locale";
|
|
import { format } from "date-fns";
|
|
import { toZonedTime, fromZonedTime } from "date-fns-tz";
|
|
|
|
export const DEFAULT_TIMEZONE = process.env.DEFAULT_TIMEZONE ?? "Europe/Berlin";
|
|
|
|
export function isValidTimeZone(value: string): boolean {
|
|
try {
|
|
Intl.DateTimeFormat("de-DE", { timeZone: value }).format(new Date());
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
export function resolveTimeZone(value?: string | null): string {
|
|
const trimmed = value?.trim();
|
|
if (!trimmed) return DEFAULT_TIMEZONE;
|
|
return isValidTimeZone(trimmed) ? trimmed : DEFAULT_TIMEZONE;
|
|
}
|
|
|
|
export function formatDateDE(
|
|
date: Date,
|
|
withWeekday = false,
|
|
timeZone: string = DEFAULT_TIMEZONE
|
|
): string {
|
|
const zone = resolveTimeZone(timeZone);
|
|
return format(toZonedTime(date, zone), withWeekday ? "EEEE, dd.MM.yyyy" : "dd.MM.yyyy", {
|
|
locale: de
|
|
});
|
|
}
|
|
|
|
export function formatTimeDE(date: Date, timeZone: string = DEFAULT_TIMEZONE): string {
|
|
const zone = resolveTimeZone(timeZone);
|
|
return format(toZonedTime(date, zone), "HH:mm", { locale: de });
|
|
}
|
|
|
|
export function zonedDateOnlyToUtc(date: string, timeZone: string = DEFAULT_TIMEZONE): Date {
|
|
const zone = resolveTimeZone(timeZone);
|
|
return fromZonedTime(`${date} 00:00:00`, zone);
|
|
}
|
|
|
|
export function zonedDateFromParts(
|
|
date: string,
|
|
time: string,
|
|
timeZone: string = DEFAULT_TIMEZONE
|
|
): Date {
|
|
const zone = resolveTimeZone(timeZone);
|
|
return fromZonedTime(`${date} ${time}:00`, zone);
|
|
}
|
|
|
|
export function atStartOfDayInZone(date: Date, timeZone: string = DEFAULT_TIMEZONE): Date {
|
|
const zone = resolveTimeZone(timeZone);
|
|
const zoned = toZonedTime(date, zone);
|
|
const yyyy = format(zoned, "yyyy");
|
|
const mm = format(zoned, "MM");
|
|
const dd = format(zoned, "dd");
|
|
return fromZonedTime(`${yyyy}-${mm}-${dd} 00:00:00`, zone);
|
|
}
|
|
|
|
export function atEndOfDayInZone(date: Date, timeZone: string = DEFAULT_TIMEZONE): Date {
|
|
const zone = resolveTimeZone(timeZone);
|
|
const zoned = toZonedTime(date, zone);
|
|
const yyyy = format(zoned, "yyyy");
|
|
const mm = format(zoned, "MM");
|
|
const dd = format(zoned, "dd");
|
|
return fromZonedTime(`${yyyy}-${mm}-${dd} 23:59:59`, zone);
|
|
}
|
|
|
|
export function combineDateAndTime(
|
|
date: Date,
|
|
hhmm: string,
|
|
timeZone: string = DEFAULT_TIMEZONE
|
|
): Date {
|
|
const zone = resolveTimeZone(timeZone);
|
|
const zoned = toZonedTime(date, zone);
|
|
const yyyy = format(zoned, "yyyy");
|
|
const mm = format(zoned, "MM");
|
|
const dd = format(zoned, "dd");
|
|
return fromZonedTime(`${yyyy}-${mm}-${dd} ${hhmm}:00`, zone);
|
|
}
|
|
|
|
function parseIsoDateToUtcNoon(value: string) {
|
|
const match = /^(\d{4})-(\d{2})-(\d{2})$/.exec(value);
|
|
if (!match) return null;
|
|
|
|
const year = Number(match[1]);
|
|
const month = Number(match[2]);
|
|
const day = Number(match[3]);
|
|
|
|
if (
|
|
!Number.isInteger(year) ||
|
|
!Number.isInteger(month) ||
|
|
!Number.isInteger(day)
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
const date = new Date(Date.UTC(year, month - 1, day, 12, 0, 0));
|
|
if (
|
|
date.getUTCFullYear() !== year ||
|
|
date.getUTCMonth() !== month - 1 ||
|
|
date.getUTCDate() !== day
|
|
) {
|
|
return null;
|
|
}
|
|
|
|
return date;
|
|
}
|
|
|
|
export function isoDateRangeInclusive(startDateIso: string, endDateIso: string) {
|
|
const start = parseIsoDateToUtcNoon(startDateIso);
|
|
const end = parseIsoDateToUtcNoon(endDateIso);
|
|
if (!start || !end) return [] as string[];
|
|
|
|
const from = start <= end ? start : end;
|
|
const to = start <= end ? end : start;
|
|
|
|
const values: string[] = [];
|
|
for (let ts = from.getTime(); ts <= to.getTime(); ts += 24 * 60 * 60 * 1000) {
|
|
values.push(new Date(ts).toISOString().slice(0, 10));
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
export function minutesBetween(start: Date, end: Date): number {
|
|
return Math.round((end.getTime() - start.getTime()) / 60000);
|
|
}
|