Centralizes time formatting with timezone support

Addresses inconsistent time formatting and lack of timezone
handling throughout the application by introducing a
`TimeFormatter` utility.

This class centralizes time formatting logic, providing
timezone conversion (defaults to Europe/Copenhagen) and
support for both 12-hour and 24-hour formats, configurable
via `CalendarConfig`.

It also updates event rendering to utilize the new
`TimeFormatter` for consistent time displays.
This commit is contained in:
Janus Knudsen 2025-09-12 22:21:56 +02:00
parent c07d83d86f
commit 8b96376d1f
7 changed files with 489 additions and 199 deletions

View file

@ -7,6 +7,7 @@ import { eventBus } from '../core/EventBus';
import { CoreEvents } from '../constants/CoreEvents';
import { OverlapDetector, OverlapResult, EventId } from '../utils/OverlapDetector';
import { SwpEventElement, SwpAllDayEventElement } from '../elements/SwpEventElement';
import { TimeFormatter } from '../utils/TimeFormatter';
/**
* Interface for event rendering strategies
@ -184,12 +185,11 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
* Create event inner structure (swp-event-time and swp-event-title)
*/
private createEventInnerStructure(event: CalendarEvent): string {
const startTime = this.formatTime(event.start);
const endTime = this.formatTime(event.end);
const timeRange = TimeFormatter.formatTimeRange(event.start, event.end);
const durationMinutes = (event.end.getTime() - event.start.getTime()) / (1000 * 60);
return `
<swp-event-time data-duration="${durationMinutes}">${startTime} - ${endTime}</swp-event-time>
<swp-event-time data-duration="${durationMinutes}">${timeRange}</swp-event-time>
<swp-event-title>${event.title}</swp-event-title>
`;
}
@ -269,33 +269,12 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
// Update display
const timeElement = clone.querySelector('swp-event-time');
if (timeElement) {
const newTimeText = `${this.formatTime(snappedStartMinutes)} - ${this.formatTime(endTotalMinutes)}`;
timeElement.textContent = newTimeText;
const startTime = TimeFormatter.formatTimeFromMinutes(snappedStartMinutes);
const endTime = TimeFormatter.formatTimeFromMinutes(endTotalMinutes);
timeElement.textContent = `${startTime} - ${endTime}`;
}
}
/**
* Unified time formatting method - handles both total minutes and Date objects
*/
private formatTime(input: number | Date | string): string {
let hours: number, minutes: number;
if (typeof input === 'number') {
// Total minutes input
hours = Math.floor(input / 60) % 24;
minutes = input % 60;
} else {
// Date or ISO string input
const date = typeof input === 'string' ? new Date(input) : input;
hours = date.getHours();
minutes = date.getMinutes();
}
const period = hours >= 12 ? 'PM' : 'AM';
const displayHours = hours > 12 ? hours - 12 : (hours === 0 ? 12 : hours);
return `${displayHours}:${minutes.toString().padStart(2, '0')} ${period}`;
}
/**
* Handle drag start event
*/
@ -590,9 +569,8 @@ export abstract class BaseEventRenderer implements EventRendererStrategy {
// Update the time display
const timeElement = element.querySelector('swp-event-time');
if (timeElement) {
const startTime = this.formatTime(event.start);
const endTime = this.formatTime(event.end);
timeElement.textContent = `${startTime} - ${endTime}`;
const timeRange = TimeFormatter.formatTimeRange(event.start, event.end);
timeElement.textContent = timeRange;
}
}