Initialisation des systèmes...

Baptiste.Dev
Retour aux notes
LangagesAvancéSérie : C# / .NET

C# : DateTimeOffset et fuseaux horaires

L'instant sans ambiguïté (date + heure + offset UTC), la comparaison par instant, et les conversions de fuseau avec TimeZoneInfo.

Par Baptiste Vidal
3 min de lecture
Mis à jour aujourd'hui
c#csharpdatetimeoffsettimezoneinfoutcfuseauoffset

C# : DateTimeOffset et fuseaux horaires

DateTimeOffset désigne un instant précis et non ambigu : une date, une heure, et son décalage par rapport à UTC. Là où un DateTime en Unspecified laisse planer le doute, un DateTimeOffset dit exactement quel point du temps il représente. C'est le type à privilégier pour un horodatage (log, création d'un enregistrement, message).


# Construire un DateTimeOffset

DateTimeOffset maintenant = DateTimeOffset.Now;     // instant courant + offset local
DateTimeOffset utc = DateTimeOffset.UtcNow;         // instant courant, offset +00:00
 
// Explicite : 1er septembre 2026, 14:30, décalage +02:00 (heure d'été de Paris)
DateTimeOffset precis = new DateTimeOffset(2026, 9, 1, 14, 30, 0, TimeSpan.FromHours(2));

L'offset est un TimeSpan : +02:00, -05:00, +00:00 pour UTC.


# Lire ses différentes vues

Un même instant se lit sous plusieurs angles :

DateTimeOffset d = new DateTimeOffset(2026, 9, 1, 14, 30, 0, TimeSpan.FromHours(2));
 
DateTime murale = d.DateTime;        // 14:30 (l'heure telle qu'affichée à cet offset)
DateTime enUtc = d.UtcDateTime;      // 12:30 (le même instant en UTC)
TimeSpan offset = d.Offset;          // +02:00
 
// Recalculer le même instant sous un autre décalage
DateTimeOffset aNewYork = d.ToOffset(TimeSpan.FromHours(-4));  // 08:30 -04:00

ToOffset ne change pas l'instant : il le ré-exprime dans un autre décalage (les chiffres de l'heure changent, le point du temps non).


# La comparaison compare l'instant

Deux DateTimeOffset avec des décalages différents mais le même instant sont égaux :

DateTimeOffset paris = new DateTimeOffset(2026, 9, 1, 14, 30, 0, TimeSpan.FromHours(2));
DateTimeOffset londres = new DateTimeOffset(2026, 9, 1, 13, 30, 0, TimeSpan.FromHours(1));
 
paris == londres;       // true : c'est le même moment vu de deux villes
paris.EqualsExact(londres); // false : mais l'offset diffère

C'est précisément ce qui manque à DateTime : deux DateTime de chiffres identiques mais de fuseaux différents seraient jugés égaux à tort.


# Convertir entre fuseaux avec TimeZoneInfo

TimeZoneInfo connaît les fuseaux du système et gère l'heure d'été (DST). Depuis .NET 6, les identifiants IANA ("Europe/Paris") marchent sur toutes les plateformes.

TimeZoneInfo paris = TimeZoneInfo.FindSystemTimeZoneById("Europe/Paris");
TimeZoneInfo tokyo = TimeZoneInfo.FindSystemTimeZoneById("Asia/Tokyo");
 
DateTimeOffset instant = DateTimeOffset.UtcNow;
 
DateTimeOffset aParis = TimeZoneInfo.ConvertTime(instant, paris);
DateTimeOffset aTokyo = TimeZoneInfo.ConvertTime(instant, tokyo);

Le fuseau sait aussi si l'heure d'été s'applique, car l'offset change dans l'année :

TimeSpan enEte = paris.GetUtcOffset(new DateTime(2026, 7, 1));   // +02:00
TimeSpan enHiver = paris.GetUtcOffset(new DateTime(2026, 1, 1)); // +01:00

Ne calcule jamais un changement de fuseau en ajoutant un offset fixe à la main : tu te tromperais aux dates de passage à l'heure d'été. Laisse TimeZoneInfo faire la conversion.


# DateTime ou DateTimeOffset ?

  • DateTimeOffset : dès qu'il s'agit d'un instant réel à stocker, comparer ou transmettre (horodatage, événement, log). Non ambigu.
  • DateTime en UTC : acceptable si tu maîtrises que c'est de l'UTC de bout en bout.
  • DateTime local : seulement pour un affichage immédiat, jamais pour persister.

La règle inchangée : stocke un instant en UTC (ou en DateTimeOffset), convertis vers le fuseau de l'utilisateur au dernier moment, pour l'affichage.


# La suite