Sdílet prostřednictvím


Osvědčené postupy kódování pomocí dateTime v rozhraní .NET Framework

 

Dan Rogers
Microsoft Corporation

Dne

Platí pro
   Microsoft® .NET Framework
   Webové služby Microsoft® ASP.NET
   serializace XML

Shrnutí: Psaní programů, které ukládají, provádějí výpočty a serializují časové hodnoty pomocí typu DateTime v rozhraní Microsoft .NET Framework, vyžaduje povědomí o různých problémech souvisejících s časovými reprezentacemi dostupnými v systémech Windows a .NET. Tento článek se zaměřuje na klíčové scénáře testování a vývoje zahrnující čas a definuje doporučení osvědčených postupů pro psaní programů, které používají typ DateTime v Microsoftu . Aplikace a sestavení založená na technologii NET. (18 vytištěných stránek)

Obsah

Pozadí
   Co je vlastně DateTime?
   Pravidla
Strategie úložiště
   Osvědčené postupy č. 1
   Osvědčené postupy č. 2
Provádění výpočtů
   Nenechte se znovu zmást
   Osvědčené postupy č. 3
   Řazení metod DateTime
Zvláštní případ XML
   Osvědčené postupy č. 4
   Osvědčené postupy č. 5
Třídy Coders Quandary
   Osvědčené postupy č. 6
Práce s letním časem
   Osvědčené postupy č. 7
Formátování a analýza User-Ready hodnot
   Budoucí aspekty
Problémy s metodou DateTime.Now()
   Osvědčené postupy č. 8
Pár málo známých doplňku
Závěr

Pozadí

Mnoho programátorů se setkává s přiřazeními, které vyžadují, aby přesně ukládaly a zpracovávaly data, která obsahují informace o datu a čase. Na první pohled se datový typ Common Language Runtime (CLR) DateTime jeví jako ideální pro tyto úlohy. Není však neobvyklé, že programátoři, ale spíše testeři, narazí na případy, kdy program jednoduše ztratí přehled o správných časových hodnotách. Tento článek se zaměřuje na problémy související s logikou týkající se data a času a při tom odkrývá osvědčené postupy pro psaní a testování programů, které zachytávají, ukládají, načítají a přenášejí informace DateTime.

Co je vlastně DateTime?

Když se podíváme na dokumentaci ke knihovně tříd net Framework, vidíme, že "Typ hodnoty CLR System.DateTime představuje data a časy v rozsahu od 12:00:00 o půlnoci, 1. ledna 0001 AD do 23:59:59, 31. prosince 9999 AD." Při podrobnějším čtení se dozvídáme, že hodnota DateTime představuje okamžik v určitém okamžiku a že běžným postupem je zaznamenávat hodnoty k určitému bodu v čase v koordinovaném univerzálním čase (UCT), což se běžně označuje jako Greenwichův střední čas (GMT).

Programátor pak na první pohled zjistí, že typ DateTime je docela dobrý při ukládání časových hodnot, se kterými se pravděpodobně setkáte při programovacích problémech aktuálního dne, například v obchodních aplikacích. S touto jistotou mnoho nic netušících programátorů začne kódovat a věří, že se toho mohou naučit tolik, kolik potřebují, o čase, jak jdou vpřed. Tento přístup s průběžným učením vás může vést k několika problémům, proto je pojďme identifikovat. Sahají od problémů v dokumentaci až po chování, které je potřeba zohlednit v návrzích programů.

Dokumentace v1.0 a 1.1 pro System.DateTime obsahuje několik zobecnění, která můžou nic netušícího programátora vyvést z cesty. Například dokumentace v současné době stále říká, že metody a vlastnosti nalezené ve třídě DateTime vždy používají předpoklad, že hodnota představuje místní časové pásmo místního počítače při provádění výpočtů nebo porovnání. Toto zobecnění se ukáže jako nepravdivé, protože existují určité typy výpočtů data a času, které předpokládají GMT, a jiné, které předpokládají zobrazení místního časového pásma. Na tyto oblasti se odkazuje dále v tomto článku.

Pojďme tedy začít tím, že prozkoumáme typ DateTime tím, že načteme řadu pravidel a osvědčených postupů, které vám pomůžou zajistit správné fungování kódu napoprvé.

Pravidla

  1. Výpočty a porovnání instancí DateTime jsou smysluplné pouze v případě, že porovnávané nebo používané instance představují reprezentaci bodů v čase ze stejné perspektivy časového pásma.
  2. Vývojář je zodpovědný za sledování informací o časovém pásmu spojených s hodnotou DateTime prostřednictvím externího mechanismu. Obvykle se to provádí definováním jiného pole nebo proměnné, které slouží k záznamu informací o časovém pásmu při ukládání typu hodnoty DateTime. Tento přístup (ukládání inteligentního časového pásma společně s hodnotou DateTime) je nejpřesnější a umožňuje různým vývojářům v různých bodech životního cyklu programu vždy jasně porozumět významu hodnoty DateTime. Dalším běžným přístupem je vytvořit v návrhu "pravidlo", že všechny časové hodnoty jsou uloženy v určitém kontextu časového pásma. Tento přístup nevyžaduje další úložiště k uložení uživatelského zobrazení kontextu časového pásma, ale představuje riziko, že vývojář, který nezná pravidlo, bude chybně interpretována nebo nesprávně uložena časová hodnota.
  3. Provádění výpočtů data a času u hodnot, které představují místní čas počítače, nemusí vždy přinést správný výsledek. Při provádění výpočtů s časovými hodnotami v kontextu časových pásem, které procvičují letní čas, byste měli hodnoty před provedením aritmetických výpočtů kalendářních dat převést na reprezentace univerzálního času. Konkrétní seznam operací a správných kontextů časových pásem najdete v tabulce v části Sorting out DateTime Methods (Řazení metod data a času).
  4. Výpočet hodnoty DateTime na instanci nemění hodnotu instance, takže volání MyDateTime.ToLocalTime() neupravuje hodnotu instance DateTime. Metody přidružené k Date (v jazyce Visual Basic®) a DateTime (v .NET CLR) třídy vrací nové instance, které představují výsledek výpočtu nebo operace.
  5. Při použití rozhraní .NET Framework verze 1.0 a 1.1 NEODESÍLEJTE hodnotu DateTime, která představuje čas UCT System.XML. Serializace. Platí to pro hodnoty Date, Time a DateTime. U webových služeb a jiných forem serializace do XML zahrnujících System.DateTime se vždy ujistěte, že hodnota v hodnotě DateTime představuje aktuální místní čas počítače. Serializátor správně dekóduje hodnotu DataTime definovanou schématem XML, která je zakódovaná v GMT (hodnota posunu = 0), ale dekóduje ji do časového bodu místního počítače.
  6. Obecně platí, že pokud máte co do činění s absolutním uplynulým časem, jako je měření časového limitu, provádění aritmetických dat nebo porovnávání různých hodnot DateTime, měli byste zkusit použít hodnotu Univerzální čas, pokud je to možné, abyste získali co nejlepší přesnost bez vlivu časového pásma nebo letní úspory.
  7. Pokud pracujete s koncepty vysoké úrovně, jako je plánování, a můžete bezpečně předpokládat, že každý den má z pohledu uživatele 24 hodin, může být v pořádku čelit pravidlu č. 6 provedením aritmetických atd. v místních časech.

V tomto článku slouží tento jednoduchý seznam pravidel jako základ pro sadu osvědčených postupů pro psaní a testování aplikací, které zpracovávají data.

V tuto chvíli se někteří z vás již dívají na svůj kód a říkají: "Oh, zatraceně, to není dělá to, co jsem očekával, aby udělal", což je účelem tohoto článku. Pro ty z nás, kteří ještě neměli prozřet od čtení tak daleko, se podívejme na problémy spojené se zpracováním hodnot DateTime (od této chvíle, jen zkrátím to na "dates") v . Aplikace založené na technologii NET.

Strategie úložiště

Podle výše uvedených pravidel jsou výpočty hodnot kalendářních dat smysluplné pouze v případě, že rozumíte informacím o časovém pásmu přidruženým k hodnotě data, kterou zpracováváte. To znamená, že ať už dočasně ukládáte svoji hodnotu do proměnné člena třídy nebo se rozhodnete uložit hodnoty, které jste shromáždili do databáze nebo souboru, jste jako programátor zodpovědní za použití strategie, která umožní, aby související informace o časovém pásmu byly později pochopitelné.

Osvědčené postupy č. 1

Při kódování uložte informace o časovém pásmu přidružené k typu DateTime v pomocné proměnné.

Alternativní, ale méně spolehlivou strategií je vytvořit pevné pravidlo, že uložená data budou vždy převedena na určité časové pásmo, například GMT, před uložením. To se může zdát rozumné a mnoho týmů to může zajistit, aby to fungovalo. Chybějící jasný signál, který říká, že konkrétní sloupec DateTime v tabulce v databázi je v určitém časovém pásmu, však vždy vede k chybám při interpretaci v pozdějších iteracích projektu.

Společná strategie, která se projevuje v neformálním průzkumu různých . Aplikace založené na technologii NET představují vždy data reprezentovaná v univerzálním čase (GMT). Říkám "touha", protože to není vždy praktické. Případ v tomto bodě nastává při serializaci třídy, která má proměnnou člena DateTime prostřednictvím webové služby. Důvodem je to, že typ hodnoty DateTime se mapuje na typ XSD:DateTime (podle očekávání) a typ XSD odpovídá představujícím body v čase v libovolném časovém pásmu. Případ XML si probereme později. Ještě zajímavější je, že velké procento těchto projektů ve skutečnosti nedosáhlo svého cíle a ukládá informace o datu do časového pásma serveru, aniž by si to uvědomilo.

V těchto případech je zajímavým faktem, že testeři nezaznamenali problémy s převodem času, takže si nikdo nevšiml, že kód, který měl převést informace o místním datu na čas UCT, selhává. V těchto konkrétních případech byla data později serializována prostřednictvím XML a byla správně převedena, protože informace o datu byly v místním počítači.

Podívejme se na kód, který nefunguje:

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

Výše uvedený program vezme hodnotu v proměnné d a uloží ji do databáze a očekává, že uložená hodnota bude představovat zobrazení času UCT. Tento příklad rozpozná, že Metoda Parse vykreslí výsledek v místním čase, pokud se jako volitelný argument pro řadu metod Parse nepoužije nějaká jazyková verze, která není výchozí.

Dříve zobrazenému kódu se ve skutečnosti nepodaří převést hodnotu v proměnné DateTime d na univerzální čas na třetím řádku, protože, jak je napsáno, ukázka porušuje pravidlo č. 4 (metody ve třídě DateTime nepřevádějí podkladovou hodnotu). Poznámka: Tento kód byl zobrazen ve skutečné aplikaci, která byla testována.

Jak to prošlo? Zapojeným aplikacím se podařilo úspěšně porovnat uložená data, protože během testování pocházela všechna data z počítačů nastavených na stejné časové pásmo, takže pravidlo č. 1 bylo splněno (všechna porovnávaná a počítaná data jsou lokalizována do stejného pohledu na časové pásmo). Chyba v tomto kódu je typ, který je těžké zjistit – příkaz, který se spustí, ale nic neudělá (tip: poslední příkaz v příkladu je no-op, jak je napsaný).

Osvědčené postupy č. 2

Při testování zkontrolujte, jestli uložené hodnoty představují hodnotu k určitému bodu v čase v časovém pásmu, které chcete použít.

Oprava ukázky kódu je snadná:

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

Vzhledem k tomu, že metody výpočtu spojené s typem hodnoty DateTime nikdy neovlivňují podkladovou hodnotu, ale místo toho vrací výsledek výpočtu, musí program pamatovat na uložení převedené hodnoty (samozřejmě v případě, že je to žádoucí). Dále prozkoumáme, jak ani tento zdánlivě správný výpočet nemusí za určitých okolností týkajících se letního času dosáhnout očekávaných výsledků.

Provádění výpočtů

Na první pohled jsou výpočetní funkce, které jsou součástí třídy System.DateTime, opravdu užitečné. Podpora je k dispozici pro přidávání intervalů k časovým hodnotám, provádění aritmetických hodnot časových hodnot a dokonce převod časových hodnot .NET na odpovídající typ hodnoty vhodný pro volání rozhraní API Win32® a také volání automatizace OLE. Pohled na metody podpory, které obklopují typ DateTime, evokuje snímatické ohlédnutí za různými způsoby vývoje systémů MS-DOS® a Windows® pro práci s časem a časovými razítky v průběhu let.

Skutečnost, že všechny tyto komponenty jsou stále přítomné v různých částech operačního systému, souvisí s požadavky na zpětnou kompatibilitu, které společnost Microsoft dodržuje. Pro programátora to znamená, že pokud přesouváte data představující časová razítka v souborech, adresářích nebo provádíte zprostředkovatele komunikace COM/OLE zahrnující hodnoty data a data a času, budete muset umět pracovat s převody mezi různými generacemi času, které existují ve Windows.

Nenechte se znovu zmást

Předpokládejme, že jste přijali strategii "ukládáme vše v čase UCT", pravděpodobně proto, abyste se vyhnuli režii spojenou s uložením posunu časového pásma (a možná i pohledem uživatele na časové pásmo, jako je tichomořský standardní čas nebo PST). Provádění výpočtů s využitím času UCT má několik výhod. Hlavní mezi nimi je skutečnost, že když je reprezentován ve univerzálním čase, má každý den pevnou délku a nejsou k dispozici žádné posuny časového pásma, které by bylo možné řešit.

Pokud vás překvapilo, že den může mít různou délku, mějte na paměti, že v každém časovém pásmu, které umožňuje letní čas, mají dny ve dvou dnech v roce (obvykle) odlišnou délku. Takže i když používáte místní časovou hodnotu, jako je tichomořský standardní čas (PST), pokud se pokusíte přidat rozsah času ke konkrétní hodnotě instance DateTime, nemusí se vám dostat výsledek, který jste si mysleli, že byste měli, pokud přičítané intervaly přesáhnou čas změny k datu, kdy letní čas začíná nebo končí.

Podívejme se na příklad kódu, který nefunguje v časovém pásmu Tichomoří v USA:

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)

Výsledek, který je zobrazen z tohoto výpočtu může vypadat správně na první pohled; avšak 26. října 2003, minutu po 1:59 PST, změna letního času se projevila. Správná odpověď by měla být 26.10.2003, 02:00:00, takže tento výpočet založený na místní hodnotě času nesdělil správný výsledek. Když se ale podíváme zpět na pravidlo č. 3, zdá se, že si protiřečíme, ale ne. Říkejme tomu speciální případ použití metod Sčítání/Odčítání v časových pásmech, které oslaví letní čas.

Osvědčený postup č. 3

Při kódování buďte opatrní, pokud potřebujete provádět výpočty s datem a časem (přičítáním nebo odčítáním) u hodnot představujících časová pásma, která provádějí letní čas. Může dojít k neočekávaným chybám výpočtů. Místo toho převeďte hodnotu místního času na univerzální čas, proveďte výpočet a převeďte zpět, abyste dosáhli maximální přesnosti..

Oprava tohoto poškozeného kódu je jednoduchá:

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)
  

Nejjednodušší způsob, jak spolehlivě přidat časové intervaly, je převést hodnoty založené na místním čase na univerzální čas, provést výpočty a pak hodnoty převést zpět.

Řazení metod data a času

V tomto článku jsou popsány různé metody třídy System.DateTime. Některé poskytují správný výsledek, když podkladová instance představuje místní čas, některé, když představují univerzální čas, a jiné stále nevyžadují žádnou podkladovou instanci vůbec. Některé jsou navíc zcela nezávislé na časovém pásmu (např . AddYear, AddMonth). Pro zjednodušení celkového pochopení předpokladů za nejčastěji se vyskytujícími metodami podpory DateTime je k dispozici následující tabulka.

Pokud chcete tabulku přečíst, zvažte počáteční (vstupní) a koncový bod (vrácená hodnota). Ve všech případech je koncový stav volání metody vrácen metodou . Na podkladovou instanci dat se neprovedou žádné převody. K dispozici jsou také upozornění, která popisují výjimky nebo užitečné pokyny.

Název metody Počáteční bod zobrazení Koncový bod pohledu Upozornění
Touniversaltime Místní čas UTC Nevolejte u instance DateTime, která už představuje univerzální čas.
Tolocaltime UTC Místní čas Nevolejte na instanci DateTime, která už představuje místní čas.
Tofiletime Místní čas   Metoda vrátí int64, který představuje čas souboru Win32 (čas UCT).
FromFileTime   Místní čas Statická metoda – nevyžaduje se žádná instance. Jako vstup se používá čas UCT INT64.
ToFileTimeUtc

(pouze verze 1.1)

UTC   Metoda vrátí int64, který představuje čas souboru Win32 (čas UCT).
FromFileTimeUtc

(pouze verze 1.1)

  UTC Metoda převede čas souboru INT64 Win32 na instanci DateTime UCT.
Now   Místní čas Statická metoda – nevyžaduje se žádná instance. Vrátí hodnotu DateTime, která představuje aktuální čas v čase místního počítače.
UtcNow   UTC Statická metoda – nevyžaduje se žádná instance
IsLeapYear Místní čas   Vrátí hodnotu Boolean, která označuje hodnotu true, pokud je část roku v místní instanci času přestupný rok.
Dnes   Místní čas Statická metoda – nevyžaduje se žádná instance. Vrátí hodnotu DateTime nastavenou na Hodnotu Půlnoc aktuálního dne v místním strojovém čase.

Zvláštní případ XML

Několik lidí, se kterými jsem mluvil nedávno, mělo za cíl návrhu serializovat hodnoty času přes webové služby tak, aby XML, který představuje DateTime, byl formátován v GMT (např. s nulovým posunem). I když jsem slyšel různé důvody, od přání jednoduše parsovat pole jako textový řetězec pro zobrazení v klientovi, aby chtěl zachovat "uložené v UCT" předpoklady, které existují na serveru volající webové služby, nebyl jsem přesvědčen, že někdy existuje dobrý důvod k řízení formátu zařazování na drátě. Proč? Jednoduše proto, že kódování XML pro typ DateTime je dokonale adekvátní pro reprezentaci instant v čase, a serializátor XML, který je integrován do rozhraní .NET Framework provádí skvělou práci při správě serializace a deserializace problémy spojené s časovými hodnotami.

Dále se ukáže, že vynucení System.XML. Serializační serializátor kódovat hodnotu data v GMT na drátu není možné v .NET, alespoň ne dnes. Jako programátor, návrhář nebo projektový manažer se pak ujistíte, že data předávaná ve vaší aplikaci jsou provedena přesně s minimálními náklady.

Několik skupin, se kterými jsem mluvil ve výzkumu, který šel do tohoto dokumentu, přijal strategii definování speciálních tříd a psaní vlastních serializátorů XML tak, aby měli plnou kontrolu nad tím, jak hodnoty DateTime na drátu vypadaly v jejich XML. I když obdivuji škubnutí, že vývojáři při skoku do tohoto statečného podniku, můžete si být jisti, že nuance práce s letním časem a časová pásma převodu problémy by měl dobrý manažer říci, "V žádném případě", a to zejména v případě, že mechanismy poskytované v rozhraní .NET Framework provádět dokonale přesné úlohy serializace časových hodnot již.

Musíte znát jenom jeden trik a jako návrhář musíte tomuto problému rozumět a dodržovat pravidlo (viz Pravidlo č. 5).

Kód, který nefunguje:

Nejprve definujme jednoduchou třídu XML s členskou proměnnou DateTime. Pro úplnost je tato třída zjednodušeným ekvivalentem doporučeného přístupu, který je znázorněn dále v článku.

<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

Teď použijeme tuto třídu k zápisu XML do souboru.

' 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

Při spuštění tohoto kódu obsahuje kód XML serializovaný do výstupního souboru reprezentaci DateTime XML takto:

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

Jedná se o chybu: hodnota zakódovaná v souboru XML je o osm hodin vypnutá. Vzhledem k tomu, že se jedná o posun časového pásma mého aktuálního počítače, měli bychom být podezřelí. Když se podíváte na samotný kód XML, datum je správné a datum 20:01:02 odpovídá hodinám v Londýně pro můj vlastní čas poledne, ale část posunu není správná pro hodiny založené na Londýně. Když xml vypadá jako londýnská doba, měl by posun představovat také londýnská perspektiva, které tento kód nedosáhne.

Serializátor XML vždy předpokládá, že hodnoty DateTime serializované představují čas místního počítače, takže použije posun místního časového pásma počítače jako část posunu kódovaného času XML. Když to deserializujeme na jiný počítač, odečte se původní posun od analyzované hodnoty a přičte se posun časového pásma aktuálního počítače.

Když začneme s místním časem, výsledek serializace (zakódování do XML DateTime následovaný dekódováním na místní čas počítače) je vždy správný , ale pouze v případě, že počáteční DateTime hodnota serializace představuje místní čas při zahájení serializace. V případě tohoto příkladu nefunkčního kódu jsme již upravili hodnotu DateTime v členské proměnné timeVal na čas UCT, takže když serializujeme a deserializujeme, výsledek je vypnutý o počet hodin, který se rovná posunu časového pásma původního počítače. To je špatně.

Osvědčený postup č. 4

Při testování vypočítejte hodnotu, kterou očekáváte v řetězci XML serializovaném pomocí místního časového zobrazení počítače testovaného bodu v čase. Pokud se XML v serializačním streamu liší, zapište chybu!

Oprava tohoto kódu je jednoduchá. Zakomentujte řádek, který volá ToUniversalTime().

Osvědčené postupy č. 5

Při psaní kódu pro serializaci tříd, které mají proměnné členů DateTime, musí hodnoty představovat místní čas. Pokud neobsahují místní čas, upravte je před jakýmkoli krokem serializace, včetně předávání nebo vrácení typů, které obsahují hodnoty DateTime ve webových službách.

Třídy Coders Quandary

Dříve jsme se podívali na poměrně nesofistovanou třídu, která odhalila vlastnost DateTime. V této třídě jsme jednoduše serializovali to, co jsme uložili v DateTime, bez ohledu na to, zda hodnota představovala místní nebo univerzální časový bod. Pojďme se podívat na sofistikovanější přístup, který programátorům nabízí přehlednou volbu, jaké předpoklady časového pásma chtějí, a přitom vždy správně serializovat.

Při kódování třídy, která bude mít člen proměnnou typu DateTime, programátor má možnost nastavit proměnnou člena jako veřejnou nebo zapsat logiku vlastnosti zabalit člen proměnnou s operacemi get/set . Volba typu public má několik nevýhod, které v případě typů DateTime mohou mít důsledky, které nejsou pod kontrolou vývojáře třídy.

S využitím toho, co jsme se zatím naučili, zvažte místo toho poskytnutí dvou vlastností pro každý typ DateTime.

Následující příklad znázorňuje doporučený přístup ke správě proměnných členů 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

Tento příklad je opravený ekvivalent předchozí třídy serializace příklad. V obou příkladech tříd (v tomto a dřívějším příkladu) jsou třídy implementace, které jsou popsány pomocí následujícího schématu:

<?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>

V tomto schématu a ve všech implementacích tříd definujeme členovou proměnnou, která představuje volitelnou časovou hodnotu. V našem doporučeném příkladu jsme zadali dvě vlastnosti s gettery i settery – jednu pro univerzální čas a jednu pro místní čas. Atributy s hranatými závorkami, které vidíte v kódu, říkají serializátoru XML, aby používal místní časovou verzi pro serializaci, a obecně způsobit, aby implementace třídy vyústit ve výstupu kompatibilním se schématem. Aby se třída správně zabývala volitelným nedostatkem výrazu, když v instanci není nastavena žádná hodnota, proměnná timeValSpecified a přidružená logika v setter vlastnosti řídí, zda xml element je vyjádřen v čase serializace nebo ne. Toto volitelné chování využívá funkci v subsystému serializace, která byla navržena tak, aby podporovala volitelný obsah XML.

Pomocí tohoto přístupu ke správě hodnot DateTime ve třídách .NET získáte to nejlepší z obou světů – získáte přístup k úložišti na základě univerzálního času, aby výpočty byly přesné a abyste získali správnou serializaci zobrazení místního času.

Osvědčené postupy č. 6

Při kódování nastavte proměnné členů DateTime jako soukromé a poskytněte dvě vlastnosti pro manipulaci se členy DateTime v místním nebo univerzálním čase. Předestavte úložiště v privátním členu jako čas UCT tím, že budete řídit logiku v getterech a setterech. Přidejte atributy serializace XML do deklarace vlastnosti místního času, abyste se ujistili, že hodnota místního času je to, co je serializováno (viz příklad).

Upozornění k tomuto přístupu

Doporučený přístup ke správě data a času ve univerzálním čase v rámci proměnných soukromého člena je dobrý, stejně jako doporučení poskytnout duální vlastnosti, které umožní programátorům pracovat s verzemi času, se kterými jsou nejpohodlnější. Jedním z problémů, kdy vývojář používá tento nebo jakýkoli jiný přístup, který vystavuje jakýkoli místní čas programu, je i nadále problém 25 hodin denně kolem letního času. To bude i nadále problém u programů, které používají CLR verze 1.0 a 1.1, takže musíte vědět, jestli váš program spadá do tohoto speciálního případu (přidaná nebo chybějící hodina pro daný čas), a upravit ručně. Pro ty, kteří nemůžou tolerovat jednohodinové časové období problémů za rok, je aktuální doporučení ukládat data jako řetězce nebo jiný přístup, který spravujete sami. (Vhodnou volbou jsou dlouhá celá čísla unixu.)

Pro CLR verze 2.0 (k dispozici v nadcházející verzi sady Visual Studio® s kódem s názvem "Whidbey") se do rozhraní .NET Framework přidává povědomí o tom, zda DateTime obsahuje místní čas nebo hodnotu univerzálního času. V tomto okamžiku bude doporučený vzor dál fungovat, ale u programů, které interagují s proměnnými členů prostřednictvím vlastností UTC, budou tyto chyby v intervalu chybějících hodin nebo hodin navíc eliminovány. Z tohoto důvodu se dnes důrazně doporučuje osvědčený postup kódování pomocí duálních vlastností, aby se vaše programy migrovali čistě na CLR verze 2.0.

Práce s letním časem

Vzhledem k tomu, že se připravujeme na uzavření a ponechání tématu kódování a testování pro hodnoty DateTime, zbývá ještě jeden zvláštní případ, kterému musíte porozumět. Tento případ se týká nejednoznačností, které obklopují letní čas, a opakovaného problému s hodinami za rok. Tento problém primárně ovlivňuje jenom aplikace, které shromažďují časové hodnoty ze vstupu uživatele.

Pro ty z vás ve většině zemí je tento případ triviální, protože ve většině zemí není letní čas praktikován. Ale pro ty z vás, kteří jsou v ovlivněných programech většina (to znamená, že všichni z vás, kteří mají aplikace, které potřebují pracovat s časem, který může být reprezentován nebo zdroj v místech, které DO praktikují letní úspory), musíte vědět, že tento problém existuje a počítat s ním.

V oblastech světa, které praktikují letní čas, existuje jedna hodina každý podzim a jaro, kde se čas zdá být haywire. V noci, kdy se hodinový čas posune ze standardního času na letní čas, se čas posune o hodinu dopředu. K tomu dochází na jaře. Na podzim roku, na jednu noc, místní čas hodiny přeskočí zpět o hodinu.

V těchto dnech se můžete setkat s podmínkami, kdy je den dlouhý 23 nebo 25 hodin. Pokud tedy přičítáte nebo odečtete časové intervaly od hodnot kalendářních dat a rozsah protíná tento zvláštní bod v čase, kdy se přepínají hodiny, váš kód musí provést ruční úpravu.

Pro logiku, která používá metodu DateTime.Parse() k výpočtu hodnoty DateTime na základě uživatelského vstupu konkrétního data a času, musíte zjistit, že určité hodnoty nejsou platné (23hodinový den) a že určité hodnoty mají dva významy, protože se konkrétní hodina opakuje (25hodinový den). K tomu potřebujete znát data, která se týkají, a vyhledat tyto hodiny. Může být užitečné analyzovat a znovu zobrazovat interpretované informace o datu, když uživatel opustí pole použitá k zadání kalendářních dat. Vyhýbejte se zpravidla tomu, aby uživatelé zadali letní čas ve svém vstupu.

Osvědčené postupy pro výpočty s časovým rozsahem jsme už probrali. Převodem zobrazení místního času na univerzální čas před provedením výpočtů se dostanete přes problémy s přesností času. Těžším případem je nejednoznačnost spojená s parsováním uživatelských vstupů, ke které dochází během této magické hodiny na jaře a na podzim.

V současné době neexistuje způsob, jak analyzovat řetězec, který představuje zobrazení času uživatele a má přesně přiřazenou univerzální časovou hodnotu. Důvodem je, že lidé, kteří mají letní čas, nežijí v místech, kde časové pásmo je Greenwich střední čas. Je tedy zcela možné, že někdo žijící na východním pobřeží USA typy v hodnotě jako "Oct 26, 2003 01:10:00 AM".

Toto konkrétní ráno ve 2:00 se místní hodiny resetuje na 1:00 a vytvoří se 25hodinový den. Vzhledem k tomu, že všechny hodnoty hodinového času mezi 1:00 a 2:00 dopoledne se vyskytují dvakrát v danou ránu – alespoň ve většině Spojených států a Kanady. Počítač opravdu nemá způsob, jak zjistit, která 1:10 AM byla míněna – ta, která nastane před přepnutím, nebo ta, která nastane 10 minut po přepnutí letního času.

Podobně se vaše programy musí vypořádat s problémem, ke kterému dochází na jaře, když v konkrétní ráno není čas jako 2:10. Důvodem je, že ve 2:00 toho konkrétního rána se čas v místních hodinách náhle změní na 3:00. Celý 2:00 hodin se během tohoto 23hodinového dne nikdy nestane.

Vaše programy se s těmito případy musí vypořádat, možná tak, že při zjištění nejednoznačnosti uživatele vyzve. Pokud neshromažďujete řetězce data a času od uživatelů a neanalyzujete je, pravděpodobně tyto problémy nemáte. Programy, které potřebují určit, jestli určitý čas spadá do letního času, můžou využít následující:

Timezone.CurrentTimeZone.IsDaylightSavingTime(DateTimeInstance)

nebo

DateTimeInstance.IsDaylightSavingTime

Osvědčené postupy č. 7

Pokud vaše programy při testování přijímají vstup uživatele s určením hodnoty data a času, nezapomeňte otestovat ztrátu dat během 23 a 25 hodin. Nezapomeňte také otestovat data shromážděná na počítači v jednom časovém pásmu a uložená na počítači v jiném časovém pásmu.

Formátování a analýza User-Ready hodnot

Pro programy, které od uživatelů přebírají informace o datu a čase a potřebují převést tento uživatelský vstup na hodnoty DateTime, poskytuje rozhraní podporu pro analýzu řetězců, které jsou formátovány určitými způsoby. Obecně platí, že metody DateTime.Parse a ParseExact jsou užitečné pro převod řetězců, které obsahují kalendářní data a časy na hodnoty DateTime. Naopak metody ToString, ToLongDateString, ToLongTimeString, ToShortDateString a ToShortTimeString jsou užitečné pro vykreslování hodnot DateTime do řetězců čitelných pro člověka.

Dva hlavní problémy, které mají vliv na analýzu, jsou jazyková verze a formát řetězce. Nejčastější dotazy k datetime se týkají základních problémů souvisejících s jazykovou verzí, takže se zde zaměříme na osvědčené postupy pro formátování řetězců, které mají vliv na parsování DateTime.

Doporučené formátovací řetězce pro převod DateTime na řetězce jsou:

'yyyy'-'MM'-'dd'T'HH': 'mm': 'ss.fffffff'Z' – pro hodnoty UCT

'yyyy'-'MM'-'dd'T'HH': 'mm': 'ss.fffffff'zzz' – pro místní hodnoty

'yyyy'-'MM'-'dd'T'HH': 'mm': 'ss.fffffff' – pro abstraktní časové hodnoty

Jedná se o hodnoty řetězce formátu, které by byly předány DateTime.ToString metoda, pokud chcete získat výstup, který je kompatibilní se specifikací typu XML DateTime. Uvozovky zajistí, že místní nastavení data a času na počítači nepřepíše možnosti formátování. Pokud potřebujete zadat různá rozložení, můžete předat jiné formátovací řetězce pro poměrně flexibilní funkci vykreslování kalendářních dat, ale musíte být opatrní používat pouze zápis Z k vykreslení řetězců z hodnot UCT a používat zápis zzz pro hodnoty místního času.

Parsování řetězců a jejich převod na hodnoty DateTime lze provést pomocí metod DateTime.Parse a ParseExact. Pro většinu z nás je parse dostačující, protože ParseExact vyžaduje, abyste zadali vlastní instanci objektu Formatter . Analýza je docela schopná a flexibilní a dokáže přesně převést většinu řetězců, které obsahují data a časy.

Nakonec je důležité vždy volat Parse a ToString metody pouze po nastavení CultureInfo vlákna na CultureInfo.InvariantCulture.

Budoucí aspekty

Jedna věc, kterou v současné době nemůžete s DateTime.ToString naformátovat hodnotu DateTime do libovolného časového pásma. Tato funkce je zvažována pro budoucí implementace rozhraní .NET Framework. Pokud potřebujete zjistit, že řetězec "12:00:00 EST" odpovídá "11:00:00 EDT", budete muset převod a porovnání zpracovat sami.

Problémy s metodou DateTime.Now()

Při práci s metodou s názvem Now dochází k několika problémům. Pro vývojáře v jazyce Visual Basic, kteří toto čtou, to platí také pro funkci Visual Basic Now . Vývojáři, kteří pravidelně používají metodu Now, vědí, že se běžně používá k získání aktuálního času. Hodnota vrácená metodou Now je v aktuálním kontextu časového pásma počítače a nelze ji považovat za neměnnou hodnotu. Běžným postupem je převádět časy, které se budou ukládat nebo odesílat mezi počítači, na univerzální čas (UCT).

Pokud je letní čas možné, existuje jeden postup kódování, kterému byste se měli vyhnout. Představte si následující kód, který může zavést těžko rozpoznatelné chyby:

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

Hodnota, která je výsledkem spuštění tohoto kódu, bude o hodinu vypnutá, pokud je volána během hodiny navíc, ke které dochází během přechodu letního času na podzim. (To platí jenom pro počítače v časových pásmech, které používají letní čas.) Vzhledem k tomu, že hodina navíc spadá do místa, kde se stejná hodnota, například 1:10:00, vyskytuje dvakrát ráno, nemusí vrácená hodnota odpovídat hodnotě, kterou jste chtěli.

Pokud chcete tento problém vyřešit, osvědčeným postupem je místo volání funkce DateTime.Now a následného převodu na univerzální čas volání funkce DateTime.UtcNow().

Dim timeval As DateTime
timeval = DateTime.UtcNow()  

Tento kód bude mít vždy správnou 24hodinovou denní perspektivu a pak ho lze bezpečně převést na místní čas.

Osvědčený postup č. 8

Pokud kódujete a chcete uložit aktuální čas vyjádřený jako univerzální čas, vyhněte se volání funkce DateTime.Now() následované převodem na univerzální čas. Místo toho volejte funkci DateTime.UtcNow přímo.

Upozornění: Pokud se chystáte serializovat třídu, která obsahuje hodnotu DateTime, ujistěte se, že serializovaná hodnota nepředstavuje univerzální čas. Serializace XML nepodporuje serializaci UCT až do verze Whidbey sady Visual Studio.

Několik málo známých doplňku

Někdy, když se začnete ponořit do části rozhraní API, najdete skrytý drahokam – něco, co vám pomůže dosáhnout cíle, ale pokud vám o tom není řečeno, neodhalíte ho na svých každodenních cestách. Hodnotový typ DateTime v .NET má několik takových drahokamů, které vám můžou pomoct dosáhnout konzistentnějšího používání univerzálního času.

První je výčet DateTimeStyles , který se nachází v oboru názvů System.Globalization . Výčet řídí chování Funkcí DateTime.Parse() a ParseExact, které se používají k převodu uživatelem zadané vstup a další formy reprezentace vstupního řetězce na hodnoty DateTime.

Následující tabulka uvádí některé funkce, které DateTimeStyles výčet umožňuje.

Konstanta výčtu Účel Upozornění
AdjustToUniversal Při předání jako součást Metody Parse nebo ParseExact, tento příznak způsobí, že vrácená hodnota bude univerzální čas. Dokumentace je nejednoznačná, ale funguje s parse i ParseExact.
NoCurrentDateDefault Potlačí předpoklad, že řetězce analyzované bez kalendářních komponent budou mít vrácenou hodnotu DateTime, která je časem k aktuálnímu datu. Pokud použijete tuto možnost, vrátí se hodnota DateTime jako čas zadaný v gregoriánském datu 1. ledna v roce 1.
AllowWhiteSpaces

Allowtrailingwhite

AllowLeadingWhite

AllowInnerWhite

Všechny tyto možnosti umožňují toleranci pro přidání prázdných znaků před, za a uprostřed analyzovaných řetězců kalendářních dat. Žádné

Další zajímavé podpůrné funkce najdete ve třídě System.Timezone . Pokud chcete zjistit, jestli letní čas ovlivní hodnotu DateTime, nebo pokud chcete programově určit aktuální posun časového pásma pro místní počítač, nezapomeňte je vyzkoušet.

Závěr

Třída DateTime rozhraní .NET Framework poskytuje plnohodnotné rozhraní pro psaní programů, které pracují s časem. Pochopení drobných odlišností při práci s třídou jde nad rámec toho, co můžete získat z IntelliSense®. Tady jsme probrali osvědčené postupy pro kódování a testování programů, které se zabývají daty a časem. Šťastné psaní kódu!