Adds technical date and time formatting

Adds options for technical date and time formatting and includes the option to show seconds.

Updates time formatting to use UTC-to-local conversion and ensures consistent colon separators for time values.

Adjusts all-day event handling to preserve original start/end times.
This commit is contained in:
Janus C. H. Knudsen 2025-10-03 16:05:22 +02:00
parent c8d78f472d
commit 38737762c5
7 changed files with 370 additions and 26 deletions

View file

@ -78,6 +78,8 @@ interface TimeFormatConfig {
timezone: string;
use24HourFormat: boolean;
locale: string;
dateFormat: 'locale' | 'technical';
showSeconds: boolean;
}
/**
@ -154,11 +156,13 @@ export class CalendarConfig {
showAllDay: true
};
// Time format settings - default to Denmark
// Time format settings - default to Denmark with technical format
this.timeFormatConfig = {
timezone: 'Europe/Copenhagen',
use24HourFormat: true,
locale: 'da-DK'
locale: 'da-DK',
dateFormat: 'technical',
showSeconds: false
};
// Set computed values
@ -545,6 +549,27 @@ export class CalendarConfig {
return this.timeFormatConfig.use24HourFormat;
}
/**
* Set date format (convenience method)
*/
setDateFormat(format: 'locale' | 'technical'): void {
this.updateTimeFormatSettings({ dateFormat: format });
}
/**
* Set whether to show seconds (convenience method)
*/
setShowSeconds(show: boolean): void {
this.updateTimeFormatSettings({ showSeconds: show });
}
/**
* Get current date format
*/
getDateFormat(): 'locale' | 'technical' {
return this.timeFormatConfig.dateFormat;
}
}
// Create singleton instance

View file

@ -2095,8 +2095,8 @@
{
"id": "162",
"title": "Produktudvikling Sprint",
"start": "2025-10-01T00:00:00Z",
"end": "2025-10-02T23:59:59Z",
"start": "2025-10-01T08:00:00Z",
"end": "2025-10-02T21:00:00Z",
"type": "work",
"allDay": true,
"syncStatus": "synced",

View file

@ -245,12 +245,8 @@ export class SwpAllDayEventElement extends BaseEventElement {
*/
private setAllDayAttributes(): void {
this.element.dataset.allDay = "true";
// For all-day events, preserve original start/end dates but set to full day times
const startDateStr = this.event.start.toISOString().split('T')[0];
const endDateStr = this.event.end.toISOString().split('T')[0];
this.element.dataset.start = `${startDateStr}T00:00:00`;
this.element.dataset.end = `${endDateStr}T23:59:59`;
this.element.dataset.allday = 'true';
this.element.dataset.start = this.event.start.toISOString();
this.element.dataset.end = this.event.end.toISOString();
}
/**

View file

@ -112,10 +112,10 @@ export class DateEventRenderer implements EventRendererStrategy {
/**
* Update clone timestamp based on new position
*/
private updateCloneTimestamp(clone: HTMLElement, snappedY: number): void {
private updateCloneTimestamp(payload: DragMoveEventPayload): void {
//important as events can pile up, so they will still fire after event has been converted to another rendered type
if (clone.dataset.allDay == "true") return;
if (payload.draggedClone.dataset.allDay == "true") return;
const gridSettings = calendarConfig.getGridSettings();
const hourHeight = gridSettings.hourHeight;
@ -123,7 +123,7 @@ export class DateEventRenderer implements EventRendererStrategy {
const snapInterval = gridSettings.snapInterval;
// Calculate minutes from grid start (not from midnight)
const minutesFromGridStart = (snappedY / hourHeight) * 60;
const minutesFromGridStart = (payload.snappedY / hourHeight) * 60;
// Add dayStartHour offset to get actual time
const actualStartMinutes = (dayStartHour * 60) + minutesFromGridStart;
@ -132,13 +132,13 @@ export class DateEventRenderer implements EventRendererStrategy {
const snappedStartMinutes = Math.round(actualStartMinutes / snapInterval) * snapInterval;
if (!clone.dataset.originalDuration)
if (!payload.draggedClone.dataset.originalDuration)
throw new DOMException("missing clone.dataset.originalDuration")
const endTotalMinutes = snappedStartMinutes + parseInt(clone.dataset.originalDuration);
const endTotalMinutes = snappedStartMinutes + parseInt(payload.draggedClone.dataset.originalDuration);
// Update visual time display only
const timeElement = clone.querySelector('swp-event-time');
const timeElement = payload.draggedClone.querySelector('swp-event-time');
if (timeElement) {
let startTime = TimeFormatter.formatTimeFromMinutes(snappedStartMinutes);
let endTime = TimeFormatter.formatTimeFromMinutes(endTotalMinutes);
@ -183,7 +183,7 @@ export class DateEventRenderer implements EventRendererStrategy {
this.draggedClone.style.top = (payload.snappedY - payload.mouseOffset.y) + 'px';
// Update timestamp display
this.updateCloneTimestamp(this.draggedClone, payload.snappedY);
this.updateCloneTimestamp(payload);
}

View file

@ -1,6 +1,7 @@
import { calendarConfig } from '../core/CalendarConfig';
import { ColumnBounds } from './ColumnDetectionUtils';
import { DateCalculator } from './DateCalculator';
import { TimeFormatter } from './TimeFormatter';
/**
* PositionUtils - Static positioning utilities using singleton calendarConfig
@ -209,11 +210,11 @@ export class PositionUtils {
}
/**
* Convert ISO datetime to time string using DateCalculator
* Convert ISO datetime to time string with UTC-to-local conversion
*/
public static isoToTimeString(isoString: string): string {
const date = new Date(isoString);
return DateCalculator.formatTime(date);
return TimeFormatter.formatTime(date);
}
/**

View file

@ -11,13 +11,17 @@ export interface TimeFormatSettings {
timezone: string;
use24HourFormat: boolean;
locale: string;
dateFormat: 'locale' | 'technical';
showSeconds: boolean;
}
export class TimeFormatter {
private static settings: TimeFormatSettings = {
timezone: 'Europe/Copenhagen', // Default to Denmark
use24HourFormat: true, // 24-hour format standard in Denmark
locale: 'da-DK' // Danish locale
locale: 'da-DK', // Danish locale
dateFormat: 'technical', // Use technical format yyyy-mm-dd hh:mm:ss
showSeconds: false // Don't show seconds by default
};
/**
@ -88,12 +92,10 @@ export class TimeFormatter {
static format24Hour(date: Date): string {
const localDate = TimeFormatter.convertToLocalTime(date);
return localDate.toLocaleTimeString(TimeFormatter.settings.locale, {
timeZone: TimeFormatter.settings.timezone,
hour: '2-digit',
minute: '2-digit',
hour12: false
});
// Always use colon separator, not locale-specific formatting
let hours = String(localDate.getHours()).padStart(2, '0');
let minutes = String(localDate.getMinutes()).padStart(2, '0');
return `${hours}:${minutes}`;
}
/**
@ -184,4 +186,38 @@ export class TimeFormatter {
}).split(' ').pop() || '';
}
/**
* Format date in technical format: yyyy-mm-dd
*/
static formatDateTechnical(date: Date): string {
let year = date.getFullYear();
let month = String(date.getMonth() + 1).padStart(2, '0');
let day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
/**
* Format time in technical format: hh:mm or hh:mm:ss
*/
static formatTimeTechnical(date: Date, includeSeconds: boolean = false): string {
let hours = String(date.getHours()).padStart(2, '0');
let minutes = String(date.getMinutes()).padStart(2, '0');
if (includeSeconds) {
let seconds = String(date.getSeconds()).padStart(2, '0');
return `${hours}:${minutes}:${seconds}`;
}
return `${hours}:${minutes}`;
}
/**
* Format date and time in technical format: yyyy-mm-dd hh:mm:ss
*/
static formatDateTimeTechnical(date: Date): string {
let localDate = TimeFormatter.convertToLocalTime(date);
let dateStr = TimeFormatter.formatDateTechnical(localDate);
let timeStr = TimeFormatter.formatTimeTechnical(localDate, TimeFormatter.settings.showSeconds);
return `${dateStr} ${timeStr}`;
}
}