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

View file

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

View file

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

View file

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

View file

@ -32,17 +32,18 @@ export class DateColumnRenderer implements ColumnRenderer {
const { currentWeek, config } = context;
// Initialize date calculator and work hours manager
this.dateCalculator = new DateCalculator(config);
DateCalculator.initialize(config);
this.dateCalculator = new DateCalculator();
this.workHoursManager = new WorkHoursManager(config);
const dates = this.dateCalculator.getWorkWeekDates(currentWeek);
const dates = DateCalculator.getWorkWeekDates(currentWeek);
const dateSettings = config.getDateViewSettings();
const daysToShow = dates.slice(0, dateSettings.weekDays);
daysToShow.forEach((date) => {
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
this.applyWorkHoursToColumn(column, date);

View file

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

View file

@ -256,25 +256,26 @@ export class DateHeaderRenderer extends BaseHeaderRenderer {
const { currentWeek, config } = context;
// 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 daysToShow = dates.slice(0, weekDays);
daysToShow.forEach((date, index) => {
const header = document.createElement('swp-day-header');
if (this.dateCalculator.isToday(date)) {
if (DateCalculator.isToday(date)) {
(header as any).dataset.today = 'true';
}
const dayName = this.dateCalculator.getDayName(date, 'short');
const dayName = DateCalculator.getDayName(date, 'short');
header.innerHTML = `
<swp-day-name>${dayName}</swp-day-name>
<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);
});

View file

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

View file

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

View file

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

View file

@ -6,10 +6,14 @@
import { CalendarConfig } from '../core/CalendarConfig';
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
* @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())) {
throw new Error(`${methodName}: Invalid date provided - ${date}`);
}
@ -29,14 +33,14 @@ export class DateCalculator {
* @param weekStart - Any date in the week
* @returns Array of dates for the configured work days
*/
getWorkWeekDates(weekStart: Date): Date[] {
this.validateDate(weekStart, 'getWorkWeekDates');
static getWorkWeekDates(weekStart: Date): Date[] {
DateCalculator.validateDate(weekStart, 'getWorkWeekDates');
const dates: Date[] = [];
const workWeekSettings = this.config.getWorkWeekSettings();
const workWeekSettings = DateCalculator.config.getWorkWeekSettings();
// 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
workWeekSettings.workDays.forEach(isoDay => {
@ -55,8 +59,8 @@ export class DateCalculator {
* @param date - Any date in the week
* @returns The Monday of the ISO week
*/
getISOWeekStart(date: Date): Date {
this.validateDate(date, 'getISOWeekStart');
static getISOWeekStart(date: Date): Date {
DateCalculator.validateDate(date, 'getISOWeekStart');
const monday = new Date(date);
const currentDay = monday.getDay();
@ -72,10 +76,10 @@ export class DateCalculator {
* @param date - Any date in the week
* @returns The end date of the ISO week (Sunday)
*/
getWeekEnd(date: Date): Date {
this.validateDate(date, 'getWeekEnd');
static getWeekEnd(date: Date): Date {
DateCalculator.validateDate(date, 'getWeekEnd');
const weekStart = this.getISOWeekStart(date);
const weekStart = DateCalculator.getISOWeekStart(date);
const weekEnd = new Date(weekStart);
weekEnd.setDate(weekStart.getDate() + 6);
weekEnd.setHours(23, 59, 59, 999);
@ -87,7 +91,7 @@ export class DateCalculator {
* @param date - The date to get week number for
* @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 dayNum = d.getUTCDay() || 7;
d.setUTCDate(d.getUTCDate() + 4 - dayNum);
@ -102,7 +106,7 @@ export class DateCalculator {
* @param options - Formatting options
* @returns Formatted date range string
*/
formatDateRange(
static formatDateRange(
start: Date,
end: Date,
options: {
@ -137,7 +141,7 @@ export class DateCalculator {
* @param date - Date to format
* @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')}`;
}
@ -146,7 +150,7 @@ export class DateCalculator {
* @param date - Date to check
* @returns True if the date is today
*/
isToday(date: Date): boolean {
static isToday(date: Date): boolean {
const today = new Date();
return date.toDateString() === today.toDateString();
}
@ -157,7 +161,7 @@ export class DateCalculator {
* @param days - Number of days to add (can be negative)
* @returns New date
*/
addDays(date: Date, days: number): Date {
static addDays(date: Date, days: number): Date {
const result = new Date(date);
result.setDate(result.getDate() + days);
return result;
@ -169,8 +173,8 @@ export class DateCalculator {
* @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);
static addWeeks(date: Date, weeks: number): Date {
return DateCalculator.addDays(date, weeks * 7);
}
/**
@ -178,10 +182,10 @@ export class DateCalculator {
* @param weekStart - Start of the week
* @returns Array of 7 dates for the full week
*/
getFullWeekDates(weekStart: Date): Date[] {
static getFullWeekDates(weekStart: Date): Date[] {
const dates: Date[] = [];
for (let i = 0; i < 7; i++) {
dates.push(this.addDays(weekStart, i));
dates.push(DateCalculator.addDays(weekStart, i));
}
return dates;
}
@ -192,7 +196,7 @@ export class DateCalculator {
* @param format - 'short' or 'long'
* @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', {
weekday: format
});
@ -204,7 +208,7 @@ export class DateCalculator {
* @param date - Date to format
* @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')}`;
}
@ -213,7 +217,7 @@ export class DateCalculator {
* @param date - Date to format
* @returns 12-hour time string
*/
formatTime12(date: Date): string {
static formatTime12(date: Date): string {
const hours = date.getHours();
const minutes = date.getMinutes();
const period = hours >= 12 ? 'PM' : 'AM';
@ -227,7 +231,7 @@ export class DateCalculator {
* @param minutes - Minutes since midnight
* @returns Time string
*/
minutesToTime(minutes: number): string {
static minutesToTime(minutes: number): string {
const hours = Math.floor(minutes / 60);
const mins = minutes % 60;
const period = hours >= 12 ? 'PM' : 'AM';
@ -241,7 +245,7 @@ export class DateCalculator {
* @param timeStr - Time string
* @returns Minutes since midnight
*/
timeToMinutes(timeStr: string): number {
static timeToMinutes(timeStr: string): number {
const [time] = timeStr.split('T').pop()!.split('.');
const [hours, minutes] = time.split(':').map(Number);
return hours * 60 + minutes;
@ -252,7 +256,7 @@ export class DateCalculator {
* @param date - Date or ISO string
* @returns Minutes since midnight
*/
getMinutesSinceMidnight(date: Date | string): number {
static getMinutesSinceMidnight(date: Date | string): number {
const d = typeof date === 'string' ? new Date(date) : date;
return d.getHours() * 60 + d.getMinutes();
}
@ -263,7 +267,7 @@ export class DateCalculator {
* @param end - End date or ISO string
* @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 endDate = typeof end === 'string' ? new Date(end) : end;
return Math.floor((endDate.getTime() - startDate.getTime()) / 60000);
@ -275,7 +279,7 @@ export class DateCalculator {
* @param date2 - Second date
* @returns True if same day
*/
isSameDay(date1: Date, date2: Date): boolean {
static isSameDay(date1: Date, date2: Date): boolean {
return date1.toDateString() === date2.toDateString();
}
@ -285,15 +289,20 @@ export class DateCalculator {
* @param end - End date or ISO string
* @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 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 {
return new DateCalculator(config);
DateCalculator.initialize(config);
return new DateCalculator();
}