Enhances date handling and formatting

Improves date validation and adds flexible date/time formatting capabilities.

The date validation is updated to return a boolean and is incorporated directly into calling functions to throw errors, improving code readability and maintainability. DateService is extended with functions for formatting time in 12-hour format, getting day names, and formatting date ranges with customizable options.
This commit is contained in:
Janus C. H. Knudsen 2025-10-03 19:48:04 +02:00
parent 4fea01c76b
commit 4859f42450
3 changed files with 465 additions and 25 deletions

View file

@ -25,13 +25,10 @@ export class DateCalculator {
/**
* Validate that a date is valid
* @param date - Date to validate
* @param methodName - Name of calling method for error messages
* @throws Error if date is invalid
* @returns True if date is valid, false otherwise
*/
private static validateDate(date: Date, methodName: string): void {
if (!date || !(date instanceof Date) || !DateCalculator.dateService.isValid(date)) {
throw new Error(`${methodName}: Invalid date provided - ${date}`);
}
private static validateDate(date: Date): boolean {
return date && date instanceof Date && DateCalculator.dateService.isValid(date);
}
/**
@ -40,7 +37,9 @@ export class DateCalculator {
* @returns Array of dates for the configured work days
*/
static getWorkWeekDates(weekStart: Date): Date[] {
DateCalculator.validateDate(weekStart, 'getWorkWeekDates');
if (!DateCalculator.validateDate(weekStart)) {
throw new Error('getWorkWeekDates: Invalid date provided');
}
const dates: Date[] = [];
const workWeekSettings = DateCalculator.config.getWorkWeekSettings();
@ -66,7 +65,9 @@ export class DateCalculator {
* @returns The Monday of the ISO week
*/
static getISOWeekStart(date: Date): Date {
DateCalculator.validateDate(date, 'getISOWeekStart');
if (!DateCalculator.validateDate(date)) {
throw new Error('getISOWeekStart: Invalid date provided');
}
const weekBounds = DateCalculator.dateService.getWeekBounds(date);
return DateCalculator.dateService.startOfDay(weekBounds.start);
@ -78,7 +79,9 @@ export class DateCalculator {
* @returns The end date of the ISO week (Sunday)
*/
static getWeekEnd(date: Date): Date {
DateCalculator.validateDate(date, 'getWeekEnd');
if (!DateCalculator.validateDate(date)) {
throw new Error('getWeekEnd: Invalid date provided');
}
const weekBounds = DateCalculator.dateService.getWeekBounds(date);
return DateCalculator.dateService.endOfDay(weekBounds.end);
@ -137,9 +140,12 @@ export class DateCalculator {
/**
* Format a date to ISO date string (YYYY-MM-DD) using DateService
* @param date - Date to format
* @returns ISO date string
* @returns ISO date string or empty string if invalid
*/
static formatISODate(date: Date): string {
if (!DateCalculator.validateDate(date)) {
return '';
}
return DateCalculator.dateService.formatDate(date);
}

View file

@ -3,24 +3,25 @@
* Handles all date operations, timezone conversions, and formatting
*/
import {
format,
parse,
addMinutes,
differenceInMinutes,
startOfDay,
import {
format,
parse,
addMinutes,
differenceInMinutes,
startOfDay,
endOfDay,
setHours,
setMinutes as setMins,
getHours,
getMinutes,
setHours,
setMinutes as setMins,
getHours,
getMinutes,
parseISO,
isValid,
addDays,
startOfWeek,
endOfWeek,
isValid,
addDays,
startOfWeek,
endOfWeek,
addWeeks,
isSameDay
isSameDay,
getISOWeek
} from 'date-fns';
import {
toZonedTime,
@ -109,6 +110,70 @@ export class DateService {
return this.formatDate(date);
}
/**
* Format time in 12-hour format with AM/PM
* @param date - Date to format
* @returns Time string in 12-hour format (e.g., "2:30 PM")
*/
public formatTime12(date: Date): string {
const hours = getHours(date);
const minutes = getMinutes(date);
const period = hours >= 12 ? 'PM' : 'AM';
const displayHours = hours % 12 || 12;
return `${displayHours}:${String(minutes).padStart(2, '0')} ${period}`;
}
/**
* Get day name for a date
* @param date - Date to get day name for
* @param format - 'short' (e.g., 'Mon') or 'long' (e.g., 'Monday')
* @returns Day name
*/
public getDayName(date: Date, format: 'short' | 'long' = 'short'): string {
const formatter = new Intl.DateTimeFormat('en-US', {
weekday: format
});
return formatter.format(date);
}
/**
* Format a date range with customizable options
* @param start - Start date
* @param end - End date
* @param options - Formatting options
* @returns Formatted date range string
*/
public formatDateRange(
start: Date,
end: Date,
options: {
locale?: string;
month?: 'numeric' | '2-digit' | 'long' | 'short' | 'narrow';
day?: 'numeric' | '2-digit';
year?: 'numeric' | '2-digit';
} = {}
): string {
const { locale = 'en-US', month = 'short', day = 'numeric' } = options;
const startYear = start.getFullYear();
const endYear = end.getFullYear();
const formatter = new Intl.DateTimeFormat(locale, {
month,
day,
year: startYear !== endYear ? 'numeric' : undefined
});
// @ts-ignore - formatRange is available in modern browsers
if (typeof formatter.formatRange === 'function') {
// @ts-ignore
return formatter.formatRange(start, end);
}
return `${formatter.format(start)} - ${formatter.format(end)}`;
}
// ============================================
// TIME CALCULATIONS
// ============================================
@ -193,6 +258,53 @@ export class DateService {
return addWeeks(date, weeks);
}
/**
* Get ISO week number (1-53)
* @param date - Date to get week number for
* @returns ISO week number
*/
public getWeekNumber(date: Date): number {
return getISOWeek(date);
}
/**
* Get all dates in a full week (7 days starting from given date)
* @param weekStart - Start date of the week
* @returns Array of 7 dates
*/
public getFullWeekDates(weekStart: Date): Date[] {
const dates: Date[] = [];
for (let i = 0; i < 7; i++) {
dates.push(this.addDays(weekStart, i));
}
return dates;
}
/**
* Get dates for work week using ISO 8601 day numbering (Monday=1, Sunday=7)
* @param weekStart - Any date in the week
* @param workDays - Array of ISO day numbers (1=Monday, 7=Sunday)
* @returns Array of dates for the specified work days
*/
public getWorkWeekDates(weekStart: Date, workDays: number[]): Date[] {
const dates: Date[] = [];
// Get Monday of the week
const weekBounds = this.getWeekBounds(weekStart);
const mondayOfWeek = this.startOfDay(weekBounds.start);
// Calculate dates for each work day using ISO numbering
workDays.forEach(isoDay => {
const date = new Date(mondayOfWeek);
// ISO day 1=Monday is +0 days, ISO day 7=Sunday is +6 days
const daysFromMonday = isoDay === 7 ? 6 : isoDay - 1;
date.setDate(mondayOfWeek.getDate() + daysFromMonday);
dates.push(date);
});
return dates;
}
// ============================================
// GRID HELPERS
// ============================================
@ -290,4 +402,16 @@ export class DateService {
public isValid(date: Date): boolean {
return isValid(date);
}
/**
* Check if event spans multiple days
* @param start - Start date or ISO string
* @param end - End date or ISO string
* @returns True if spans multiple days
*/
public isMultiDay(start: Date | string, end: Date | string): boolean {
const startDate = typeof start === 'string' ? this.parseISO(start) : start;
const endDate = typeof end === 'string' ? this.parseISO(end) : end;
return !this.isSameDay(startDate, endDate);
}
}