Fuseaux horaires en Kotlin : maîtrisez le temps sans perdre la tête

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

Pourquoi le temps est un casse-tête (et comment Kotlin le résout)

\n\n

Si vous avez déjà développé une application servant des utilisateurs dans différents fuseaux horaires, vous connaissez cette douleur. Une requête arrive avec un timestamp, la base de données stocke en UTC, un utilisateur à New York se plaint d'avoir reçu une notification à 3h du matin alors que la base indique 8h. Ça vous parle ?

\n\n

La gestion des fuseaux horaires est ce domaine de la programmation où une simplicité apparente cache des dizaines de bugs. Mais Kotlin, héritier des meilleures pratiques de Java, nous offre un arsenal puissant pour dompter ce chaos. Dans cet article, nous allons voir comment travailler correctement avec les fuseaux horaires en Kotlin, éviter les erreurs classiques et écrire un code qui résiste au passage à l'heure d'été.

\n\n

Petit rappel : qu'est-ce qu'un fuseau horaire ?

\n\n

Avant de plonger dans le code, mettons-nous d'accord sur les termes. Un fuseau horaire (time zone) n'est pas simplement un décalage par rapport à UTC. C'est aussi un ensemble de règles de passage à l'heure d'été/hiver (DST). Par exemple, Europe/Paris n'est pas seulement +1 ou +2 heures, c'est aussi un historique de changements (la France applique toujours le changement d'heure).

\n\n

Dans le monde Java/Kotlin, deux concepts clés gèrent cela :

\n\n
    \n
  • ZoneId — l'identifiant du fuseau horaire (ex : "America/New_York"). Il contient tout l'historique DST.
  • \n
  • ZoneOffset — un simple décalage (ex : +05:00). Sans règles de transition.
  • \n
\n\n

La différence est cruciale : si vous utilisez ZoneOffset au lieu de ZoneId, votre code plantera le jour du changement d'heure. Souvenez-vous-en.

\n\n

Les classes de base java.time en Kotlin

\n\n

Kotlin supporte entièrement le package java.time (Java 8+). Si vous développez pour Android, n'oubliez pas d'ajouter le desugaring ou d'utiliser ThreeTenABP. Pour le Kotlin serveur, aucun problème.

\n\n

Instant — un moment dans le temps

\n\n

Instant représente un moment absolu sur la ligne temporelle. Il n'est lié à aucun fuseau horaire. C'est ce qu'il faut stocker en base de données.

\n\n
import java.time.Instant\n\nfun main() {\n    val now = Instant.now()\n    println(now) // 2025-03-15T10:30:00.123Z\n}
\n\n

Remarquez la lettre Z à la fin — c'est l'heure Zulu, soit UTC. Stockez tout en UTC — c'est la règle d'or.

\n\n

ZonedDateTime — l'heure avec fuseau

\n\n

Quand vous devez afficher l'heure à un utilisateur, utilisez ZonedDateTime. Il stocke la date, l'heure, le décalage et l'identifiant du fuseau.

\n\n
import java.time.ZonedDateTime\nimport java.time.ZoneId\n\nfun main() {\n    val parisTime = ZonedDateTime.now(ZoneId.of("Europe/Paris"))\n    println(parisTime) // 2025-03-15T11:30:00.123+01:00[Europe/Paris]\n}
\n\n

Vous voyez les crochets ? C'est justement le ZoneId qui permet au système de gérer correctement l'heure d'été.

\n\n

OffsetDateTime — l'heure avec décalage

\n\n

Si vous n'avez pas besoin d'informations sur le fuseau, mais seulement du décalage, utilisez OffsetDateTime. Il est plus léger et plus rapide.

\n\n
import java.time.OffsetDateTime\nimport java.time.ZoneOffset\n\nfun main() {\n    val offsetTime = OffsetDateTime.now(ZoneOffset.ofHours(1))\n    println(offsetTime) // 2025-03-15T11:30:00.123+01:00\n}
\n\n

Mais attention : si vous enregistrez un OffsetDateTime pour New York et que les États-Unis passent à l'heure d'été, votre décalage deviendra obsolète.

\n\n

Conversion entre fuseaux horaires en Kotlin

\n\n

La tâche la plus courante : prendre une heure depuis la base (UTC) et l'afficher dans le fuseau horaire de l'utilisateur. Voyons comment faire correctement.

\n\n

De l'UTC vers le fuseau local

\n\n
import java.time.Instant\nimport java.time.ZoneId\nimport java.time.ZonedDateTime\n\nfun convertUtcToLocal(utcInstant: Instant, targetZone: String): ZonedDateTime {\n    return utcInstant.atZone(ZoneId.of(targetZone))\n}\n\nfun main() {\n    val utcNow =                

Blogs

Book Recommendations