Choisir entre DateTime, DateOnly, DateTimeOffset, TimeSpan, TimeOnly et TimeZoneInfo

Les applications .NET peuvent utiliser les informations de date et d’heure de plusieurs façons. Les utilisations les plus courantes des informations de date et d’heure sont :

  • Pour refléter seulement une date, les informations d'heure n'étant pas importantes.
  • Pour refléter seulement une heure, les informations de date n'étant pas importantes.
  • Pour refléter une date et une heure abstraites, qui ne sont pas liées à un moment et un endroit spécifiques (par exemple, la plupart des magasins d’une chaîne internationale ouvrent en semaine à 9h00).
  • Pour récupérer les informations de date et d’heure à partir de sources externes à .NET, en général là où les informations de date et d’heure sont stockées dans un type de données simple.
  • Pour identifier de façon univoque et non ambiguë un point unique dans le temps. Certaines applications exigent qu’une date et une heure soient non ambiguës uniquement sur le système hôte. Les autres applications requièrent qu’elle soit non ambiguë entre les systèmes (autrement dit, une date sérialisée sur un système peut être désérialisée de manière significative et utilisée sur un autre système n’importe où dans le monde).
  • Pour conserver plusieurs dates/heures ayant un lien les unes avec les autres (comme la date/heure locale du demandeur et la date/heure de réception par le serveur d’une demande web).
  • Pour effectuer des calculs de date et d'heure, éventuellement avec un résultat qui identifie de façon univoque et non ambiguë un point unique dans le temps.

.NET comprend les types DateTime, DateOnly, DateTimeOffset, TimeSpan, TimeOnly et TimeZoneInfo tous pouvant être utilisés pour créer des applications qui fonctionnent avec des dates et des heures.

Notes

Cet article ne traite pas TimeZone, car ses fonctionnalités sont presque entièrement incorporées dans la classe TimeZoneInfo. Chaque fois que c’est possible, utilisez la classe TimeZoneInfo au lieu de la classe TimeZone.

La structure DateTimeOffset

La structure DateTimeOffset représente une valeur de date et d'heure, ainsi qu'un décalage qui indique de combien cette valeur diffère du temps UTC. Ainsi, la valeur toujours identifie toujours de façon non ambiguë un point unique dans le temps.

Le type DateTimeOffset comprend toutes les fonctionnalités du type DateTime , ainsi que la gestion des fuseaux horaires. Cela convient ainsi pour les applications qui :

  • Identifient de façon univoque et non ambiguë un point unique dans le temps. Le type DateTimeOffset peut être utilisé pour définir de façon non ambiguë la signification de "maintenant", pour consigner les dates/heures des transactions, pour consigner les dates/heures des événements système ou des événements d'une application, et pour enregistrer les dates/heures de création et de modification des fichiers.
  • Effectuent des calculs de date et d'heure.
  • Conservent plusieurs dates/heures ayant un lien entre elles, comme les dates/heures qui sont stockées sous la forme de deux valeurs distinctes ou de deux membres d'une structure.

Notes

Ces utilisations pour des valeurs DateTimeOffset sont beaucoup plus courantes que celles pour les valeurs DateTime . Par conséquent, considérez DateTimeOffset comme le type de date et d’heure par défaut pour le développement d’applications.

Une valeur DateTimeOffset n’est pas liée à un fuseau horaire particulier, mais peut provenir de n’importe quel fuseau horaire. L’exemple suivant répertorie les fuseaux horaires auxquels plusieurs valeurs DateTimeOffset (y compris une Heure du Pacifique locale) peuvent appartenir.

using System;
using System.Collections.ObjectModel;

public class TimeOffsets
{
   public static void Main()
   {
      DateTime thisDate = new DateTime(2007, 3, 10, 0, 0, 0);
      DateTime dstDate = new DateTime(2007, 6, 10, 0, 0, 0);
      DateTimeOffset thisTime;

      thisTime = new DateTimeOffset(dstDate, new TimeSpan(-7, 0, 0));
      ShowPossibleTimeZones(thisTime);

      thisTime = new DateTimeOffset(thisDate, new TimeSpan(-6, 0, 0));
      ShowPossibleTimeZones(thisTime);

      thisTime = new DateTimeOffset(thisDate, new TimeSpan(+1, 0, 0));
      ShowPossibleTimeZones(thisTime);
   }

   private static void ShowPossibleTimeZones(DateTimeOffset offsetTime)
   {
      TimeSpan offset = offsetTime.Offset;
      ReadOnlyCollection<TimeZoneInfo> timeZones;

      Console.WriteLine("{0} could belong to the following time zones:",
                        offsetTime.ToString());
      // Get all time zones defined on local system
      timeZones = TimeZoneInfo.GetSystemTimeZones();
      // Iterate time zones
      foreach (TimeZoneInfo timeZone in timeZones)
      {
         // Compare offset with offset for that date in that time zone
         if (timeZone.GetUtcOffset(offsetTime.DateTime).Equals(offset))
            Console.WriteLine("   {0}", timeZone.DisplayName);
      }
      Console.WriteLine();
   }
}
// This example displays the following output to the console:
//       6/10/2007 12:00:00 AM -07:00 could belong to the following time zones:
//          (GMT-07:00) Arizona
//          (GMT-08:00) Pacific Time (US & Canada)
//          (GMT-08:00) Tijuana, Baja California
//
//       3/10/2007 12:00:00 AM -06:00 could belong to the following time zones:
//          (GMT-06:00) Central America
//          (GMT-06:00) Central Time (US & Canada)
//          (GMT-06:00) Guadalajara, Mexico City, Monterrey - New
//          (GMT-06:00) Guadalajara, Mexico City, Monterrey - Old
//          (GMT-06:00) Saskatchewan
//
//       3/10/2007 12:00:00 AM +01:00 could belong to the following time zones:
//          (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna
//          (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague
//          (GMT+01:00) Brussels, Copenhagen, Madrid, Paris
//          (GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb
//          (GMT+01:00) West Central Africa
Imports System.Collections.ObjectModel

Module TimeOffsets
    Public Sub Main()
        Dim thisTime As DateTimeOffset

        thisTime = New DateTimeOffset(#06/10/2007#, New TimeSpan(-7, 0, 0))
        ShowPossibleTimeZones(thisTime)

        thisTime = New DateTimeOffset(#03/10/2007#, New TimeSpan(-6, 0, 0))
        ShowPossibleTimeZones(thisTime)

        thisTime = New DateTimeOffset(#03/10/2007#, New TimeSpan(+1, 0, 0))
        ShowPossibleTimeZones(thisTime)
    End Sub

    Private Sub ShowPossibleTimeZones(offsetTime As DateTimeOffset)
        Dim offset As TimeSpan = offsetTime.Offset
        Dim timeZones As ReadOnlyCollection(Of TimeZoneInfo)

        Console.WriteLine("{0} could belong to the following time zones:", _
                          offsetTime.ToString())
        ' Get all time zones defined on local system
        timeZones = TimeZoneInfo.GetSystemTimeZones()
        ' Iterate time zones
        For Each timeZone As TimeZoneInfo In timeZones
            ' Compare offset with offset for that date in that time zone
            If timeZone.GetUtcOffset(offsetTime.DateTime).Equals(offset) Then
                Console.WriteLine("   {0}", timeZone.DisplayName)
            End If
        Next
        Console.WriteLine()
    End Sub
End Module
' This example displays the following output to the console:
'       6/10/2007 12:00:00 AM -07:00 could belong to the following time zones:
'          (GMT-07:00) Arizona
'          (GMT-08:00) Pacific Time (US & Canada)
'          (GMT-08:00) Tijuana, Baja California
'       
'       3/10/2007 12:00:00 AM -06:00 could belong to the following time zones:
'          (GMT-06:00) Central America
'          (GMT-06:00) Central Time (US & Canada)
'          (GMT-06:00) Guadalajara, Mexico City, Monterrey - New
'          (GMT-06:00) Guadalajara, Mexico City, Monterrey - Old
'          (GMT-06:00) Saskatchewan
'       
'       3/10/2007 12:00:00 AM +01:00 could belong to the following time zones:
'          (GMT+01:00) Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna
'          (GMT+01:00) Belgrade, Bratislava, Budapest, Ljubljana, Prague
'          (GMT+01:00) Brussels, Copenhagen, Madrid, Paris
'          (GMT+01:00) Sarajevo, Skopje, Warsaw, Zagreb
'          (GMT+01:00) West Central Africa

La sortie montre que chaque valeur de date et d'heure de cet exemple peut appartenir à au moins trois fuseaux horaires différents. La valeur DateTimeOffset de 6/10/2007 indique que si une valeur de date/heure représente une heure d’été, son décalage avec le temps UTC ne correspond pas nécessairement au décalage UTC de base du fuseau horaire d’origine ou au décalage avec le temps UTC indiqué par son nom d’affichage. Comme une valeur DateTimeOffset n’est pas fortement couplée avec son fuseau horaire, elle ne peut pas refléter la transition d’un fuseau horaire vers et depuis l’heure d’été. Cela peut être problématique quand des calculs de date et d’heure sont utilisés pour manipuler une valeur DateTimeOffset . (Pour une présentation de la façon d’effectuer des opérations arithmétiques avec des dates et des heures en prenant en compte les règles d’ajustement d’un fuseau horaire, consultez Exécution d’opérations arithmétiques avec des dates et heures.

La structure DateTime

Une valeur DateTime définit une date/heure spécifique. Elle inclut une propriété Kind qui fournit des informations limitées sur le fuseau horaire auquel appartiennent cette date et cette heure. La valeur DateTimeKind retournée par la propriété Kind indique si la valeur DateTime représente la date/heure locale (DateTimeKind.Local), la date/heure UTC (DateTimeKind.Utc) ou une date/heure non spécifiée (DateTimeKind.Unspecified).

La structure DateTime convient aux applications présentant une ou plusieurs des caractéristiques suivantes :

  • Utilisent des dates et des heures abstraites.
  • Utilisent des dates et des heures pour lesquelles les informations de fuseau horaire sont manquantes.
  • Utilisent seulement des dates et des heures UTC.
  • Effectuent des calculs de dates et d'heures, mais sont surtout concernées par des résultats d'ordre général. Par exemple, dans une opération d'addition qui ajoute six mois à une date/heure, il n'est souvent pas important que le résultat soit ajusté pour l'heure d'été.

Sauf si une valeur DateTime particulière représente le temps UTC, cette valeur de date/heure est souvent ambiguë ou limitée en termes de portabilité. Par exemple, si une valeur DateTime représente l’heure locale, elle est portable dans ce fuseau horaire local (c’est-à-dire que si la valeur est désérialisée sur un autre système dans le même fuseau horaire, cette valeur continue d’identifier de façon non ambiguë un point unique dans le temps). En dehors du fuseau horaire local, cette valeur DateTime peut être interprétée de plusieurs façons. Si la propriété Kind de la valeur est DateTimeKind.Unspecified, elle est encore moins portable : elle est maintenant ambiguë dans le même fuseau horaire, voire même sur le système où elle a été sérialisée pour la première fois. Seulement dans le cas où une valeur DateTime représente le temps UTC, celle-ci identifie de façon non ambiguë un point unique dans le temps, indépendamment du système ou du fuseau horaire où la valeur est utilisée.

Important

Lors de l’enregistrement ou du partage de données DateTime, utilisez UTC et définissez la propriété Kind de la valeur DateTime sur DateTimeKind.Utc.

Structure DateOnly

La structure DateOnly représente une date spécifique, sans heure. Étant donné qu’elle n’a aucun composant d’heure, elle représente une date du début de la journée à la fin de la journée. Cette structure est idéale pour stocker des dates spécifiques, comme une date de naissance, une date d’anniversaire, un jour férié ou des dates liées à l’entreprise.

Bien que vous puissiez utiliser DateTime tout en ignorant le composant de temps, il existe quelques avantages à utiliser DateOnly plutôt que DateTime :

  • La structure DateTime peut être déployée dans le jour précédent ou le jour suivant si elle est décalée par un fuseau horaire. DateOnly ne peut pas être décalé par un fuseau horaire, et représente toujours la date qui a été définie.
  • La sérialisation d’une structure DateTime inclut le composant d’heure, qui peut masquer l’intention des données. De plus, DateOnly sérialise moins de données.
  • Lorsque le code interagit avec une base de données, comme SQL Server, des dates entières sont généralement stockées en tant que type de données date, qui n’inclut pas d’heure. DateOnly correspond mieux au type de base de données.

Pour plus d’informations sur DateOnly, consultez Utilisation des structures DateOnly et TimeOnly.

Important

DateOnly n’est pas disponible dans .NET Framework.

La structure TimeSpan

La structure TimeSpan représente un intervalle de temps. Ses deux utilisations courantes sont :

  • Refléter un intervalle de temps entre deux valeurs de date/heure. Par exemple, la soustraction d'une valeur DateTime d'une autre retourne une valeur TimeSpan .
  • Mesurer un temps écoulé. Par exemple, la propriété Stopwatch.Elapsed retourne une valeur TimeSpan qui reflète l’intervalle de temps écoulé depuis l’appel à une des méthodes Stopwatch qui commence à mesurer le temps écoulé.

Une valeur TimeSpan peut également être utilisée en remplacement d’une valeur DateTime quand cette valeur reflète un moment sans référence à un jour précis. Cette utilisation est similaire aux propriétés DateTime.TimeOfDay et DateTimeOffset.TimeOfDay, qui retournent une valeur TimeSpan représentant l’heure sans référence à une date. Par exemple, la structure TimeSpan peut être utilisée pour refléter les heures d'ouverture ou de fermeture quotidiennes d'un magasin, ou elle peut être utilisée pour représenter l'heure où se produit un événement régulier.

L'exemple suivant définit une structure StoreInfo qui inclut des objets TimeSpan pour les heures d'ouverture et de fermeture d'un magasin, ainsi qu'un objet TimeZoneInfo qui représente le fuseau horaire du magasin. La structure comprend également deux méthodes, IsOpenNow et IsOpenAt, qui indiquent si le magasin est ouvert à une heure spécifiée par l'utilisateur, qui est supposé être dans le fuseau horaire local.

using System;

public struct StoreInfo
{
   public String store;
   public TimeZoneInfo tz;
   public TimeSpan open;
   public TimeSpan close;

   public bool IsOpenNow()
   {
      return IsOpenAt(DateTime.Now.TimeOfDay);
   }

   public bool IsOpenAt(TimeSpan time)
   {
      TimeZoneInfo local = TimeZoneInfo.Local;
      TimeSpan offset = TimeZoneInfo.Local.BaseUtcOffset;

      // Is the store in the same time zone?
      if (tz.Equals(local)) {
         return time >= open & time <= close;
      }
      else {
         TimeSpan delta = TimeSpan.Zero;
         TimeSpan storeDelta = TimeSpan.Zero;

         // Is it daylight saving time in either time zone?
         if (local.IsDaylightSavingTime(DateTime.Now.Date + time))
            delta = local.GetAdjustmentRules()[local.GetAdjustmentRules().Length - 1].DaylightDelta;

         if (tz.IsDaylightSavingTime(TimeZoneInfo.ConvertTime(DateTime.Now.Date + time, local, tz)))
            storeDelta = tz.GetAdjustmentRules()[tz.GetAdjustmentRules().Length - 1].DaylightDelta;

         TimeSpan comparisonTime = time + (offset - tz.BaseUtcOffset).Negate() + (delta - storeDelta).Negate();
         return comparisonTime >= open & comparisonTime <= close;
      }
   }
}
Public Structure StoreInfo
    Dim store As String
    Dim tz As TimeZoneInfo
    Dim open As TimeSpan
    Dim close As TimeSpan

    Public Function IsOpenNow() As Boolean
        Return IsOpenAt(Date.Now.TimeOfDay)
    End Function

    Public Function IsOpenAt(time As TimeSpan) As Boolean
        Dim local As TimeZoneInfo = TimeZoneInfo.Local
        Dim offset As TimeSpan = TimeZoneInfo.Local.BaseUtcOffset

        ' Is the store in the same time zone?
        If tz.Equals(local) Then
            Return time >= open AndAlso time <= close
        Else
            Dim delta As TimeSpan = TimeSpan.Zero
            Dim storeDelta As TimeSpan = TimeSpan.Zero

            ' Is it daylight saving time in either time zone?
            If local.IsDaylightSavingTime(Date.Now.Date + time) Then
                delta = local.GetAdjustmentRules(local.GetAdjustmentRules().Length - 1).DaylightDelta
            End If
            If tz.IsDaylightSavingTime(TimeZoneInfo.ConvertTime(Date.Now.Date + time, local, tz))
                storeDelta = tz.GetAdjustmentRules(tz.GetAdjustmentRules().Length - 1).DaylightDelta
            End If
            Dim comparisonTime As TimeSpan = time + (offset - tz.BaseUtcOffset).Negate() + (delta - storeDelta).Negate
            Return (comparisonTime >= open AndAlso comparisonTime <= close)
        End If
    End Function
End Structure

La structure StoreInfo peut ensuite être utilisée par le code du client comme ce qui suit.

public class Example
{
   public static void Main()
   {
      // Instantiate a StoreInfo object.
      var store103 = new StoreInfo();
      store103.store = "Store #103";
      store103.tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
      // Store opens at 8:00.
      store103.open = new TimeSpan(8, 0, 0);
      // Store closes at 9:30.
      store103.close = new TimeSpan(21, 30, 0);

      Console.WriteLine("Store is open now at {0}: {1}",
                        DateTime.Now.TimeOfDay, store103.IsOpenNow());
      TimeSpan[] times = { new TimeSpan(8, 0, 0), new TimeSpan(21, 0, 0),
                           new TimeSpan(4, 59, 0), new TimeSpan(18, 31, 0) };
      foreach (var time in times)
         Console.WriteLine("Store is open at {0}: {1}",
                           time, store103.IsOpenAt(time));
   }
}
// The example displays the following output:
//       Store is open now at 15:29:01.6129911: True
//       Store is open at 08:00:00: True
//       Store is open at 21:00:00: False
//       Store is open at 04:59:00: False
//       Store is open at 18:31:00: False
Module Example
    Public Sub Main()
        ' Instantiate a StoreInfo object.
        Dim store103 As New StoreInfo()
        store103.store = "Store #103"
        store103.tz = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time")
        ' Store opens at 8:00.
        store103.open = new TimeSpan(8, 0, 0)
        ' Store closes at 9:30.
        store103.close = new TimeSpan(21, 30, 0)

        Console.WriteLine("Store is open now at {0}: {1}",
                          Date.Now.TimeOfDay, store103.IsOpenNow())
        Dim times() As TimeSpan = {New TimeSpan(8, 0, 0),
                                    New TimeSpan(21, 0, 0),
                                    New TimeSpan(4, 59, 0),
                                    New TimeSpan(18, 31, 0)}
        For Each time In times
            Console.WriteLine("Store is open at {0}: {1}",
                              time, store103.IsOpenAt(time))
        Next
    End Sub
End Module
' The example displays the following output:
'       Store is open now at 15:29:01.6129911: True
'       Store is open at 08:00:00: True
'       Store is open at 21:00:00: False
'       Store is open at 04:59:00: False
'       Store is open at 18:31:00: False

Structure TimeOnly

La structure TimeOnly représente une valeur d’heure du jour, comme une alarme quotidienne ou l’heure à laquelle vous mangez le déjeuner chaque jour. TimeOnly est limité à la plage 00:00:00.0 000 000 - 23:59:59.9 999 999, une heure spécifique de la journée.

Avant l’introduction du type TimeOnly, les programmeurs utilisaient généralement le type DateTime ou le type TimeSpan pour représenter une heure spécifique. Toutefois, l’utilisation de ces structures pour simuler une heure sans date peut introduire certains problèmes, que TimeOnly résout :

  • TimeSpan représente le temps écoulé, comme le temps mesuré avec un chronomètre. La plage supérieure est supérieure à 29 000 ans, et sa valeur peut être négative pour indiquer un recul dans le temps. Un TimeSpan négatif n’indique pas une heure spécifique de la journée.
  • Si TimeSpan est utilisé comme heure de la journée, il existe un risque qu’il soit manipulé à une valeur en dehors de la journée de 24 heures. TimeOnly ne présente pas ce risque. Par exemple, si le poste d’un employé commence à 18 h et dure 8 heures, l’ajout de 8 heures à la structure TimeOnly passe la valeur à 2:00.
  • L’utilisation de DateTime pour une heure de la journée nécessite qu’une date arbitraire soit associée à l’heure, puis ignorée ultérieurement. Il est courant de choisir DateTime.MinValue (0001-01-01) comme date. Toutefois, si des heures sont soustraites de la valeur DateTime, une exception OutOfRange peut se produire. TimeOnly ne présente pas ce problème, car le temps avance et recule autour de la période de 24 heures.
  • La sérialisation d’une structure DateTime inclut le composant de date, qui peut masquer l’intention des données. De plus, TimeOnly sérialise moins de données.

Pour plus d’informations sur TimeOnly, consultez Utilisation des structures DateOnly et TimeOnly.

Important

TimeOnly n’est pas disponible dans .NET Framework.

La classe TimeZoneInfo

La classe TimeZoneInfo class represents any of the Earth's time zones, and enables the conversion of any date and time in one time zone to its equivalent in another time zone. La classe TimeZoneInfo permet de travailler avec des dates et des heures de façon à ce qu'une valeur de date/heure identifie d'une manière non ambiguë un point unique dans le temps. La classe TimeZoneInfo est également extensible. Bien que dépendante des informations de fuseau horaire fournies pour les systèmes Windows et définies dans le Registre, elle prend en charge la création de fuseaux horaires personnalisés. Elle prend également en charge la sérialisation et la désérialisation des informations de fuseau horaire.

Dans certains cas, tirer parti de la classe TimeZoneInfo peut nécessiter du travail de développement supplémentaire. Si les valeurs de date et d’heure ne sont pas étroitement couplées avec les fuseaux horaires auxquels elles appartiennent, davantage de travail est requis. À moins que votre application ne fournisse un mécanisme permettant de lier une date/heure avec son fuseau horaire associé, une valeur de date/heure peut être facilement dissociée de son fuseau horaire. Une méthode pour lier ces informations consiste à définir une classe ou une structure qui contient à la fois la date/heure et son objet de fuseau horaire associé.

Pour tirer parti de la prise en charge des fuseaux horaires dans .NET, vous devez connaître le fuseau horaire auquel appartient une valeur de date/heure quand cet objet de date/heure est instancié. Le fuseau horaire est souvent inconnu, en particulier dans les applications web ou réseau.

Voir aussi