Odebírání posunu časového pásma z DateTimeOffset. Správná práce s datem a časem Plán naplánovaných akcí

Humánní možnost ([pro zobrazení odkazu se musíte zaregistrovat]).

Podstatou problému je toto.. Pokud jste omylem nasadili databázi do SQL server s „datovým posunem“ 0, pak nastal problém, když databáze obsahuje atribut s typem TIME, tj. tento atribut je nastaven na 01.01.0001 10:30:00 nebo je datum zapsáno jako prázdné 01.01.0001 00:00 :00. Při záznamu takových detailů se nezaznamenávají.
Na internetu nabízejí k tvorbě nová základna s offsetem 2000.
Ale ve skutečnosti jsem nechtěl vytvářet novou základnu. A změnit cestu k databázi pro všechny uživatele.
Pak jsem sledoval cestu, kde je tato hodnota uložena v SQL. Našel jsem to a změnil na 2000 a vše bylo v pořádku.
A teď krok za krokem, kde se změnit.

Vykopněte všechny uživatele.

POZORNOST!!!

1. Udělejte to jako první záložní kopie pomocí 1C tzn. nahrajte jej do *.dt
Toto je nutné provést před změnou „offsetu“
Pokud tak neučiníte, pak v celé vaší databázi budou odkazy, dokumenty atd.
kde je požadavek, datum bude řekněme 02.10.0009
CO NENÍ POVOLENO...
Takže jste nahráli do *.dt

2. Přejděte na SQL Server Management Studio
Najděte svou základnu v seznamu a stiskněte znaménko plus.
Najděte tam složku „Tabulky“ a otevřete ji.
Otevře se hromada stolů, jděte až úplně dole, najděte stůl
_YearOffset, postavte se na něj a pravým tlačítkem vyberte položku „Otevřít tabulku“, viz obr. 1
Změňte hodnotu 0 na 2000
Zavřete SQL Server Management Studio

3. Přejděte do konfigurátoru a načtěte dříve uloženou databázi.

Pokud tak neučiníte, všechna data budou s rokem 0009.
Po načtení databáze... Můžete přejít na 1C a ujistit se, že data jsou normální.
Výsledkem je, že jsme změnili „posun data z 0 na 2000“

Někdy se stane, že tuto možnost nelze z toho či onoho důvodu využít. Pak je tu tvrdší možnost ([pro zobrazení odkazu se musíte zaregistrovat]):

Deklarujte kurzor TablesAndFields pro

SELECT Objects.name jako Tablename, columns.name jako columnname
Z dbo.sysobjects tak jako objektů
levé spojení dbo.syscolumns jako sloupce na objects.id = columns.id
kde objects.xtype = "U" a columns.xtype = 61

otevřete TablesAndFields

WHILE @@FETCH_STATUS = 0
BEGIN Exec(" Aktualizace"+ @TableName + "
nastavit " + @ColumnName + " = ""2000- 01- 01 00:00:00" "
kde" + @ColumnName + " > ""3999- 12- 31 23:59:59" "")

To se provádí, dokud je předchozí načtení úspěšné.
NAČÍST NEXT Z TablesAndFields do @TableName, @ColumnName
KONEC

zavřete TablesAndFields
deallocateTablesAndFields
jít

Před jakoukoliv manipulací si nezapomeňte pořídit kopie databází!

Problém nemá nic společného s databází. Pokud nastavíte bod přerušení nebo někde zadáte výstup, měli byste vidět, jak se offset zachytí krátce po tomto kódu:

TestDateAndTime = testDateAndTime.DateTime.Date;

Pojďme si to rozebrat:

  • Začali jste s hodnotou DateTimeOffset 2008-05-01T08:06:32+01:00
  • Potom jste zavolali .DateTime, což vedlo k hodnotě DateTime 2008-05-01T08:06:32 s DateTimeKind.Unspecified.
  • Potom jste zavolali .Date, což vedlo k hodnotě DateTime 2008-05-01T00:00:00 s DateTimeKind.Unspecified.
  • Výsledek přiřazujete k testDateAndTime , což je typ DateTimeOffset . To způsobí implicitní přetypování z DateTime na DateTimeOffset - , která platí místníČasové pásmo. Ve vašem případě se zdá, že posun pro tuto hodnotu ve vašem místním časovém pásmu je -04:00 , takže výsledná hodnota je DateTimeOffset z 2008-05-01T00:00:00-04:00, jak jste popsali.

Řekl jsi:

Konečným cílem je jednoduše mít datum bez posunu času nebo časového pásma.

No, je v současné době není nativní datový typ C#, což je pouze datum bez času. Existuje čistý typ Date Systémový čas balíček v corefxlabu, ale ještě není zcela připraven pro typickou produkční aplikaci. V knihovně Noda Time je LocalDate, které můžete použít dnes, ale před uložením do databáze budete muset převést zpět na nativní typ. Takže mezitím to nejlepší, co můžete udělat, je:

  • Změňte svůj SQL Server tak, aby v poli používal typ data.
  • V kódu .NET použijte DateTime s časem 00:00:00 a DateTimeKind.Unspecified. Nezapomeňte ignorovat časovou část (protože v určitých časových pásmech skutečně existují data bez místní půlnoci).
  • Změňte testovací prop tak, aby byl DateTime spíše než DateTimeOffset.

Obecně platí, že zatímco DateTimeOffset je vhodný pro velké množství scénářů (např časová razítka události), nehodí se pro hodnoty pouze pro datum.

Chci dnešní datum s nulovým posunem.

jestli ty opravdu chci je to jako DateTimeOffset , můžete udělat:

TestDateAndTime = new DateTimeOffset(testDateAndTime.Date, TimeSpan.Zero);

Nicméně nedoporučuji to dělat. Tím si vezmete místní datum původní hodnoty a tvrzení, že je v UTC. Pokud je původní offset něco jiného než nula, bude to nepravdivé tvrzení. Následně to povede k dalším chybám, protože ve skutečnosti mluvíte o jiném časovém okamžiku (s potenciálně jiným datem), než který jste vytvořili.

Ohledně dodatečné otázky položené vaší radě. Zadáním DateTimeKind.Utc se změní chování implicitního přetypování. Místo použití místního časového pásma se používá čas UTC, který má vždy nulový posun. Výsledek je stejný jako názornější pohled, který jsem uvedl výše. Stále to doporučuji ze stejných důvodů.

Zvažte příklad začínající 2016-12-31T22:00:00-04:00. Podle vašeho přístupu byste měli uložit 2016-12-31T00:00:00+00:00 do databáze. Jedná se však o dva různé časové body. První, normalizovaný na UTC, by byl 2017-01-01T02:00:00+00:00 a druhý, převedený na jiné časové pásmo, by byl 2016-12-30T20:00:00-04:00. Vezměte prosím na vědomí změnu dat v převodu. Toto pravděpodobně není chování, které byste ve své aplikaci chtěli.

DateTimeOffset testDateAndTime = new DateTimeOffset(2008, 5, 1, 8, 6, 32, new TimeSpan(1, 0, 0)); //VYČISTIT ČAS A DATUM testDateAndTime = testDateAndTime.DateTime.Date; var dateTableEntry = db.DatesTable.First(dt => dt.Id == someTestId); dateTableEntry.test= testDateAndTime; db.SaveChangesAsync();

VÝSLEDEK V DATABÁZI: 2008-05-01 00:00:00.0000000 -04:00

Jak povolit -4:00 až +00:00 (z kódu před uložením)?

Zkusil jsem:

Veřejný úkol SetTimeZoneOffsetToZero(DateTimeOffset dateTimeOffSetObj) (TimeSpan zeroOffsetTimeSpan = new TimeSpan(0, 0, 0, 0, 0); return dateTimeOffSetObj.ToOffset(zeroOffsetTimeSpan); )

Nedělá nic.

Konečným cílem je jednoduše mít datum bez posunu času nebo časového pásma. NECHCI převádět čas na jiné časové pásmo (tj. nechci, aby 00:00:00.0000000 odečítalo 4 hodiny od času 00:00:00.0000000 a 00:00:00.0000000 posunulo nastavený čas o +00 :00, jen chci, aby nastavil posun na +00:00). Chci aktuální datum s nulovým posunem.

Upravit:

Zde je to, co můžete nabídnout jinde:

DateTimeOffset testDateAndTime = new DateTimeOffset(2008, 5, 1, 8, 6, 32, new TimeSpan(1, 0, 0)); testDateAndTime = testDateAndTime.DateTime.Date; //Vynulování časové části testDateAndTime = DateTime.SpecifyKind(testDateAndTime.Date, DateTimeKind.Utc); //"Vynulovat" část offsetu

Byl jsem si jistý, že SpecifyKind by SpecifyKind můj dateTimeOffset, jako změna časového i časového posunu, ale při testování se zdá, že jen mění posun časového pásma, což je to, co chci. Je s tím problém?

1 odpověď

Problém nemá nic společného s databází. Pokud nastavíte bod přerušení nebo někde zaregistrujete výstup, měli byste vidět, že offset je vázán krátce po tomto kódu:

TestDateAndTime = testDateAndTime.DateTime.Date;

Pojďme si to rozebrat:

  • Začali jste s DateTimeOffset 2008-05-01T08:06:32+01:00
  • Potom jste zavolali .DateTime, což vedlo k hodnotě DateTime 2008-05-01T08:06:32 s DateTimeKind.Unspecified.
  • Potom jste zavolali .Date, což vedlo k hodnotě DateTime 2008-05-01T00:00:00 s DateTimeKind.Unspecified.
  • Výsledek vrátíte v testDateAndTime, což je typ DateTimeOffset. To způsobí implicitní převod z DateTime na DateTimeOffset který používá místní časové pásmo. Ve vašem případě se zdá, že posun této hodnoty ve vašem místním časovém pásmu je -04:00 , takže výsledná hodnota je DateTimeOffset 2008-05-01T00:00:00-04:00, jak jste popsali,

Řekl jsi:

Konečným cílem je jednoduše mít datum bez posunu času nebo časového pásma.

No, v současné době neexistuje žádný nativní datový typ C#, který je pouze datem bez času. V balíku System.Time v corefxlab je čistý typ Date, ale není zcela připraven pro typickou produkční aplikaci. V časové knihovně Noda je LocalDate, který můžete používat již dnes, ale před uložením do databáze budete muset převést zpět na nativní typ. Takže mezitím to nejlepší, co můžete udělat, je:

  • Změňte svůj SQL Server tak, aby v tomto poli používal typ data.
  • V kódu .NET použijte DateTime s časem 00:00:00 a DateTimeKind.Unspecified. Nezapomeňte ignorovat časovou část (protože v určitých časových pásmech skutečně existují data bez místní půlnoci).
  • Změňte testovací upozornění na DateTime spíše než DateTimeOffset.

Obecně platí, že zatímco DateTimeOffset je vhodný pro velké množství scénářů (jako jsou události s časovým razítkem), není vhodný pro hodnoty pouze pro datum.

Chci aktuální datum s nulovým posunem.

Pokud to opravdu chcete jako DateTimeOffset, udělali byste:

TestDateAndTime = new DateTimeOffset(testDateAndTime.Date, TimeSpan.Zero);

Nedoporučuji to však. Tímto způsobem berete místní datum původní hodnoty a tvrdíte, že je v UTC. Pokud je původní offset něco jiného než nula, bude to nepravdivé tvrzení. Následně to povede k dalším chybám, protože ve skutečnosti mluvíte o jiném časovém okamžiku (s potenciálně jiným datem), než který jste vytvořili.

Pokud jde o další otázku položenou ve vaší úpravě - zadání DateTimeKind.Utc změní chování implicitního přetypování. Místo použití místního časového pásma se používá čas UTC, který má vždy nulový posun. Výsledek je stejný jako názornější pohled, který jsem uvedl výše. Stále to doporučuji ze stejných důvodů.

Podívejme se na příklad začátku od 2016-12-31T22:00:00-04:00. Podle vašeho přístupu byste měli uložit 2016-12-31T00:00:00+00:00 do databáze. Jedná se však o dva různé časové body. První, normalizovaný na UTC, by byl 2017-01-01T02:00:00+00:00 a druhý, převedený na jiné časové pásmo, by byl 2016-12-30T20:00:00-04:00. Vezměte prosím na vědomí změnu dat v převodu. Toto pravděpodobně není chování, které byste ve své aplikaci chtěli.

Téměř všechny projekty narážejí na problémy způsobené nesprávným zacházením a ukládáním data a času. I když je projekt používán ve stejném časovém pásmu, stále můžete po přechodu na zimní/letní čas zažít nepříjemná překvapení. Zároveň je málo lidí zmateno implementací správného mechanismu od začátku, protože se zdá, že s tím nemohou být žádné problémy, protože vše je triviální. Bohužel pozdější realita ukazuje, že tomu tak není.

Logicky lze rozlišit následující typy hodnot souvisejících s datem a časem:


Zvažme každý bod zvlášť a nezapomínejme na to.

datum a čas

Řekněme, že laboratoř, která odebírala materiál pro analýzu, se nachází v časové zóně +2 a centrální pobočka, která hlídá včasné dokončení analýz, je v zóně +1. Časy uvedené v příkladu byly zaznamenány při odběru materiálu první laboratoří. Nabízí se otázka – jaký časový údaj by měla centrála vidět? Je zřejmé, že software centrály by měl ukazovat 15. ledna 2014 12:17:15 - o hodinu méně, protože podle jejich hodin k události došlo právě v tu chvíli.

Uvažujme jeden z možných řetězců akcí, kterými procházejí data z klienta na server a zpět, což umožňuje vždy správně zobrazit datum/čas podle aktuálního časového pásma klienta:

  1. Hodnota se vytváří na klientovi, například 2.3.2016 15 :13:36 klient je v èasovém pásmu +2.
  2. Hodnota je převedena na reprezentaci řetězce pro přenos na server – „2016-03-02T 15 :13:36+02:00”.
  3. Serializovaná data jsou odeslána na server.
  4. Server deserializuje čas do objektu datum/čas a přenese jej do aktuálního časového pásma. Pokud například server běží na +1, bude objekt obsahovat 2. března 2016 14 :13:36.
  5. Server ukládá data do databáze, ale neobsahuje žádné informace o časovém pásmu - nejpoužívanější typy data/času o něm prostě nic nevědí. Do databáze tak bude uložen 2. březen 2016 14 :13:36 v "neznámém" èasovém pásmu.
  6. Server načte data z databáze a vytvoří odpovídající objekt s hodnotou 2. března 2016 14 :13:36. A protože server pracuje v časovém pásmu +1, bude tato hodnota také interpretována ve stejném časovém pásmu.
  7. Hodnota je převedena na reprezentaci řetězce pro přenos klientovi – „2016-03-02T 14 :13:36+01:00”.
  8. Serializovaná data jsou odeslána klientovi.
  9. Klient deserializuje přijatou hodnotu do objektu datum/čas a přetypuje ji do aktuálního časového pásma. Pokud je například -5, zobrazená hodnota by měla být 2. března 2016 09 :13:36.
Vše se zdá být neporušené, ale pojďme se zamyslet nad tím, co by se v tomto procesu mohlo pokazit. Problémy zde mohou nastat téměř na každém kroku.
  • Čas na klientovi lze generovat zcela bez časového pásma - například typ DateTime v .NET s DateTimeKind.Unspecified.
  • Serializační stroj může používat formát, který nezahrnuje posun časového pásma.
  • Při deserializaci na objekt může být posunutí časového pásma ignorováno, zejména v "domácích" deserializérech - jak na serveru, tak na klientovi.
  • Při čtení z databáze lze objekt data/času vygenerovat zcela bez časového pásma – například typ DateTime v .NET s DateTimeKind.Unspecified. Navíc s DateTime v .NET se v praxi přesně toto stane, pokud ihned po korektuře výslovně neurčíte jiný DateTimeKind.
  • Pokud jsou aplikační servery pracující se společnou databází umístěny v různých časových pásmech, dojde k vážným nejasnostem v časových posunech. Hodnota data/času zapsaná do databáze serverem A a načtená serverem B se bude výrazně lišit od stejné původní hodnoty zapsané serverem B a načtené serverem A.
  • Přenos aplikačních serverů z jedné zóny do druhé povede k nesprávné interpretaci již uložených hodnot data/času.
Ale nejzávažnější nevýhodou ve výše popsaném řetězci je použití místního časového pásma na serveru. Pokud nedojde k přechodu na letní/zimní čas, nevzniknou žádné další problémy. Ale jinak můžete získat spoustu nepříjemných překvapení.

Pravidla pro přepočet na letní/zimní čas jsou přísně vzato variabilní. Různé země mohou čas od času změnit svá pravidla a tyto změny by měly být zabudovány do aktualizací systému s dostatečným předstihem. V praxi k situacím opakovaně docházelo nesprávný provoz tohoto mechanismu, které byly nakonec vyřešeny instalací hotfixů popř operační systém nebo použité knihovny třetích stran. Pravděpodobnost opakování stejných problémů není nulová, takže je lepší mít způsob, jak se jim vyhnout.

Vezmeme-li v úvahu výše popsané úvahy, formulujeme nejspolehlivější a nejjednodušší přístup k přenosu a ukládání času: na serveru a v databázi musí být všechny hodnoty převedeny na časové pásmo UTC.

Podívejme se, co nám toto pravidlo dává:

  • Při odesílání dat na server musí klient předat posun časového pásma, aby server mohl správně převést čas na UTC. Alternativní možnost— zavázat klienta k provedení této transformace, ale první možnost je flexibilnější. Při příjmu dat zpět ze serveru klient převede datum a čas na své místní časové pásmo s vědomím, že v každém případě obdrží čas v UTC.
  • V UTC nejsou žádné přechody mezi letním a zimním časem, takže problémy s tím spojené nebudou relevantní.
  • Na serveru při čtení z databáze nemusíte převádět časové hodnoty, stačí explicitně uvést, že odpovídá UTC. Například v .NET toho lze dosáhnout nastavením DateTimeKind na časovém objektu na DateTimeKind.Utc.
  • Rozdíl v časových pásmech mezi servery pracujícími se společnou databází, stejně jako přesun serverů z jedné zóny do druhé, nijak neovlivní správnost přijímaných dat.
K implementaci takového pravidla stačí postarat se o tři věci:
  1. Udělejte mechanismus serializace a deserializace tak, aby se hodnoty data/času správně překládaly z UTC do místního časového pásma a zpět.
  2. Ujistěte se, že deserializátor na straně serveru vytváří objekty data a času v UTC.
  3. Ujistěte se, že při čtení z databáze jsou objekty data/času vytvořeny v UTC. Tato položka je někdy poskytována beze změn kódu - jednoduše je systémové časové pásmo na všech serverech nastaveno na UTC.
Výše uvedené úvahy a doporučení fungují skvěle když se spojí dvě podmínky:
  • Systémové požadavky nevyžadují, aby byl místní čas a/nebo posun časového pásma zobrazen přesně tak, jak byl uložen. Například letenky musí tisknout časy odletu a příletu v časovém pásmu odpovídajícím umístění letiště. Nebo pokud server odesílá tiskové faktury vytvořené v různých zemích, každá by měla skončit s místním časem, a nikoli převedena na časové pásmo serveru.
  • Všechny hodnoty data a času v systému jsou "absolutní" - tzn. popisují časový bod v budoucnosti nebo minulosti, který odpovídá jedné hodnotě v UTC. Například „nosná raketa se konala ve 23:00 kyjevského času“ nebo „schůzka se bude konat od 13:30 do 14:30 minského času“. Čísla těchto událostí se budou v různých časových pásmech lišit, ale budou popisovat stejný časový okamžik. Může se ale stát, že požadavky na software v některých případech implikuje „relativní“ místní čas. Například „tento televizní program se bude vysílat od 9:00 do 10:00 v každé zemi, kde je přidružený televizní kanál.“ Ukazuje se, že vysílání pořadu není jedna událost, ale několik, a potenciálně se všechny mohou odehrávat v různých časových obdobích v „absolutním“ měřítku.
V případě porušení první podmínky lze problém vyřešit použitím datových typů obsahujících časové pásmo - jak na serveru, tak v databázi. Níže je uveden krátký seznam příkladů pro různé platformy a DBMS.
.SÍŤ DateTimeOffset
Jáva org.joda.time.DateTime, java.time.ZonedDateTime
MS SQL datum a čas offset
Oracle, PostgreSQL ČASOVÉ RAZÍTKO S ČASOVÝM PÁSMOM
MySQL

Porušení druhé podmínky je složitější případ. Pokud je třeba tento „relativní“ čas uložit jednoduše pro zobrazení a neexistuje úkol určit „absolutní“ časový okamžik, kdy událost nastala nebo nastane pro dané časové pásmo, stačí převod času jednoduše zakázat. Uživatel například zadal začátek programu pro všechny pobočky televizní společnosti 25. března 2016 v 9:00 a v této podobě bude přenášen, ukládán a zobrazován. Může se ale stát, že některý plánovač by měl hodinu před začátkem každého pořadu automaticky provádět speciální akce (rozesílat upozornění nebo kontrolovat přítomnost některých dat v databázi televizní společnosti). Spolehlivě implementovat takový plánovač není triviální úkol. Řekněme, že plánovač ví, v jakém časovém pásmu se každá větev nachází. A jedna ze zemí, kde je pobočka, se po nějaké době rozhodne změnit časové pásmo. Případ není tak vzácný, jak by se mohlo zdát - za tento a dva předchozí roky jsem napočítal více než 10 podobných akcí (http://www.timeanddate.com/news/time/). Ukazuje se, že oba uživatelé musí podporovat vazby na časové pásmo aktuální stav nebo musí plánovač automaticky převzít tyto informace z globálních zdrojů, jako je Google mapy Time Zone API. Nezavazuji se nabízet univerzální řešení pro takové případy, pouze podotýkám, že takové situace vyžadují seriózní studium.

Jak je vidět z výše uvedeného, neexistuje jediný přístup, který by pokryl 100 % případů. Nejprve tedy musíte z požadavků jasně pochopit, která z výše uvedených situací ve vašem systému nastane. S největší pravděpodobností bude vše omezeno na první navrhovaný přístup s ukládáním v UTC. No, popsané výjimečné situace to neruší, ale prostě přidávají další řešení pro speciální případy.

Datum bez času

Řekněme, že jsme vyřešili správné zobrazení data a času s ohledem na časové pásmo klienta. Přejděme k datům bez času a příkladu uvedenému pro tento případ na začátku – „nová smlouva vstupuje v platnost 2. února 2016“. Co se stane, když se pro takové hodnoty použijí stejné typy a stejný mechanismus jako pro „běžná“ data a časy?

Ne všechny platformy, jazyky a DBMS mají typy pouze pro datum. Například v .NET existuje pouze typ DateTime, neexistuje žádný samostatný typ „jen datum“. I když bylo při vytváření takového objektu zadáno pouze datum, čas je stále přítomen a je roven 00:00:00. Pokud přeneseme hodnotu „2. února 2016 00:00:00“ ze zóny s offsetem +2 až +1, dostaneme „1. února 2016 23:00:00“. Ve výše uvedeném příkladu by to bylo ekvivalentní nové smlouvě začínající 2. února v jednom časovém pásmu a 1. února ve druhém. Z právního hlediska je to absurdní a samozřejmě by to tak být nemělo. Obecné pravidlo pro „čistá“ data je to extrémně jednoduché - takové hodnoty by se neměly převádět v žádném kroku ukládání a čtení.

Existuje několik způsobů, jak se vyhnout převodu dat:

  • Pokud platforma podporuje typ, který představuje datum bez času, měl by být použit.
  • Přidejte do metadat objektu speciální atribut, který serializátoru řekne, že pro daná hodnotačasové pásmo by mělo být ignorováno.
  • Předejte datum od klienta a zpět jako řetězec a uložte jej jako datum. Tento přístup je nepohodlný, pokud potřebujete nejen zobrazit datum na klientovi, ale také s ním provádět některé operace: srovnání, odečítání atd.
  • Předat a uložit jako řetězec a převést na datum pouze pro formátování na základě místního nastavení klienta. Má ještě více nevýhod než předchozí možnost – pokud například části data v uloženém řetězci nejsou v pořadí „rok, měsíc, den“, nebude možné provést efektivní indexované vyhledávání podle rozsahu dat.
Můžete samozřejmě zkusit uvést protipříklad a říci, že smlouva má smysl pouze v rámci země, ve které je uzavřena, země je ve stejném časovém pásmu, a tudíž lze jednoznačně určit okamžik její platnosti. Ale ani v tomto případě nebude uživatele z jiných časových pásem zajímat, ve kterém okamžiku jejich místního času tato událost nastane. A i kdyby byla potřeba zobrazit tento okamžik v čase, pak by musel zobrazovat nejen datum, ale i čas, což odporuje původnímu stavu.

Časový interval

S ukládáním a zpracováním časových intervalů je vše jednoduché: jejich hodnota nezávisí na časovém pásmu, takže zde nejsou žádná speciální doporučení. Mohou být uloženy a přenášeny jako počet časových jednotek (celé číslo nebo pohyblivá řádová čárka, v závislosti na požadované přesnosti). Pokud je důležitá přesnost v sekundách, pak jako počet sekund, pokud je důležitá přesnost v milisekundách, pak jako počet milisekund atd.

Výpočet intervalu ale může mít úskalí. Řekněme, že máme nějaký ukázkový kód C#, který počítá časový interval mezi dvěma událostmi:

DateTime start = DateTime.Now; //... DateTime end = DateTime.Now; double hours = (konec - začátek).TotalHours;
Na první pohled zde nejsou žádné problémy, ale není tomu tak. Za prvé, s testováním takového kódu mohou být problémy, ale o tom budeme hovořit o něco později. Za druhé, představme si, že počáteční okamžik času připadl na zimní čas a konečný okamžik připadl na letní čas (takto se například měří počet pracovních hodin a pracovníci mají noční směnu).

Předpokládejme, že kód běží v časovém pásmu, ve kterém letní čas v roce 2016 nastává v noci 27. března, a simulujme výše popsanou situaci:

DateTime start = DateTime.Parse("2016-03-26T20:00:15+02"); DateTime end = DateTime.Parse("2016-03-27T05:00:15+03"); double hours = (konec - začátek).TotalHours;
Tento kód bude mít za následek 9 hodin, ačkoli ve skutečnosti mezi těmito okamžiky uplynulo 8 hodin. Můžete to snadno ověřit změnou kódu takto:

DateTime start = DateTime.Parse("2016-03-26T20:00:15+02").ToUniversalTime(); DateTime end = DateTime.Parse("2016-03-27T05:00:15+03").ToUniversalTime(); double hours = (konec - začátek).TotalHours;
Proto závěr - jakýkoli aritmetické operace Musíte se vypořádat s datem a časem pomocí hodnot UTC nebo typů, které ukládají informace o časovém pásmu. A pak v případě potřeby přeneste zpět na místní. Z tohoto pohledu lze původní příklad snadno opravit změnou DateTime.Now na DateTime.UtcNow.

Tato nuance nezávisí na konkrétní platformě nebo jazyce. Zde je podobný kód v Javě, který má stejný problém:

LocalDateTime start = LocalDateTime.now(); //... LocalDateTime end = LocalDateTime.now(); dlouhé hodiny = ChronoUnit.HOURS.between(start, end);
Dá se to také jednoduše opravit – například použitím ZonedDateTime místo LocalDateTime.

Rozpis plánovaných akcí

Plánování naplánovaných událostí je složitější situace. Neexistuje žádný univerzální typ, který by umožňoval ukládat plány do standardních knihoven. Ale takový úkol nevzniká velmi zřídka, takže hotová řešení lze najít bez problémů. Dobrým příkladem je formát plánovače cron, který v té či oné podobě používají jiná řešení, jako je Quartz: http://quartz-scheduler.org/api/2.2.0/org/quartz/CronExpression.html. Pokrývá téměř všechny potřeby plánování, včetně možností jako „druhý pátek v měsíci“.

Ve většině případů nemá smysl psát svůj vlastní plánovač, protože existují flexibilní, časem prověřená řešení, ale pokud je z nějakého důvodu potřeba vytvořit vlastní mechanismus, lze si zapůjčit alespoň formát rozvrhu z cronu.

Kromě výše popsaných doporučení týkajících se ukládání a zpracování různých typů časových hodnot existuje několik dalších, které bych také rád zmínil.

Za prvé, pokud jde o použití statických členů třídy k získání aktuálního času - DateTime.UtcNow, ZonedDateTime.now() atd. Jak bylo řečeno, jejich použití přímo v kódu může vážně zkomplikovat testování jednotek, protože bez speciálních mocking frameworků nebude možné nahradit aktuální čas. Pokud tedy plánujete psát unit testy, měli byste se ujistit, že implementaci takových metod lze nahradit. Tento problém lze vyřešit minimálně dvěma způsoby:

  • Poskytněte rozhraní IDateTimeProvider s jedinou metodou, která vrací aktuální čas. Poté přidejte do tohoto rozhraní závislost ve všech jednotkách kódu, kde potřebujete získat aktuální čas. Během normálního provádění programu bude do všech těchto míst vložena „výchozí“ implementace, která vrátí skutečný aktuální čas, a v jednotkových testech jakákoli jiná nutná implementace. Tato metoda je z testovacího hlediska nejflexibilnější.
  • Vytvořte si vlastní statickou třídu s metodou pro získání aktuálního času a možností nainstalovat jakoukoli implementaci této metody zvenčí. Například v případě kódu C# může tato třída vystavit vlastnost UtcNow a metodu SetImplementation(Func) impl). Použití statické vlastnosti nebo metody pro získání aktuálního času eliminuje nutnost všude explicitně specifikovat závislost na dodatečném rozhraní, ale z hlediska principů OOP to není ideální řešení. Pokud však z nějakého důvodu není předchozí možnost vhodná, můžete použít tuto.
Dalším problémem, který by měl být vyřešen při migraci na vaši aktuální implementaci poskytovatele času, je zajistit, aby nikdo nadále nepoužíval standardní třídy „starým způsobem“. Tento úkol je snadno řešitelný ve většině systémů kontroly kvality kódu. V podstatě jde o hledání „nežádoucího“ podřetězce ve všech souborech kromě toho, kde je deklarována „výchozí“ implementace.

Druhou výhradou k získání aktuálního času je to klientovi nelze věřit. Aktuální čas na počítačích uživatelů se může velmi lišit od toho skutečného, ​​a pokud je k němu vázaná logika, pak tento rozdíl může vše zhatit. Všechna místa, kde je potřeba získat aktuální čas, by měla být pokud možno provedena na straně serveru. A jak již bylo zmíněno dříve, všechny aritmetické operace s časem musí být prováděny buď v hodnotách UTC nebo pomocí typů, které ukládají posun časového pásma.

A ještě jedna věc, kterou jsem chtěl zmínit, je norma ISO 8601, která popisuje formát data a času pro výměnu informací. Zejména řetězcová reprezentace data a času použitá při serializaci musí odpovídat tomuto standardu, aby se předešlo potenciálním problémům s kompatibilitou. V praxi je extrémně vzácné, že musíte formátování implementovat sami, takže samotný standard může být užitečný hlavně pro informační účely.

Štítky: Přidat štítky