73 votes

DateTime.Parse("2012-09-30T23:00:00.0000000Z") se convertit toujours en DateTimeKind.Local

Je veux analyser une chaîne qui représente une date et une heure au format UTC.

Ma représentation de la chaîne comprend la spécification de l'heure zouloue, qui devrait indiquer que la chaîne représente une heure UTC.

var myDate = DateTime.Parse("2012-09-30T23:00:00.0000000Z");    

D'après ce qui précède, je m'attendrais à ce que myDate.Kind soit DateTimeKind.Utc, mais c'est plutôt DatetimeKind.Local.

Qu'est-ce que je fais mal et comment analyser une chaîne de caractères qui représente une heure UTC ?

Merci beaucoup !

99voto

Jon Skeet Points 692016

J'utiliserais mon Heure de Noda personnellement. (Certes, je suis partial en tant qu'auteur, mais ce serait plus propre...) Mais si vous ne pouvez pas le faire...

Soit utiliser DateTime.ParseExact en spécifiant le format exact que vous attendez, et inclure DateTimeStyles.AssumeUniversal et DateTimeStyles.AdjustToUniversal dans le code d'analyse :

using System;
using System.Globalization;

class Test
{
    static void Main()        
    {
        var date = DateTime.ParseExact("2012-09-30T23:00:00.0000000Z",
                                       "yyyy-MM-dd'T'HH:mm:ss.fffffff'Z'",
                                       CultureInfo.InvariantCulture,
                                       DateTimeStyles.AssumeUniversal |
                                       DateTimeStyles.AdjustToUniversal);
        Console.WriteLine(date);
        Console.WriteLine(date.Kind);
    }
}

(On peut se demander pourquoi il s'ajusterait à local par défaut sans AdjustToUniversal me dépasse, mais tant pis...)

EDIT : Pour développer mes objections à la suggestion de mattytommo, j'ai cherché à prouver qu'il y aurait perte d'information. J'ai échoué jusqu'à présent - mais d'une manière très particulière. Jetez un coup d'oeil à ceci - fonctionnant dans le fuseau horaire Europe/Londres, où les horloges reculent le 28 octobre 2012, à 2 heures du matin heure locale (1 heure UTC) :

DateTime local1 = DateTime.Parse("2012-10-28T00:30:00.0000000Z");
DateTime local2 = DateTime.Parse("2012-10-28T01:30:00.0000000Z");
Console.WriteLine(local1 == local2); // True

DateTime utc1 = TimeZoneInfo.ConvertTimeToUtc(local1);
DateTime utc2 = TimeZoneInfo.ConvertTimeToUtc(local2);
Console.WriteLine(utc1 == utc2); // False. Hmm.

On dirait qu'il y a un drapeau "avec ou sans DST" qui est stocké. quelque part mais je serai soufflé si je peux trouver où. Les documents pour TimeZoneInfo.ConvertTimeToUtc état

Si dateTime correspond à une heure ambiguë, cette méthode suppose qu'il s'agit de l'heure standard du fuseau horaire source.

Ce n'est pas apparaître pour être le cas ici en convertissant local2 ...

EDIT : Ok, ça devient encore plus étrange - cela dépend de la version du framework que vous utilisez. Considérez ce programme :

using System;
using System.Globalization;

class Test
{
    static void Main()        
    {
        DateTime local1 = DateTime.Parse("2012-10-28T00:30:00.0000000Z");
        DateTime local2 = DateTime.Parse("2012-10-28T01:30:00.0000000Z");

        DateTime utc1 = TimeZoneInfo.ConvertTimeToUtc(local1);
        DateTime utc2 = TimeZoneInfo.ConvertTimeToUtc(local2);
        Console.WriteLine(utc1);
        Console.WriteLine(utc2);

        DateTime utc3 = local1.ToUniversalTime();
        DateTime utc4 = local2.ToUniversalTime();
        Console.WriteLine(utc3);
        Console.WriteLine(utc4);
    }
}

Donc cela prend deux différents UTC, les analyse avec DateTime.Parse puis les reconvertit en UTC de deux manières différentes.

Résultats sous .NET 3.5 :

28/10/2012 01:30:00 // Look - we've lost information
28/10/2012 01:30:00
28/10/2012 00:30:00 // But ToUniversalTime() seems okay...
28/10/2012 01:30:00

Résultats sous .NET 4.5 beta :

28/10/2012 00:30:00 // It's okay!
28/10/2012 01:30:00
28/10/2012 00:30:00
28/10/2012 01:30:00

1 votes

Une valeur DateTime stocke son Kind dans ses deux bits les plus significatifs : Unspecified (00), Utc (01), Local (10) et LocalAmbiguousDst (11). Cependant, LocalAmbiguousDst est exposé publiquement comme Local.

1 votes

@MichaelLiu : Exact. Donc jusqu'à ce que vous le reconvertissiez en universel, vous ne pouvez pas faire la différence. Comme c'est charmant. Ick.

0 votes

@JonSkeet Cela signifie-t-il que mon code était correct (à l'exception de ParseExact) car il doit d'abord être reconverti en universel ?

33voto

Simon Gillbee Points 1906

Comme d'habitude, la réponse de Jon est très complète. Cela dit, personne n'a encore mentionné DateTimeStyles.RoundtripKind . Si vous voulez convertir une date-heure en une chaîne de caractères et la reconvertir en la même date-heure (y compris en conservant l'attribut DateTime.Kind ), utilisez le DateTimeStyles.RoundtripKind drapeau.

Comme l'a dit Jon, la bonne chose à faire est d'utiliser le formateur "O" pour convertir un objet DateTime en chaîne. Cela permet de préserver à la fois la précision et les informations relatives au fuseau horaire. Encore une fois, comme l'a dit Jon, utilisez DateTime.ParseExact lors de la reconversion. Mais si vous utilisez DateTimeStyles.RoundtripKind, vous récupérez toujours ce que vous avez introduit :

var now = DateTime.UtcNow;
var strNow = now.ToString("O");
var newNow = DateTime.ParseExact(strNow, "O", CultureInfo.InvariantCulture, DateTimeStyles.RoundtripKind);

Dans le code ci-dessus, newNow est exactement au même moment que now y compris le fait que c'est UTC. Si l'on exécute le même code, mais en remplaçant DateTime.Now pour DateTime.UtcNow vous obtiendrez une copie exacte de now comme newNow mais cette fois à l'heure locale.

Pour mes besoins, c'était la bonne chose à faire puisque je voulais m'assurer que tout ce qui a été transmis et converti soit reconverti en exactement la même chose.

0 votes

Merci beaucoup. J'étais à la recherche de ce drapeau ! Le conseil de Jon convertira toutes les dates en UTC, quel que soit le fuseau horaire d'origine dans la chaîne.

0 votes

Réponse fantastique !

5voto

mattytommo Points 27587

Utilisez le TimeZoneInfo en utilisant les éléments suivants :

var myDate = TimeZoneInfo.ConvertTimeToUtc(DateTime.Parse("2012-09-30T23:00:00.0000000Z"));

4 votes

Mauvaise idée IMO. Si l'heure analysée finit par être ambiguë dans le fuseau horaire local, vous avez fondamentalement perdu l'information. Évitez la conversion inutile en demandant à l'analyseur syntaxique de l'analyser comme un temps universel pour commencer.

0 votes

@JonSkeet Et si ParseExact était utilisé à la place de parse, mais en utilisant toujours la classe TimeZoneInfo, considéreriez-vous toujours cela comme une mauvaise idée ?

0 votes

@mattytommo : Oui, pour être honnête. Il n'y a pas besoin d'utiliser TimeZoneInfo ici - pourquoi le convertir en heure locale et inversement quand cela ne sert à rien ? Il semble qu'en fait DateTime sécrète des informations sur le décalage quelque part en interne, mais j'ai du mal à trouver où en ce moment...

4voto

vandsh Points 31

J'ai rencontré un problème similaire auparavant et plusieurs heures (et des cheveux tirés) plus tard j'ai fini par utiliser DateTime.SpecifyKind :

DateTime.SpecifyKind(inputDate, DateTimeKind.Utc);

Je crois que quelqu'un y a également fait allusion dans un commentaire ci-dessus.

4voto

port443 Points 138

Vous pouvez utiliser le format suivant pour la méthode du parseur : yyyy-MM-ddTHH:mm:ss.ffffffK

Cela permettra de traiter correctement les informations relatives au fuseau horaire à la fin ( à partir de .NET 2.0 ).

RE : ISO 8601

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X