diff --git a/app/components/malio/date/composables/datetimeFormat.test.ts b/app/components/malio/date/composables/datetimeFormat.test.ts new file mode 100644 index 0000000..70e2b08 --- /dev/null +++ b/app/components/malio/date/composables/datetimeFormat.test.ts @@ -0,0 +1,61 @@ +import {describe, expect, it} from 'vitest' +import { + composeDateTime, + formatIsoDateTimeToDisplay, + isValidIsoDateTime, + splitDateTime, +} from './datetimeFormat' + +describe('datetimeFormat', () => { + describe('isValidIsoDateTime', () => { + it('accepte un datetime ISO complet valide', () => { + expect(isValidIsoDateTime('2026-05-20T14:30:00')).toBe(true) + expect(isValidIsoDateTime('2026-01-01T00:00:00')).toBe(true) + expect(isValidIsoDateTime('2026-12-31T23:59:59')).toBe(true) + }) + + it('rejette une date seule, des composants invalides ou une chaîne vide', () => { + expect(isValidIsoDateTime('2026-05-20')).toBe(false) + expect(isValidIsoDateTime('2026-13-01T00:00:00')).toBe(false) + expect(isValidIsoDateTime('2026-05-20T24:00:00')).toBe(false) + expect(isValidIsoDateTime('2026-05-20T14:60:00')).toBe(false) + expect(isValidIsoDateTime('2026-05-20T14:30:60')).toBe(false) + expect(isValidIsoDateTime('2026-05-20T14:30')).toBe(false) + expect(isValidIsoDateTime('')).toBe(false) + }) + }) + + describe('formatIsoDateTimeToDisplay', () => { + it('formate un datetime valide en JJ/MM/AAAA HH:MM', () => { + expect(formatIsoDateTimeToDisplay('2026-05-20T14:30:00')).toBe('20/05/2026 14:30') + }) + + it('renvoie une chaîne vide pour nul ou invalide', () => { + expect(formatIsoDateTimeToDisplay(null)).toBe('') + expect(formatIsoDateTimeToDisplay('2026-05-20')).toBe('') + expect(formatIsoDateTimeToDisplay('nope')).toBe('') + }) + }) + + describe('splitDateTime', () => { + it('découpe un datetime valide', () => { + expect(splitDateTime('2026-05-20T14:30:00')).toEqual({date: '2026-05-20', time: '14:30'}) + }) + + it('renvoie date null et time vide pour nul, date seule ou invalide', () => { + expect(splitDateTime(null)).toEqual({date: null, time: ''}) + expect(splitDateTime('2026-05-20')).toEqual({date: null, time: ''}) + expect(splitDateTime('nope')).toEqual({date: null, time: ''}) + }) + }) + + describe('composeDateTime', () => { + it('recompose un datetime ISO avec secondes à 00', () => { + expect(composeDateTime('2026-05-20', '14:30')).toBe('2026-05-20T14:30:00') + }) + + it('utilise 00:00 quand l\'heure est vide', () => { + expect(composeDateTime('2026-05-20', '')).toBe('2026-05-20T00:00:00') + }) + }) +}) diff --git a/app/components/malio/date/composables/datetimeFormat.ts b/app/components/malio/date/composables/datetimeFormat.ts new file mode 100644 index 0000000..6c6bd6c --- /dev/null +++ b/app/components/malio/date/composables/datetimeFormat.ts @@ -0,0 +1,33 @@ +import {isValidIso} from './dateFormat' + +const DATETIME_RE = /^(\d{4}-\d{2}-\d{2})T(\d{2}):(\d{2}):(\d{2})$/ + +export function isValidIsoDateTime(s: string): boolean { + const m = DATETIME_RE.exec(s) + if (!m) return false + const [, date, hh, mm, ss] = m + if (!isValidIso(date)) return false + const h = Number(hh) + const min = Number(mm) + const sec = Number(ss) + return h >= 0 && h <= 23 && min >= 0 && min <= 59 && sec >= 0 && sec <= 59 +} + +export function formatIsoDateTimeToDisplay(s: string | null): string { + if (!s || !isValidIsoDateTime(s)) return '' + const [date, time] = s.split('T') + const [y, mo, d] = date.split('-') + const [hh, mm] = time.split(':') + return `${d}/${mo}/${y} ${hh}:${mm}` +} + +export function splitDateTime(s: string | null): {date: string | null; time: string} { + if (!s || !isValidIsoDateTime(s)) return {date: null, time: ''} + const [date, time] = s.split('T') + return {date, time: time.slice(0, 5)} +} + +export function composeDateTime(date: string, time: string): string { + const t = time || '00:00' + return `${date}T${t}:00` +}