Nocly.fr
Retour aux articles
6 min de lecture

Gérer les fuseaux horaires dans un système de réservation

Comment afficher les créneaux dans le fuseau du visiteur sans casser la cohérence côté admin — retour sur l'architecture du booking de Nocly.fr.

Next.jsi18nSupabaseUX

Le problème

Quand un visiteur de Montréal réserve un créneau qui apparaît à 14h00 et qu'il reçoit un email confirmant un rendez-vous à 20h00, il décroche. Multipliez par quatre locales — fr-FR, fr-CA, en-US, en-CA — et le moindre créneau devient un casse-tête. La règle est simple : le serveur stocke en UTC, le client affiche dans le fuseau du visiteur, et chaque email rappelle explicitement le fuseau utilisé.

Côté client : Intl.DateTimeFormat

Plutôt que d'embarquer une lib comme moment-timezone (180 ko gzip), j'utilise l'API native Intl.DateTimeFormat. Elle détecte le fuseau de l'utilisateur et formate la date en conséquence, sans dépendance.

const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
// Ex: "Europe/Paris" ou "America/Toronto"

const formatted = new Intl.DateTimeFormat(locale, {
  dateStyle: "full",
  timeStyle: "short",
  timeZone: tz,
}).format(new Date(slot.starts_at));

Côté serveur : stocker le fuseau du visiteur

Au moment de la réservation, le client envoie son fuseau IANA dans le payload. Je le stocke dans la colonne timezone de la table bookings. Ça permet de regénérer les emails et le fichier ICS dans le bon fuseau, même si le visiteur revient depuis un autre appareil.

Pièges à éviter

Premier piège : ne jamais comparer des heures locales — toujours convertir en UTC avant. Deuxième piège : un fichier ICS doit indiquer le fuseau dans la propriété DTSTART;TZID=…, sinon Outlook et Google Calendar interprètent différemment. Troisième piège : le passage à l'heure d'été peut casser un créneau récurrent — privilégier des slots ponctuels stockés en UTC plutôt qu'une règle locale.


Cet article fait partie d'une série sur les choix techniques de Nocly.fr. Une question ? Le formulaire de contact est ouvert.