2025-10-03 16:47:42 +02:00
|
|
|
import { describe, it, expect, beforeEach } from 'vitest';
|
|
|
|
|
import { DateService } from '../../src/utils/DateService';
|
2025-10-30 23:47:30 +01:00
|
|
|
import { CalendarConfig } from '../../src/core/CalendarConfig';
|
2025-10-03 16:47:42 +02:00
|
|
|
|
|
|
|
|
describe('DateService', () => {
|
|
|
|
|
let dateService: DateService;
|
|
|
|
|
|
|
|
|
|
beforeEach(() => {
|
2025-10-30 23:47:30 +01:00
|
|
|
const config = new CalendarConfig();
|
|
|
|
|
dateService = new DateService(config);
|
2025-10-03 16:47:42 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Core Conversions', () => {
|
|
|
|
|
it('should convert local date to UTC', () => {
|
|
|
|
|
// 2024-01-15 10:00:00 Copenhagen (UTC+1 in winter)
|
|
|
|
|
const localDate = new Date(2024, 0, 15, 10, 0, 0);
|
|
|
|
|
const utcString = dateService.toUTC(localDate);
|
|
|
|
|
|
|
|
|
|
// Should be 09:00:00 UTC
|
|
|
|
|
expect(utcString).toContain('2024-01-15T09:00:00');
|
|
|
|
|
expect(utcString).toContain('Z');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should convert UTC to local date', () => {
|
|
|
|
|
const utcString = '2024-01-15T09:00:00.000Z';
|
|
|
|
|
const localDate = dateService.fromUTC(utcString);
|
|
|
|
|
|
|
|
|
|
// Should be 10:00 in Copenhagen (UTC+1)
|
|
|
|
|
expect(localDate.getHours()).toBe(10);
|
|
|
|
|
expect(localDate.getMinutes()).toBe(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle summer time (DST)', () => {
|
|
|
|
|
// 2024-07-15 10:00:00 Copenhagen (UTC+2 in summer)
|
|
|
|
|
const localDate = new Date(2024, 6, 15, 10, 0, 0);
|
|
|
|
|
const utcString = dateService.toUTC(localDate);
|
|
|
|
|
|
|
|
|
|
// Should be 08:00:00 UTC
|
|
|
|
|
expect(utcString).toContain('2024-07-15T08:00:00');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Time Formatting', () => {
|
|
|
|
|
it('should format time without seconds', () => {
|
|
|
|
|
const date = new Date(2024, 0, 15, 14, 30, 45);
|
|
|
|
|
const formatted = dateService.formatTime(date);
|
|
|
|
|
|
|
|
|
|
expect(formatted).toBe('14:30');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should format time with seconds', () => {
|
|
|
|
|
const date = new Date(2024, 0, 15, 14, 30, 45);
|
|
|
|
|
const formatted = dateService.formatTime(date, true);
|
|
|
|
|
|
|
|
|
|
expect(formatted).toBe('14:30:45');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should format time range', () => {
|
|
|
|
|
const start = new Date(2024, 0, 15, 9, 0, 0);
|
|
|
|
|
const end = new Date(2024, 0, 15, 10, 30, 0);
|
|
|
|
|
const formatted = dateService.formatTimeRange(start, end);
|
|
|
|
|
|
|
|
|
|
expect(formatted).toBe('09:00 - 10:30');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should format technical datetime', () => {
|
|
|
|
|
const date = new Date(2024, 0, 15, 14, 30, 45);
|
|
|
|
|
const formatted = dateService.formatTechnicalDateTime(date);
|
|
|
|
|
|
|
|
|
|
expect(formatted).toBe('2024-01-15 14:30:45');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should format date as ISO', () => {
|
|
|
|
|
const date = new Date(2024, 0, 15, 14, 30, 0);
|
|
|
|
|
const formatted = dateService.formatDate(date);
|
|
|
|
|
|
|
|
|
|
expect(formatted).toBe('2024-01-15');
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Time Calculations', () => {
|
|
|
|
|
it('should convert time string to minutes', () => {
|
|
|
|
|
expect(dateService.timeToMinutes('09:00')).toBe(540);
|
|
|
|
|
expect(dateService.timeToMinutes('14:30')).toBe(870);
|
|
|
|
|
expect(dateService.timeToMinutes('00:00')).toBe(0);
|
|
|
|
|
expect(dateService.timeToMinutes('23:59')).toBe(1439);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should convert minutes to time string', () => {
|
|
|
|
|
expect(dateService.minutesToTime(540)).toBe('09:00');
|
|
|
|
|
expect(dateService.minutesToTime(870)).toBe('14:30');
|
|
|
|
|
expect(dateService.minutesToTime(0)).toBe('00:00');
|
|
|
|
|
expect(dateService.minutesToTime(1439)).toBe('23:59');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should get minutes since midnight', () => {
|
|
|
|
|
const date = new Date(2024, 0, 15, 14, 30, 0);
|
|
|
|
|
const minutes = dateService.getMinutesSinceMidnight(date);
|
|
|
|
|
|
|
|
|
|
expect(minutes).toBe(870); // 14*60 + 30
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should calculate duration in minutes', () => {
|
|
|
|
|
const start = new Date(2024, 0, 15, 9, 0, 0);
|
|
|
|
|
const end = new Date(2024, 0, 15, 10, 30, 0);
|
|
|
|
|
const duration = dateService.getDurationMinutes(start, end);
|
|
|
|
|
|
|
|
|
|
expect(duration).toBe(90);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should calculate duration from ISO strings', () => {
|
|
|
|
|
const start = '2024-01-15T09:00:00.000Z';
|
|
|
|
|
const end = '2024-01-15T10:30:00.000Z';
|
|
|
|
|
const duration = dateService.getDurationMinutes(start, end);
|
|
|
|
|
|
|
|
|
|
expect(duration).toBe(90);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Week Operations', () => {
|
|
|
|
|
it('should get week bounds (Monday to Sunday)', () => {
|
|
|
|
|
// Wednesday, January 17, 2024
|
|
|
|
|
const date = new Date(2024, 0, 17);
|
|
|
|
|
const bounds = dateService.getWeekBounds(date);
|
|
|
|
|
|
|
|
|
|
// Should start on Monday, January 15
|
|
|
|
|
expect(bounds.start.getDate()).toBe(15);
|
|
|
|
|
expect(bounds.start.getDay()).toBe(1); // Monday
|
|
|
|
|
|
|
|
|
|
// Should end on Sunday, January 21
|
|
|
|
|
expect(bounds.end.getDate()).toBe(21);
|
|
|
|
|
expect(bounds.end.getDay()).toBe(0); // Sunday
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should add weeks', () => {
|
|
|
|
|
const date = new Date(2024, 0, 15);
|
|
|
|
|
const newDate = dateService.addWeeks(date, 2);
|
|
|
|
|
|
|
|
|
|
expect(newDate.getDate()).toBe(29);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should subtract weeks', () => {
|
|
|
|
|
const date = new Date(2024, 0, 15);
|
|
|
|
|
const newDate = dateService.addWeeks(date, -1);
|
|
|
|
|
|
|
|
|
|
expect(newDate.getDate()).toBe(8);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Grid Helpers', () => {
|
|
|
|
|
it('should create date at specific time', () => {
|
|
|
|
|
const baseDate = new Date(2024, 0, 15);
|
|
|
|
|
const date = dateService.createDateAtTime(baseDate, 870); // 14:30
|
|
|
|
|
|
|
|
|
|
expect(date.getHours()).toBe(14);
|
|
|
|
|
expect(date.getMinutes()).toBe(30);
|
|
|
|
|
expect(date.getDate()).toBe(15);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should snap to 15-minute interval', () => {
|
|
|
|
|
const date = new Date(2024, 0, 15, 14, 37, 0); // 14:37
|
|
|
|
|
const snapped = dateService.snapToInterval(date, 15);
|
|
|
|
|
|
|
|
|
|
// 14:37 is closer to 14:30 than 14:45, so should snap to 14:30
|
|
|
|
|
expect(snapped.getHours()).toBe(14);
|
|
|
|
|
expect(snapped.getMinutes()).toBe(30);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should snap to 30-minute interval', () => {
|
|
|
|
|
const date = new Date(2024, 0, 15, 14, 20, 0); // 14:20
|
|
|
|
|
const snapped = dateService.snapToInterval(date, 30);
|
|
|
|
|
|
|
|
|
|
// Should snap to 14:30
|
|
|
|
|
expect(snapped.getHours()).toBe(14);
|
|
|
|
|
expect(snapped.getMinutes()).toBe(30);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Utility Methods', () => {
|
|
|
|
|
it('should check if same day', () => {
|
|
|
|
|
const date1 = new Date(2024, 0, 15, 10, 0, 0);
|
|
|
|
|
const date2 = new Date(2024, 0, 15, 14, 30, 0);
|
|
|
|
|
const date3 = new Date(2024, 0, 16, 10, 0, 0);
|
|
|
|
|
|
|
|
|
|
expect(dateService.isSameDay(date1, date2)).toBe(true);
|
|
|
|
|
expect(dateService.isSameDay(date1, date3)).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should get start of day', () => {
|
|
|
|
|
const date = new Date(2024, 0, 15, 14, 30, 45);
|
|
|
|
|
const start = dateService.startOfDay(date);
|
|
|
|
|
|
|
|
|
|
expect(start.getHours()).toBe(0);
|
|
|
|
|
expect(start.getMinutes()).toBe(0);
|
|
|
|
|
expect(start.getSeconds()).toBe(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should get end of day', () => {
|
|
|
|
|
const date = new Date(2024, 0, 15, 14, 30, 45);
|
|
|
|
|
const end = dateService.endOfDay(date);
|
|
|
|
|
|
|
|
|
|
expect(end.getHours()).toBe(23);
|
|
|
|
|
expect(end.getMinutes()).toBe(59);
|
|
|
|
|
expect(end.getSeconds()).toBe(59);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should add days', () => {
|
|
|
|
|
const date = new Date(2024, 0, 15);
|
|
|
|
|
const newDate = dateService.addDays(date, 5);
|
|
|
|
|
|
|
|
|
|
expect(newDate.getDate()).toBe(20);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should add minutes', () => {
|
|
|
|
|
const date = new Date(2024, 0, 15, 10, 0, 0);
|
|
|
|
|
const newDate = dateService.addMinutes(date, 90);
|
|
|
|
|
|
|
|
|
|
expect(newDate.getHours()).toBe(11);
|
|
|
|
|
expect(newDate.getMinutes()).toBe(30);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should parse ISO string', () => {
|
|
|
|
|
const isoString = '2024-01-15T10:30:00.000Z';
|
|
|
|
|
const date = dateService.parseISO(isoString);
|
|
|
|
|
|
|
|
|
|
expect(date.toISOString()).toBe(isoString);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should validate dates', () => {
|
|
|
|
|
const validDate = new Date(2024, 0, 15);
|
|
|
|
|
const invalidDate = new Date('invalid');
|
|
|
|
|
|
|
|
|
|
expect(dateService.isValid(validDate)).toBe(true);
|
|
|
|
|
expect(dateService.isValid(invalidDate)).toBe(false);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
describe('Edge Cases', () => {
|
|
|
|
|
it('should handle midnight', () => {
|
|
|
|
|
const date = new Date(2024, 0, 15, 0, 0, 0);
|
|
|
|
|
const minutes = dateService.getMinutesSinceMidnight(date);
|
|
|
|
|
|
|
|
|
|
expect(minutes).toBe(0);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle end of day', () => {
|
|
|
|
|
const date = new Date(2024, 0, 15, 23, 59, 0);
|
|
|
|
|
const minutes = dateService.getMinutesSinceMidnight(date);
|
|
|
|
|
|
|
|
|
|
expect(minutes).toBe(1439);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
it('should handle cross-midnight duration', () => {
|
|
|
|
|
const start = new Date(2024, 0, 15, 23, 0, 0);
|
|
|
|
|
const end = new Date(2024, 0, 16, 1, 0, 0);
|
|
|
|
|
const duration = dateService.getDurationMinutes(start, end);
|
|
|
|
|
|
|
|
|
|
expect(duration).toBe(120); // 2 hours
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
});
|