2025-08-18 23:42:03 +02:00
|
|
|
/**
|
|
|
|
|
* 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;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-19 23:04:56 +02:00
|
|
|
* Get dates for work week based on ISO week (starts Monday)
|
|
|
|
|
* @param weekStart - Any date in the week
|
2025-08-18 23:42:03 +02:00
|
|
|
* @returns Array of dates for the configured work days
|
|
|
|
|
*/
|
|
|
|
|
getWorkWeekDates(weekStart: Date): Date[] {
|
|
|
|
|
const dates: Date[] = [];
|
|
|
|
|
const workWeekSettings = this.config.getWorkWeekSettings();
|
|
|
|
|
|
2025-08-19 23:04:56 +02:00
|
|
|
// Get Monday of the ISO week
|
|
|
|
|
const monday = this.getISOWeekStart(weekStart);
|
2025-08-18 23:42:03 +02:00
|
|
|
|
|
|
|
|
// Calculate dates for each work day
|
|
|
|
|
workWeekSettings.workDays.forEach(dayOfWeek => {
|
2025-08-19 23:04:56 +02:00
|
|
|
const date = new Date(monday);
|
2025-08-18 23:42:03 +02:00
|
|
|
|
2025-08-19 23:04:56 +02:00
|
|
|
if (dayOfWeek === 0) {
|
|
|
|
|
// Sunday is 6 days after Monday
|
|
|
|
|
date.setDate(monday.getDate() + 6);
|
2025-08-18 23:42:03 +02:00
|
|
|
} else {
|
2025-08-19 23:04:56 +02:00
|
|
|
// Monday=1 becomes 0 days after, Tuesday=2 becomes 1 day after, etc.
|
|
|
|
|
date.setDate(monday.getDate() + dayOfWeek - 1);
|
2025-08-18 23:42:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
dates.push(date);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
return dates;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
2025-08-19 23:04:56 +02:00
|
|
|
* 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)
|
2025-08-18 23:42:03 +02:00
|
|
|
* @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 {
|
2025-08-19 23:04:56 +02:00
|
|
|
return this.getISOWeekStart(date);
|
2025-08-18 23:42:03 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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);
|
|
|
|
|
}
|