Files
mdm-ordinis/ordinis-admin-ui/src/lib/dates.ts
T

100 lines
4.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Helpers для работы с датами в формах. Все функции делают TZ-aware преобразования
* чтобы избежать day-shift на 1 при парсинге/форматировании в timezones отличных
* от UTC.
*/
/** Парсит ISO-строку в Date. Pure-date "YYYY-MM-DD" → local midnight, иначе new Date(s). */
export const parseFormDate = (s: unknown): Date | null => {
if (typeof s !== 'string' || !s) return null
const pureDate = /^(\d{4})-(\d{2})-(\d{2})$/.exec(s)
const d = pureDate
? new Date(Number(pureDate[1]), Number(pureDate[2]) - 1, Number(pureDate[3]))
: new Date(s)
if (Number.isNaN(d.getTime())) return null
if (d.getFullYear() >= 9999) return null
return d
}
/** Возвращает дату как "YYYY-MM-DD" в локальном TZ. */
export const formatIsoDate = (d: Date): string => {
const pad = (n: number) => String(n).padStart(2, '0')
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`
}
/** Локальный TZ offset в формате ±HH:mm для ISO строк. */
export const localTzOffset = (d: Date): string => {
const pad = (n: number) => String(n).padStart(2, '0')
const total = -d.getTimezoneOffset()
const sign = total >= 0 ? '+' : '-'
const abs = Math.abs(total)
return `${sign}${pad(Math.floor(abs / 60))}:${pad(abs % 60)}`
}
/** ISO-8601 datetime в local TZ: "2026-05-08T23:59:00+03:00". */
export const formatIsoDateTime = (d: Date, time: string = '00:00'): string => {
const pad = (n: number) => String(n).padStart(2, '0')
const [hh, mm] = time.split(':').map(Number)
const local = new Date(d.getFullYear(), d.getMonth(), d.getDate(), hh || 0, mm || 0, 0)
return `${formatIsoDate(local)}T${pad(local.getHours())}:${pad(local.getMinutes())}:00${localTzOffset(local)}`
}
/** Извлекает HH:mm из ISO datetime в local TZ. */
export const extractTime = (s: string | undefined, fallback: string): string => {
if (!s) return fallback
const d = new Date(s)
if (Number.isNaN(d.getTime())) return fallback
const pad = (n: number) => String(n).padStart(2, '0')
return `${pad(d.getHours())}:${pad(d.getMinutes())}`
}
/** Собирает Date + HH:mm в local-TZ ISO datetime. null Date → пустая строка. */
export const combineDateTime = (
d: Date | null,
time: string,
_current: string,
): string => {
if (!d) return ''
const safeTime = /^\d{2}:\d{2}$/.test(time) ? time : '00:00'
return formatIsoDateTime(d, safeTime)
}
/** "Сейчас" как local-TZ ISO datetime. Используется как default validFrom при Edit. */
export const nowIsoLocal = (): string => {
const d = new Date()
const pad = (n: number) => String(n).padStart(2, '0')
return `${formatIsoDate(d)}T${pad(d.getHours())}:${pad(d.getMinutes())}:00${localTzOffset(d)}`
}
/**
* Форматирует ISO datetime в формат для `<input type="datetime-local">`:
* "YYYY-MM-DDTHH:mm" в local TZ (без offset).
*
* <p>Use case: time-travel picker. Раньше использовался
* {@code new Date(s).toISOString().slice(0,16)} что отдаёт UTC — у пользователя в
* +03:00 picker показывал значение на 3 часа раньше выбранного, а на onChange
* value трактовался как local → time drift на каждое редактирование.
*/
export const toDateTimeLocalInput = (s: string | undefined): string => {
if (!s) return ''
const d = new Date(s)
if (Number.isNaN(d.getTime())) return ''
const pad = (n: number) => String(n).padStart(2, '0')
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}T${pad(d.getHours())}:${pad(d.getMinutes())}`
}
/**
* Парсит value из `<input type="datetime-local">` ("YYYY-MM-DDTHH:mm") в local-TZ
* ISO datetime со смещением: "2026-05-08T14:30:00+03:00".
*
* <p>Pair с {@link toDateTimeLocalInput} — round-trip preserves wall clock time.
*/
export const fromDateTimeLocalInput = (v: string): string => {
if (!v) return ''
const m = /^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2})/.exec(v)
if (!m) return ''
const [, y, mo, day, hh, mm] = m
const d = new Date(Number(y), Number(mo) - 1, Number(day), Number(hh), Number(mm), 0)
return `${formatIsoDate(d)}T${hh}:${mm}:00${localTzOffset(d)}`
}