DateTime vs DateTimeOffset

743

Actuellement, nous avons un moyen standard de traiter les .NET DateTimede manière consciente de TimeZone: chaque fois que nous en produisons un, DateTimenous le faisons en UTC (par exemple en utilisant DateTime.UtcNow), et chaque fois que nous en affichons un, nous convertissons à partir de l'UTC en heure locale de l'utilisateur. .

Cela fonctionne bien, mais j'ai lu DateTimeOffsetet comment il capture l'heure locale et UTC dans l'objet lui-même. La question est donc de savoir quels seraient les avantages de l'utilisation DateTimeOffsetpar rapport à ce que nous avons déjà fait?

David Reis
la source
3
Il y a quelques bonnes réponses ci-dessous. Mais je me demande toujours ce qui, le cas échéant, aurait pu vous convaincre de commencer à utiliser DateTimeOffset.
HappyNomad
1
Pourrait aussi bien voir quand-vous-préférez-datetime-over-datetimeoffset
nawfal
En matière de stockage, stackoverflow.com/questions/4715620/… est également intéressant.
Dejan

Réponses:

1168

DateTimeOffsetest une représentation du temps instantané (également appelé temps absolu ). Par cela, je veux dire un moment dans le temps qui est universel pour tout le monde (sans compter les secondes intercalaires ou les effets relativistes de la dilatation du temps ). Une autre façon de représenter le temps instantané est avec un DateTime.Kindest DateTimeKind.Utc.

Ceci est distinct du temps du calendrier (également connu sous le nom de temps civil ), qui est une position sur le calendrier de quelqu'un, et il existe de nombreux calendriers différents dans le monde entier. Nous appelons ces calendriers des fuseaux horaires . L'heure du calendrier est représentée par un DateTime.Kindest DateTimeKind.Unspecified, ou DateTimeKind.Local. Et .Localn'a de sens que dans les scénarios où vous avez une compréhension implicite de l'emplacement de l'ordinateur qui utilise le résultat. (Par exemple, le poste de travail d'un utilisateur)

Alors, pourquoi DateTimeOffsetau lieu d'un UTC DateTime? Tout est question de perspective. Prenons une analogie - nous prétendons être des photographes.

Imaginez que vous vous tenez sur une chronologie de calendrier, pointant une caméra vers une personne sur la chronologie instantanée disposée devant vous. Vous alignez votre caméra selon les règles de votre fuseau horaire - qui changent périodiquement en raison de l'heure d'été ou en raison d'autres modifications de la définition légale de votre fuseau horaire. (Vous n'avez pas une main ferme, donc votre appareil photo est fragile.)

La personne debout sur la photo verrait l'angle sous lequel votre appareil photo est venu. Si d'autres prenaient des photos, elles pourraient prendre des angles différents. C'est ce que représente la Offsetpartie du DateTimeOffset.

Donc, si vous étiquetez votre appareil photo "heure de l'Est", parfois vous pointez à partir de -5, et parfois vous pointez à partir de -4. Il y a des caméras partout dans le monde, toutes étiquetées des choses différentes, et toutes pointant vers la même chronologie instantanée sous différents angles. Certains d'entre eux sont juste à côté (ou au-dessus) les uns des autres, il ne suffit donc pas de connaître le décalage pour déterminer le fuseau horaire auquel l'heure est liée.

Et qu'en est-il de l'UTC? Eh bien, c'est la seule caméra qui est garantie d'avoir une main ferme. C'est sur un trépied, fermement ancré dans le sol. Ça ne va nulle part. Nous appelons son angle de perspective le décalage nul.

Visualisation du temps instantané vs du calendrier

Alors - que nous dit cette analogie? Il fournit des directives intuitives-

  • Si vous représentez le temps par rapport à un endroit en particulier, représentez-le en temps calendaire avec a DateTime. Assurez-vous simplement de ne jamais confondre un calendrier avec un autre. Unspecifieddevrait être votre hypothèse. Localest seulement utile venant de DateTime.Now. Par exemple, je pourrais l'obtenir DateTime.Nowet l'enregistrer dans une base de données - mais quand je le récupère, je dois supposer que c'est le cas Unspecified. Je ne peux pas compter sur le fait que mon calendrier local est le même que celui à l'origine.

  • Si vous devez toujours être certain du moment, assurez-vous de représenter le temps instantané. Utilisez DateTimeOffsetpour l'appliquer, ou utilisez UTC DateTimepar convention.

  • Si vous avez besoin de suivre un moment instantané, mais que vous voulez également savoir "À quelle heure l'utilisateur pensait-il qu'il était sur son calendrier local?" - alors vous devez utiliser un DateTimeOffset. C'est très important pour les systèmes de chronométrage, par exemple - à la fois pour des questions techniques et juridiques.

  • Si vous devez modifier un enregistrement antérieur DateTimeOffset, vous n'avez pas suffisamment d'informations dans le décalage seul pour vous assurer que le nouveau décalage est toujours pertinent pour l'utilisateur. Vous devez également stocker un identifiant de fuseau horaire (pensez - j'ai besoin du nom de cette caméra pour pouvoir prendre une nouvelle photo même si la position a changé).

    Il convient également de noter que Noda Time a une représentation appelée ZonedDateTimeà cet effet, tandis que la bibliothèque de classes de base .Net n'a rien de similaire. Vous devez stocker à la fois DateTimeOffsetune TimeZoneInfo.Idvaleur et une valeur.

  • Parfois, vous souhaiterez représenter une heure de calendrier qui est locale à "celui qui la regarde". Par exemple, lors de la définition de ce que signifie aujourd'hui . Aujourd'hui est toujours de minuit à minuit, mais ceux-ci représentent un nombre presque infini de plages qui se chevauchent sur la chronologie instantanée. (Dans la pratique, nous avons un nombre fini de fuseaux horaires, mais vous pouvez exprimer des décalages jusqu'à la graduation). Donc, dans ces situations, assurez-vous de comprendre comment limiter le "qui demande?" question vers le bas à un seul fuseau horaire, ou de les traduire en temps instantané, le cas échéant.

Voici quelques autres petits détails à DateTimeOffsetce sujet pour sauvegarder cette analogie, et quelques conseils pour la garder droite:

  • Si vous comparez deux DateTimeOffsetvaleurs, elles sont d'abord normalisées à un décalage nul avant de les comparer. En d'autres termes, 2012-01-01T00:00:00+00:00et se 2012-01-01T02:00:00+02:00réfèrent au même moment instantané, et sont donc équivalents.

  • Si vous effectuez un test unitaire et devez être certain du décalage, testez à la fois la DateTimeOffsetvaleur et la .Offsetpropriété séparément.

  • Il existe une conversion implicite unidirectionnelle intégrée au framework .Net qui vous permet de passer un DateTimedans n'importe quel DateTimeOffsetparamètre ou variable. Ce faisant, les .Kindquestions . Si vous passez un type UTC, il se poursuivra avec un décalage nul, mais si vous passez l'un .Localou l' autre .Unspecified, il supposera être local . Le cadre dit essentiellement: "Eh bien, vous m'avez demandé de convertir le temps du calendrier en temps instantané, mais je n'ai aucune idée d'où cela vient, donc je vais simplement utiliser le calendrier local." C'est un énorme problème si vous chargez un fichier non spécifié DateTimesur un ordinateur avec un fuseau horaire différent. (À mon humble avis - cela devrait lever une exception - mais ce n'est pas le cas.)

Prise sans vergogne:

Beaucoup de gens ont partagé avec moi qu'ils trouvent cette analogie extrêmement précieuse, donc je l'ai incluse dans mon cours Pluralsight, Fondamentaux de la date et de l'heure . Vous trouverez une procédure pas à pas de l'analogie de la caméra dans le deuxième module, "Context Matters", dans le clip intitulé "Calendar Time vs. Instantaneous Time".

Matt Johnson-Pint
la source
4
@ZackJannsen Si vous avez un fichier DateTimeOffseten C #, vous devez le conserver sur un DATETIMEOFFSETserveur SQL Server. DATETIME2ou tout simplement DATETIME(selon la plage requise) conviennent pour les DateTimevaleurs normales . Oui - vous pouvez résoudre une heure locale à partir de n'importe quel appariement de fuseau horaire + dto ou utc. La différence est - voulez-vous toujours calculer les règles avec chaque résolution, ou voulez-vous les précalculer? Dans de nombreux cas (parfois pour des raisons juridiques), un DTO est un meilleur choix.
Matt Johnson-Pint
3
@ZackJannsen Pour la deuxième partie de votre question, je vous recommande d'en faire autant que possible côté serveur. Javascript n'est pas génial pour le calcul du fuseau horaire. Si vous devez le faire, utilisez l'une de ces bibliothèques . Mais le côté serveur est le meilleur. Si vous avez d'autres questions plus détaillées, veuillez commencer une nouvelle question SO pour eux et je répondrai si je le peux. Merci.
Matt Johnson-Pint
4
@JoaoLeme - Cela dépend d'où vous l'avez obtenu. Vous avez raison de dire que si vous dites DateTimeOffset.Nowsur le serveur, vous obtiendrez en effet l'offset du serveur. Le fait est que le DateTimeOffsettype peut conserver ce décalage. Vous pouvez tout aussi facilement le faire sur le client, l'envoyer au serveur, puis votre serveur connaîtra le décalage du client.
Matt Johnson-Pint
8
J'adore vraiment l'analogie avec la caméra.
Sachin Kainth
2
Oui c'est correct. Sauf que le DTO est stocké en tant que paire (heure locale, décalage) et non en tant que paire (temps utc, décalage). En d'autres termes, le décalage par rapport à l'UTC se reflète déjà dans l'heure locale. Pour reconvertir en utc, inversez le signe du décalage et appliquez-le à l'heure locale.
Matt Johnson-Pint
328

De Microsoft:

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

source: "Choix entre DateTime, DateTimeOffset, TimeSpan et TimeZoneInfo" , MSDN

Nous utilisons DateTimeOffsetpresque tout car notre application traite des moments particuliers (par exemple, lorsqu'un enregistrement a été créé / mis à jour). En remarque, nous utilisons également DATETIMEOFFSETdans SQL Server 2008.

Je considère DateTimeque cela est utile lorsque vous souhaitez traiter uniquement les dates, les heures uniquement ou les traiter dans un sens générique. Par exemple, si vous avez une alarme que vous voulez partir tous les jours à 7 heures, vous pouvez stocker que dans un DateTimeutilisant un DateTimeKindde Unspecifiedparce que vous voulez aller au large à 7h du matin quelle que soit l' heure d' été. Mais si vous souhaitez représenter l'historique des événements d'alarme, vous devez utiliser DateTimeOffset.

Soyez prudent lorsque vous utilisez un mélange de DateTimeOffsetet DateTimesurtout lorsque vous attribuez et comparez les types. En outre, ne comparez que les DateTimeinstances identiques DateTimeKindcar elles DateTimeignorent le décalage du fuseau horaire lors de la comparaison.

Argile
la source
146
La réponse acceptée est trop longue et l'analogie est tendue, c'est une réponse bien meilleure et plus concise OMI.
Nexus dit
10
Je dirai simplement que j'aime aussi cette réponse et que j'ai voté positivement. Bien que dans la dernière partie - même en garantissant Kindla même chose, la comparaison pourrait être erronée. Si les deux parties l'ont, DateTimeKind.Unspecifiedvous ne savez pas vraiment qu'elles viennent du même fuseau horaire. Si les deux côtés le sont DateTimeKind.Local, la plupart des comparaisons vont bien, mais vous pouvez toujours avoir des erreurs si un côté est ambigu dans le fuseau horaire local. Vraiment, seules les DateTimeKind.Utccomparaisons sont infaillibles, et oui, DateTimeOffsetc'est généralement préférable. (Acclamations!)
Matt Johnson-Pint
1
+1 J'ajouterais à ceci: le DataType que vous choisissez doit refléter votre intention. N'utilisez pas DateTimeOffset partout, juste pour cause. Si le décalage est important pour vos calculs et vos lectures à partir / persistantes dans la base de données, utilisez DateTimeOffset. Si cela n'a pas d'importance, utilisez DateTime, afin que vous compreniez (juste en regardant le DataType) que le décalage ne devrait pas avoir d'incidence et que les temps devraient rester relatifs à la localisation du serveur / de la machine sur laquelle votre code C # s'exécute.
MikeTeeVee
"Mais si vous voulez représenter l'historique des événements d'alarme, vous utiliseriez DateTimeOffset." Pourriez-vous expliquer pourquoi vous pensez que c'est le cas? Je pourrais compléter cela en lisant toutes les informations sur cette page, mais peut-être pourriez-vous également fournir ces informations d'une manière facile à lire. Ceci est une réponse très lisible.
Barrosy
78

DateTime est capable de stocker seulement deux heures distinctes, l'heure locale et UTC. La propriété Kind indique lequel.

DateTimeOffset développe cela en étant capable de stocker les heures locales de n'importe où dans le monde. Il stocke également le décalage entre cette heure locale et UTC. Notez que DateTime ne peut pas faire cela sauf si vous ajoutez un membre supplémentaire à votre classe pour stocker ce décalage UTC. Ou ne travaillez qu'avec UTC. Ce qui en soi est une belle idée d'ailleurs.

Hans Passant
la source
33

Il y a quelques endroits où DateTimeOffsetcela a du sens. La première est lorsque vous avez affaire à des événements récurrents et à l'heure d'été. Disons que je veux régler une alarme pour qu'elle se déclenche à 9 h tous les jours. Si j'utilise la règle "stocker en UTC, afficher comme heure locale", l'alarme se déclenchera à une heure différente lorsque l'heure d'été est en vigueur.

Il y en a probablement d'autres, mais l'exemple ci-dessus est en fait celui que j'ai rencontré par le passé (c'était avant l'ajout de DateTimeOffsetla BCL - ma solution à l'époque était de stocker explicitement l'heure dans le fuseau horaire local et d'enregistrer les informations de fuseau horaire à côté d'elle: essentiellement ce qui DateTimeOffsetfait en interne).

Dean Harding
la source
12
DateTimeOffset ne résout pas le problème de l'heure d'été
JarrettV
2
L'utilisation de la classe TimeZoneInfo comporte des règles pour l'heure d'été. si vous êtes sur .net 3.5 ou version ultérieure, utilisez les classes TimeZone ou TimeZoneInfo pour gérer les dates qui doivent gérer l'heure d'été en conjonction avec le décalage de fuseau horaire.
Zack Jannsen
1
Oui, bon exemple d'exception (l'application d'alarme), mais lorsque l'heure est plus importante que la date, vous devez vraiment les stocker séparément dans votre structure de données de planification pour l'application, c'est-à-dire le type d'occurrence = Quotidien et l'heure = 09h00. Le point ici est que le développeur doit savoir quel type de date il enregistre, calcule ou présente aux utilisateurs. En particulier, les applications ont tendance à être plus globales maintenant, nous avons Internet comme magasins d'applications standard et grands pour lesquels écrire des logiciels. En tant que nœud latéral, j'aimerais également que Microsoft ajoute une structure de date et d'heure distincte.
Tony Wall
3
Résumant les commentaires de Jarrett et Zack: Il semble que DateTimeOffset seul ne gérera pas le problème DST, mais l'utilisation de DateTimeOffset en conjonction avec TimeZoneInfo le gèrera. Ce n'est pas différent de DateTime où le type est Utc. Dans les deux cas, je dois connaître le fuseau horaire (pas seulement le décalage) du calendrier vers lequel je projette le moment. (Je pourrais stocker cela dans le profil d'un utilisateur ou l'obtenir du client (par exemple Windows) si possible). Ça sonne bien?
Jeremy Cook
"Il y a quelques endroits où DateTimeOffset a du sens." --- Sans doute, il est plus souvent logique que non.
Ronnie Overby
23

La distinction la plus importante est que DateTime ne stocke pas les informations de fuseau horaire, contrairement à DateTimeOffset.

Bien que DateTime fasse la distinction entre UTC et Local, aucun décalage de fuseau horaire explicite ne lui est associé. Si vous effectuez un type de sérialisation ou de conversion, le fuseau horaire du serveur sera utilisé. Même si vous créez manuellement une heure locale en ajoutant des minutes pour compenser une heure UTC, vous pouvez toujours obtenir un bit dans l'étape de sérialisation, car (en raison de l'absence de décalage explicite dans DateTime), il utilisera le décalage de fuseau horaire du serveur.

Par exemple, si vous sérialisez une valeur DateTime avec Kind = Local en utilisant Json.Net et un format de date ISO, vous obtiendrez une chaîne comme 2015-08-05T07:00:00-04. Notez que la dernière partie (-04) n'a rien à voir avec votre DateTime ou tout décalage que vous avez utilisé pour le calculer ... c'est simplement le décalage de fuseau horaire du serveur.

Pendant ce temps, DateTimeOffset inclut explicitement le décalage. Il peut ne pas inclure le nom du fuseau horaire, mais au moins il comprend l'offset, et si vous le sérialisez, vous obtiendrez l'offset explicitement inclus dans votre valeur au lieu de l'heure locale du serveur.

Triynko
la source
14
avec toutes les réponses ci-dessus, je me demande pourquoi personne n'a pris la peine d'écrire votre seule phrase qui résume toutThe most important distinction is that DateTime does not store time zone information, while DateTimeOffset does.
Korayem
9
DateTimeOffset ne stocke PAS les informations de fuseau horaire. Le document MS intitulé "Choisir entre DateTime, DateTimeOffset, TimeSpan et TimeZoneInfo" spécifie ceci en déclarant: "Une valeur DateTimeOffset n'est pas liée à un fuseau horaire particulier, mais peut provenir de n'importe lequel d'une variété de fuseaux horaires". Cela dit, le fuseau horaire DateTimeOffset EST AWARE, contenant le décalage par rapport à l'UTC, ce qui fait toute la différence et c'est pourquoi il est recommandé par MS de classe par défaut lors du développement d'applications qui traitent des informations de date. Si vous vous souciez vraiment du fuseau horaire spécifique d'où proviennent les données, vous devez le conserver séparément
stonedauwg
1
Oui, mais comme cela a été montré dans de nombreux endroits, les heures + ou - ne disent rien sur le fuseau horaire dans lequel vous vous trouviez et sont finalement inutiles. Selon ce que vous devez faire, vous pouvez tout aussi bien stocker une date / heure que Kind.Unspecified, puis stocker l'ID de son fuseau horaire et je pense que vous êtes en fait mieux.
Arwin
13

Ce morceau de code de Microsoft explique tout:

// Find difference between Date.Now and Date.UtcNow
  date1 = DateTime.Now;
  date2 = DateTime.UtcNow;
  difference = date1 - date2;
  Console.WriteLine("{0} - {1} = {2}", date1, date2, difference);

  // Find difference between Now and UtcNow using DateTimeOffset
  dateOffset1 = DateTimeOffset.Now;
  dateOffset2 = DateTimeOffset.UtcNow;
  difference = dateOffset1 - dateOffset2;
  Console.WriteLine("{0} - {1} = {2}", 
                    dateOffset1, dateOffset2, difference);
  // If run in the Pacific Standard time zone on 4/2/2007, the example
  // displays the following output to the console:
  //    4/2/2007 7:23:57 PM - 4/3/2007 2:23:57 AM = -07:00:00
  //    4/2/2007 7:23:57 PM -07:00 - 4/3/2007 2:23:57 AM +00:00 = 00:00:00
Mojtaba
la source
9

La plupart des réponses sont bonnes, mais j'ai pensé à ajouter d'autres liens de MSDN pour plus d'informations

dekdev
la source
7

Une différence majeure est qu'il DateTimeOffsetpeut être utilisé en conjonction avec TimeZoneInfopour convertir en heures locales dans des fuseaux horaires autres que l'actuel.

Ceci est utile sur une application serveur (par exemple ASP.NET) accessible aux utilisateurs dans différents fuseaux horaires.

Joe
la source
3
@Bugeo Bugeo est vrai, mais il y a un risque. Vous pouvez comparer deux DateTimes en appelant d'abord "ToUniversalTime" sur chacun. Si vous avez exactement une valeur dans la comparaison qui est DateTimeKind = Non spécifié, votre stratégie échouera. Ce risque d'échec est une raison pour considérer DateTimeOffset sur DateTime lorsque des conversions en heure locale sont requises.
Zack Jannsen
Comme ci-dessus, je pense que dans ce scénario, il vaut mieux stocker le TimeZoneId que d'utiliser DateTimeOffset, ce qui ne signifie finalement rien.
Arwin
2

Le seul côté négatif de DateTimeOffset que je vois est que Microsoft "a oublié" (par conception) de le prendre en charge dans leur classe XmlSerializer. Mais il a depuis été ajouté à la classe d'utilitaires XmlConvert.

XmlConvert.ToDateTimeOffset

XmlConvert.ToString

Je dis allez-y et utilisez DateTimeOffset et TimeZoneInfo en raison de tous les avantages, faites simplement attention lors de la création d'entités qui seront ou peuvent être sérialisées vers ou depuis XML (tous les objets métier alors).

Tony Wall
la source