Refactors DateCalculator to be a static class

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.
This commit is contained in:
Janus Knudsen 2025-09-03 18:38:52 +02:00
parent 0da875a224
commit b8b44ddae8
11 changed files with 107 additions and 83 deletions

View file

@ -3,6 +3,7 @@ import { eventBus } from './core/EventBus.js';
import { calendarConfig } from './core/CalendarConfig.js'; import { calendarConfig } from './core/CalendarConfig.js';
import { CalendarTypeFactory } from './factories/CalendarTypeFactory.js'; import { CalendarTypeFactory } from './factories/CalendarTypeFactory.js';
import { ManagerFactory } from './factories/ManagerFactory.js'; import { ManagerFactory } from './factories/ManagerFactory.js';
import { DateCalculator } from './utils/DateCalculator.js';
/** /**
* Initialize the calendar application with simple direct calls * Initialize the calendar application with simple direct calls
@ -13,6 +14,9 @@ async function initializeCalendar(): Promise<void> {
// Use the singleton calendar configuration // Use the singleton calendar configuration
const config = calendarConfig; const config = calendarConfig;
// Initialize DateCalculator with config first
DateCalculator.initialize(config);
// Initialize the CalendarTypeFactory before creating managers // Initialize the CalendarTypeFactory before creating managers
CalendarTypeFactory.initialize(); CalendarTypeFactory.initialize();

View file

@ -41,7 +41,8 @@ export class CalendarManager {
this.eventRenderer = eventRenderer; this.eventRenderer = eventRenderer;
this.scrollManager = scrollManager; this.scrollManager = scrollManager;
this.eventFilterManager = new EventFilterManager(); this.eventFilterManager = new EventFilterManager();
this.dateCalculator = new DateCalculator(config); DateCalculator.initialize(config);
this.dateCalculator = new DateCalculator();
this.setupEventListeners(); this.setupEventListeners();
} }
@ -434,10 +435,10 @@ export class CalendarManager {
const lastDate = new Date(lastDateStr); const lastDate = new Date(lastDateStr);
// Calculate week number from first date // Calculate week number from first date
const weekNumber = this.dateCalculator.getWeekNumber(firstDate); const weekNumber = DateCalculator.getWeekNumber(firstDate);
// Format date range // Format date range
const dateRange = this.dateCalculator.formatDateRange(firstDate, lastDate); const dateRange = DateCalculator.formatDateRange(firstDate, lastDate);
// Emit week info update // Emit week info update
this.eventBus.emit(CoreEvents.WEEK_CHANGED, { this.eventBus.emit(CoreEvents.WEEK_CHANGED, {

View file

@ -23,9 +23,10 @@ export class NavigationManager {
constructor(eventBus: IEventBus, eventRenderer: EventRenderingService) { constructor(eventBus: IEventBus, eventRenderer: EventRenderingService) {
this.eventBus = eventBus; this.eventBus = eventBus;
this.dateCalculator = new DateCalculator(calendarConfig); DateCalculator.initialize(calendarConfig);
this.dateCalculator = new DateCalculator();
this.navigationRenderer = new NavigationRenderer(eventBus, calendarConfig, eventRenderer); this.navigationRenderer = new NavigationRenderer(eventBus, calendarConfig, eventRenderer);
this.currentWeek = this.dateCalculator.getISOWeekStart(new Date()); this.currentWeek = DateCalculator.getISOWeekStart(new Date());
this.targetWeek = new Date(this.currentWeek); this.targetWeek = new Date(this.currentWeek);
this.init(); this.init();
} }
@ -135,7 +136,7 @@ export class NavigationManager {
private navigateToToday(): void { private navigateToToday(): void {
const today = new Date(); const today = new Date();
const todayWeekStart = this.dateCalculator.getISOWeekStart(today); const todayWeekStart = DateCalculator.getISOWeekStart(today);
// Reset to today // Reset to today
this.targetWeek = new Date(todayWeekStart); this.targetWeek = new Date(todayWeekStart);
@ -153,7 +154,7 @@ export class NavigationManager {
} }
private navigateToDate(date: Date): void { private navigateToDate(date: Date): void {
const weekStart = this.dateCalculator.getISOWeekStart(date); const weekStart = DateCalculator.getISOWeekStart(date);
this.targetWeek = new Date(weekStart); this.targetWeek = new Date(weekStart);
const currentTime = this.currentWeek.getTime(); const currentTime = this.currentWeek.getTime();
@ -247,9 +248,9 @@ export class NavigationManager {
} }
private updateWeekInfo(): void { private updateWeekInfo(): void {
const weekNumber = this.dateCalculator.getWeekNumber(this.currentWeek); const weekNumber = DateCalculator.getWeekNumber(this.currentWeek);
const weekEnd = this.dateCalculator.addDays(this.currentWeek, 6); const weekEnd = DateCalculator.addDays(this.currentWeek, 6);
const dateRange = this.dateCalculator.formatDateRange(this.currentWeek, weekEnd); const dateRange = DateCalculator.formatDateRange(this.currentWeek, weekEnd);
// Notify other managers about week info update - DOM manipulation should happen via events // Notify other managers about week info update - DOM manipulation should happen via events
this.eventBus.emit(CoreEvents.WEEK_CHANGED, { this.eventBus.emit(CoreEvents.WEEK_CHANGED, {

View file

@ -39,7 +39,8 @@ export class WorkHoursManager {
constructor(config: CalendarConfig) { constructor(config: CalendarConfig) {
this.config = config; this.config = config;
this.dateCalculator = new DateCalculator(config); DateCalculator.initialize(config);
this.dateCalculator = new DateCalculator();
// Default work schedule - will be loaded from JSON later // Default work schedule - will be loaded from JSON later
this.workSchedule = { this.workSchedule = {
@ -64,7 +65,7 @@ export class WorkHoursManager {
* Get work hours for a specific date * Get work hours for a specific date
*/ */
getWorkHoursForDate(date: Date): DayWorkHours | 'off' { getWorkHoursForDate(date: Date): DayWorkHours | 'off' {
const dateString = this.dateCalculator.formatISODate(date); const dateString = DateCalculator.formatISODate(date);
// Check for date-specific override first // Check for date-specific override first
if (this.workSchedule.dateOverrides[dateString]) { if (this.workSchedule.dateOverrides[dateString]) {
@ -83,7 +84,7 @@ export class WorkHoursManager {
const workHoursMap = new Map<string, DayWorkHours | 'off'>(); const workHoursMap = new Map<string, DayWorkHours | 'off'>();
dates.forEach(date => { dates.forEach(date => {
const dateString = this.dateCalculator.formatISODate(date); const dateString = DateCalculator.formatISODate(date);
const workHours = this.getWorkHoursForDate(date); const workHours = this.getWorkHoursForDate(date);
workHoursMap.set(dateString, workHours); workHoursMap.set(dateString, workHours);
}); });

View file

@ -32,17 +32,18 @@ export class DateColumnRenderer implements ColumnRenderer {
const { currentWeek, config } = context; const { currentWeek, config } = context;
// Initialize date calculator and work hours manager // Initialize date calculator and work hours manager
this.dateCalculator = new DateCalculator(config); DateCalculator.initialize(config);
this.dateCalculator = new DateCalculator();
this.workHoursManager = new WorkHoursManager(config); this.workHoursManager = new WorkHoursManager(config);
const dates = this.dateCalculator.getWorkWeekDates(currentWeek); const dates = DateCalculator.getWorkWeekDates(currentWeek);
const dateSettings = config.getDateViewSettings(); const dateSettings = config.getDateViewSettings();
const daysToShow = dates.slice(0, dateSettings.weekDays); const daysToShow = dates.slice(0, dateSettings.weekDays);
daysToShow.forEach((date) => { daysToShow.forEach((date) => {
const column = document.createElement('swp-day-column'); const column = document.createElement('swp-day-column');
(column as any).dataset.date = this.dateCalculator.formatISODate(date); (column as any).dataset.date = DateCalculator.formatISODate(date);
// Apply work hours styling // Apply work hours styling
this.applyWorkHoursToColumn(column, date); this.applyWorkHoursToColumn(column, date);

View file

@ -28,7 +28,10 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
constructor(config: CalendarConfig, dateCalculator?: DateCalculator) { constructor(config: CalendarConfig, dateCalculator?: DateCalculator) {
this.config = config; this.config = config;
this.dateCalculator = dateCalculator || new DateCalculator(config); if (!dateCalculator) {
DateCalculator.initialize(config);
}
this.dateCalculator = dateCalculator || new DateCalculator();
} }
/** /**
@ -628,7 +631,7 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
private calculateEventGridSpan(event: CalendarEvent, dateToColumnMap: Map<string, number>): { startColumn: number, columnSpan: number } { private calculateEventGridSpan(event: CalendarEvent, dateToColumnMap: Map<string, number>): { startColumn: number, columnSpan: number } {
const startDate = new Date(event.start); const startDate = new Date(event.start);
const endDate = new Date(event.end); const endDate = new Date(event.end);
const startDateKey = this.dateCalculator.formatISODate(startDate); const startDateKey = DateCalculator.formatISODate(startDate);
const startColumn = dateToColumnMap.get(startDateKey); const startColumn = dateToColumnMap.get(startDateKey);
if (!startColumn) { if (!startColumn) {
@ -641,7 +644,7 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
while (currentDate <= endDate) { while (currentDate <= endDate) {
currentDate.setDate(currentDate.getDate() + 1); currentDate.setDate(currentDate.getDate() + 1);
const dateKey = this.dateCalculator.formatISODate(currentDate); const dateKey = DateCalculator.formatISODate(currentDate);
const col = dateToColumnMap.get(dateKey); const col = dateToColumnMap.get(dateKey);
if (col) { if (col) {
endColumn = col; endColumn = col;
@ -696,7 +699,7 @@ export class DateEventRenderer extends BaseEventRenderer {
const columnEvents = events.filter(event => { const columnEvents = events.filter(event => {
const eventDate = new Date(event.start); const eventDate = new Date(event.start);
const eventDateStr = this.dateCalculator.formatISODate(eventDate); const eventDateStr = DateCalculator.formatISODate(eventDate);
const matches = eventDateStr === columnDate; const matches = eventDateStr === columnDate;

View file

@ -256,25 +256,26 @@ export class DateHeaderRenderer extends BaseHeaderRenderer {
const { currentWeek, config } = context; const { currentWeek, config } = context;
// Initialize date calculator with config // Initialize date calculator with config
this.dateCalculator = new DateCalculator(config); DateCalculator.initialize(config);
this.dateCalculator = new DateCalculator();
const dates = this.dateCalculator.getWorkWeekDates(currentWeek); const dates = DateCalculator.getWorkWeekDates(currentWeek);
const weekDays = config.getDateViewSettings().weekDays; const weekDays = config.getDateViewSettings().weekDays;
const daysToShow = dates.slice(0, weekDays); const daysToShow = dates.slice(0, weekDays);
daysToShow.forEach((date, index) => { daysToShow.forEach((date, index) => {
const header = document.createElement('swp-day-header'); const header = document.createElement('swp-day-header');
if (this.dateCalculator.isToday(date)) { if (DateCalculator.isToday(date)) {
(header as any).dataset.today = 'true'; (header as any).dataset.today = 'true';
} }
const dayName = this.dateCalculator.getDayName(date, 'short'); const dayName = DateCalculator.getDayName(date, 'short');
header.innerHTML = ` header.innerHTML = `
<swp-day-name>${dayName}</swp-day-name> <swp-day-name>${dayName}</swp-day-name>
<swp-day-date>${date.getDate()}</swp-day-date> <swp-day-date>${date.getDate()}</swp-day-date>
`; `;
(header as any).dataset.date = this.dateCalculator.formatISODate(date); (header as any).dataset.date = DateCalculator.formatISODate(date);
calendarHeader.appendChild(header); calendarHeader.appendChild(header);
}); });

View file

@ -24,7 +24,8 @@ export class NavigationRenderer {
this.eventBus = eventBus; this.eventBus = eventBus;
this.config = config; this.config = config;
this.eventRenderer = eventRenderer; this.eventRenderer = eventRenderer;
this.dateCalculator = new DateCalculator(config); DateCalculator.initialize(config);
this.dateCalculator = new DateCalculator();
this.setupEventListeners(); this.setupEventListeners();
} }
@ -127,7 +128,7 @@ export class NavigationRenderer {
* Render a complete container with content and events * Render a complete container with content and events
*/ */
public renderContainer(parentContainer: HTMLElement, weekStart: Date): HTMLElement { public renderContainer(parentContainer: HTMLElement, weekStart: Date): HTMLElement {
const weekEnd = this.dateCalculator.addDays(weekStart, 6); const weekEnd = DateCalculator.addDays(weekStart, 6);
// Create new grid container // Create new grid container
@ -179,22 +180,22 @@ export class NavigationRenderer {
dayColumns.innerHTML = ''; dayColumns.innerHTML = '';
// Get dates using DateCalculator // Get dates using DateCalculator
const dates = this.dateCalculator.getWorkWeekDates(weekStart); const dates = DateCalculator.getWorkWeekDates(weekStart);
// Render headers for target week // Render headers for target week
dates.forEach((date, i) => { dates.forEach((date, i) => {
const headerElement = document.createElement('swp-day-header'); const headerElement = document.createElement('swp-day-header');
if (this.dateCalculator.isToday(date)) { if (DateCalculator.isToday(date)) {
headerElement.dataset.today = 'true'; headerElement.dataset.today = 'true';
} }
const dayName = this.dateCalculator.getDayName(date, 'short'); const dayName = DateCalculator.getDayName(date, 'short');
headerElement.innerHTML = ` headerElement.innerHTML = `
<swp-day-name>${dayName}</swp-day-name> <swp-day-name>${dayName}</swp-day-name>
<swp-day-date>${date.getDate()}</swp-day-date> <swp-day-date>${date.getDate()}</swp-day-date>
`; `;
headerElement.dataset.date = this.dateCalculator.formatISODate(date); headerElement.dataset.date = DateCalculator.formatISODate(date);
header.appendChild(headerElement); header.appendChild(headerElement);
}); });
@ -209,7 +210,7 @@ export class NavigationRenderer {
// Render day columns for target week // Render day columns for target week
dates.forEach(date => { dates.forEach(date => {
const column = document.createElement('swp-day-column'); const column = document.createElement('swp-day-column');
column.dataset.date = this.dateCalculator.formatISODate(date); column.dataset.date = DateCalculator.formatISODate(date);
const eventsLayer = document.createElement('swp-events-layer'); const eventsLayer = document.createElement('swp-events-layer');
column.appendChild(eventsLayer); column.appendChild(eventsLayer);

View file

@ -11,7 +11,8 @@ export class MonthViewStrategy implements ViewStrategy {
private dateCalculator: DateCalculator; private dateCalculator: DateCalculator;
constructor() { constructor() {
this.dateCalculator = new DateCalculator(calendarConfig); DateCalculator.initialize(calendarConfig);
this.dateCalculator = new DateCalculator();
} }
getLayoutConfig(): ViewLayoutConfig { getLayoutConfig(): ViewLayoutConfig {
@ -72,7 +73,7 @@ export class MonthViewStrategy implements ViewStrategy {
dates.forEach(date => { dates.forEach(date => {
const cell = document.createElement('div'); const cell = document.createElement('div');
cell.className = 'month-day-cell'; cell.className = 'month-day-cell';
cell.dataset.date = this.dateCalculator.formatISODate(date); cell.dataset.date = DateCalculator.formatISODate(date);
cell.style.border = '1px solid #e0e0e0'; cell.style.border = '1px solid #e0e0e0';
cell.style.minHeight = '100px'; cell.style.minHeight = '100px';
cell.style.padding = '4px'; cell.style.padding = '4px';
@ -86,7 +87,7 @@ export class MonthViewStrategy implements ViewStrategy {
dayNumber.style.marginBottom = '4px'; dayNumber.style.marginBottom = '4px';
// Check if today // Check if today
if (this.dateCalculator.isToday(date)) { if (DateCalculator.isToday(date)) {
dayNumber.style.color = '#1976d2'; dayNumber.style.color = '#1976d2';
cell.style.backgroundColor = '#f5f5f5'; cell.style.backgroundColor = '#f5f5f5';
} }
@ -101,12 +102,12 @@ export class MonthViewStrategy implements ViewStrategy {
const firstOfMonth = new Date(monthDate.getFullYear(), monthDate.getMonth(), 1); const firstOfMonth = new Date(monthDate.getFullYear(), monthDate.getMonth(), 1);
// Get Monday of the week containing first day // Get Monday of the week containing first day
const startDate = this.dateCalculator.getISOWeekStart(firstOfMonth); const startDate = DateCalculator.getISOWeekStart(firstOfMonth);
// Generate 42 days (6 weeks) // Generate 42 days (6 weeks)
const dates: Date[] = []; const dates: Date[] = [];
for (let i = 0; i < 42; i++) { for (let i = 0; i < 42; i++) {
dates.push(this.dateCalculator.addDays(startDate, i)); dates.push(DateCalculator.addDays(startDate, i));
} }
return dates; return dates;
@ -141,10 +142,10 @@ export class MonthViewStrategy implements ViewStrategy {
const firstOfMonth = new Date(baseDate.getFullYear(), baseDate.getMonth(), 1); const firstOfMonth = new Date(baseDate.getFullYear(), baseDate.getMonth(), 1);
// Get Monday of the week containing first day // Get Monday of the week containing first day
const startDate = this.dateCalculator.getISOWeekStart(firstOfMonth); const startDate = DateCalculator.getISOWeekStart(firstOfMonth);
// End date is 41 days after start (42 total days) // End date is 41 days after start (42 total days)
const endDate = this.dateCalculator.addDays(startDate, 41); const endDate = DateCalculator.addDays(startDate, 41);
return { return {
startDate, startDate,

View file

@ -15,7 +15,8 @@ export class WeekViewStrategy implements ViewStrategy {
private styleManager: GridStyleManager; private styleManager: GridStyleManager;
constructor() { constructor() {
this.dateCalculator = new DateCalculator(calendarConfig); DateCalculator.initialize(calendarConfig);
this.dateCalculator = new DateCalculator();
this.gridRenderer = new GridRenderer(calendarConfig); this.gridRenderer = new GridRenderer(calendarConfig);
this.styleManager = new GridStyleManager(calendarConfig); this.styleManager = new GridStyleManager(calendarConfig);
} }
@ -42,28 +43,28 @@ export class WeekViewStrategy implements ViewStrategy {
} }
getNextPeriod(currentDate: Date): Date { getNextPeriod(currentDate: Date): Date {
return this.dateCalculator.addWeeks(currentDate, 1); return DateCalculator.addWeeks(currentDate, 1);
} }
getPreviousPeriod(currentDate: Date): Date { getPreviousPeriod(currentDate: Date): Date {
return this.dateCalculator.addWeeks(currentDate, -1); return DateCalculator.addWeeks(currentDate, -1);
} }
getPeriodLabel(date: Date): string { getPeriodLabel(date: Date): string {
const weekStart = this.dateCalculator.getISOWeekStart(date); const weekStart = DateCalculator.getISOWeekStart(date);
const weekEnd = this.dateCalculator.addDays(weekStart, 6); const weekEnd = DateCalculator.addDays(weekStart, 6);
const weekNumber = this.dateCalculator.getWeekNumber(date); const weekNumber = DateCalculator.getWeekNumber(date);
return `Week ${weekNumber}: ${this.dateCalculator.formatDateRange(weekStart, weekEnd)}`; return `Week ${weekNumber}: ${DateCalculator.formatDateRange(weekStart, weekEnd)}`;
} }
getDisplayDates(baseDate: Date): Date[] { getDisplayDates(baseDate: Date): Date[] {
return this.dateCalculator.getWorkWeekDates(baseDate); return DateCalculator.getWorkWeekDates(baseDate);
} }
getPeriodRange(baseDate: Date): { startDate: Date; endDate: Date } { getPeriodRange(baseDate: Date): { startDate: Date; endDate: Date } {
const weekStart = this.dateCalculator.getISOWeekStart(baseDate); const weekStart = DateCalculator.getISOWeekStart(baseDate);
const weekEnd = this.dateCalculator.addDays(weekStart, 6); const weekEnd = DateCalculator.addDays(weekStart, 6);
return { return {
startDate: weekStart, startDate: weekStart,

View file

@ -6,10 +6,14 @@
import { CalendarConfig } from '../core/CalendarConfig'; import { CalendarConfig } from '../core/CalendarConfig';
export class DateCalculator { export class DateCalculator {
private config: CalendarConfig; private static config: CalendarConfig;
constructor(config: CalendarConfig) { /**
this.config = config; * Initialize DateCalculator with configuration
* @param config - Calendar configuration
*/
static initialize(config: CalendarConfig): void {
DateCalculator.config = config;
} }
/** /**
@ -18,7 +22,7 @@ export class DateCalculator {
* @param methodName - Name of calling method for error messages * @param methodName - Name of calling method for error messages
* @throws Error if date is invalid * @throws Error if date is invalid
*/ */
private validateDate(date: Date, methodName: string): void { private static validateDate(date: Date, methodName: string): void {
if (!date || !(date instanceof Date) || isNaN(date.getTime())) { if (!date || !(date instanceof Date) || isNaN(date.getTime())) {
throw new Error(`${methodName}: Invalid date provided - ${date}`); throw new Error(`${methodName}: Invalid date provided - ${date}`);
} }
@ -29,14 +33,14 @@ export class DateCalculator {
* @param weekStart - Any date in the week * @param weekStart - Any date in the week
* @returns Array of dates for the configured work days * @returns Array of dates for the configured work days
*/ */
getWorkWeekDates(weekStart: Date): Date[] { static getWorkWeekDates(weekStart: Date): Date[] {
this.validateDate(weekStart, 'getWorkWeekDates'); DateCalculator.validateDate(weekStart, 'getWorkWeekDates');
const dates: Date[] = []; const dates: Date[] = [];
const workWeekSettings = this.config.getWorkWeekSettings(); const workWeekSettings = DateCalculator.config.getWorkWeekSettings();
// Always use ISO week start (Monday) // Always use ISO week start (Monday)
const mondayOfWeek = this.getISOWeekStart(weekStart); const mondayOfWeek = DateCalculator.getISOWeekStart(weekStart);
// Calculate dates for each work day using ISO numbering // Calculate dates for each work day using ISO numbering
workWeekSettings.workDays.forEach(isoDay => { workWeekSettings.workDays.forEach(isoDay => {
@ -55,8 +59,8 @@ export class DateCalculator {
* @param date - Any date in the week * @param date - Any date in the week
* @returns The Monday of the ISO week * @returns The Monday of the ISO week
*/ */
getISOWeekStart(date: Date): Date { static getISOWeekStart(date: Date): Date {
this.validateDate(date, 'getISOWeekStart'); DateCalculator.validateDate(date, 'getISOWeekStart');
const monday = new Date(date); const monday = new Date(date);
const currentDay = monday.getDay(); const currentDay = monday.getDay();
@ -72,10 +76,10 @@ export class DateCalculator {
* @param date - Any date in the week * @param date - Any date in the week
* @returns The end date of the ISO week (Sunday) * @returns The end date of the ISO week (Sunday)
*/ */
getWeekEnd(date: Date): Date { static getWeekEnd(date: Date): Date {
this.validateDate(date, 'getWeekEnd'); DateCalculator.validateDate(date, 'getWeekEnd');
const weekStart = this.getISOWeekStart(date); const weekStart = DateCalculator.getISOWeekStart(date);
const weekEnd = new Date(weekStart); const weekEnd = new Date(weekStart);
weekEnd.setDate(weekStart.getDate() + 6); weekEnd.setDate(weekStart.getDate() + 6);
weekEnd.setHours(23, 59, 59, 999); weekEnd.setHours(23, 59, 59, 999);
@ -87,7 +91,7 @@ export class DateCalculator {
* @param date - The date to get week number for * @param date - The date to get week number for
* @returns Week number (1-53) * @returns Week number (1-53)
*/ */
getWeekNumber(date: Date): number { static getWeekNumber(date: Date): number {
const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate())); const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
const dayNum = d.getUTCDay() || 7; const dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum); d.setUTCDate(d.getUTCDate() + 4 - dayNum);
@ -102,7 +106,7 @@ export class DateCalculator {
* @param options - Formatting options * @param options - Formatting options
* @returns Formatted date range string * @returns Formatted date range string
*/ */
formatDateRange( static formatDateRange(
start: Date, start: Date,
end: Date, end: Date,
options: { options: {
@ -137,7 +141,7 @@ export class DateCalculator {
* @param date - Date to format * @param date - Date to format
* @returns ISO date string * @returns ISO date string
*/ */
formatISODate(date: Date): string { static formatISODate(date: Date): string {
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`;
} }
@ -146,7 +150,7 @@ export class DateCalculator {
* @param date - Date to check * @param date - Date to check
* @returns True if the date is today * @returns True if the date is today
*/ */
isToday(date: Date): boolean { static isToday(date: Date): boolean {
const today = new Date(); const today = new Date();
return date.toDateString() === today.toDateString(); return date.toDateString() === today.toDateString();
} }
@ -157,7 +161,7 @@ export class DateCalculator {
* @param days - Number of days to add (can be negative) * @param days - Number of days to add (can be negative)
* @returns New date * @returns New date
*/ */
addDays(date: Date, days: number): Date { static addDays(date: Date, days: number): Date {
const result = new Date(date); const result = new Date(date);
result.setDate(result.getDate() + days); result.setDate(result.getDate() + days);
return result; return result;
@ -169,8 +173,8 @@ export class DateCalculator {
* @param weeks - Number of weeks to add (can be negative) * @param weeks - Number of weeks to add (can be negative)
* @returns New date * @returns New date
*/ */
addWeeks(date: Date, weeks: number): Date { static addWeeks(date: Date, weeks: number): Date {
return this.addDays(date, weeks * 7); return DateCalculator.addDays(date, weeks * 7);
} }
/** /**
@ -178,10 +182,10 @@ export class DateCalculator {
* @param weekStart - Start of the week * @param weekStart - Start of the week
* @returns Array of 7 dates for the full week * @returns Array of 7 dates for the full week
*/ */
getFullWeekDates(weekStart: Date): Date[] { static getFullWeekDates(weekStart: Date): Date[] {
const dates: Date[] = []; const dates: Date[] = [];
for (let i = 0; i < 7; i++) { for (let i = 0; i < 7; i++) {
dates.push(this.addDays(weekStart, i)); dates.push(DateCalculator.addDays(weekStart, i));
} }
return dates; return dates;
} }
@ -192,7 +196,7 @@ export class DateCalculator {
* @param format - 'short' or 'long' * @param format - 'short' or 'long'
* @returns Day name * @returns Day name
*/ */
getDayName(date: Date, format: 'short' | 'long' = 'short'): string { static getDayName(date: Date, format: 'short' | 'long' = 'short'): string {
const formatter = new Intl.DateTimeFormat('en-US', { const formatter = new Intl.DateTimeFormat('en-US', {
weekday: format weekday: format
}); });
@ -204,7 +208,7 @@ export class DateCalculator {
* @param date - Date to format * @param date - Date to format
* @returns Time string * @returns Time string
*/ */
formatTime(date: Date): string { static formatTime(date: Date): string {
return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`; return `${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
} }
@ -213,7 +217,7 @@ export class DateCalculator {
* @param date - Date to format * @param date - Date to format
* @returns 12-hour time string * @returns 12-hour time string
*/ */
formatTime12(date: Date): string { static formatTime12(date: Date): string {
const hours = date.getHours(); const hours = date.getHours();
const minutes = date.getMinutes(); const minutes = date.getMinutes();
const period = hours >= 12 ? 'PM' : 'AM'; const period = hours >= 12 ? 'PM' : 'AM';
@ -227,7 +231,7 @@ export class DateCalculator {
* @param minutes - Minutes since midnight * @param minutes - Minutes since midnight
* @returns Time string * @returns Time string
*/ */
minutesToTime(minutes: number): string { static minutesToTime(minutes: number): string {
const hours = Math.floor(minutes / 60); const hours = Math.floor(minutes / 60);
const mins = minutes % 60; const mins = minutes % 60;
const period = hours >= 12 ? 'PM' : 'AM'; const period = hours >= 12 ? 'PM' : 'AM';
@ -241,7 +245,7 @@ export class DateCalculator {
* @param timeStr - Time string * @param timeStr - Time string
* @returns Minutes since midnight * @returns Minutes since midnight
*/ */
timeToMinutes(timeStr: string): number { static timeToMinutes(timeStr: string): number {
const [time] = timeStr.split('T').pop()!.split('.'); const [time] = timeStr.split('T').pop()!.split('.');
const [hours, minutes] = time.split(':').map(Number); const [hours, minutes] = time.split(':').map(Number);
return hours * 60 + minutes; return hours * 60 + minutes;
@ -252,7 +256,7 @@ export class DateCalculator {
* @param date - Date or ISO string * @param date - Date or ISO string
* @returns Minutes since midnight * @returns Minutes since midnight
*/ */
getMinutesSinceMidnight(date: Date | string): number { static getMinutesSinceMidnight(date: Date | string): number {
const d = typeof date === 'string' ? new Date(date) : date; const d = typeof date === 'string' ? new Date(date) : date;
return d.getHours() * 60 + d.getMinutes(); return d.getHours() * 60 + d.getMinutes();
} }
@ -263,7 +267,7 @@ export class DateCalculator {
* @param end - End date or ISO string * @param end - End date or ISO string
* @returns Duration in minutes * @returns Duration in minutes
*/ */
getDurationMinutes(start: Date | string, end: Date | string): number { static getDurationMinutes(start: Date | string, end: Date | string): number {
const startDate = typeof start === 'string' ? new Date(start) : start; const startDate = typeof start === 'string' ? new Date(start) : start;
const endDate = typeof end === 'string' ? new Date(end) : end; const endDate = typeof end === 'string' ? new Date(end) : end;
return Math.floor((endDate.getTime() - startDate.getTime()) / 60000); return Math.floor((endDate.getTime() - startDate.getTime()) / 60000);
@ -275,7 +279,7 @@ export class DateCalculator {
* @param date2 - Second date * @param date2 - Second date
* @returns True if same day * @returns True if same day
*/ */
isSameDay(date1: Date, date2: Date): boolean { static isSameDay(date1: Date, date2: Date): boolean {
return date1.toDateString() === date2.toDateString(); return date1.toDateString() === date2.toDateString();
} }
@ -285,15 +289,20 @@ export class DateCalculator {
* @param end - End date or ISO string * @param end - End date or ISO string
* @returns True if spans multiple days * @returns True if spans multiple days
*/ */
isMultiDay(start: Date | string, end: Date | string): boolean { static isMultiDay(start: Date | string, end: Date | string): boolean {
const startDate = typeof start === 'string' ? new Date(start) : start; const startDate = typeof start === 'string' ? new Date(start) : start;
const endDate = typeof end === 'string' ? new Date(end) : end; const endDate = typeof end === 'string' ? new Date(end) : end;
return !this.isSameDay(startDate, endDate); return !DateCalculator.isSameDay(startDate, endDate);
} }
// Legacy constructor for backward compatibility
constructor() {
// Empty constructor - all methods are now static
}
} }
// Create singleton instance with config // Legacy factory function - deprecated, use static methods instead
export function createDateCalculator(config: CalendarConfig): DateCalculator { export function createDateCalculator(config: CalendarConfig): DateCalculator {
return new DateCalculator(config); DateCalculator.initialize(config);
return new DateCalculator();
} }