Dates
Analyse, formatage, comparaison et arithmétique de dates en UTC, et conversion vers un fuseau horaire.
Helpers de manipulation de dates. Toutes les opérations travaillent en UTC : une date de calendrier stockée en base ne doit pas glisser d'un jour selon le fuseau du serveur ou du navigateur.
Import
import {
parseUTCDate, getTodayUTC, getTodayString,
formatDateForDisplay, formatDateShort, formatDateTimeForDisplay,
formatDateForInput, formatDateTimeForInput,
addDays, subtractDays, getDaysBetween,
compareDates, isOverdue,
getTimeZoneOffset, utcToZonedParts, zonedPartsToUtc, formatInTimeZone,
} from '@lrvo/core/shared/dates';Sous-chemin neutre : ces helpers ne lisent aucune variable d'environnement et n'importent rien de Node. Ils sont donc utilisables depuis un fichier 'use client' comme depuis un composant serveur.
Le barrel racine les expose aussi en espace de noms, import { dates } from '@lrvo/core', mais celui-ci tire tout le graphe serveur : le réserver au code serveur.
Analyse
parseUTCDate(dateInput)
Convertit une chaîne YYYY-MM-DD ou un objet Date en Date positionnée en UTC. C'est le point d'entrée de tous les autres helpers.
getTodayUTC() et getTodayString()
Retournent la date du jour, en objet Date UTC ou en chaîne YYYY-MM-DD.
Formatage
| Fonction | Sortie |
|---|---|
formatDateForDisplay(date, locale?, options?) | Date lisible. Locale par défaut en-US. |
formatDateShort(date, locale?) | Forme abrégée. Locale par défaut en-US. |
formatDateTimeForDisplay(date, locale?, options?) | Date et heure lisibles. Locale par défaut fr-FR. |
formatDateForInput(date) | YYYY-MM-DD, pour un <input type="date">. |
formatDateTimeForInput(date) | YYYY-MM-DDTHH:MM, pour un <input type="datetime-local">. |
Les fonctions de formatage acceptent des options d'Intl.DateTimeFormat.
Les fonctions ForInput produisent le format exact attendu par les champs HTML. Une date affichée puis renvoyée par un formulaire fait donc l'aller-retour sans reformatage manuel.
Arithmétique
| Fonction | Retour |
|---|---|
addDays(date, days) | Nouvelle Date. days peut être négatif. |
subtractDays(date, days) | Nouvelle Date. |
getDaysBetween(date1, date2) | Nombre de jours, éventuellement négatif. |
Ces fonctions ne mutent jamais l'argument : elles retournent une nouvelle Date.
Comparaison
| Fonction | Retour |
|---|---|
compareDates(date1, date2) | -1, 0 ou 1. Utilisable directement comme comparateur de tri. |
isOverdue(dueDate, today?) | true si l'échéance est dépassée. today vaut la date courante par défaut. |
const sorted = items.sort((a, b) => compareDates(a.due_date, b.due_date));Fuseaux horaires
Tout ce qui précède travaille en UTC, ce qui suffit à une date de calendrier. Un instant, lui, doit parfois être lu dans le fuseau d'exploitation du site : un rendez-vous de 9 h reste 9 h de part et d'autre du changement d'heure, alors que son décalage UTC, lui, change. Ces quatre fonctions sont le seul endroit du produit qui traite cette conversion, et elles n'utilisent qu'Intl, sans aucune dépendance.
| Fonction | Retour |
|---|---|
utcToZonedParts(instant, timeZone) | { year, month, day, hour, minute, second, weekday, minuteOfDay }, l'heure murale lue dans ce fuseau. month va de 1 à 12, weekday de 0 pour dimanche à 6. |
getTimeZoneOffset(instant, timeZone) | Décalage en minutes à cet instant. Positif à l'est de Greenwich : l'heure locale vaut UTC plus le décalage. |
zonedPartsToUtc(parts, timeZone) | L'instant que nomme une heure murale. Accepte { year, month, day, hour, minute } ou { year, month, day, minuteOfDay }. |
formatInTimeZone(instant, timeZone, options?, locale?) | Chaîne formatée dans ce fuseau. Locale par défaut fr-CA. |
// 9 h à Montréal, l'hiver puis l'été : deux décalages, une seule écriture.
zonedPartsToUtc({ year: 2026, month: 1, day: 15, hour: 9 }, 'America/Toronto');
// 2026-01-15T14:00:00.000Z
zonedPartsToUtc({ year: 2026, month: 7, day: 15, hour: 9 }, 'America/Toronto');
// 2026-07-15T13:00:00.000ZLes deux jours particuliers
Le décalage dépend de l'instant, et l'instant dépend du décalage. zonedPartsToUtc atteint le point fixe en mesurant le décalage deux fois, puis en gardant le candidat dont la relecture redonne bien l'heure demandée.
- Passage à l'heure avancée : 2 h 30 n'existe pas ce matin-là, aucun candidat ne se relit, et l'heure est repoussée après le saut. Un appelant qui doit refuser ce créneau plutôt que le déplacer compare le résultat à ce qu'il a demandé, avec
utcToZonedParts. - Retour à l'heure normale : 1 h 30 existe deux fois, les deux candidats se relisent, et c'est le premier qui est retenu. Un horaire ne glisse donc jamais silencieusement d'une heure plus tard.
minuteOfDay a le droit de dépasser 1440 : la minute 1500 est 1 h le lendemain. C'est ce qui garde exprimable une plage d'ouverture qui traverse minuit.
Notes
Le fuseau d'affichage de l'application vient de LRVO_TIMEZONE, lu par getAppConfig(). Ces helpers ne le lisent pas : ils travaillent en UTC, ou dans le fuseau qu'on leur passe, et laissent la présentation au composant. Les calculs restent déterministes et testables sans environnement.
Une date de calendrier n'est pas un instant. Une échéance ou une date de publication est une date de calendrier : la stocker et la comparer en UTC évite qu'elle change de jour selon l'endroit d'où on la lit. Un horodatage d'évènement, lui, est un instant et se stocke en timestamptz.