This change refactors the DateCalculator class to be a static class. This removes the need to instantiate DateCalculator in multiple managers and renderers, simplifying dependency management and ensuring consistent date calculations across the application. The configuration is now initialized once at the application start.
308 lines
No EOL
9.2 KiB
TypeScript
308 lines
No EOL
9.2 KiB
TypeScript
/**
|
|
* DateCalculator - Centralized date calculation logic for calendar
|
|
* Handles all date computations with proper week start handling
|
|
*/
|
|
|
|
import { CalendarConfig } from '../core/CalendarConfig';
|
|
|
|
export class DateCalculator {
|
|
private static config: CalendarConfig;
|
|
|
|
/**
|
|
* Initialize DateCalculator with configuration
|
|
* @param config - Calendar configuration
|
|
*/
|
|
static initialize(config: CalendarConfig): void {
|
|
DateCalculator.config = config;
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
private static validateDate(date: Date, methodName: string): void {
|
|
if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
|
|
throw new Error(`${methodName}: Invalid date provided - ${date}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get dates for work week using ISO 8601 day numbering (Monday=1, Sunday=7)
|
|
* @param weekStart - Any date in the week
|
|
* @returns Array of dates for the configured work days
|
|
*/
|
|
static getWorkWeekDates(weekStart: Date): Date[] {
|
|
DateCalculator.validateDate(weekStart, 'getWorkWeekDates');
|
|
|
|
const dates: Date[] = [];
|
|
const workWeekSettings = DateCalculator.config.getWorkWeekSettings();
|
|
|
|
// Always use ISO week start (Monday)
|
|
const mondayOfWeek = DateCalculator.getISOWeekStart(weekStart);
|
|
|
|
// Calculate dates for each work day using ISO numbering
|
|
workWeekSettings.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;
|
|
}
|
|
|
|
/**
|
|
* Get the start of the ISO week (Monday) for a given date
|
|
* @param date - Any date in the week
|
|
* @returns The Monday of the ISO week
|
|
*/
|
|
static getISOWeekStart(date: Date): Date {
|
|
DateCalculator.validateDate(date, 'getISOWeekStart');
|
|
|
|
const monday = new Date(date);
|
|
const currentDay = monday.getDay();
|
|
const daysToSubtract = currentDay === 0 ? 6 : currentDay - 1;
|
|
monday.setDate(monday.getDate() - daysToSubtract);
|
|
monday.setHours(0, 0, 0, 0);
|
|
return monday;
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the end of the ISO week for a given date
|
|
* @param date - Any date in the week
|
|
* @returns The end date of the ISO week (Sunday)
|
|
*/
|
|
static getWeekEnd(date: Date): Date {
|
|
DateCalculator.validateDate(date, 'getWeekEnd');
|
|
|
|
const weekStart = DateCalculator.getISOWeekStart(date);
|
|
const weekEnd = new Date(weekStart);
|
|
weekEnd.setDate(weekStart.getDate() + 6);
|
|
weekEnd.setHours(23, 59, 59, 999);
|
|
return weekEnd;
|
|
}
|
|
|
|
/**
|
|
* Get week number for a date (ISO 8601)
|
|
* @param date - The date to get week number for
|
|
* @returns Week number (1-53)
|
|
*/
|
|
static getWeekNumber(date: Date): number {
|
|
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
|
|
const dayNum = d.getUTCDay() || 7;
|
|
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
|
|
const yearStart = new Date(Date.UTC(d.getUTCFullYear(),0,1));
|
|
return Math.ceil((((d.getTime() - yearStart.getTime()) / 86400000) + 1)/7);
|
|
}
|
|
|
|
/**
|
|
* Format a date range with customizable options
|
|
* @param start - Start date
|
|
* @param end - End date
|
|
* @param options - Formatting options
|
|
* @returns Formatted date range string
|
|
*/
|
|
static 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
|
|
if (typeof formatter.formatRange === 'function') {
|
|
// @ts-ignore
|
|
return formatter.formatRange(start, end);
|
|
}
|
|
|
|
return `${formatter.format(start)} - ${formatter.format(end)}`;
|
|
}
|
|
|
|
/**
|
|
* Format a date to ISO date string (YYYY-MM-DD)
|
|
* @param date - Date to format
|
|
* @returns ISO date string
|
|
*/
|
|
static formatISODate(date: Date): string {
|
|
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
|
|
}
|
|
|
|
/**
|
|
* Check if a date is today
|
|
* @param date - Date to check
|
|
* @returns True if the date is today
|
|
*/
|
|
static isToday(date: Date): boolean {
|
|
const today = new Date();
|
|
return date.toDateString() === today.toDateString();
|
|
}
|
|
|
|
/**
|
|
* Add days to a date
|
|
* @param date - Base date
|
|
* @param days - Number of days to add (can be negative)
|
|
* @returns New date
|
|
*/
|
|
static addDays(date: Date, days: number): Date {
|
|
const result = new Date(date);
|
|
result.setDate(result.getDate() + days);
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Add weeks to a date
|
|
* @param date - Base date
|
|
* @param weeks - Number of weeks to add (can be negative)
|
|
* @returns New date
|
|
*/
|
|
static addWeeks(date: Date, weeks: number): Date {
|
|
return DateCalculator.addDays(date, weeks * 7);
|
|
}
|
|
|
|
/**
|
|
* Get all dates in a week
|
|
* @param weekStart - Start of the week
|
|
* @returns Array of 7 dates for the full week
|
|
*/
|
|
static getFullWeekDates(weekStart: Date): Date[] {
|
|
const dates: Date[] = [];
|
|
for (let i = 0; i < 7; i++) {
|
|
dates.push(DateCalculator.addDays(weekStart, i));
|
|
}
|
|
return dates;
|
|
}
|
|
|
|
/**
|
|
* Get the day name for a date using Intl.DateTimeFormat
|
|
* @param date - Date to get day name for
|
|
* @param format - 'short' or 'long'
|
|
* @returns Day name
|
|
*/
|
|
static getDayName(date: Date, format: 'short' | 'long' = 'short'): string {
|
|
const formatter = new Intl.DateTimeFormat('en-US', {
|
|
weekday: format
|
|
});
|
|
return formatter.format(date);
|
|
}
|
|
|
|
/**
|
|
* Format time to HH:MM
|
|
* @param date - Date to format
|
|
* @returns Time string
|
|
*/
|
|
static formatTime(date: Date): string {
|
|
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
|
|
}
|
|
|
|
/**
|
|
* Format time to 12-hour format
|
|
* @param date - Date to format
|
|
* @returns 12-hour time string
|
|
*/
|
|
static formatTime12(date: Date): string {
|
|
const hours = date.getHours();
|
|
const minutes = date.getMinutes();
|
|
const period = hours >= 12 ? 'PM' : 'AM';
|
|
const displayHours = hours % 12 || 12;
|
|
|
|
return `${displayHours}:${String(minutes).padStart(2, '0')} ${period}`;
|
|
}
|
|
|
|
/**
|
|
* Convert minutes since midnight to time string
|
|
* @param minutes - Minutes since midnight
|
|
* @returns Time string
|
|
*/
|
|
static minutesToTime(minutes: number): string {
|
|
const hours = Math.floor(minutes / 60);
|
|
const mins = minutes % 60;
|
|
const period = hours >= 12 ? 'PM' : 'AM';
|
|
const displayHours = hours % 12 || 12;
|
|
|
|
return `${displayHours}:${String(mins).padStart(2, '0')} ${period}`;
|
|
}
|
|
|
|
/**
|
|
* Convert time string to minutes since midnight
|
|
* @param timeStr - Time string
|
|
* @returns Minutes since midnight
|
|
*/
|
|
static timeToMinutes(timeStr: string): number {
|
|
const [time] = timeStr.split('T').pop()!.split('.');
|
|
const [hours, minutes] = time.split(':').map(Number);
|
|
return hours * 60 + minutes;
|
|
}
|
|
|
|
/**
|
|
* Get minutes since start of day
|
|
* @param date - Date or ISO string
|
|
* @returns Minutes since midnight
|
|
*/
|
|
static getMinutesSinceMidnight(date: Date | string): number {
|
|
const d = typeof date === 'string' ? new Date(date) : date;
|
|
return d.getHours() * 60 + d.getMinutes();
|
|
}
|
|
|
|
/**
|
|
* Calculate duration in minutes between two dates
|
|
* @param start - Start date or ISO string
|
|
* @param end - End date or ISO string
|
|
* @returns Duration in minutes
|
|
*/
|
|
static 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.floor((endDate.getTime() - startDate.getTime()) / 60000);
|
|
}
|
|
|
|
/**
|
|
* Check if two dates are on the same day
|
|
* @param date1 - First date
|
|
* @param date2 - Second date
|
|
* @returns True if same day
|
|
*/
|
|
static isSameDay(date1: Date, date2: Date): boolean {
|
|
return date1.toDateString() === date2.toDateString();
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
static isMultiDay(start: Date | string, end: Date | string): boolean {
|
|
const startDate = typeof start === 'string' ? new Date(start) : start;
|
|
const endDate = typeof end === 'string' ? new Date(end) : end;
|
|
return !DateCalculator.isSameDay(startDate, endDate);
|
|
}
|
|
|
|
// Legacy constructor for backward compatibility
|
|
constructor() {
|
|
// Empty constructor - all methods are now static
|
|
}
|
|
}
|
|
|
|
// Legacy factory function - deprecated, use static methods instead
|
|
export function createDateCalculator(config: CalendarConfig): DateCalculator {
|
|
DateCalculator.initialize(config);
|
|
return new DateCalculator();
|
|
} |