Actuellement, nous disposons d’un moyen standard de traiter les dates et les heures .net de manière cohérente: chaque fois que nous produisons une DateTime
, nous le faisons en UTC (par exemple, en utilisant DateTime.UtcNow
), et chaque reconvertir de l'heure UTC à l'heure locale de l'utilisateur.
Cela fonctionne bien, mais je lisais à propos de DateTimeOffset
et de la manière dont il capture l'heure locale et l'heure UTC dans l'objet lui-même. La question est donc de savoir quels seraient les avantages d'utiliser DateTimeOffset
par rapport à ce que nous avons déjà fait.
DateTimeOffset
est une représentation de temps instantané (également appelé temps absolu ). J'entends par là un moment universel universel pour tout le monde (sans tenir compte de secondes bissextiles , ni des effets relativistes de dilatation du temps ). Une autre façon de représenter le temps instantané consiste à utiliser un DateTime
où .Kind
est DateTimeKind.Utc
.
Ceci est distinct de heure du calendrier (également appelé heure civile ), qui est une position sur le calendrier de quelqu'un, et il y a beaucoup différents calendriers dans le monde entier. Nous appelons ces calendriers les fuseaux horaires . L'heure du calendrier est représentée par un DateTime
où .Kind
est DateTimeKind.Unspecified
ou DateTimeKind.Local
. Et .Local
n'a de sens que dans les scénarios dans lesquels vous comprenez implicitement l'emplacement de l'ordinateur utilisant le résultat. (Par exemple, le poste de travail d'un utilisateur)
Alors, pourquoi DateTimeOffset
au lieu d’un UTC DateTime
? Tout est une question de perspective. Prenons une analogie: nous ferons semblant d'être des photographes.
Imaginez que vous vous teniez sur une chronologie de calendrier, pointant une caméra sur une personne sur la chronologie instantanée affichée devant vous. Vous alignez votre appareil photo en fonction des règles de votre fuseau horaire - qui changent périodiquement en raison de l'heure d'été ou en raison d'autres modifications apportées à la définition légale de votre fuseau horaire. (Vous n'avez pas la main ferme, votre appareil photo est donc fragile.)
La personne debout sur la photo verrait l’angle d’origine de votre appareil photo. Si d'autres prenaient des photos, elles pourraient prendre des angles différents. C'est ce que la partie Offset
de la DateTimeOffset
représente.
Donc, si vous appelez votre appareil photo "Heure de l'Est", vous pointez parfois à partir de -5 et parfois à partir de -4. Il y a des caméras dans le monde entier, toutes étiquetées de manière différente et pointant toutes sur la même timeline instantanée sous des angles différents. Certains d'entre eux sont juste l'un à côté de l'autre (ou l'un au-dessus de l'autre), il suffit donc de connaître le décalage, cela ne suffit pas 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 assurée 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 d'origine.
Alors, que nous dit cette analogie? Il fournit des indications intuitives.
Si vous représentez le temps par rapport à un endroit en particulier, représentez-le dans le calendrier avec un DateTime
. Assurez-vous simplement de ne jamais confondre un calendrier avec un autre. Unspecified
devrait être votre hypothèse. Local
est utile uniquement en provenance de DateTime.Now
. Par exemple, je pourrais obtenir DateTime.Now
et le sauvegarder dans une base de données - mais lorsque je le récupérerai, je dois supposer qu'il s'agit de Unspecified
. Je ne peux pas compter sur le fait que mon calendrier local est identique au calendrier d'origine.
Si vous devez toujours être certain du moment, assurez-vous de représenter le temps instantané. Utilisez DateTimeOffset
pour l'appliquer ou utilisez UTC DateTime
par convention.
Si vous devez suivre un instant instantané, mais que vous souhaitez également savoir "À quelle heure l'utilisateur pensait-il que cela figurait-il sur son calendrier local?" - alors vous devez utiliser un DateTimeOffset
. Ceci est très important pour les systèmes de chronométrage, par exemple, pour des raisons techniques et juridiques.
Si vous avez besoin de modifier un DateTimeOffset
enregistré précédemment, vous ne disposez pas de 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 enregistrer 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 pour cela une représentation appelée ZonedDateTime
, tandis que la bibliothèque de classes de base .Net n’a rien de semblable. Vous devez enregistrer à la fois une valeur DateTimeOffset
et TimeZoneInfo.Id
.
De temps en temps, vous voudrez représenter une heure de calendrier qui est locale pour "quiconque le regarde". Par exemple, lorsque vous définissez ce que aujourd'hui signifie. Nous sommes toujours de minuit à minuit aujourd'hui, mais ils représentent un nombre presque infini de plages qui se chevauchent sur la ligne de temps instantanée. (En pratique, nous avons un nombre fini de fuseaux horaires, mais vous pouvez exprimer les décalages jusqu'au bout.) Dans ces situations, assurez-vous de bien comprendre comment limiter le "qui demande?" interrogez-les sur un fuseau horaire unique ou prenez en charge leur conversion en temps instantané, selon le cas.
Voici quelques autres petites informations sur DateTimeOffset
qui corroborent cette analogie, ainsi que des astuces pour la maintenir droite:
Si vous comparez deux valeurs DateTimeOffset
, elles sont d'abord normalisées au décalage d'origine avant la comparaison. En d'autres termes, 2012-01-01T00:00:00+00:00
et 2012-01-01T02:00:00+02:00
se rapportent au même moment instantané et sont donc équivalents.
Si vous effectuez des tests unitaires et que vous devez vous assurer du décalage, testez à la fois la valeur DateTimeOffset
et la propriété .Offset
séparément.
Il existe une conversion implicite unidirectionnelle intégrée au framework .Net qui vous permet de passer un DateTime
dans n’importe quel paramètre ou variable DateTimeOffset
. Ce faisant, le .Kind
importe . Si vous transmettez un type UTC, il sera reporté avec un décalage de zéro, mais si vous transmettez .Local
ou .Unspecified
, il sera supposé être local . Le framework dit en gros "Eh bien, vous m'avez demandé de convertir l'heure du calendrier en heure instantanée, mais je ne sais pas d'où cela vient, donc je vais simplement utiliser le calendrier local." C’est un énorme piège si vous chargez un DateTime
non spécifié sur un ordinateur avec un fuseau horaire différent. (IMHO - cela devrait jeter une exception - mais ce n'est pas le cas.)
Plug sans vergogne:
De nombreuses personnes ont partagé avec moi le fait qu'elles trouvaient cette analogie extrêmement utile. Je l'ai donc incluse dans mon cours Pluralsight, Notions fondamentales sur la date et l'heure . Vous trouverez une explication pas à pas de l'analogie de la caméra dans le deuxième module, "Contexte Matters", dans le clip intitulé "Calendrier par rapport au temps instantané".
De Microsoft:
Ces utilisations des valeurs DateTimeOffset sont beaucoup plus courantes que celles des valeurs DateTime. En conséquence, DateTimeOffset doit être considéré comme le type de date et heure par défaut pour le développement d'applications.
source: "Choix entre DateTime, DateTimeOffset, TimeSpan et TimeZoneInfo" , MSDN
Nous utilisons DateTimeOffset
pour presque tout, car notre application traite des instants particuliers (par exemple, lorsqu’un enregistrement a été créé/mis à jour). En remarque, nous utilisons également DATETIMEOFFSET
dans SQL Server 2008.
Je considère que DateTime
est utile lorsque vous souhaitez traiter uniquement les dates, les heures ou uniquement dans un sens générique. Par exemple, si vous avez une alarme que vous voulez déclencher tous les jours à 7 heures, vous pouvez la stocker dans une DateTime
en utilisant une DateTimeKind
sur Unspecified
parce que vous voulez qu'elle disparaisse. off à 7h quel que soit l'heure d'été. Mais si vous souhaitez représenter l'historique des occurrences d'alarme, utilisez DateTimeOffset
.
Soyez prudent lorsque vous utilisez une combinaison de DateTimeOffset
et DateTime
, en particulier lors de l’affectation et de la comparaison des types. En outre, comparez uniquement les instances DateTime
identiques DateTimeKind
car DateTime
ignore le décalage du fuseau horaire lors de la comparaison.
DateTime est capable de stocker uniquement deux heures distinctes, l'heure locale et l'heure UTC. La propriété Kind indique lequel.
DateTimeOffset se développe sur ceci en étant capable de stocker des heures locales de n'importe où dans le monde. Il stocke également le offset entre cette heure locale et UTC. Notez que DateTime ne peut pas faire cela à moins d'ajouter un membre supplémentaire à votre classe pour stocker ce décalage UTC. Ou seulement travailler avec UTC. Ce qui en soi est une bonne idée d'ailleurs.
Il y a quelques endroits où DateTimeOffset
a un sens. La première concerne les événements récurrents et l'heure avancée. Disons que je veux déclencher une alarme à 9h tous les jours. Si j'utilise la règle "stocker en UTC, afficher en heure locale", l'alarme se déclenchera à une heure différente lorsque l'heure avancée est indiquée. en effet.
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 DateTimeOffset
à la BCL - ma solution à l'époque consistait à enregistrer explicitement l'heure dans la fuseau horaire local et enregistrez les informations de fuseau horaire à côté: en gros ce que DateTimeOffset
fait en interne).
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, il n’existe absolument aucun décalage de fuseau horaire explicite. Si vous effectuez un type quelconque 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 entrer 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 à l'aide de Json.Net et un format de date ISO, vous obtiendrez une chaîne telle que 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 purement 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 inclut le décalage. Si vous le sérialisez, vous obtiendrez le décalage explicitement inclus dans votre valeur au lieu de l'heure locale du serveur.
La plupart des réponses sont bonnes, mais j'ai pensé ajouter quelques liens supplémentaires à MSDN pour plus d'informations.
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
Une différence majeure est que DateTimeOffset
peut être utilisé conjointement avec TimeZoneInfo
pour convertir les heures locales dans des fuseaux horaires autres que le fuseau horaire actuel.
Cela est utile sur une application serveur (par exemple ASP.NET) accessible aux utilisateurs dans des fuseaux horaires différents.
Le seul côté négatif de DateTimeOffset que je vois est que Microsoft "a oublié" (de par sa conception) de le prendre en charge dans sa classe XmlSerializer. Mais il a depuis été ajouté à la classe d’utilitaires XmlConvert.
Je vous conseille d'utiliser DateTimeOffset et TimeZoneInfo en raison de tous les avantages, mais méfiez-vous de la création d'entités qui seront ou pourront être sérialisées vers ou à partir de XML (tous les objets métier ensuite).