refactor: extract shared formatting utilities (duration, date, age)
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* Shared formatting utilities for consistent display across the app.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert minutes to a human-readable duration string.
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* 45 → "45m"
|
||||||
|
* 60 → "1h"
|
||||||
|
* 90 → "1h 30m"
|
||||||
|
* 120 → "2h"
|
||||||
|
* 150 → "2h 30m"
|
||||||
|
*/
|
||||||
|
export function formatDuration(minutes: number): string {
|
||||||
|
const hours = Math.floor(minutes / 60);
|
||||||
|
const mins = minutes % 60;
|
||||||
|
if (hours === 0) return `${mins}m`;
|
||||||
|
if (mins === 0) return `${hours}h`;
|
||||||
|
return `${hours}h ${mins}m`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a date/time to "Weekday, Month Day at HH:MM AM/PM".
|
||||||
|
*
|
||||||
|
* Examples:
|
||||||
|
* "2026-05-29T10:00:00Z" → "Friday, May 29 at 10:00 AM"
|
||||||
|
* "2026-06-05T14:30:00Z" → "Friday, Jun 5 at 2:30 PM"
|
||||||
|
*/
|
||||||
|
export function formatDateTime(date: Date | string): string {
|
||||||
|
const d = typeof date === 'string' ? new Date(date) : date;
|
||||||
|
const dateStr = d.toLocaleDateString('en-US', {
|
||||||
|
weekday: 'long',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
const timeStr = d.toLocaleTimeString('en-US', {
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: true
|
||||||
|
});
|
||||||
|
return `${dateStr} at ${timeStr}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a date value to "Weekday, Month Day" only (no time).
|
||||||
|
*
|
||||||
|
* Example: "2026-05-29" → "Friday, May 29"
|
||||||
|
*/
|
||||||
|
export function formatDate(date: Date | string): string {
|
||||||
|
const d = typeof date === 'string' ? new Date(date) : date;
|
||||||
|
return d.toLocaleDateString('en-US', {
|
||||||
|
weekday: 'long',
|
||||||
|
month: 'short',
|
||||||
|
day: 'numeric'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format a time value to "HH:MM AM/PM".
|
||||||
|
*
|
||||||
|
* Example: new Date("2026-05-29T14:30:00Z") → "2:30 PM"
|
||||||
|
*/
|
||||||
|
export function formatTime(date: Date | string): string {
|
||||||
|
const d = typeof date === 'string' ? new Date(date) : date;
|
||||||
|
return d.toLocaleTimeString('en-US', {
|
||||||
|
hour: 'numeric',
|
||||||
|
minute: '2-digit',
|
||||||
|
hour12: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate age from a date-of-birth string.
|
||||||
|
* Returns the number of full years, or null if DOB is invalid.
|
||||||
|
*/
|
||||||
|
export function calculateAge(dateOfBirth: string | undefined | null): number | null {
|
||||||
|
if (!dateOfBirth) return null;
|
||||||
|
const dob = new Date(dateOfBirth);
|
||||||
|
if (isNaN(dob.getTime())) return null;
|
||||||
|
const today = new Date();
|
||||||
|
let age = today.getFullYear() - dob.getFullYear();
|
||||||
|
const monthDiff = today.getMonth() - dob.getMonth();
|
||||||
|
if (monthDiff < 0 || (monthDiff === 0 && today.getDate() < dob.getDate())) {
|
||||||
|
age--;
|
||||||
|
}
|
||||||
|
return age;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user