Add proposal, design, specs and tasks for the fix-timezone-double-conversion change. Documents the root cause (icsDateToJsDate returning fake UTC causing double browser offset), the Intl.DateTimeFormat-based solution, and 40+ test scenarios covering DST, cross-timezone, half-hour offsets, and round-trip conversions. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
6.3 KiB
6.3 KiB
1. Helper de conversion timezone
- 1.1 Ajouter le type
DateComponents(interface avec year, month, day, hours, minutes, seconds) dansEventCalendarAdapter.ts - 1.2 Ajouter la méthode
getDateComponentsInTimezone(date: Date, timezone: string)dansEventCalendarAdapter.ts— utiliseIntl.DateTimeFormatavecformatToParts()pour extraire les composants dans le timezone cible
2. Fix du chemin de lecture (affichage)
- 2.1 Modifier
icsDateToJsDate()dansEventCalendarAdapter.tspour retournericsDate.dateau lieu deicsDate.local?.date— c'est le fix principal de l'affichage scheduler
3. Fix du chemin d'écriture (sauvegarde)
- 3.1 Refactorer
jsDateToIcsDate()dansEventCalendarAdapter.ts: supprimer le paramètreisFakeUtc, utilisergetDateComponentsInTimezone()pour obtenir les composants dans le timezone cible, créer le fake UTC avecDate.UTC()à partir de ces composants - 3.2 Modifier
toIcsEvent()dansEventCalendarAdapter.ts: supprimer la variableisFakeUtc(ligne 347) et ne plus passer ce paramètre àjsDateToIcsDate()
4. Tests unitaires — getDateComponentsInTimezone
Créer src/frontend/apps/calendars/src/features/calendar/services/dav/__tests__/timezone-conversion.test.ts
- 4.1 Test Europe/Paris hiver :
Date("2026-01-29T14:00:00Z")→{ hours: 15 }(CET, UTC+1) - 4.2 Test Europe/Paris été :
Date("2026-07-15T13:00:00Z")→{ hours: 15 }(CEST, UTC+2) - 4.3 Test America/New_York hiver :
Date("2026-01-29T15:00:00Z")→{ hours: 10 }(EST, UTC-5) - 4.4 Test America/New_York été :
Date("2026-07-15T14:00:00Z")→{ hours: 10 }(EDT, UTC-4) - 4.5 Test Asia/Tokyo (pas de DST) :
Date("2026-01-29T06:00:00Z")→{ hours: 15 }(JST, UTC+9) - 4.6 Test UTC :
Date("2026-01-29T15:30:45Z")→{ hours: 15, minutes: 30, seconds: 45 } - 4.7 Test changement de jour (UTC tard → lendemain en avance) :
Date("2026-01-29T23:00:00Z")+ Asia/Tokyo →{ day: 30, hours: 8 } - 4.8 Test changement de jour (UTC tôt → veille en retard) :
Date("2026-01-29T03:00:00Z")+ America/New_York →{ day: 28, hours: 22 } - 4.9 Test changement d'année :
Date("2026-01-01T00:30:00Z")+ America/Los_Angeles →{ year: 2025, month: 12, day: 31 } - 4.10 Test offset demi-heure (Inde) :
Date("2026-01-29T10:00:00Z")+ Asia/Kolkata →{ hours: 15, minutes: 30 }(UTC+5:30) - 4.11 Test offset 45min (Népal) :
Date("2026-01-29T10:00:00Z")+ Asia/Kathmandu →{ hours: 15, minutes: 45 }(UTC+5:45) - 4.12 Test transition DST CET→CEST (mars) : vérifier avant et après la transition du dernier dimanche de mars
- 4.13 Test transition DST CEST→CET (octobre) : vérifier avant et après la transition du dernier dimanche d'octobre
- 4.14 Test minutes et secondes non-zéro :
Date("2026-01-29T14:37:42Z")+ Europe/Paris →{ hours: 15, minutes: 37, seconds: 42 }
5. Tests unitaires — icsDateToJsDate (fix du bug)
- 5.1 Test : retourne
icsDate.date(vrai UTC) quandlocalest présent — vérifie que c'est biendateet PASlocal.date - 5.2 Test : retourne
icsDate.datequandlocalest absent (événements UTC purs) - 5.3 Test : retourne
icsDate.datepour les événements all-day (type DATE)
6. Tests unitaires — jsDateToIcsDate (conversion timezone)
- 6.1 Test all-day : produit un
IcsDateObjectde typeDATEsans timezone - 6.2 Test Europe/Paris hiver :
Date(UTC 14:00)+ tz Paris → fake UTC avecgetUTCHours() === 15 - 6.3 Test America/New_York hiver :
Date(UTC 15:00)+ tz NY → fake UTC avecgetUTCHours() === 10 - 6.4 Test Asia/Tokyo :
Date(UTC 06:00)+ tz Tokyo → fake UTC avecgetUTCHours() === 15 - 6.5 Test Europe/Paris été (DST) :
Date(UTC 13:00)+ tz Paris → fake UTC avecgetUTCHours() === 15(CEST, UTC+2) - 6.6 Test préservation minutes/secondes :
Date(UTC 14:37:42)+ tz Paris → fake UTC avecgetUTCMinutes() === 37,getUTCSeconds() === 42 - 6.7 Test changement de jour :
Date(UTC 23:00)+ tz Tokyo → fake UTC avecgetUTCDate()= jour suivant - 6.8 Test que
local.timezoneest correctement défini dans l'objet retourné - 6.9 Test que
local.tzoffsetest correctement calculé (format "+HHMM" / "-HHMM")
7. Tests unitaires — getTimezoneOffset
- 7.1 Test offset positif hiver : Europe/Paris → "+0100"
- 7.2 Test offset positif été : Europe/Paris → "+0200"
- 7.3 Test offset négatif hiver : America/New_York → "-0500"
- 7.4 Test offset négatif été : America/New_York → "-0400"
- 7.5 Test offset zéro : UTC → "+0000"
- 7.6 Test offset demi-heure : Asia/Kolkata → "+0530"
- 7.7 Test timezone invalide : retourne "+0000" (fallback gracieux)
8. Tests unitaires — Round-trip complet (parse ICS → adapter → display string → adapter → ICS)
- 8.1 Round-trip Europe/Paris hiver : parse
DTSTART;TZID=Europe/Paris:20260129T150000→ icsDateToJsDate → dateToLocalISOString → parse string → jsDateToIcsDate → vérifiergetUTCHours() === 15 - 8.2 Round-trip Europe/Paris été : idem avec
20260715T150000(CEST) - 8.3 Round-trip America/New_York : parse
DTSTART;TZID=America/New_York:20260129T100000→ round-trip → vérifiergetUTCHours() === 10 - 8.4 Round-trip Asia/Tokyo : parse
DTSTART;TZID=Asia/Tokyo:20260129T150000→ round-trip → vérifiergetUTCHours() === 15 - 8.5 Round-trip UTC pur : parse
DTSTART:20260129T140000Z→ round-trip (pas de TZID, utilise browser tz) - 8.6 Round-trip all-day : parse
DTSTART;VALUE=DATE:20260129→ round-trip → vérifiergetUTCDate() === 29 - 8.7 Round-trip cross-timezone (NY créé, Paris affiché) : vérifier que l'heure NY est préservée après un round-trip depuis un browser Paris
9. Mise à jour des tests existants
- 9.1 Mettre à jour le test
icsDateToJsDatedansevent-calendar-helper.test.ts(ligne 514-533) : le test "returns local date when present" doit maintenant vérifier que c'esticsDate.date(vrai UTC) qui est retourné, paslocal.date
10. Vérification finale
- 10.1 Vérifier que le TypeScript compile sans erreurs (
yarn tsc --noEmit) - 10.2 Vérifier que le linter passe (
yarn lint) - 10.3 Vérifier que tous les tests passent (
yarn test)