Condividi tramite


Procedure consigliate per la codifica tramite DateTime in .NET Framework

 

Dan Rogers
Microsoft Corporation

Febbraio 2004

Si applica a
   Microsoft® .NET Framework
   Servizi Web di Microsoft® ASP.NET
   serializzazione XML

Riepilogo: La scrittura di programmi che archiviano, eseguono calcoli e serializzano i valori di tempo usando il tipo DateTime in Microsoft .NET Framework richiede una consapevolezza dei diversi problemi associati alle rappresentazioni temporali disponibili in Windows e .NET. Questo articolo è incentrato sui principali scenari di test e sviluppo che coinvolgono il tempo e definisce le raccomandazioni consigliate per la scrittura di programmi che usano il tipo DateTime in Microsoft . Applicazioni e assembly basati su NET. (18 pagine stampate)

Contenuto

Sfondo
   Che cos'è dateTime, comunque?
   Regole
Strategie di archiviazione
   Procedura consigliata #1
   Procedura consigliata #2
Esecuzione di calcoli
   Non scherzare di nuovo
   Procedura consigliata #3
   Ordinamento dei metodi DateTime
Caso speciale di XML
   Procedura consigliata #4
   Procedura consigliata #5
Codice di classe Quandary
   Procedura consigliata #6
Gestione dell'ora legale
   Procedura consigliata #7
Formattazione e analisi dei valori User-Ready
   Considerazioni future
Problemi relativi al metodo DateTime.Now()
   Procedura consigliata #8
Un paio di extra poco noti
Conclusione

Sfondo

Molti programmatori riscontrano assegnazioni che richiedono loro di archiviare e elaborare in modo accurato i dati che contengono informazioni di data e ora. In primo luogo, il tipo di dati DateTime (CLR) common language runtime sembra essere perfetto per queste attività. Non è raro, tuttavia, per i programmatori, ma più probabilmente i tester, riscontrare casi in cui un programma perde semplicemente traccia dei valori temporali corretti. Questo articolo è incentrato sui problemi associati alla logica che coinvolgono DateTime e, in questo modo, individua le procedure consigliate per la scrittura e il test di programmi che acquisisce, archivia, recupera e trasmette informazioni DateTime.

Che cos'è dateTime, comunque?

Quando si esamina la documentazione della libreria di classi di NET Framework, si noterà che "Il tipo di valore CLR System.DateTime rappresenta date e orari compresi tra le 12:00:00 mezzanotte, il 1 gennaio 0001 AD e le 11:59:59, il 31 dicembre 9999 AD". Leggendo ulteriormente, si apprende, in modo imprevisto, che un valore DateTime rappresenta un'istantanea in un momento e che una pratica comune consiste nel registrare i valori temporizzato in Tempo universale coordinata (UCT), più comunemente noto come Greenwich Mean Time (GMT).

A prima vista, un programmatore individua che un tipo DateTime è abbastanza buono per archiviare i valori temporali che potrebbero essere rilevati nei problemi di programmazione correnti, ad esempio nelle applicazioni aziendali. Con questa fiducia, molti programmatori insospettabili iniziano a scrivere codice, sicuro che possano imparare quanto devono circa il tempo quando vanno avanti. Questo approccio "learn-as-you-go" può portare a pochi problemi, quindi iniziamo a identificarli. Essi variano da problemi nella documentazione a comportamenti che devono essere fattoriati nei progetti del programma.

La documentazione V1.0 e 1.1 per System.DateTime rende alcune generalizzazioni che possono generare il programmatore insospettabile fuori traccia. Ad esempio, la documentazione indica ancora che i metodi e le proprietà trovati nella classe DateTime usano sempre il presupposto che il valore rappresenti il fuso orario locale del computer locale durante l'esecuzione di calcoli o confronti. Questa generalizzazione risulta non veritiera perché esistono determinati tipi di calcoli di data e ora che presuppongono GMT e altri che presuppongono una visualizzazione del fuso orario locale. Queste aree sono indicate più avanti in questo articolo.

Si inizierà quindi esplorando il tipo DateTime delineando una serie di regole e procedure consigliate che consentono di ottenere correttamente il funzionamento del codice la prima volta.

Regole

  1. I calcoli e i confronti delle istanze DateTime sono significativi solo quando le istanze confrontate o usate sono rappresentazioni di punti nel tempo dalla stessa prospettiva del fuso orario.
  2. Uno sviluppatore è responsabile di tenere traccia delle informazioni sul fuso orario associate a un valore DateTime tramite un meccanismo esterno. In genere, questa operazione viene eseguita definendo un altro campo o variabile usato per registrare le informazioni sul fuso orario quando si archivia un tipo di valore DateTime. Questo approccio (archiviando il senso del fuso orario insieme al valore DateTime) è il più accurato e consente agli sviluppatori diversi in punti diversi nel ciclo di vita di un programma di avere sempre una chiara comprensione del significato di un valore DateTime. Un altro approccio comune consiste nel rendere la "regola" nella progettazione che tutti i valori di tempo vengono archiviati in un contesto di fuso orario specifico. Questo approccio non richiede un'archiviazione aggiuntiva per salvare la visualizzazione del fuso orario di un utente, ma introduce il rischio che un valore di tempo venga interpretato erroneamente o archiviato in modo errato in base alla strada da parte di uno sviluppatore che non è consapevole della regola.
  3. L'esecuzione di calcoli di data e ora sui valori che rappresentano l'ora locale del computer potrebbe non restituire sempre il risultato corretto. Quando si eseguono calcoli sui valori temporali nei contesti del fuso orario che praticano l'ora legale, è necessario convertire i valori in rappresentazioni di ora universale prima di eseguire calcoli aritmetici di data. Per un elenco specifico di operazioni e contesti di fuso orario appropriati, vedere la tabella nella sezione Ordina i metodi DateTime.
  4. Un calcolo su un'istanza di un valore DateTime non modifica il valore dell'istanza, quindi una chiamata a MyDateTime.ToLocalTime() non modifica il valore dell'istanza di DateTime. I metodi associati alle classi Date (in Visual Basic®) e DateTime (nelle classi CLR .NET) restituiscono nuove istanze che rappresentano il risultato di un calcolo o di un'operazione.
  5. Quando si usa .NET Framework versione 1.0 e 1.1, NON inviare un valore DateTime che rappresenta l'ora UCTSystem.XML . Serializzazione. Ciò vale per i valori Date, Time e DateTime. Per i servizi Web e altre forme di serializzazione in XML che coinvolgono System.DateTime, assicurarsi sempre che il valore nel valore DateTime rappresenti l'ora locale del computer corrente. Il serializzatore decodifica correttamente un valore DateTime definito da XML Schema che viene codificato in GMT (valore offset = 0), ma lo decodifica nel punto di vista dell'ora del computer locale.
  6. In generale, se si tratta di un tempo trascorso assoluto, ad esempio la misurazione di un timeout, l'esecuzione di un'aritmetica o l'esecuzione di confronti di valori DateTime diversi, è consigliabile provare a usare un valore di ora universale, se possibile, in modo da ottenere la migliore accuratezza possibile senza effetti del fuso orario e/o dei risparmi di luce legale che hanno un impatto.
  7. Quando si tratta di concetti di alto livello, ad esempio la pianificazione e si può presupporre che ogni giorno abbia 24 ore dal punto di vista di un utente, può essere ok per contrastare la regola #6 eseguendo aritmetica, e così via, nei tempi locali.

In questo articolo questo semplice elenco di regole funge da base per un set di procedure consigliate per la scrittura e il test delle applicazioni che elaborano le date.

A questo momento, molti di voi stanno già esaminando il codice e dicendo: "Oh darn, non fa quello che mi aspettavo di fare", che è lo scopo di questo articolo. Per coloro di noi che non hanno avuto un epifania dalla lettura di questo lontano, esaminiamo i problemi associati all'elaborazione dei valori DateTime (da ora in poi, lo abbrevierò solo in "date") in . Applicazioni basate su NET.

Strategie di archiviazione

In base alle regole precedenti, i calcoli sui valori di data sono significativi solo quando si comprendono le informazioni sul fuso orario associate al valore di data che si esegue l'elaborazione. Ciò significa che se si archivia il valore temporaneamente in una variabile membro della classe o si sceglie di salvare i valori raccolti in un database o in un file, quando il programmatore è responsabile dell'applicazione di una strategia che consente di comprendere le informazioni relative al fuso orario associato in un secondo momento.

Procedura consigliata #1

Quando si codifica, archiviare le informazioni sul fuso orario associate a un tipo DateTime in una variabile aggiuntiva.

Un'alternativa, ma meno affidabile, strategia consiste nel rendere una regola stabile che le date archiviate verranno sempre convertite in un determinato fuso orario, ad esempio GMT, prima dell'archiviazione. Questo può sembrare ragionevole, e molti team possono renderlo funzionante. Tuttavia, la mancanza di un segnale overt che indica che una determinata colonna DateTime in una tabella in un database si trova in un fuso orario specifico causa invariabilmente errori nell'interpretazione nelle iterazioni successive di un progetto.

Una strategia comune vista in un sondaggio informale di diversi . Le applicazioni basate su NET sono il desiderio di avere sempre date rappresentate nell'ora universale (GMT). Dico "desiderio" perché questo non è sempre pratico. Un caso in questo caso si verifica quando si serializza una classe con una variabile membro DateTime tramite un servizio Web. Il motivo è che un tipo di valore DateTime esegue il mapping a un tipo XSD:DateTime (come si prevede) e il tipo XSD ospita i punti in tempo in qualsiasi fuso orario. Verrà illustrato il caso XML in un secondo momento. Più interessante, una buona percentuale di questi progetti non è stata effettivamente raggiunto il loro obiettivo e sono state archiviate le informazioni sulla data nel fuso orario del server senza renderlo conto.

In questi casi, un fatto interessante è che i tester non visualizzavano problemi di conversione temporale, quindi nessuno aveva notato che il codice che doveva convertire le informazioni sulla data locale in tempo UCT non è riuscito. In questi casi specifici, i dati sono stati serializzati successivamente tramite XML e sono stati convertiti correttamente perché le informazioni sulla data erano presenti nell'ora locale del computer per iniziare.

Esaminiamo un codice che non funziona:

Dim d As DateTime
d = DateTime.Parse("Dec 03, 2003 12:00:00 PM") 'date assignment
d.ToUniversalTime()

Il programma precedente accetta il valore nella variabile d e lo salva in un database, prevedendo che il valore archiviato rappresenti una visualizzazione UCT del tempo. In questo esempio viene riconosciuto che il metodo Parse esegue il rendering del risultato nell'ora locale, a meno che non vengano usate impostazioni cultura non predefinite come argomento facoltativo per la famiglia di metodi Parse.

Il codice visualizzato in precedenza non riesce effettivamente a convertire il valore nella variabile DateTime d nell'ora universale nella terza riga perché, come scritto, l'esempio viola rule #4 (i metodi della classe DateTime non convertono il valore sottostante). Nota: questo codice è stato visualizzato in un'applicazione effettiva che è stata testata.

Come è passato? Le applicazioni coinvolte sono state in grado di confrontare correttamente le date archiviate perché, durante i test, tutti i dati provengono dai computer impostati nello stesso fuso orario, quindi la regola #1 è stata soddisfatta (tutte le date confrontate e calcolate vengono localizzate nello stesso punto di fuso orario della vista). Il bug in questo codice è il tipo difficile da individuare, un'istruzione che esegue ma che non esegue nulla (hint: l'ultima istruzione nell'esempio è un'istruzione no-op come scritta).

Procedura consigliata #2

Quando si esegue il test, verificare che i valori archiviati rappresentino il valore temporizzato previsto nel fuso orario desiderato.

La correzione dell'esempio di codice è semplice:

Dim d As DateTime
d = DateTime.Parse("Dec 03, 2003 12:00:00 PM").ToUniversalTime()

Poiché i metodi di calcolo associati al tipo di valore DateTime non influisce mai sul valore sottostante, ma invece restituiscono il risultato del calcolo, un programma deve ricordare di archiviare il valore convertito (se è desiderato, naturalmente). Si esaminerà quindi come anche questo calcolo apparentemente corretto non riesca a ottenere i risultati previsti in determinate circostanze che coinvolgono l'ora legale.

Esecuzione di calcoli

A prima vista, le funzioni di calcolo fornite con la classe System.DateTime sono davvero utili. Il supporto viene fornito per l'aggiunta di intervalli a valori temporali, l'esecuzione di aritmetici sui valori temporali e anche la conversione dei valori di ora .NET nel tipo di valore corrispondente appropriato per le chiamate API Win32®, nonché le chiamate di automazione OLE. Un'occhiata ai metodi di supporto che circondano il tipo DateTime evoca uno sguardo nostalgico ai diversi modi in cui MS-DOS® e Windows® si sono evoluti per gestire i timestamp e i timestamp nel corso degli anni.

Il fatto che tutti questi componenti siano ancora presenti in varie parti del sistema operativo è correlato ai requisiti di compatibilità con le versioni precedenti che Microsoft gestisce. Per un programmatore, ciò significa che se si spostano dati che rappresentano timestamp su file, directory o si esegue l'interoperabilità COM/OLE che coinvolgono valori date e DateTime, sarà necessario acquisire competenza nella gestione delle conversioni tra le diverse generazioni di tempo presenti in Windows.

Non farti ingannare di nuovo

Si supponga di aver adottato la strategia "archiviamo tutto in tempo UCT", presumibilmente per evitare il sovraccarico di dover archiviare una differenza di fuso orario (e forse una visualizzazione del fuso orario degli utenti, ad esempio l'ora solare del Pacifico o PST). L'esecuzione di calcoli tramite il tempo UCT offre diversi vantaggi. Il capo tra di essi è il fatto che, quando rappresentato nell'ora universale, ogni giorno ha una lunghezza fissa e non ci sono offset di fuso orario da gestire.

Se si è rimasti sorpresi di leggere che un giorno può avere lunghezze diverse, tenere presente che in qualsiasi fuso orario che consenta l'ora legale, in due giorni dell'anno (in genere), i giorni hanno una lunghezza diversa. Pertanto, anche se si usa un valore di ora locale, ad esempio Pacific Standard Time (PST), se si tenta di aggiungere un intervallo di tempo a un valore di istanza DateTime specifico, è possibile che non si ottiene il risultato che si ritiene che si debba se l'intervallo aggiunto richiede l'ora di modifica in una data in cui l'ora legale viene avviata o termina.

Di seguito è riportato un esempio di codice che non funziona nel fuso orario Pacifico nel Stati Uniti:

Dim d As DateTime
d = DateTime.Parse("Oct 26, 2003 12:00:00 AM") 'date assignment
d = d.AddHours(3.0)
' - displays 10/26/2003 03:00:00 AM – an ERROR!
MsgBox(d.ToString)

Il risultato visualizzato da questo calcolo può sembrare corretto a prima vista; Tuttavia, il 26 ottobre 2003, un minuto dopo le 1:59 PST, la modifica dell'ora legale è stata applicata. La risposta corretta dovrebbe essere stata 10/26/2003, 02:00:00 AM, quindi questo calcolo basato su un valore di ora locale non è riuscito a restituire il risultato corretto. Ma se guardiamo indietro alla regola n. 3, sembriamo avere una contraddizione, ma non lo facciamo. Chiamiamolo un caso speciale per l'uso dei metodi Add/Subtract nei fusi orari che celebrano l'ora legale.

Procedura consigliata n. 3

Quando si scrive codice, prestare attenzione se è necessario eseguire calcoli DateTime (aggiunta/sottrazione) sui valori che rappresentano i fusi orari che praticano l'ora legale. Possono verificarsi errori di calcolo imprevisti. Convertire invece il valore dell'ora locale in ora universale, eseguire il calcolo e riconvertire per ottenere la massima accuratezza.

La correzione di questo codice interrotto è semplice:

Dim d As DateTime
d = DateTime.Parse("Oct 26, 2003 12:00:00 AM") 'date assignment
d = d.ToUniversalTime().AddHours(3.0).ToLocalTime()
' - displays 10/26/2003 02:00:00 AM – Correct!
MsgBox(d.ToString)
  

Il modo più semplice per aggiungere in modo affidabile intervalli di tempo consiste nel convertire i valori basati sul tempo locale in un'ora universale, eseguire i calcoli e quindi convertire nuovamente i valori.

Ordinamento dei metodi DateTime di ordinamento

In questo articolo vengono illustrati diversi metodi di classe System.DateTime. Alcuni producono un risultato corretto quando l'istanza sottostante rappresenta l'ora locale, alcuni quando rappresentano l'ora universale e altri ancora non richiedono alcuna istanza sottostante. Inoltre, alcuni sono completamente indipendenti dal fuso orario (ad esempio , AddYear, AddMonth). Per semplificare la comprensione complessiva dei presupposti alla base dei metodi di supporto DateTime più comunemente rilevati, viene fornita la tabella seguente.

Per leggere la tabella, prendere in considerazione il punto di vista iniziale (input) e finale (valore restituito). In tutti i casi, lo stato finale della chiamata a un metodo viene restituito dal metodo . Non viene eseguita alcuna conversione nell'istanza sottostante dei dati. Vengono inoltre fornite avvertenze che descrivono le eccezioni o indicazioni utili.

Nome metodo Punto di vista iniziale Punto di vista finale Precisazioni
ToUniversalTime Ora locale UTC Non chiamare su un'istanza DateTime che rappresenta già l'ora universale
Tolocaltime UTC Ora locale Non chiamare su un'istanza DateTime che rappresenta già l'ora locale
ToFileTime Ora locale   Il metodo restituisce un INT64 che rappresenta l'ora del file Win32 (ora UCT)
FromFileTime   Ora locale Metodo statico: nessuna istanza richiesta. Richiede un tempo UCT INT64 come input
ToFileTimeUtc

(solo V1.1)

UTC   Il metodo restituisce un INT64 che rappresenta un'ora di file Win32 (ora UCT)
FromFileTimeUtc

(solo V1.1)

  UTC Il metodo converte l'ora del file WIN32 INT64 in un'istanza di DateTime UCT
Adesso   Ora locale Metodo statico: nessuna istanza richiesta. Restituisce un valore DateTime che rappresenta l'ora corrente nell'ora del computer locale
UtcNow   UTC Metodo statico: nessuna istanza richiesta
IsLeapYear Ora locale   Restituisce un valore booleano che indica true se la parte relativa all'anno dell'istanza dell'ora locale è un anno bisestile.
Oggi   Ora locale Metodo statico: nessuna istanza richiesta. Restituisce un valore DateTime impostato su Mezzanotte del giorno corrente nell'ora del computer locale.

Caso speciale di XML

Diverse persone che ho parlato di recente hanno avuto l'obiettivo di progettazione di serializzare i valori temporali nei servizi Web in modo che il codice XML che rappresenta dateTime verrebbe formattato in GMT (ad esempio, con un offset zero). Anche se ho sentito diversi motivi che vanno dal desiderio di analizzare semplicemente il campo come stringa di testo per la visualizzazione in un client di voler mantenere i presupposti "archiviati in UCT" che esistono sul server ai chiamanti dei servizi Web, non sono stato convinto che ci sia mai un buon motivo per controllare il formato di marshalling in rete fino a questo punto. Perché? Semplicemente perché la codifica XML per un tipo DateTime è perfettamente adeguata per rappresentare un istante nel tempo e il serializzatore XML integrato in .NET Framework esegue un processo corretto per gestire i problemi di serializzazione e deserializzazione associati ai valori di ora.

Inoltre, si scopre che forzare la System.XML. Il serializzatore di serializzazione per codificare un valore di data in GMT in transito non è possibile in .NET, almeno non oggi. In qualità di programmatore, progettista o project manager, il processo viene quindi verificato che i dati passati nell'applicazione vengano eseguiti in modo accurato con un costo minimo.

Molti dei gruppi con cui ho parlato nella ricerca che sono andati in questo documento avevano adottato la strategia di definizione di classi speciali e scrittura di propri serializzatori XML in modo che abbiano il pieno controllo sui valori DateTime sul filo sembravano nel loro XML. Anche se ammiro il fatto che gli sviluppatori abbiano quando fanno il salto in questa impresa coraggiosa, riposo certo che le sfumature di affrontare i problemi di conversione dell'ora legale e del fuso orario da soli dovrebbero fare un buon manager dire, "No way", soprattutto quando i meccanismi forniti in .NET Framework fanno un lavoro perfettamente accurato di serializzare i valori di ora già.

C'è solo un trucco che devi conoscere e come finestra di progettazione devi comprendere questo e rispettare la regola (vedere Regola 5).

Codice che non funziona:

Definiamo prima di tutto una classe XML semplice con una variabile membro DateTime. Per completezza, questa classe è l'equivalente semplificato dell'approccio consigliato illustrato più avanti nell'articolo.

<XmlType(TypeName:="timeTestDef", _
    Namespace:= "http://tempuri.org/Timetester.xsd")>), _
    XmlRoot(), Serializable()> _
Public Class timeTestDef
    Private __timeVal As DateTime

    <XmlIgnore()> _
    Public timeValSpecified As Boolean

    <XmlElement(ElementName:="timeVal", IsNullable:=False, _
        Form:=XmlSchemaForm.Qualified, DataType:="dateTime", _
        Namespace:="http://tempuri.org/Timetester.xsd")> _
    Public Property timeVal() As DateTime
        Get
            timeVal = __timeVal
        End Get
        Set(ByVal Value As DateTime)
            __timeVal = Value
            timeValSpecified = True
        End Set
    End Property
End Class

A questo punto, si userà questa classe per scrivere codice XML in un file.

' write out to the file
Dim t As Xml.XmlTextWriter
Dim ser As XmlSerializer
Dim tt As New timeTest ' a class that has a DateTime variable
' set the fields in your class
tt.timeVal = DateTime.Parse("12/12/2003 12:01:02 PM")
tt.timeVal = tt.TimeVal.ToUniversalTime()

' get a serializer for the root type, and serialize this UTC time
ser = New XmlSerializer(GetType(timeTest))
t = New Xml.XmlTextWriter("c:\timetest.xml", System.Text.Encoding.UTF8)
ser.Serialize(t, tt)
t.Close()
t = Nothing
tt = Nothing

Quando viene eseguito questo codice, il codice XML serializzato nel file di output contiene una rappresentazione DateTime XML come indicato di seguito:

<timeVal>2003-12-12T20:01:02.0000000-08:00</timeVal> 

Si tratta di un errore: il valore codificato nel codice XML è disattivato di otto ore. Poiché ciò si verifica come la differenza di fuso orario del computer corrente, è consigliabile essere sospetti. Esaminando il codice XML stesso, la data è corretta e la data 20:01:02 corrisponde all'ora dell'orologio a Londra per la mia ora di mezzogiorno, ma la parte di offset non è corretta per un orologio basato su Londra. Quando il codice XML è simile all'ora di Londra, l'offset deve rappresentare anche il punto di vista di Londra, che questo codice non ottiene.

Il serializzatore XML presuppone sempre che i valori DateTime serializzati rappresentino l'ora del computer locale, quindi applica la differenza di fuso orario locale del computer come parte offset dell'ora XML codificata. Quando si deserializza l'oggetto in un altro computer, l'offset originale viene sottratto dal valore analizzato e viene aggiunto l'offset del fuso orario del computer corrente.

Quando si inizia con un'ora locale, il risultato della serializzazione (codifica in XML DateTime seguito da decodifica in ora del computer locale) è sempre corretto, ma solo se il valore DateTime iniziale da serializzare rappresenta l'ora locale all'inizio della serializzazione. Nel caso di questo esempio di codice interrotto, è già stato modificato il valore DateTime nella variabile membro timeVal in ora UCT, quindi quando si serializza e si deserializza, il risultato è disattivato dal numero di ore uguale alla differenza di fuso orario del computer di origine. Questo è male.

Procedura consigliata n. 4

Durante il test, calcolare il valore che si prevede di visualizzare nella stringa XML serializzata usando una visualizzazione ora locale del computer del punto nel tempo sottoposto a test. Se il codice XML nel flusso di serializzazione è diverso, registrare un bug.

La correzione di questo codice è semplice. Impostare come commento la riga che chiama ToUniversalTime().

Procedura consigliata n. 5

Quando si scrive codice per serializzare classi con variabili membro DateTime, i valori devono rappresentare l'ora locale. Se non contengono l'ora locale, modificarle prima di qualsiasi passaggio di serializzazione, incluso il passaggio o la restituzione di tipi che contengono valori DateTime nei servizi Web.

Codice di classe Quandary

In precedenza è stata esaminata una classe piuttosto non sofisticata che ha esposto una proprietà DateTime. In questa classe è stato semplicemente serializzato ciò che è stato archiviato in un oggetto DateTime, indipendentemente dal fatto che il valore rappresenta un punto di vista ora locale o universale. Si esamini ora un approccio più sofisticato che offre ai programmatori una scelta eccessiva rispetto a quali presupposti di fuso orario desiderano, serializzando sempre correttamente.

Quando si codifica una classe che avrà una variabile membro di tipo DateTime, un programmatore ha la possibilità di rendere pubblica la variabile membro o di scrivere la logica della proprietà per eseguire il wrapping della variabile membro con operazioni get/set . La scelta di rendere pubblico il tipo presenta diversi svantaggi che, nel caso dei tipi DateTime, possono avere conseguenze che non sono sotto il controllo dello sviluppatore della classe.

Usando quanto appreso finora, è consigliabile invece fornire due proprietà per ogni tipo DateTime.

L'esempio seguente illustra l'approccio consigliato per la gestione delle variabili membro DateTime:

<XmlType(TypeName:="timeTestDef", _
    Namespace:= "http://tempuri.org/Timetester.xsd")>), _
    XmlRoot(), Serializable(), _
    EditorBrowsable(EditorBrowsableState.Advanced)> _
Public Class timeTestDef
    Private __timeVal As DateTime

    <XmlIgnore()> _
    Public timeValSpecified As Boolean

    <XmlElement(ElementName:="timeVal", IsNullable:=False, _
        Form:=XmlSchemaForm.Qualified, DataType:="dateTime", _
        Namespace:="http://tempuri.org/Timetester.xsd")> _
    Public Property timeVal() As DateTime
        Get
            timeVal = __timeVal.ToLocalTime()
        End Get
        Set(ByVal Value As DateTime)
            __timeVal = Value.ToUniversalTime()
            timeValSpecified = True
        End Set
    End Property

    <XmlIgnore()> _
    Public Property timeValUTC() As DateTime
        Get
            timeValUTC = __timeVal
        End Get
        Set(ByVal Value As DateTime)
            __timeVal = Value
            timeValSpecified = True
        End Set
    End Property
End Class

Questo esempio è l'equivalente corretto all'esempio di serializzazione della classe precedente. In entrambi gli esempi di classe (questo e quello precedente), le classi sono implementazioni descritte con lo schema seguente:

<?xml version="1.0" encoding="utf-8" ?> 
<xs:schema id="Timetester" 
     targetNamespace="http://tempuri.org/Timetester.xsd"
     elementFormDefault="qualified"
     xmlns="http://tempuri.org/Timetester.xsd"
     xmlns:mstns="http://tempuri.org/Timetester.xsd"
     xmlns:xs="http://www.w3.org/2001/XMLSchema">

     <xs:element name="timeTest" type="timeTestDef"/>
     <xs:complexType name="timeTestDef">
      <xs:sequence>
         <xs:element name="timeVal" type="xs:dateTime"/>
      </xs:sequence>
     </xs:complexType>
</xs:schema>

In questo schema e in qualsiasi implementazione di classe viene definita una variabile membro che rappresenta un valore di ora facoltativo. Nell'esempio consigliato sono state fornite due proprietà con getter e setter, una per l'ora universale e una per l'ora locale. Gli attributi tra parentesi angolari visualizzati nel codice indicano al serializzatore XML di usare la versione dell'ora locale per la serializzazione e in genere rendere l'implementazione della classe un output conforme allo schema. Per fare in modo che la classe gestisca correttamente la mancanza facoltativa di espressione quando non viene impostato alcun valore nell'istanza, la variabile timeValSpecified e la logica associata nel setter della proprietà controlla se l'elemento XML viene espresso in fase di serializzazione o meno. Questo comportamento facoltativo sfrutta una funzionalità nel sottosistema di serializzazione progettata per supportare il contenuto XML facoltativo.

L'uso di questo approccio per la gestione dei valori DateTime nelle classi .NET offre il meglio di entrambi i mondi: si ottiene l'accesso alle risorse di archiviazione in base all'ora universale in modo che i calcoli siano accurati e si ottiene una serializzazione corretta delle visualizzazioni ora locali.

Procedura consigliata n. 6

Quando si scrive codice, rendere private le variabili membro DateTime e fornire due proprietà per modificare i membri DateTime in un'ora locale o universale. Distorsione dell'archiviazione nel membro privato come ora UCT controllando la logica nei getter e nei setter. Aggiungere gli attributi di serializzazione XML alla dichiarazione della proprietà ora locale per assicurarsi che il valore dell'ora locale sia ciò che viene serializzato (vedere l'esempio).

Avvertenze a questo approccio

L'approccio consigliato per la gestione di un valore DateTime nell'ora universale all'interno delle variabili membro privato è audio, come è consigliabile fornire due proprietà per consentire ai coder di gestire le versioni temporali con cui sono più confortevoli. Un problema che uno sviluppatore usa questo o qualsiasi altro approccio che espone qualsiasi ora locale a un programma continua a essere il problema di 25 ore al giorno intorno all'ora legale. Questo continuerà a essere un problema per i programmi che usano CLR versione 1.0 e 1.1, quindi è necessario essere consapevoli del fatto che il programma rientra in questo caso speciale (l'ora aggiunta o mancante per il tempo rappresentato) e regolare manualmente. Per coloro che non possono tollerare una finestra di problema di un'ora all'anno, la raccomandazione corrente consiste nell'archiviare le date come stringhe o un altro approccio autogestito. Gli interi long Unix sono un'opzione valida.

Per CLR versione 2.0 (disponibile nella prossima versione di Visual Studio® code denominata "Whidbey"), tenere presente se un oggetto DateTime contiene un'ora locale o un valore di ora universale viene aggiunto a .NET Framework. A questo punto, il modello consigliato continuerà a funzionare, ma per i programmi che interagiscono con le variabili membro tramite le proprietà UTC, questi errori nel periodo dell'ora mancante/aggiuntiva verranno eliminati. Per questo motivo, la procedura consigliata per la scrittura di codice con due proprietà è fortemente consigliata oggi, in modo che i programmi esegeranno la migrazione pulita a CLR versione 2.0.

Gestione dell'ora legale

Quando si prepara a chiudere e lasciare l'argomento relativo alle procedure di codifica e test per i valori DateTime, rimane un caso speciale che è necessario comprendere. Questo caso implica le ambiguità che circondano l'ora legale e il problema ripetuto di un'ora all'anno. Questo problema riguarda principalmente le applicazioni che raccolgono i valori di ora dall'input dell'utente.

Per coloro di voi nella maggioranza del conteggio dei paesi, questo caso è semplice perché nella maggior parte dei paesi l'ora legale non è praticata. Ma per coloro che sono nella maggioranza dei programmi interessati (cioè, tutti coloro che hanno applicazioni che devono gestire il tempo che possono essere rappresentati in o originati in luoghi che praticano l'ora legale), è necessario sapere che questo problema esiste e tenere conto di esso.

Nelle aree del mondo che praticano l'ora legale, c'è un'ora ogni autunno e primavera dove l'ora apparentemente va in tilt. Nella notte in cui l'ora dell'orologio passa dall'ora solare all'ora legale, l'ora salta avanti un'ora. Ciò si verifica in primavera. Nell'autunno dell'anno, una notte, l'orologio dell'ora locale salta indietro un'ora.

In questi giorni, è possibile riscontrare condizioni in cui il giorno è lungo 23 o 25 ore. Pertanto, se si aggiungono o sottraggono intervalli di tempo dai valori di data e l'intervallo attraversa questo strano punto nel tempo in cui gli orologi cambiano, il codice deve apportare una regolazione manuale.

Per la logica che usa il metodo DateTime.Parse() per calcolare un valore DateTime in base all'input dell'utente di una data e di un'ora specifiche, è necessario rilevare che determinati valori non sono validi (nel giorno di 23 ore) e alcuni valori hanno due significati perché una determinata ora si ripete (nel giorno di 25 ore). A tale scopo, è necessario conoscere le date coinvolte e cercare queste ore. Può essere utile analizzare e riprodurre nuovamente le informazioni sulla data interpretate quando l'utente esce dai campi usati per immettere le date. Di norma, evitare che gli utenti specifichino l'ora legale nell'input.

È già stata illustrata la procedura consigliata per i calcoli dell'intervallo di tempo. Convertendo le visualizzazioni ora locale in un'ora universale prima di eseguire i calcoli, si superano i problemi di accuratezza dell'ora. Il caso più difficile da gestire è il caso di ambiguità associato all'analisi dell'input dell'utente che si verifica durante questa magica ora in primavera e autunno.

Attualmente non esiste alcun modo per analizzare una stringa che rappresenta la visualizzazione dell'ora di un utente e avere assegnato in modo accurato un valore di ora universale. Il motivo è che le persone che sperimentano l'ora legale non vivono in luoghi in cui il fuso orario è Greenwich Mean Time. Pertanto, è del tutto possibile che qualcuno che vive sulla costa orientale del Stati Uniti tipi in un valore come "Ott 26, 2003 01:10:00 AM".

In questa particolare mattina, alle 2:00, l'orologio locale viene reimpostato sulle 13:00, creando un giorno di 25 ore. Poiché tutti i valori dell'ora dell'orologio tra le 13:00 e le 2:00 si verificano due volte su quella particolare mattina, almeno nella maggior parte degli Stati Uniti e del Canada. Il computer non ha davvero modo di sapere quali 1:10 è stato significato, quello che si verifica prima dell'interruttore, o quello che si verifica 10 minuti dopo l'interruttore dell'ora legale.

Analogamente, i programmi devono affrontare il problema che si verifica in primavera quando, in una particolare mattina, non c'è tale orario come le 2:10. Il motivo è che alle 2:00 di quella particolare mattina, l'ora degli orologi locali cambia improvvisamente alle 3:00. L'intera 2:00 ora non viene mai eseguita in questo giorno di 23 ore.

I programmi devono gestire questi casi, eventualmente richiedendo all'utente di rilevare l'ambiguità. Se non si raccolgono stringhe di data e ora dagli utenti e si analizzano questi problemi, probabilmente non si verificano questi problemi. I programmi che devono determinare se un determinato orario rientra nell'ora legale possono utilizzare quanto segue:

Timezone.CurrentTimeZone.IsDaylightSavingTime(DateTimeInstance)

oppure

DateTimeInstance.IsDaylightSavingTime

Procedura consigliata n. 7

Durante il test, se i programmi accettano l'input dell'utente specificando valori di data e ora, assicurarsi di verificare la perdita di dati in "spring-ahead", "fallback" 23 e 25 ore. Assicurarsi anche di testare le date raccolte in un computer in un fuso orario e archiviate in un computer in un altro fuso orario.

Formattazione e analisi dei valori User-Ready

Per i programmi che accettano informazioni di data e ora dagli utenti e devono convertire questo input utente in valori DateTime, il framework fornisce il supporto per l'analisi delle stringhe formattate in modi specifici. In generale, i metodi DateTime.Parse e ParseExact sono utili per convertire stringhe contenenti date e ore in valori DateTime. Al contrario, i metodi ToString, ToLongDateString, ToLongTimeString, ToShortDateString e ToShortTimeString sono tutti utili per il rendering dei valori DateTime in stringhe leggibili.

Due problemi principali che influiscono sull'analisi sono le impostazioni cultura e la stringa di formato. Le domande frequenti su DateTime illustrano i problemi di base relativi alle impostazioni cultura, quindi qui ci concentreremo sulle procedure consigliate per la stringa di formato che influiscono sull'analisi di DateTime.

Le stringhe di formato consigliate per la conversione di DateTime in stringhe sono:

'yyyy'-'MM'-'dd'T'HH': 'mm': 'ss.fffffff'Z' — Per i valori UCT

'yyyy'-'MM'-'dd'T'HH': 'mm': 'ss.fffffff'zzz' — Per i valori locali

'yyyy'-'MM'-'dd'T'HH': 'mm': 'ss.fffffffff' — Per i valori temporali astratti

Si tratta dei valori stringa di formato che verrebbero passati al metodo DateTime.ToString se si desidera ottenere un output compatibile con la specifica del tipo DateTime XML. Le virgolette assicurano che le impostazioni di data e ora locali nel computer non eseseguono l'override delle opzioni di formattazione. Se è necessario specificare layout diversi, è possibile passare altre stringhe di formato per una funzionalità di rendering della data abbastanza flessibile, ma è necessario prestare attenzione a usare solo la notazione Z per eseguire il rendering delle stringhe dai valori UCT e usare la notazione zzz per i valori di ora locale.

È possibile analizzare le stringhe e convertirle in valori DateTime con i metodi DateTime.Parse e ParseExact. Per la maggior parte di noi, Parse è sufficiente perché ParseExact richiede di fornire la propria istanza dell'oggetto Formatter . L'analisi è abbastanza capace e flessibile e può convertire in modo accurato la maggior parte delle stringhe che contengono date e ore.

Infine, è importante chiamare sempre i metodi Parse e ToString solo dopo aver impostato CultureInfo del thread su CultureInfo.InvariantCulture.

Considerazioni future

Una cosa che non è possibile eseguire facilmente con DateTime.ToString è formattare un valore DateTime in un fuso orario arbitrario. Questa funzionalità viene considerata per le implementazioni future di .NET Framework. Se è necessario essere in grado di determinare che la stringa "12:00:00 EST" equivale a "11:00:00 EDT", sarà necessario gestire manualmente la conversione e il confronto.

Problemi con il metodo DateTime.Now()

Esistono diversi problemi durante la gestione del metodo denominato Now. Per gli sviluppatori Visual Basic che leggono questo aspetto, questo vale anche per la funzione Visual Basic Now . Gli sviluppatori che usano regolarmente il metodo Now sanno che viene comunemente usato per ottenere l'ora corrente. Il valore restituito dal metodo Now si trova nel contesto del fuso orario del computer corrente e non può essere considerato come un valore non modificabile. Una pratica comune consiste nel convertire i tempi che verranno archiviati o inviati tra computer in tempo universale (UCT).

Quando l'ora legale è una possibilità, è consigliabile evitare una procedura di codifica. Prendere in considerazione il codice seguente che può introdurre un bug difficile da rilevare:

Dim timeval As DateTime
timeval = DateTime.Now().ToUniversalTime()  

Il valore risultante dall'esecuzione di questo codice verrà disattivato per un'ora se chiamato durante l'ora aggiuntiva che si verifica durante il cambio di ora legale nell'autunno. Questo vale solo per i computer che si trovano nei fusi orari che praticano l'ora legale. Poiché l'ora aggiuntiva rientra in quel posto in cui lo stesso valore, ad esempio 1:10:00 AM, si verifica due volte il mattino, il valore restituito potrebbe non corrispondere al valore desiderato.

Per risolvere questo problema, è consigliabile chiamare DateTime.UtcNow() anziché chiamare DateTime.Now e quindi convertire in ora universale.

Dim timeval As DateTime
timeval = DateTime.UtcNow()  

Questo codice avrà sempre la prospettiva corretta di 24 ore e potrebbe quindi essere convertita in modo sicuro in ora locale.

Procedura consigliata #8

Quando si sta codificando e si desidera archiviare l'ora corrente rappresentata come ora universale, evitare di chiamare DateTime.Now() seguita da una conversione all'ora universale. Chiamare invece direttamente la funzione DateTime.UtcNow.

Caveat: se si intende serializzare una classe contenente un valore DateTime, assicurarsi che il valore serializzato non rappresenti l'ora universale. La serializzazione XML non supporta la serializzazione UCT fino alla versione Whidbey di Visual Studio.

Un paio di extra poco noti

A volte quando si inizia a immergersi in una parte di un'API si trova un gioiello nascosto, qualcosa che ti aiuta a raggiungere un obiettivo, ma che, se non lo hai detto, non scopri nei tuoi viaggi giornalieri. Il tipo di valore DateTime in .NET include diverse gemme di questo tipo che consentono di ottenere un uso più coerente dell'ora universale.

La prima è l'enumerazione DateTimeStyles disponibile nello spazio dei nomi System.Globalization . L'enumerazione controlla i comportamenti delle funzioni DateTime.Parse() e ParseExact usate per convertire l'input specificato dall'utente e altre forme di rappresentazioni stringa di input in valori DateTime.

La tabella seguente evidenzia alcune delle funzionalità abilitate dall'enumerazione DateTimeStyles.

Costante di enumerazione Scopo Precisazioni
RegolaToUniversal Quando viene passato come parte di un metodo Parse o ParseExact, questo flag causa la restituzione del valore come ora universale. La documentazione è ambigua, ma funziona con Parse e ParseExact.
NoCurrentDateDefault Elimina il presupposto che le stringhe analizzate senza componenti di data abbiano un valore DateTime restituito che corrisponde all'ora della data corrente. Se questa opzione viene usata, il valore DateTime restituito è l'ora specificata nella data gregoriana 1 gennaio dell'anno 1.
AllowWhiteSpaces

AllowTrailingWhite

AllowLeadingWhite

AllowInnerWhite

Queste opzioni consentono di abilitare la tolleranza per gli spazi vuoti aggiunti davanti, dietro e al centro delle stringhe di data analizzate. Nessuno

Altre funzioni di supporto interessanti sono disponibili nella classe System.Timezone . Assicurarsi di verificare se si vuole rilevare se l'ora legale influisce su un valore DateTime o se si vuole determinare a livello di codice l'offset del fuso orario corrente per il computer locale.

Conclusione

La classe DateTime di .NET Framework offre un'interfaccia completa per la scrittura di programmi che gestiscono il tempo. Comprendere le sfumature di gestione della classe va oltre ciò che è possibile glean da Intellisense®. Di seguito sono illustrate le procedure consigliate per la codifica e i test dei programmi che gestiscono date e ora. Buon lavoro.