Fuseaux horaires en Go : ne perdez plus une heure, un jour ou des données

Formateur Python en ligne pour débutants

Apprenez Python facilement sans surcharge théorique. Résolvez des tâches pratiques avec vérification automatique et écrivez du code directement dans le navigateur.

Start Course

Introduction : pourquoi le temps n'est pas qu'une simple affaire de tic-tac

\n\n

Si vous avez déjà développé une application en Go manipulant des dates avec des utilisateurs répartis dans différents pays, vous avez forcément rencontré le casse-tête des fuseaux horaires. Rien de bien compliqué, pensez-vous ? Il suffit d'utiliser time.Now() et le tour est joué. Jusqu'au jour où votre serveur est en UTC, votre client à Paris (UTC+1 ou +2), et votre « aujourd'hui » se transforme mystérieusement en « hier ».

\n\n

Dans cet article, nous allons voir comment travailler efficacement avec les fuseaux horaires en Go, à travers des exemples concrets, sans blabla, avec du code prêt à l'emploi. Vous apprendrez à ne pas perdre de données, à ne pas devenir fou lors du débogage et à rendre votre application robuste face aux décalages horaires. C'est parti !

\n\n

Les bases : que cache time.Time ?

\n\n

En Go, le type time.Time n'est pas qu'un simple nombre de millisecondes. C'est une structure qui stocke deux éléments clés :

\n\n
    \n
  • Le temps absolu — le nombre de nanosecondes écoulées depuis le 1er janvier 1970 (timestamp Unix).
  • \n
  • Le Location — un pointeur vers le fuseau horaire.
  • \n
\n\n

C'est ce Location qui détermine l'affichage : 14h00 à Paris ou 12h00 UTC. L'erreur la plus fréquente chez les débutants est de croire que time.Now() renvoie l'heure locale. En réalité, il renvoie l'heure actuelle avec le fuseau horaire du serveur. Déplacez votre code sur un autre serveur, et tout se dérègle.

\n\n

Créer une date avec un fuseau spécifique

\n\n

La méthode la plus simple est d'utiliser time.LoadLocation. Elle charge le fuseau depuis la base de données système IANA (généralement dans /usr/share/zoneinfo).

\n\n
package main\n\nimport (\n    "fmt"\n    "time"\n)\n\nfunc main() {\n    loc, err := time.LoadLocation("Europe/Paris")\n    if err != nil {\n        panic(err)\n    }\n\n    // Créer une date dans un fuseau spécifique\n    t := time.Date(2025, 3, 15, 14, 30, 0, 0, loc)\n    fmt.Println(t) // 2025-03-15 14:30:00 +0100 CET\n}\n
\n\n

Notez que time.Date prend le Location en dernier argument. Si vous passez time.UTC ou time.Local, le comportement sera différent.

\n\n

Et si le fuseau n'est pas disponible sur le système ?

\n\n

Parfois (par exemple dans des images Docker minimalistes), la base zoneinfo est absente. La solution est d'utiliser time.FixedZone pour créer un fuseau avec un décalage fixe :

\n\n
loc := time.FixedZone("CET", 1*60*60) // +1 heure par rapport à UTC\nt := time.Now().In(loc)\nfmt.Println(t)\n
\n\n

Inconvénient : FixedZone ne gère pas l'heure d'été ni les changements historiques. Pour la plupart des applications modernes, cela suffit. Mais si vous avez besoin d'une précision historique (par exemple pour des transactions financières datant de 2014), utilisez LoadLocation.

\n\n

La règle d'or du développement serveur : stocker en UTC

\n\n

La règle d'or pour travailler avec les fuseaux horaires en Go (et dans tout autre langage) est de toujours stocker l'heure en UTC et de ne convertir qu'au moment de l'affichage. Pourquoi ?

\n\n
    \n
  • Le serveur peut changer de fuseau horaire.
  • \n
  • Les utilisateurs peuvent être dans des zones différentes.
  • \n
  • La comparaison et le tri des dates UTC sont triviaux.
  • \n
  • Les logs sont plus faciles à analyser.
  • \n
\n\n

Voici comment procéder en pratique :

\n\n
// Récupérer l'heure de l'utilisateur (ex : depuis un formulaire)\nuserTime := "2025-03-15 14:30"\nuserLoc, _ := time.LoadLocation("Asia/Tokyo")\n\n// Analyser comme heure locale de l'utilisateur\nt, _ := time.ParseInLocation("2006-01-02 15:04", userTime, userLoc)\n\n// Convertir en UTC pour le stockage\nutcTime := t.UTC()\nfmt.Println(utcTime) // 2025-03-15 05:30:00 +0000 UTC\n
\n\n

Erreur fréquente : perte du Location lors de la sérialisation

\n\n

Lorsque vous enregistrez un time.Time en base de données (par exemple PostgreSQL via database/sql), le driver peut « réinitialiser » le Location en UTC ou Local. Pour éviter les mauvaises surprises, convertissez toujours en UTC avant l'enregistrement.

Blogs

Book Recommendations