| export const MINUTE_MS = 60 * 1000; |
| export const DAY_MS = 24 * 60 * MINUTE_MS; |
|
|
| const WEEKDAY_NAMES = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; |
| const MONTH_NAMES = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; |
|
|
| |
| export function compactDateTime(value) { |
| if (value == null) return ''; |
| return String(value) |
| .replace('T', ' ') |
| .replace(/(\d{2}:\d{2}):\d{2}$/, '$1'); |
| } |
|
|
| |
| export function parseDateTimeMs(value) { |
| if (value == null || value === '') return null; |
| if (typeof value === 'number' && Number.isFinite(value)) return value; |
|
|
| const normalized = String(value).trim().replace(' ', 'T'); |
| const match = normalized.match(/^(\d{4})-(\d{2})-(\d{2})(?:T(\d{2}):(\d{2})(?::(\d{2}))?)?/); |
| if (match) { |
| |
| return Date.UTC( |
| Number(match[1]), |
| Number(match[2]) - 1, |
| Number(match[3]), |
| Number(match[4] || 0), |
| Number(match[5] || 0), |
| Number(match[6] || 0), |
| ); |
| } |
|
|
| const parsed = Date.parse(normalized); |
| return Number.isFinite(parsed) ? parsed : null; |
| } |
|
|
| |
| export function normalizeShiftBounds(startMs, endMs) { |
| if (startMs == null && endMs == null) { |
| return { startMs: null, endMs: null }; |
| } |
| let resolvedStart = startMs; |
| let resolvedEnd = endMs; |
| if (resolvedStart == null) resolvedStart = resolvedEnd; |
| if (resolvedEnd == null || resolvedEnd <= resolvedStart) resolvedEnd = resolvedStart + MINUTE_MS; |
| return { startMs: resolvedStart, endMs: resolvedEnd }; |
| } |
|
|
| |
| export function wallTimeDayStartMs(ms) { |
| const date = new Date(ms); |
| return Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate()); |
| } |
|
|
| |
| export function formatAxisDayLabel(ms) { |
| const date = new Date(ms); |
| return `${WEEKDAY_NAMES[date.getUTCDay()]} ${date.getUTCDate()} ${MONTH_NAMES[date.getUTCMonth()]}`; |
| } |
|
|