192 lines
5.9 KiB
TypeScript
192 lines
5.9 KiB
TypeScript
/**
|
|
* Time formatting utilities for baby sleep tracker
|
|
*/
|
|
|
|
/**
|
|
* Convert minutes after a reference time to clock time string
|
|
* @param minutes - minutes after reference time (can be negative)
|
|
* @param reference - reference time as HH:MM string
|
|
* @returns formatted time string like "07:15"
|
|
*/
|
|
export function formatMinutesAfter(minutes: number | null, reference: string = "04:00"): string {
|
|
if (minutes === null) return "N/A";
|
|
|
|
const [refHours, refMinutes] = reference.split(":").map(Number);
|
|
const totalRefMinutes = refHours * 60 + refMinutes;
|
|
const targetMinutes = totalRefMinutes + minutes;
|
|
// Handle wrap around midnight
|
|
const normalizedMinutes = ((targetMinutes % 1440) + 1440) % 1440;
|
|
const hours = Math.floor(normalizedMinutes / 60);
|
|
const mins = normalizedMinutes % 60;
|
|
|
|
return `${String(hours).padStart(2, "0")}:${String(mins).padStart(2, "0")}`;
|
|
}
|
|
|
|
/**
|
|
* Adjust minutes by the local timezone offset
|
|
* @param utcMinutes - minutes from UTC midnight
|
|
* @returns adjusted minutes for local time
|
|
*/
|
|
export function adjustForTimezone(utcMinutes: number): number {
|
|
const offsetInMinutes = new Date().getTimezoneOffset();
|
|
return utcMinutes - offsetInMinutes;
|
|
}
|
|
|
|
/**
|
|
* Convert a Date to datetime-local input value format (local time)
|
|
* @param date - Date object
|
|
* @returns string in "YYYY-MM-DDTHH:MM" format
|
|
*/
|
|
export function toLocalDatetimeInputValue(date: Date): string {
|
|
const offset = date.getTimezoneOffset() * 60 * 1000;
|
|
const localDate = new Date(date.getTime() - offset);
|
|
return localDate.toISOString().slice(0, 16);
|
|
}
|
|
|
|
export function formatElapsedTime(utcTimestamp: string): string {
|
|
let isoString = utcTimestamp;
|
|
if (utcTimestamp.includes(' ')) {
|
|
isoString = utcTimestamp.replace(' ', 'T');
|
|
}
|
|
if (!isoString.endsWith('Z')) {
|
|
isoString += 'Z';
|
|
}
|
|
const then = new Date(isoString);
|
|
const now = new Date();
|
|
const diffMs = now.getTime() - then.getTime();
|
|
const diffMins = Math.floor(diffMs / 60000);
|
|
const hours = Math.floor(diffMins / 60);
|
|
const mins = diffMins % 60;
|
|
|
|
if (hours > 0) return `${hours}h ${mins}m`;
|
|
return `${mins}m`;
|
|
}
|
|
|
|
/**
|
|
* Format a Date or ISO string to a human-readable datetime string (YYYY-MM-DD HH:mm)
|
|
* @param date - Date object or ISO string
|
|
* @returns formatted datetime string
|
|
*/
|
|
export function formatDateTime(date: Date | string): string {
|
|
let dateToParse = date;
|
|
if (typeof date === 'string' && !date.endsWith('Z') && !date.includes('+')) {
|
|
dateToParse = date + 'Z';
|
|
}
|
|
const d = typeof dateToParse === 'string' ? new Date(dateToParse) : dateToParse;
|
|
return d.toLocaleString('sv-SE', {
|
|
year: 'numeric',
|
|
month: '2-digit',
|
|
day: '2-digit',
|
|
hour: '2-digit',
|
|
minute: '2-digit',
|
|
hour12: false
|
|
}).replace(',', ' ');
|
|
}
|
|
|
|
/**
|
|
* Convert a datetime-local input value to an ISO UTC string
|
|
* @param inputValue - datetime-local string (YYYY-MM-DDTHH:mm)
|
|
* @returns ISO UTC string
|
|
*/
|
|
export function toUTCFromLocalInput(inputValue: string): string {
|
|
const d = new Date(inputValue);
|
|
return d.toISOString();
|
|
}
|
|
|
|
/**
|
|
* Calculate duration in minutes between two dates or ISO strings
|
|
* @param start - Start date object or ISO string
|
|
* @param end - End date object or ISO string
|
|
* @returns duration in minutes
|
|
*/
|
|
export function getDurationMinutes(start: Date | string, end: Date | string): number {
|
|
const startDate = typeof start === 'string' ? new Date(start) : start;
|
|
const endDate = typeof end === 'string' ? new Date(end) : end;
|
|
return Math.round((endDate.getTime() - startDate.getTime()) / 60000);
|
|
}
|
|
|
|
/**
|
|
* Clamp a date to day boundaries (start or end of day)
|
|
* @param date - Date to clamp
|
|
* @param bound - 'start' for 00:00, 'end' for 23:59:59.999
|
|
* @returns new Date clamped to day boundary
|
|
*/
|
|
export function clampToDay(date: Date, bound: "start" | "end"): Date {
|
|
const result = new Date(date);
|
|
if (bound === "start") {
|
|
result.setHours(0, 0, 0, 0);
|
|
} else {
|
|
result.setHours(23, 59, 59, 999);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Parse HH:MM time string to Date on a given day
|
|
* @param timeStr - HH:MM format
|
|
* @param baseDate - base date (defaults to today)
|
|
* @returns Date object
|
|
* @throws Error if timeStr is invalid
|
|
*/
|
|
export function parseTime(timeStr: string, baseDate: Date = new Date()): Date {
|
|
const [hours, minutes] = timeStr.split(":").map(Number);
|
|
const result = new Date(baseDate);
|
|
result.setHours(hours, minutes, 0, 0);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Get date string for N days ago
|
|
* @param days - number of days ago
|
|
* @returns ISO date string (YYYY-MM-DD)
|
|
*/
|
|
export function getDaysAgo(days: number): string {
|
|
const date = new Date();
|
|
date.setDate(date.getDate() - days);
|
|
return date.toISOString().split("T")[0];
|
|
}
|
|
|
|
/**
|
|
* Get date string for today
|
|
* @returns ISO date string (YYYY-MM-DD)
|
|
*/
|
|
export function getToday(): string {
|
|
return new Date().toISOString().split("T")[0];
|
|
}
|
|
|
|
/**
|
|
* Convert HH:MM time string to total minutes from start of day
|
|
* @param timeStr - HH:MM format
|
|
* @returns total minutes
|
|
*/
|
|
export function timeToMinutes(timeStr: string): number {
|
|
const [hours, minutes] = timeStr.split(":").map(Number);
|
|
return hours * 60 + minutes;
|
|
}
|
|
|
|
/**
|
|
* Get the offset from a reference time in local minutes
|
|
* @param utcMinutesFromMidnight - minutes from UTC midnight
|
|
* @param referenceHHMM - reference time as HH:MM string
|
|
* @returns local offset from reference in minutes
|
|
*/
|
|
export function getLocalOffsetFromReference(utcMinutesFromMidnight: number, referenceHHMM: string): number {
|
|
const localMinsFromMidnight = adjustForTimezone(utcMinutesFromMidnight);
|
|
return localMinsFromMidnight - timeToMinutes(referenceHHMM);
|
|
}
|
|
|
|
/**
|
|
* Convert total minutes to a human-readable string (e.g., "1h 30m" or "45m")
|
|
* @param totalMinutes - Total number of minutes
|
|
* @returns Formatted string
|
|
*/
|
|
export function formatMinutes(totalMinutes: number): string {
|
|
const hours = Math.floor(totalMinutes / 60);
|
|
const mins = totalMinutes % 60;
|
|
|
|
if (hours > 0) {
|
|
return `${hours}h ${mins}m`;
|
|
}
|
|
return `${mins}m`;
|
|
}
|