refactor: move shared utilities to lib/shared/ sub-packages
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
123
frontend/src/lib/shared/utils/date.ts
Normal file
123
frontend/src/lib/shared/utils/date.ts
Normal file
@@ -0,0 +1,123 @@
|
||||
/**
|
||||
* Format an ISO date string (YYYY-MM-DD) for display.
|
||||
* Uses T12:00:00 to avoid UTC timezone off-by-one when converting to local time.
|
||||
* Defaults to 'long' (e.g. "24. Dezember 1943"); pass 'short' for DD.MM.YYYY.
|
||||
*/
|
||||
export function formatDate(isoDate: string, format: 'short' | 'long' = 'long'): string {
|
||||
const date = new Date(isoDate + 'T12:00:00');
|
||||
if (format === 'short') {
|
||||
return new Intl.DateTimeFormat('de-DE', {
|
||||
day: '2-digit',
|
||||
month: '2-digit',
|
||||
year: 'numeric'
|
||||
}).format(date);
|
||||
}
|
||||
return new Intl.DateTimeFormat('de-DE', {
|
||||
day: 'numeric',
|
||||
month: 'long',
|
||||
year: 'numeric'
|
||||
}).format(date);
|
||||
}
|
||||
|
||||
/**
|
||||
* Format an ISO date string for medium-length display (e.g. "15. Jun. 1920").
|
||||
* Uses T12:00:00 to avoid UTC timezone off-by-one.
|
||||
* Pass an explicit BCP 47 locale tag to respect the app locale; defaults to 'de-DE'.
|
||||
*/
|
||||
export function formatMCDate(isoDate: string, locale: string = 'de-DE'): string {
|
||||
return new Intl.DateTimeFormat(locale, {
|
||||
day: 'numeric',
|
||||
month: 'short',
|
||||
year: 'numeric'
|
||||
}).format(new Date(isoDate + 'T12:00:00'));
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an ISO date string (YYYY-MM-DD) to German display format (DD.MM.YYYY).
|
||||
* Returns an empty string for invalid or empty input.
|
||||
*/
|
||||
export function isoToGerman(iso: string): string {
|
||||
if (!iso || !/^\d{4}-\d{2}-\d{2}$/.test(iso)) return '';
|
||||
const [y, m, d] = iso.split('-');
|
||||
return `${d}.${m}.${y}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a German date string (DD.MM.YYYY) to ISO format (YYYY-MM-DD).
|
||||
* Returns an empty string for invalid or empty input.
|
||||
*/
|
||||
export function germanToIso(german: string): string {
|
||||
const match = german.match(/^(\d{2})\.(\d{2})\.(\d{4})$/);
|
||||
if (!match) return '';
|
||||
const [, d, m, y] = match;
|
||||
return `${y}-${m}-${d}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats a raw date string into German DD.MM.YYYY format.
|
||||
*
|
||||
* Handles two modes:
|
||||
* - Pure digit stream (no dots): auto-inserts dots after position 2 and 4
|
||||
* - Manual dot entry: preserves user-typed dots, pads single-digit day/month,
|
||||
* and overflows extra digits from day→month and month→year
|
||||
*/
|
||||
export function formatGermanDateInput(raw: string): string {
|
||||
if (!raw.includes('.')) {
|
||||
const digits = raw.replace(/\D/g, '').slice(0, 8);
|
||||
if (digits.length <= 2) return digits;
|
||||
if (digits.length <= 4) return `${digits.slice(0, 2)}.${digits.slice(2)}`;
|
||||
return `${digits.slice(0, 2)}.${digits.slice(2, 4)}.${digits.slice(4)}`;
|
||||
}
|
||||
|
||||
const trailingDot = raw.endsWith('.');
|
||||
const parts = raw.split('.').map((p) => p.replace(/\D/g, ''));
|
||||
|
||||
let day = parts[0] ?? '';
|
||||
let month = parts[1] ?? '';
|
||||
let year = parts[2] ?? '';
|
||||
|
||||
let dayOverflowed = false;
|
||||
if (day.length > 2) {
|
||||
month = day.slice(2) + month;
|
||||
day = day.slice(0, 2);
|
||||
dayOverflowed = true;
|
||||
}
|
||||
|
||||
let monthOverflowed = false;
|
||||
if (month.length > 2) {
|
||||
year = month.slice(2) + year;
|
||||
month = month.slice(0, 2);
|
||||
monthOverflowed = true;
|
||||
}
|
||||
|
||||
year = year.slice(0, 4);
|
||||
|
||||
const afterDay = !dayOverflowed && parts.length >= 2;
|
||||
|
||||
if (day.length === 1 && (month || (trailingDot && !dayOverflowed))) {
|
||||
day = '0' + day;
|
||||
}
|
||||
if (month.length === 1 && (year || (trailingDot && afterDay && !monthOverflowed))) {
|
||||
month = '0' + month;
|
||||
}
|
||||
|
||||
if (year) return `${day}.${month}.${year}`;
|
||||
if (month) {
|
||||
const dot2 = trailingDot && afterDay && !monthOverflowed ? '.' : '';
|
||||
return `${day}.${month}${dot2}`;
|
||||
}
|
||||
const dot1 = trailingDot && !dayOverflowed ? '.' : '';
|
||||
return `${day}${dot1}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles a date input event for German-format date fields (DD.MM.YYYY).
|
||||
* Strips non-digits, formats with dots, mutates the input's displayed value,
|
||||
* and returns the display string and its ISO equivalent.
|
||||
*/
|
||||
export function handleGermanDateInput(e: Event): { display: string; iso: string } {
|
||||
const input = e.target as HTMLInputElement;
|
||||
const display = formatGermanDateInput(input.value);
|
||||
input.value = display;
|
||||
return { display, iso: germanToIso(display) };
|
||||
}
|
||||
Reference in New Issue
Block a user