Struktura DateTime je v .NET Frameworku asi jednou z nejpoužívanějších. Lze do ní uložit datum a čas a s hodnotou dále pracovat. Důležité je poznamenat, že tento typ neobsahuje definici časového pásma. Je tedy z pohledu dat jedno, jestli ukládáte čas aktuální pro Českou Republiku nebo New York, či jestli zohledňujete letní čas. Datově je to jen hodnota roku, měsíce, dne, hodiny, minuty, vteřiny a milisekundy.
Nejčastěji se s tímto typem pracuje tzv. lokálním způsobem. Jinými slovy vás nezajímá, jestli v jiné oblasti někde na světě může být jiný čas. Vy budete pracovat pouze s časem, který je nastavený v systému. Představte si modelovou situaci - zákazník si v objednávkovém systému koupí službu a vy chcete, aby vypršela za 20 minut. Zjistíte tedy aktuální datum a čas, přičtete 20 minut a hodnotu si někam uložíte. Při následné kontrole, zda služba zákazníkovi vypršela, si načtete hodnotu a porovnáte ji s aktuálním časem. Vše se tedy točí okolo získání aktuálního času.
Now & UtcNow - získání aktuálního data a času
Nejčastěji se používá statická metoda DateTime.Now, která vrací aktuální datum a čas nastavený v systému. Na serveru s časovým pásmem “UTC-10 Hawaii” bude tedy vracet jiný čas, než ve stejnou dobu na serveru s časovým pásmem “UTC+1 Prague”.
Avšak může nastat hned několik situací, kdy budete potřebovat čas v jiném pásmu, většinou v univerzálním UTC (časové pásmo 0, bez letního času). Pokud se vrátím k modelové situaci se zákazníkem a vypršením nějaké služby za 20 minut – představte si, že máte několik serverů po světě (každý v jiném pásmu), které vyřizují objednávky služeb a posílají je na centrální server. Pokud by nákupní servery zasílaly datum a čas objednávky ve svém lokálním pásmu, budeme si muset pamatovat jejich nastavení pásma a datum konvertovat na lokální pásmo při přijetí dat. Z důvodu přílišné složitosti je jednodušší systém navrhnout tak, aby všechny servery posílaly datum v univerzálním UTC.
K tomu slouží metoda DateTime.UtcNow vracející aktuální čas UTC. Pro Českou Republiku tedy o jednu nebo dvě hodiny méně než je lokální čas (podle toho, zda je nebo není letní čas). Důležité je, že hodnota bude ve stejný čas stejná na všech počítačích, ať už je v ČR nebo na Hawaii.
Následující kód obě metody demonstruje:
// lokální čas (letní čas - UTC+2) - například: 21.6.2012 19:23:27
Console.WriteLine(DateTime.Now);
// UTC aktuální čas - například: 21.6.2012 17:23:27
Console.WriteLine(DateTime.UtcNow);
Je vhodné se co nejdříve rozhodnout při psaní nového projektu, který přístup budete používat a v ideálním případě je nemíchat. Pokud na jednom místě v kódu použijete Now a na druhém UtcNow, výsledek bude při nejmenším dosti nekonzistentní. Pokud i přes to chcete (nebo musíte) používat oba druhy, doporučuji přidat ke jménu vlastností příponu “Utc”, popřípadě “Local”. Na první pohled bude tedy jasné, jaký formát času je uložený uvnitř proměnné.
DateTimeKind a konverze mezi lokálním a UTC časem
Datový typ DateTime nám nabízí několik základních metod pro konverzi převážně mezi lokálním a UTC časem. Mimo to obsahuje vlastnost Kind (typ DateTimeKind), která může s touto konverzí určitým způsobem pomoci. Nabývá těchto hodnot:
- Local – datum a čas byl vytvořen podle lokálního času
- Utc – datum a čas byl vytvořen podle pásma UTC
- Unspecified – původ data a času nebyl specifikován
Následující kód všechny tyto hodnoty vypisuje:
// vrací "Local"
Console.WriteLine(DateTime.Now.Kind);
// vrací: "Utc"
Console.WriteLine(DateTime.UtcNow.Kind);
// vrací: "Unspecified"
Console.WriteLine(new DateTime(2012, 6, 22, 10, 57, 15).Kind);
Význam hodnot Local a Utc je zde celkem jasný. Získávám totiž čas skrze vlastnosti Now a UtcNow, které vlastnost Kind přednastaví. Poslední řádek ale vrací Unspecified – důvodem je to, že jsem při vytváření hodnoty času nijak nespecifikoval, zda byla hodnota získána podle UTC nebo lokálního času. Pokud však chcete vlastnost nastavit, lze její hodnotu předat konstruktoru struktury DateTime jako další parametr. Například:
// explicitně určujeme druh času
Console.WriteLine(new DateTime(2012, 6, 22, 10, 57, 15, DateTimeKind.Local).Kind);
Význam DateTimeKind a konverze
Tato vlastnost Kind slouží pouze funkcím pro konverzi mezi časovými pásmy. V první řadě nijak neovlivňuje běžnou práci s datem a časem jako je například porovnávání. To demonstruje následující příklad:
// výsledek bude "true" - hodnoty mají sice nastavený jiný druh, ale ten na porovnávání nemá vliv
var dateTime1 = new DateTime(2012, 6, 22, 10, 57, 15, DateTimeKind.Local);
var dateTime2 = new DateTime(2012, 6, 22, 10, 57, 15, DateTimeKind.Utc);
Console.WriteLine(dateTime1 == dateTime2);
Kdy tedy tuto vlastnost uplatníme? Existují dvě konverzní metody nad hodnotou DateTime:
- ToUniversalTime – konvertuje čas z lokálního na UTC – pokud je Kind již nastaven na “Utc”, příkaz hodnotu nekonvertuje a vrací stejnou hodnotou
- ToLocalTime – konvertuje čas z UTC na lokální – pokud je Kind již nastaven na “Local”, příkaz hodnotu nekonvertuje a vrací stejnou hodnotou
V následujícím příkladu je konverze předvedena:
var dateTime1 = DateTime.Now;
// 22.6.2012 12:22:41 / Local
Console.WriteLine("{0} / {1}", dateTime1, dateTime1.Kind);
// 22.6.2012 10:22:41 / Local
var dateTimeLocal = dateTime1.ToLocalTime();
Console.WriteLine("{0} / {1}", dateTimeLocal, dateTimeLocal.Kind);
// 22.6.2012 12:22:41 / Utc
var dateTimeUtc = dateTime1.ToUniversalTime();
Console.WriteLine("{0} / {1}", dateTimeUtc, dateTimeUtc.Kind);
V ukázce jsem si získal lokální čas (DateTime.Now), který měl Kind=Local. Při následném volání “ToLocalTime” se ale čas nezměnil, protože vlastnost Kind již byla nastavena na Local. U volání “ToUniversalTime” se konverze provedla a funkce vrátila novou hodnotu s časem UTC a nastavenou vlastností Kind=Utc.
Zcela opačné chování zaznamenáte, pokud jako vstup bude UTC čas. Funkce “ToUniversalTime” neprovede žádnou konverzi, ale “ToLocalTime” čas zkonvertuje. Kód si můžete vyzkoušet podle následujícího příkladu:
var dateTime2 = DateTime.UtcNow;
// 22.6.2012 10:22:41 / Utc
Console.WriteLine("{0} / {1}", dateTime2, dateTime2.Kind);
// 22.6.2012 12:22:41 / Local
var dateTimeLocal = dateTime2.ToLocalTime();
Console.WriteLine("{0} / {1}", dateTimeLocal, dateTimeLocal.Kind);
// 22.6.2012 10:22:41 / Utc
var dateTimeUtc = dateTime2.ToUniversalTime();
Console.WriteLine("{0} / {1}", dateTimeUtc, dateTimeUtc.Kind);
Jinými slovy vlastnost Kind slouží k rozpoznání, zda se má při konverzi mezi lokálním a UTC časem konverze vůbec provést, nebo zda se ponechá aktuální hodnota, protože je již v požadovaném formátu. A samozřejmě může sloužit i vám k rozpoznávání, případně validaci, zda je datum a čas v potřebném formátu.
Poslední ukázka demonstruje chování konverzních metod u hodnoty, která nemá specifikovaný druh původu (Kind=Unspecified), protože nebyla při vytváření DateTime uvedena. U takto definovaných hodnot se konverze na lokální / UTC čas provede v obou případech. Viz následující kód:
var dateTime3 = new DateTime(2012, 6, 22, 10, 22, 41);
// 22.6.2012 10:22:41 / Unspecified
Console.WriteLine("{0} / {1}", dateTime3, dateTime3.Kind);
// 22.6.2012 12:22:41 / Local
var dateTimeLocal = dateTime3.ToLocalTime();
Console.WriteLine("{0} / {1}", dateTimeLocal, dateTimeLocal.Kind);
// 22.6.2012 8:22:41 / Utc
var dateTimeUtc = dateTime3.ToUniversalTime();
Console.WriteLine("{0} / {1}", dateTimeUtc, dateTimeUtc.Kind);
Konverzi do konkrétního časového pásma (jiného než lokálního a UTC) proberu v jednom z dalších dílů.