Calendar/src/utils/DateCalculator.ts
Janus Knudsen efc1742dad Refactors date calculations to use ISO week
Adopts ISO week date calculation (Monday-based) for consistency
and accuracy across calendar components. This change ensures
standardized week determination, resolving potential discrepancies
in week-based operations.
2025-08-19 23:04:56 +02:00

193 lines
No EOL
5.7 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 config: CalendarConfig;
constructor(config: CalendarConfig) {
this.config = config;
}
/**
* Get dates for work week based on ISO week (starts Monday)
* @param weekStart - Any date in the week
* @returns Array of dates for the configured work days
*/
getWorkWeekDates(weekStart: Date): Date[] {
const dates: Date[] = [];
const workWeekSettings = this.config.getWorkWeekSettings();
// Get Monday of the ISO week
const monday = this.getISOWeekStart(weekStart);
// Calculate dates for each work day
workWeekSettings.workDays.forEach(dayOfWeek => {
const date = new Date(monday);
if (dayOfWeek === 0) {
// Sunday is 6 days after Monday
date.setDate(monday.getDate() + 6);
} else {
// Monday=1 becomes 0 days after, Tuesday=2 becomes 1 day after, etc.
date.setDate(monday.getDate() + dayOfWeek - 1);
}
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
*/
getISOWeekStart(date: Date): Date {
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 start of the week for a given date (legacy method)
* @param date - Any date in the week
* @param firstDayOfWeek - 0 for Sunday, 1 for Monday
* @returns The start date of the week
*/
getWeekStart(date: Date, firstDayOfWeek: number = 1): Date {
return this.getISOWeekStart(date);
}
/**
* Get the end of the week for a given date
* @param date - Any date in the week
* @param firstDayOfWeek - 0 for Sunday, 1 for Monday
* @returns The end date of the week
*/
getWeekEnd(date: Date, firstDayOfWeek: number = 1): Date {
const weekStart = this.getWeekStart(date, firstDayOfWeek);
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)
*/
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 for display
* @param start - Start date
* @param end - End date
* @returns Formatted date range string
*/
formatDateRange(start: Date, end: Date): string {
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
const startMonth = months[start.getMonth()];
const endMonth = months[end.getMonth()];
const startDay = start.getDate();
const endDay = end.getDate();
const startYear = start.getFullYear();
const endYear = end.getFullYear();
if (startYear !== endYear) {
return `${startMonth} ${startDay}, ${startYear} - ${endMonth} ${endDay}, ${endYear}`;
} else if (startMonth === endMonth) {
return `${startMonth} ${startDay} - ${endDay}, ${startYear}`;
} else {
return `${startMonth} ${startDay} - ${endMonth} ${endDay}, ${startYear}`;
}
}
/**
* Format a date to ISO date string (YYYY-MM-DD)
* @param date - Date to format
* @returns ISO date string
*/
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
*/
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
*/
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
*/
addWeeks(date: Date, weeks: number): Date {
return this.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
*/
getFullWeekDates(weekStart: Date): Date[] {
const dates: Date[] = [];
for (let i = 0; i < 7; i++) {
dates.push(this.addDays(weekStart, i));
}
return dates;
}
/**
* Get the day name for a date
* @param date - Date to get day name for
* @param format - 'short' or 'long'
* @returns Day name
*/
getDayName(date: Date, format: 'short' | 'long' = 'short'): string {
const days = {
short: ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
long: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']
};
return days[format][date.getDay()];
}
}
// Create singleton instance with config
export function createDateCalculator(config: CalendarConfig): DateCalculator {
return new DateCalculator(config);
}