Dov’è l’identificatore di formato DateTime ‘Z’?

[ Aggiornamento : gli specificatori di formato non sono la stessa cosa delle stringhe di formato; un identificatore di formato è un pezzo di una stringa di formato personalizzata, in cui una stringa di formato è ‘stock’ e non fornisce personalizzazione. Il mio problema è con gli specificatori non i formati ]

Ho cercato di eseguire conversioni DateTime roundtrip con una stringa di formato che utilizza l’identificatore di formato ‘zzz’, che so è legato all’ora locale. Quindi, se tento di effettuare il round trip con una data UTC, genera un’eccezione DateTimeInvalidLocalFormat, che dovrebbe, con questo testo:

Un UTC DateTime viene convertito in testo in un formato corretto solo per le ore locali. Questo può accadere quando si chiama DateTime.ToString usando l’identificatore di formato ‘z’, che includerà un offset del fuso orario locale nell’output. In tal caso, utilizzare l’identificatore di formato ‘Z’, che designa l’ora UTC o utilizzare la stringa di formato ‘o’, che è il metodo consigliato per mantenere un DateTime nel testo. Questo può verificarsi anche quando si passa un DateTime per essere serializzato da XmlConvert o DataSet. Se si utilizza XmlConvert.ToString, passare in XmlDateTimeSerializationMode.RoundtripKind per serializzare correttamente. Se si utilizza DataSet, impostare DateTimeMode sull’object DataColumn su DataSetDateTime.Utc.

Sulla base di questo suggerimento, tutto quello che devo fare per far funzionare il mio codice è sostituire ‘zzz’ con ‘ZZZ’ in modo da poter stare in un formato UTC. Il problema è che ‘Z’ non si trova da nessuna parte nella documentazione e qualsiasi combinazione di formato ‘Z’ che provo, cioè ‘Z’, ‘ZZ’, ‘ZZZ’, converte sempre l’istanza DateTime con quelle Z trattate come letterali .

Qualcuno ha dimenticato di implementare la “Z” senza dire all’autore del messaggio di eccezione, o mi manca come scambiare un offset dell’ora locale valido con “+0000” senza hacking?

Esempio di codice:

// This is the format with 'zzzzz' representing local time offset const string format = "ddd MMM dd HH:mm:ss zzzzz yyyy"; // create a UTC time const string expected = "Fri Dec 19 17:24:18 +0000 2008"; var time = new DateTime(2008, 12, 19, 17, 24, 18, 0, DateTimeKind.Utc); // If you're using a debugger this will rightfully throw an exception // with .NET 3.5 SP1 because 'z' is for local time only; however, the exception // asks me to use the 'Z' specifier for UTC times, but it doesn't exist, so it // just spits out 'Z' as a literal. var actual = time.ToString(format, CultureInfo.InvariantCulture); Assert.AreEqual(expected, actual); 

Forse l’identificatore di formato “K” sarebbe di qualche utilità. Questo è l’unico che sembra menzionare l’uso della “Z” maiuscola.

“Z” è una specie di caso unico per DateTimes. La “Z” letterale è in realtà parte dello standard datetime ISO 8601 per i tempi UTC. Quando “Z” (Zulu) viene virato alla fine di un tempo, indica che quell’ora è UTC, quindi in realtà la Z letterale è parte del tempo. Ciò probabilmente crea alcuni problemi per la libreria di formati di data in .NET, dal momento che è in realtà un letterale, piuttosto che un identificatore di formato.

Quando usi DateTime puoi memorizzare una data e un’ora all’interno di una variabile.

La data può essere un’ora locale o UTC, dipende da te.

Ad esempio, sono in Italia (+2 UTC)

 var dt1 = new DateTime(2011, 6, 27, 12, 0, 0); // store 2011-06-27 12:00:00 var dt2 = dt1.ToUniversalTime() // store 2011-06-27 10:00:00 

Quindi, cosa succede quando stampo dt1 e dt2 incluso il fuso orario?

 dt1.ToString("MM/dd/yyyy hh:mm:ss z") // Compiler alert... // Output: 06/27/2011 12:00:00 +2 dt2.ToString("MM/dd/yyyy hh:mm:ss z") // Compiler alert... // Output: 06/27/2011 10:00:00 +2 

dt1 e dt2 contengono solo una data e un’ora di informazione. dt1 e dt2 non contengono l’offset del fuso orario.

Quindi da dove viene il “+2” se non è contenuto nella variabile dt1 e dt2?

Viene dall’impostazione dell’orologio della tua macchina.

Il compilatore ti sta dicendo che quando usi il formato ‘zzz’ stai scrivendo una stringa che combina “DATE ​​+ TIME” (che sono memorizza in dt1 e dt2) + “TIMEZONE OFFSET” (che non è contenuto in dt1 e dt2 perché sono di tipo DateTyme) e userà l’offset della macchina server che sta eseguendo il codice.

Il compilatore dice “Attenzione: l’uscita del tuo codice dipende dall’offset dell’orologio della macchina”

Se eseguo questo codice su un server posizionato a Londra (+1 UTC), il risultato sarà completamente diverso: invece di ” +2 ” scriverà ” +1

 ... dt1.ToString("MM/dd/yyyy hh:mm:ss z") // Output: 06/27/2011 12:00:00 +1 dt2.ToString("MM/dd/yyyy hh:mm:ss z") // Output: 06/27/2011 10:00:00 +1 

La soluzione corretta è utilizzare il tipo di dati DateTimeOffset al posto di DateTime. È disponibile in SQL Server a partire dalla versione 2008 e nel framework .Net a partire dalla versione 3.5

Le date di scatto tonde tramite stringhe sono sempre state un problema … ma i documenti per indicare che l’identificatore ‘o’ è quello da utilizzare per il round trip che cattura lo stato UTC. Se analizzato, il risultato di solito è Kind == Utc se l’originale era UTC. Ho trovato che la cosa migliore da fare è sempre normalizzare le date in UTC o in locale prima della serializzazione, quindi istruire il parser su quale normalizzazione hai scelto.

 DateTime now = DateTime.Now; DateTime utcNow = now.ToUniversalTime(); string nowStr = now.ToString( "o" ); string utcNowStr = utcNow.ToString( "o" ); now = DateTime.Parse( nowStr ); utcNow = DateTime.Parse( nowStr, null, DateTimeStyles.AdjustToUniversal ); Debug.Assert( now == utcNow ); 

Questa pagina su MSDN elenca stringhe di formato DateTime standard, stringhe di esclusione che utilizzano la ‘Z’.

Aggiornamento: dovrai assicurarti che anche il resto della stringa della data segua lo schema corretto (non hai fornito un esempio di ciò che hai inviato, quindi è difficile dire se lo hai fatto o meno). Perché il formato UTC funzioni dovrebbe assomigliare a questo:

 // yyyy'-'MM'-'dd HH':'mm':'ss'Z' DateTime utcTime = DateTime.Parse("2009-05-07 08:17:25Z"); 
 Label1.Text = dt.ToString("dd MMM yyyy | hh:mm | ff | zzz | zz | z"); 

produrrà:

 07 Mai 2009 | 08:16 | 13 | +02:00 | +02 | +2 

Sono in Danimarca, il mio offset da GMT è di +2 ore, la strega è corretta.

se hai bisogno di ottenere l’ offset del cliente , ti consiglio di controllare un piccolo trucco che ho fatto. La pagina è in un server nel Regno Unito, dove GMT è +00: 00 e, come puoi vedere, otterrai il tuo GMT Offset locale.


Per quanto riguarda il tuo commento, ho fatto:

 DateTime dt1 = DateTime.Now; DateTime dt2 = dt1.ToUniversalTime(); Label1.Text = dt1.ToString("dd MMM yyyy | hh:mm | ff | zzz | zz | z"); Label2.Text = dt2.ToString("dd MMM yyyy | hh:mm | FF | ZZZ | ZZ | Z"); 

e ho capito:

 07 Mai 2009 | 08:24 | 14 | +02:00 | +02 | +2 07 Mai 2009 | 06:24 | 14 | ZZZ | ZZ | Z 

Non ottengo eccezioni, solo … non fa nulla con Z:

Mi dispiace, ma mi sfugge qualcosa?


Leggendo attentamente MSDN su Custom Date and Time Format Strings

non c’è supporto per maiuscolo ‘Z’.

Avevo a che fare con DateTimeOffset e sfortunatamente la “o” stampa “+0000” e non “Z”.

Così ho finito con:

 dateTimeOffset.UtcDateTime.ToString("o")