Refactors date handling with DateService

Replaces DateCalculator with DateService for improved date and time operations, including timezone handling.

This change enhances the calendar's accuracy and flexibility in managing dates, especially concerning timezone configurations.
It also corrects a typo in the `allDay` dataset attribute.
This commit is contained in:
Janus C. H. Knudsen 2025-10-03 20:50:40 +02:00
parent 4859f42450
commit 6bbf2d8adb
17 changed files with 159 additions and 749 deletions

View file

@ -1,300 +0,0 @@
/**
* DateCalculator - Centralized date calculation logic for calendar
* Now uses DateService internally for all date operations
* Handles all date computations with proper week start handling
*/
import { CalendarConfig } from '../core/CalendarConfig';
import { DateService } from './DateService';
export class DateCalculator {
private static config: CalendarConfig;
private static dateService: DateService = new DateService('Europe/Copenhagen');
/**
* Initialize DateCalculator with configuration
* @param config - Calendar configuration
*/
static initialize(config: CalendarConfig): void {
DateCalculator.config = config;
// Update DateService with timezone from config if available
const timezone = config.getTimezone?.() || 'Europe/Copenhagen';
DateCalculator.dateService = new DateService(timezone);
}
/**
* Validate that a date is valid
* @param date - Date to validate
* @returns True if date is valid, false otherwise
*/
private static validateDate(date: Date): boolean {
return date && date instanceof Date && DateCalculator.dateService.isValid(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[] {
if (!DateCalculator.validateDate(weekStart)) {
throw new Error('getWorkWeekDates: Invalid date provided');
}
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 using DateService
* @param date - Any date in the week
* @returns The Monday of the ISO week
*/
static getISOWeekStart(date: Date): Date {
if (!DateCalculator.validateDate(date)) {
throw new Error('getISOWeekStart: Invalid date provided');
}
const weekBounds = DateCalculator.dateService.getWeekBounds(date);
return DateCalculator.dateService.startOfDay(weekBounds.start);
}
/**
* Get the end of the ISO week for a given date using DateService
* @param date - Any date in the week
* @returns The end date of the ISO week (Sunday)
*/
static getWeekEnd(date: Date): Date {
if (!DateCalculator.validateDate(date)) {
throw new Error('getWeekEnd: Invalid date provided');
}
const weekBounds = DateCalculator.dateService.getWeekBounds(date);
return DateCalculator.dateService.endOfDay(weekBounds.end);
}
/**
* 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) using DateService
* @param date - Date to format
* @returns ISO date string or empty string if invalid
*/
static formatISODate(date: Date): string {
if (!DateCalculator.validateDate(date)) {
return '';
}
return DateCalculator.dateService.formatDate(date);
}
/**
* Check if a date is today using DateService
* @param date - Date to check
* @returns True if the date is today
*/
static isToday(date: Date): boolean {
return DateCalculator.dateService.isSameDay(date, new Date());
}
/**
* Add days to a date using DateService
* @param date - Base date
* @param days - Number of days to add (can be negative)
* @returns New date
*/
static addDays(date: Date, days: number): Date {
return DateCalculator.dateService.addDays(date, days);
}
/**
* Add weeks to a date using DateService
* @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.dateService.addWeeks(date, weeks);
}
/**
* 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 using DateService
* @param date - Date to format
* @returns Time string
*/
static formatTime(date: Date): string {
return DateCalculator.dateService.formatTime(date);
}
/**
* 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 using DateService
* @param minutes - Minutes since midnight
* @returns Time string
*/
static minutesToTime(minutes: number): string {
return DateCalculator.dateService.minutesToTime(minutes);
}
/**
* Convert time string to minutes since midnight using DateService
* @param timeStr - Time string
* @returns Minutes since midnight
*/
static timeToMinutes(timeStr: string): number {
return DateCalculator.dateService.timeToMinutes(timeStr);
}
/**
* Get minutes since start of day using DateService
* @param date - Date or ISO string
* @returns Minutes since midnight
*/
static getMinutesSinceMidnight(date: Date | string): number {
const d = typeof date === 'string' ? DateCalculator.dateService.parseISO(date) : date;
return DateCalculator.dateService.getMinutesSinceMidnight(d);
}
/**
* Calculate duration in minutes between two dates using DateService
* @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 {
return DateCalculator.dateService.getDurationMinutes(start, end);
}
/**
* Check if two dates are on the same day using DateService
* @param date1 - First date
* @param date2 - Second date
* @returns True if same day
*/
static isSameDay(date1: Date, date2: Date): boolean {
return DateCalculator.dateService.isSameDay(date1, date2);
}
/**
* 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' ? DateCalculator.dateService.parseISO(start) : start;
const endDate = typeof end === 'string' ? DateCalculator.dateService.parseISO(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();
}

View file

@ -1,15 +1,17 @@
import { calendarConfig } from '../core/CalendarConfig';
import { ColumnBounds } from './ColumnDetectionUtils';
import { DateCalculator } from './DateCalculator';
import { DateService } from './DateService';
import { TimeFormatter } from './TimeFormatter';
/**
* PositionUtils - Static positioning utilities using singleton calendarConfig
* Focuses on pixel/position calculations while delegating date operations
*
* Note: Uses DateCalculator and TimeFormatter which internally use DateService with date-fns
* Note: Uses DateService with date-fns for all date/time operations
*/
export class PositionUtils {
private static dateService = new DateService('Europe/Copenhagen');
/**
* Convert minutes to pixels
*/
@ -29,10 +31,10 @@ export class PositionUtils {
}
/**
* Convert time (HH:MM) to pixels from day start using DateCalculator
* Convert time (HH:MM) to pixels from day start using DateService
*/
public static timeToPixels(timeString: string): number {
const totalMinutes = DateCalculator.timeToMinutes(timeString);
const totalMinutes = PositionUtils.dateService.timeToMinutes(timeString);
const gridSettings = calendarConfig.getGridSettings();
const dayStartMinutes = gridSettings.dayStartHour * 60;
const minutesFromDayStart = totalMinutes - dayStartMinutes;
@ -41,10 +43,10 @@ export class PositionUtils {
}
/**
* Convert Date object to pixels from day start using DateCalculator
* Convert Date object to pixels from day start using DateService
*/
public static dateToPixels(date: Date): number {
const totalMinutes = DateCalculator.getMinutesSinceMidnight(date);
const totalMinutes = PositionUtils.dateService.getMinutesSinceMidnight(date);
const gridSettings = calendarConfig.getGridSettings();
const dayStartMinutes = gridSettings.dayStartHour * 60;
const minutesFromDayStart = totalMinutes - dayStartMinutes;
@ -53,7 +55,7 @@ export class PositionUtils {
}
/**
* Convert pixels to time using DateCalculator
* Convert pixels to time using DateService
*/
public static pixelsToTime(pixels: number): string {
const minutes = PositionUtils.pixelsToMinutes(pixels);
@ -61,7 +63,7 @@ export class PositionUtils {
const dayStartMinutes = gridSettings.dayStartHour * 60;
const totalMinutes = dayStartMinutes + minutes;
return DateCalculator.minutesToTime(totalMinutes);
return PositionUtils.dateService.minutesToTime(totalMinutes);
}
/**
@ -109,15 +111,15 @@ export class PositionUtils {
}
/**
* Snap time to interval using DateCalculator
* Snap time to interval using DateService
*/
public static snapTimeToInterval(timeString: string): string {
const totalMinutes = DateCalculator.timeToMinutes(timeString);
const totalMinutes = PositionUtils.dateService.timeToMinutes(timeString);
const gridSettings = calendarConfig.getGridSettings();
const snapInterval = gridSettings.snapInterval;
const snappedMinutes = Math.round(totalMinutes / snapInterval) * snapInterval;
return DateCalculator.minutesToTime(snappedMinutes);
return PositionUtils.dateService.minutesToTime(snappedMinutes);
}
/**
@ -220,10 +222,10 @@ export class PositionUtils {
}
/**
* Convert time string to ISO datetime using DateCalculator
* Convert time string to ISO datetime using DateService
*/
public static timeStringToIso(timeString: string, date: Date = new Date()): string {
const totalMinutes = DateCalculator.timeToMinutes(timeString);
const totalMinutes = PositionUtils.dateService.timeToMinutes(timeString);
const hours = Math.floor(totalMinutes / 60);
const minutes = totalMinutes % 60;
@ -234,10 +236,10 @@ export class PositionUtils {
}
/**
* Calculate event duration using DateCalculator
* Calculate event duration using DateService
*/
public static calculateDuration(startTime: string | Date, endTime: string | Date): number {
return DateCalculator.getDurationMinutes(startTime, endTime);
return PositionUtils.dateService.getDurationMinutes(startTime, endTime);
}
/**