From 18f7953db4a5007a9f1d1b07270b1cc1f1e8a6b2 Mon Sep 17 00:00:00 2001 From: Janus Knudsen Date: Mon, 18 Aug 2025 23:42:03 +0200 Subject: [PATCH] Refactors date calculations into DateCalculator Centralizes date calculation logic into a dedicated DateCalculator class for improved maintainability and testability. Removes redundant date calculation methods from CalendarManager, ColumnRenderer, HeaderRenderer and NavigationRenderer, and utilizes the DateCalculator for these calculations. Updates components to use DateCalculator for consistent date handling, including week start determination, date formatting, and "is today" checks. --- src/managers/CalendarManager.ts | 47 +------ src/renderers/ColumnRenderer.ts | 30 ++--- src/renderers/HeaderRenderer.ts | 43 ++---- src/renderers/NavigationRenderer.ts | 43 ++---- src/utils/DateCalculator.ts | 194 ++++++++++++++++++++++++++++ 5 files changed, 233 insertions(+), 124 deletions(-) create mode 100644 src/utils/DateCalculator.ts diff --git a/src/managers/CalendarManager.ts b/src/managers/CalendarManager.ts index 3fda062..2cf9e20 100644 --- a/src/managers/CalendarManager.ts +++ b/src/managers/CalendarManager.ts @@ -6,6 +6,7 @@ import { EventManager } from './EventManager.js'; import { GridManager } from './GridManager.js'; import { EventRenderingService } from '../renderers/EventRendererManager.js'; import { ScrollManager } from './ScrollManager.js'; +import { DateCalculator } from '../utils/DateCalculator.js'; /** * CalendarManager - Main coordinator for all calendar managers @@ -18,6 +19,7 @@ export class CalendarManager { private gridManager: GridManager; private eventRenderer: EventRenderingService; private scrollManager: ScrollManager; + private dateCalculator: DateCalculator; private currentView: CalendarView = 'week'; private currentDate: Date = new Date(); private isInitialized: boolean = false; @@ -36,6 +38,7 @@ export class CalendarManager { this.gridManager = gridManager; this.eventRenderer = eventRenderer; this.scrollManager = scrollManager; + this.dateCalculator = new DateCalculator(config); this.setupEventListeners(); console.log('📋 CalendarManager: Created with proper dependency injection'); } @@ -428,8 +431,8 @@ export class CalendarManager { // Trigger event rendering for the current date range using correct method this.eventRenderer.renderEvents({ container: container as HTMLElement, - startDate: new Date(periodData.startDate), - endDate: new Date(periodData.endDate) + startDate: new Date(periodData.start), + endDate: new Date(periodData.end) }); } @@ -471,10 +474,10 @@ export class CalendarManager { const lastDate = new Date(lastDateStr); // Calculate week number from first date - const weekNumber = this.getWeekNumber(firstDate); + const weekNumber = this.dateCalculator.getWeekNumber(firstDate); // Format date range - const dateRange = this.formatDateRange(firstDate, lastDate); + const dateRange = this.dateCalculator.formatDateRange(firstDate, lastDate); console.log('CalendarManager: Week info from columns:', { firstDate: firstDateStr, @@ -492,41 +495,5 @@ export class CalendarManager { }); } - /** - * Helper method to get week number - */ - private getWeekNumber(date: Date): number { - const start = new Date(date.getFullYear(), 0, 1); - const days = Math.floor((date.getTime() - start.getTime()) / (24 * 60 * 60 * 1000)); - return Math.ceil((days + start.getDay() + 1) / 7); - } - - /** - * Helper method to get week start (Sunday) - */ - private getWeekStart(date: Date): Date { - const weekStart = new Date(date); - weekStart.setDate(date.getDate() - date.getDay()); - return weekStart; - } - - /** - * Helper method to format date range - */ - private 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 year = start.getFullYear(); - - if (startMonth === endMonth) { - return `${startMonth} ${startDay} - ${endDay}, ${year}`; - } else { - return `${startMonth} ${startDay} - ${endMonth} ${endDay}, ${year}`; - } - } } \ No newline at end of file diff --git a/src/renderers/ColumnRenderer.ts b/src/renderers/ColumnRenderer.ts index a6e55f9..19a896e 100644 --- a/src/renderers/ColumnRenderer.ts +++ b/src/renderers/ColumnRenderer.ts @@ -2,6 +2,7 @@ import { CalendarConfig } from '../core/CalendarConfig'; import { ResourceCalendarData } from '../types/CalendarTypes'; +import { DateCalculator } from '../utils/DateCalculator'; /** * Interface for column rendering strategies @@ -23,10 +24,15 @@ export interface ColumnRenderContext { * Date-based column renderer (original functionality) */ export class DateColumnRenderer implements ColumnRenderer { + private dateCalculator: DateCalculator; + render(columnContainer: HTMLElement, context: ColumnRenderContext): void { const { currentWeek, config } = context; - const dates = this.getWeekDates(currentWeek, config); + // Initialize date calculator + this.dateCalculator = new DateCalculator(config); + + const dates = this.dateCalculator.getWorkWeekDates(currentWeek); const dateSettings = config.getDateViewSettings(); const daysToShow = dates.slice(0, dateSettings.weekDays); @@ -34,7 +40,7 @@ export class DateColumnRenderer implements ColumnRenderer { daysToShow.forEach((date) => { const column = document.createElement('swp-day-column'); - (column as any).dataset.date = this.formatDate(date); + (column as any).dataset.date = this.dateCalculator.formatISODate(date); const eventsLayer = document.createElement('swp-events-layer'); column.appendChild(eventsLayer); @@ -43,26 +49,6 @@ export class DateColumnRenderer implements ColumnRenderer { }); } - private getWeekDates(weekStart: Date, config: CalendarConfig): Date[] { - const dates: Date[] = []; - const workWeekSettings = config.getWorkWeekSettings(); - - // Calculate dates based on actual work days (e.g., [1,2,3,4] for Mon-Thu) - workWeekSettings.workDays.forEach(dayOfWeek => { - const date = new Date(weekStart); - // Set to the start of the week (Sunday = 0) - date.setDate(weekStart.getDate() - weekStart.getDay()); - // Add the specific day of week - date.setDate(date.getDate() + dayOfWeek); - dates.push(date); - }); - - return dates; - } - - private formatDate(date: Date): string { - return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; - } } /** diff --git a/src/renderers/HeaderRenderer.ts b/src/renderers/HeaderRenderer.ts index cf2d734..95488a0 100644 --- a/src/renderers/HeaderRenderer.ts +++ b/src/renderers/HeaderRenderer.ts @@ -2,6 +2,7 @@ import { CalendarConfig } from '../core/CalendarConfig'; import { ResourceCalendarData } from '../types/CalendarTypes'; +import { DateCalculator } from '../utils/DateCalculator'; /** * Interface for header rendering strategies @@ -24,17 +25,22 @@ export interface HeaderRenderContext { * Date-based header renderer (original functionality) */ export class DateHeaderRenderer implements HeaderRenderer { + private dateCalculator: DateCalculator; + render(calendarHeader: HTMLElement, context: HeaderRenderContext): void { const { currentWeek, config, allDayEvents = [] } = context; - const dates = this.getWeekDates(currentWeek, config); + // Initialize date calculator with config + this.dateCalculator = new DateCalculator(config); + + const dates = this.dateCalculator.getWorkWeekDates(currentWeek); const weekDays = config.get('weekDays'); const daysToShow = dates.slice(0, weekDays); const workWeekSettings = config.getWorkWeekSettings(); daysToShow.forEach((date, index) => { const header = document.createElement('swp-day-header'); - if (this.isToday(date)) { + if (this.dateCalculator.isToday(date)) { (header as any).dataset.today = 'true'; } @@ -42,7 +48,7 @@ export class DateHeaderRenderer implements HeaderRenderer { ${workWeekSettings.dayNames[index]} ${date.getDate()} `; - (header as any).dataset.date = this.formatDate(date); + (header as any).dataset.date = this.dateCalculator.formatISODate(date); calendarHeader.appendChild(header); }); @@ -54,7 +60,7 @@ export class DateHeaderRenderer implements HeaderRenderer { private renderAllDayEvents(calendarHeader: HTMLElement, context: HeaderRenderContext): void { const { currentWeek, config, allDayEvents = [] } = context; - const dates = this.getWeekDates(currentWeek, config); + const dates = this.dateCalculator.getWorkWeekDates(currentWeek); const weekDays = config.get('weekDays'); const daysToShow = dates.slice(0, weekDays); @@ -68,8 +74,8 @@ export class DateHeaderRenderer implements HeaderRenderer { let endColumnIndex = -1; daysToShow.forEach((date, index) => { - const dateStr = this.formatDate(date); - const startDateStr = this.formatDate(startDate); + const dateStr = this.dateCalculator.formatISODate(date); + const startDateStr = this.dateCalculator.formatISODate(startDate); if (dateStr === startDateStr) { startColumnIndex = index; @@ -102,31 +108,6 @@ export class DateHeaderRenderer implements HeaderRenderer { }); } - private getWeekDates(weekStart: Date, config: CalendarConfig): Date[] { - const dates: Date[] = []; - const workWeekSettings = config.getWorkWeekSettings(); - - // Calculate dates based on actual work days (e.g., [1,2,3,4] for Mon-Thu) - workWeekSettings.workDays.forEach(dayOfWeek => { - const date = new Date(weekStart); - // Set to the start of the week (Sunday = 0) - date.setDate(weekStart.getDate() - weekStart.getDay()); - // Add the specific day of week - date.setDate(date.getDate() + dayOfWeek); - dates.push(date); - }); - - return dates; - } - - private isToday(date: Date): boolean { - const today = new Date(); - return date.toDateString() === today.toDateString(); - } - - private formatDate(date: Date): string { - return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; - } } diff --git a/src/renderers/NavigationRenderer.ts b/src/renderers/NavigationRenderer.ts index bc22ce3..0d3f614 100644 --- a/src/renderers/NavigationRenderer.ts +++ b/src/renderers/NavigationRenderer.ts @@ -2,6 +2,7 @@ import { IEventBus } from '../types/CalendarTypes'; import { EventTypes } from '../constants/EventTypes'; import { DateUtils } from '../utils/DateUtils'; import { CalendarConfig } from '../core/CalendarConfig'; +import { DateCalculator } from '../utils/DateCalculator'; /** * NavigationRenderer - Handles DOM rendering for navigation containers @@ -10,10 +11,12 @@ import { CalendarConfig } from '../core/CalendarConfig'; export class NavigationRenderer { private eventBus: IEventBus; private config: CalendarConfig; + private dateCalculator: DateCalculator; constructor(eventBus: IEventBus, config: CalendarConfig) { this.eventBus = eventBus; this.config = config; + this.dateCalculator = new DateCalculator(config); this.setupEventListeners(); } @@ -99,17 +102,14 @@ export class NavigationRenderer { header.innerHTML = ''; dayColumns.innerHTML = ''; - // Render headers for target week + // Get dates using DateCalculator + const dates = this.dateCalculator.getWorkWeekDates(weekStart); const workWeekSettings = this.config.getWorkWeekSettings(); - workWeekSettings.workDays.forEach((dayOfWeek, i) => { - const date = new Date(weekStart); - // Set to the start of the week (Sunday = 0) - date.setDate(weekStart.getDate() - weekStart.getDay()); - // Add the specific day of week - date.setDate(date.getDate() + dayOfWeek); - + + // Render headers for target week + dates.forEach((date, i) => { const headerElement = document.createElement('swp-day-header'); - if (this.isToday(date)) { + if (this.dateCalculator.isToday(date)) { headerElement.dataset.today = 'true'; } @@ -117,20 +117,15 @@ export class NavigationRenderer { ${workWeekSettings.dayNames[i]} ${date.getDate()} `; - headerElement.dataset.date = this.formatDate(date); + headerElement.dataset.date = this.dateCalculator.formatISODate(date); header.appendChild(headerElement); }); // Render day columns for target week - workWeekSettings.workDays.forEach(dayOfWeek => { + dates.forEach(date => { const column = document.createElement('swp-day-column'); - const date = new Date(weekStart); - // Set to the start of the week (Sunday = 0) - date.setDate(weekStart.getDate() - weekStart.getDay()); - // Add the specific day of week - date.setDate(date.getDate() + dayOfWeek); - column.dataset.date = this.formatDate(date); + column.dataset.date = this.dateCalculator.formatISODate(date); const eventsLayer = document.createElement('swp-events-layer'); column.appendChild(eventsLayer); @@ -139,18 +134,4 @@ export class NavigationRenderer { }); } - /** - * Utility method to format date - */ - private formatDate(date: Date): string { - return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')}`; - } - - /** - * Check if date is today - */ - private isToday(date: Date): boolean { - const today = new Date(); - return date.toDateString() === today.toDateString(); - } } \ No newline at end of file diff --git a/src/utils/DateCalculator.ts b/src/utils/DateCalculator.ts new file mode 100644 index 0000000..3ca32f5 --- /dev/null +++ b/src/utils/DateCalculator.ts @@ -0,0 +1,194 @@ +/** + * 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 week start and work days configuration + * @param weekStart - The start date of the week (could be Sunday or Monday) + * @returns Array of dates for the configured work days + */ + getWorkWeekDates(weekStart: Date): Date[] { + const dates: Date[] = []; + const workWeekSettings = this.config.getWorkWeekSettings(); + const dateSettings = this.config.getDateViewSettings(); + const firstDayOfWeek = dateSettings.firstDayOfWeek; // 0=Sunday, 1=Monday + + // Get the actual start of the week based on configuration + const actualWeekStart = this.getWeekStart(weekStart, firstDayOfWeek); + + // Calculate dates for each work day + workWeekSettings.workDays.forEach(dayOfWeek => { + const date = new Date(actualWeekStart); + + if (firstDayOfWeek === 1 && dayOfWeek === 0) { + // For Monday-based weeks, Sunday is at the end (day 7) + date.setDate(actualWeekStart.getDate() + 7); + } else { + // Normal calculation + date.setDate(actualWeekStart.getDate() + dayOfWeek); + } + + dates.push(date); + }); + + return dates; + } + + /** + * Get the start of the week for a given date + * @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 { + const weekStart = new Date(date); + const currentDay = weekStart.getDay(); + + if (firstDayOfWeek === 1) { + // Monday as first day + const daysToSubtract = currentDay === 0 ? 6 : currentDay - 1; + weekStart.setDate(weekStart.getDate() - daysToSubtract); + } else { + // Sunday as first day + weekStart.setDate(weekStart.getDate() - currentDay); + } + + weekStart.setHours(0, 0, 0, 0); + return weekStart; + } + + /** + * 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); +} \ No newline at end of file