Seznámíme se s novým vestavěným objektem: Date. Tento objekt v sobě ukládá datum a čas a poskytuje metody pro práci s nimi.
Můžeme jej například použít k uložení času vytvoření nebo modifikace něčeho, k měření času nebo jen k vypsání dnešního data.
Vytvoření
Abychom vytvořili objekt Date, zavoláme new Date() s jedním z následujících argumentů:
new Date()-
Bez argumentů – vytvoří se objekt
Datepro aktuální datum a čas:let nyní = new Date(); alert( nyní ); // zobrazí aktuální datum a čas new Date(milisekundy)-
Vytvoří objekt
Dates časem, který odpovídá počtu milisekund (1/1000 sekundy), které uplynuly od 1. ledna 1970 UTC+0.// 0 znamená 01.01.1970 UTC+0 let leden01_1970 = new Date(0); alert( leden01_1970 ); // nyní přidáme 24 hodin a získáme 02.01.1970 UTC+0 let leden02_1970 = new Date(24 * 3600 * 1000); alert( leden02_1970 );Celé číslo, které představuje počet milisekund, které uplynuly od začátku roku 1970, se nazývá časové razítko nebo časová značka (anglicky timestamp).
Je to číselná reprezentace data. Z časového razítka můžeme vždy vytvořit datum pomocí
new Date(časovéRazítko)a existující objektDatemůžeme převést na časové razítko pomocí metodydatum.getTime()(viz níže).Data před 1. lednem 1970 mají záporná časová razítka, např.:
// 31. prosinec 1969 let pros31_1969 = new Date(-24 * 3600 * 1000); alert( pros31_1969 ); new Date(datovýŘetězec)-
Jestliže je uveden jediný argument a je to řetězec, bude z něj automaticky vytvořeno datum. Algoritmus je stejný, jaký používá
Date.parse. Probereme ho později.let datum= new Date("2017-01-26"); alert(datum); // Čas není nastaven, takže se předpokládá půlnoc GMT a // přizpůsobí se časovému pásmu, ve kterém je kód spuštěn. // Výsledek tedy může být: // Thu Jan 26 2017 11:00:00 GMT+1100 (Australian Eastern Daylight Time) // nebo: // Wed Jan 25 2017 16:00:00 GMT-0800 (Pacific Standard Time) // V Česku zřejmě bude: // Thu Jan 26 2017 01:00:00 GMT+1100 (Středoevropský standardní čas) - pozn. překl. new Date(rok, měsíc, den, hodiny, minuty, sekundy, ms)-
Vytvoří datum ze zadaných složek v místním časovém pásmu. Povinné jsou jen první dva argumenty.
- Parametr
rokby měl mít 4 číslice. Z důvodu kompatibility se přijímají i 2 číslice a považují se za19xx, např.98je zde totéž jako1998, ale důrazně se doporučuje používat vždy 4 číslice. - Parametr
měsícmůže být od0(leden) do11(prosinec). - Parametr
denznamená den v měsíci. Není-li uveden, předpokládá se1. - Nejsou-li uvedeny parametry
hodiny/minuty/sekundy/ms, předpokládá se, že jsou0.
Příklad:
new Date(2011, 0, 1, 0, 0, 0, 0); // 1. leden 2011, 00:00:00 new Date(2011, 0, 1); // totéž, hodiny atd. jsou standardně 0Maximální přesnost je 1 ms (1/1000 s):
let datum = new Date(2011, 0, 1, 2, 3, 4, 567); alert( datum ); // 1.01.2011, 02:03:04.567 - Parametr
Přístup ke složkám data
Existují metody, kterými můžeme přistupovat k roku, měsíci a dalším složkám objektu Date:
- getFullYear()
- Vrací rok (4-číslicový).
- getMonth()
- Vrací měsíc od 0 do 11.
- getDate()
- Vrací den v měsíci od 1 do 31. Název metody skutečně vypadá trochu podivně.
- getHours(), getMinutes(), getSeconds(), getMilliseconds()
- Vracejí odpovídající časové složky (
getHours()vrací hodiny,getMinutes()minuty,getSeconds()sekundy agetMilliseconds()milisekundy – pozn. překl.).
getYear(), ale getFullYear()Mnoho JavaScriptových motorů implementuje nestandardní metodu getYear(). Tato metoda je zastaralá. Někdy vrací 2-číslicový letopočet. Prosíme nepoužívejte ji. Ke zjištění roku je určena metoda getFullYear().
Navíc můžeme získat i den v týdnu:
- getDay()
- Vrací den v týdnu od
0(neděle) do6(sobota). První den je vždy neděle. V některých zemích to tak není, ale nedá se to změnit.
Všechny výše uvedené metody vracejí složky relativní k místnímu časovému pásmu.
Existují také jejich UTC-protějšky, které vracejí den, měsíc atd. pro časové pásmo UTC+0: getUTCFullYear(), getUTCMonth(), getUTCDay(). Stačí vložit "UTC" rovnou za "get".
Jestliže je vaše místní časové pásmo vzhledem k UTC posunuto, pak následující kód zobrazí různé hodiny:
// nynější datum
let datum = new Date();
// hodina ve vašem nynějším časovém pásmu
alert( datum.getHours() );
// hodina v časovém pásmu UTC+0 (londýnský zimní čas)
alert( datum.getUTCHours() );
Kromě uvedených metod existují i dvě speciální, které nemají UTC-variantu:
- getTime()
-
Vrací časové razítko data – počet milisekund, které uplynuly od 1. ledna 1970 UTC+0.
- getTimezoneOffset()
-
Vrací rozdíl mezi UTC a místním časovým pásmem v minutách:
// jestliže jste v časovém pásmu UTC-1, vypíše 60 // jestliže jste v časovém pásmu UTC+3, vypíše -180 // (v Česku vypíše v letním čase -120, v zimním -60 - pozn. překl.) alert( new Date().getTimezoneOffset() );
Nastavení složek data
Následující metody umožňují nastavit složky data a času:
setFullYear(rok, [měsíc], [den])setMonth(měsíc, [den])setDate(den)setHours(hodina, [minuty], [sekundy], [ms])setMinutes(minuty, [sekundy], [ms])setSeconds(sekundy, [ms])setMilliseconds(ms)setTime(milisekundy)(nastaví celé datum z milisekund uplynulých od 1. 1. 1970 UTC)
Každá z nich kromě setTime() má UTC-variantu, např. setUTCHours().
Jak vidíme, některé metody umožňují nastavit více složek najednou, např. setHours. Složky, které nejsou uvedeny, nebudou změněny.
Příklad:
let dnes = new Date();
dnes.setHours(0);
alert(dnes); // stále dnes, ale hodina se změní na 0
dnes.setHours(0, 0, 0, 0);
alert(dnes); // stále dnes, nyní 00:00:00
Automatická oprava
Automatická oprava je velmi šikovná vlastnost objektů Date. Můžeme nastavit hodnoty mimo rozsah a ty se automaticky přizpůsobí.
Příklad:
let datum = new Date(2013, 0, 32); // 32. leden 2013 ?!?
alert(datum); // ...je 1. únor 2013!
Datové složky mimo povolený rozsah se automaticky přepočítají.
Řekněme, že potřebujeme zvýšit datum „28. únor 2016“ o 2 dny. Může to být „2. březen“ nebo v případě přestupného roku „1. březen“. Na to nemusíme myslet. Stačí přičíst 2 dny. O zbytek se postará objekt Date:
let datum = new Date(2016, 1, 28);
datum.setDate(datum.getDate() + 2);
alert( datum ); // 1. březen 2016
Tato vlastnost se často používá k získání data, které nastane po uplynutí zadaného času. Například získejme datum, které bude za „70 sekund od nynějška“:
let datum = new Date();
datum.setSeconds(datum.getSeconds() + 70);
alert( datum ); // zobrazí správné datum
Můžeme nastavit i nulové nebo záporné hodnoty. Například:
let datum = new Date(2016, 0, 2); // 2. leden 2016
datum.setDate(1); // nastavíme 1. den v měsíci
alert( datum );
datum.setDate(0); // minimální den je 1, takže se předpokládá poslední den předchozího měsíce
alert( datum ); // 31. prosinec 2015
Konverze data na číslo, rozdíl dat
Když je objekt Date konvertován na číslo, převede se na časové razítko, stejné, jaké vrací metoda datum.getTime():
let datum = new Date();
alert(+datum); // počet milisekund, totéž jako datum.getTime()
Důležitý vedlejší efekt: data lze od sebe odečítat, výsledkem je jejich rozdíl v milisekundách.
To můžeme využít k měření času:
let začátek = new Date(); // spustíme měření času
// uděláme práci
for (let i = 0; i < 100000; i++) {
let dělejNěco = i * i * i;
}
let konec = new Date(); // zastavíme měření času
alert( `Cyklus trval ${konec - začátek} ms` );
Date.now()
Jestliže chceme jenom měřit čas, nepotřebujeme objekt Date.
Existuje speciální metoda Date.now(), která vrací aktuální časové razítko.
Je sémanticky ekvivalentní new Date().getTime(), ale nevytváří dočasný objekt Date. Je tedy rychlejší a nezatěžuje sběrač odpadků.
Používá se většinou proto, že je pohodlnější, nebo tehdy, když záleží na výkonu, například v JavaScriptových hrách nebo jiných specializovaných aplikacích.
Tohle je tedy pravděpodobně lepší:
let začátek = Date.now(); // počet milisekund od 1. ledna 1970
// uděláme práci
for (let i = 0; i < 100000; i++) {
let dělejNěco = i * i * i;
}
let konec = Date.now(); // hotovo
alert( `Cyklus trval ${konec - začátek} ms` ); // odečítáme čísla, ne data
Benchmarky
Jestliže chceme spolehlivý benchmark funkce náročné na CPU, měli bychom být opatrní.
Například změřme dvě funkce, které počítají rozdíl mezi dvěma daty: která z nich je rychlejší?
Taková měření výkonu se často nazývají „benchmarky“.
// máme datum1 a datum2, která funkce vrátí rychleji jejich rozdíl v ms?
function rozdílOdečtením(datum1, datum2) {
return datum2 - datum1;
}
// nebo
function rozdílPomocíGetTime(datum1, datum2) {
return datum2.getTime() - datum1.getTime();
}
Tyto dvě funkce dělají přesně totéž, ale jedna z nich používá explicitní datum.getTime(), aby získala datum v milisekundách, zatímco druhá se spoléhá na transformaci data na číslo. Jejich výsledek je vždy stejný.
Která je tedy rychlejší?
Jako první můžeme dostat nápad, že je spustíme mnohokrát za sebou a změříme rozdíl časů. V našem případě jsou tyto funkce velmi jednoduché, takže to musíme udělat alespoň 100 000krát.
Změřme to:
function rozdílOdečtením(datum1, datum2) {
return datum2 - datum1;
}
function rozdílPomocíGetTime(datum1, datum2) {
return datum2.getTime() - datum1.getTime();
}
function bench(f) {
let datum1 = new Date(0);
let datum2 = new Date();
let začátek = Date.now();
for (let i = 0; i < 100000; i++) f(datum1, datum2);
return Date.now() - začátek;
}
alert( 'Čas funkce rozdílOdečtením: ' + bench(rozdílOdečtením) + ' ms' );
alert( 'Čas funkce rozdílPomocíGetTime: ' + bench(rozdílPomocíGetTime) + ' ms' );
Páni! Použití getTime() je mnohem rychlejší! Je to proto, že tam není žádná typová konverze, a proto je pro motory mnohem snadnější funkci optimalizovat.
Dobrá, něco tedy máme. To však ještě není dobrý benchmark.
Představme si, že v době spuštění bench(rozdílOdečtením) CPU prováděl paralelně něco jiného a to mu ubíralo zdroje. A v době spuštění bench(rozdílPomocíGetTime) tahle práce skončila.
V moderních multiprocesních operačních systémech je to dosti reálný scénář.
Výsledkem je, že první benchmark měl méně zdrojů CPU než druhý. To mohlo vést ke špatným výsledkům.
Pro spolehlivější benchmarkové testování bychom měli celý balíček benchmarků spustit několikrát za sebou.
Například takto:
function rozdílOdečtením(datum1, datum2) {
return datum2 - datum1;
}
function rozdílPomocíGetTime(datum1, datum2) {
return datum2.getTime() - datum1.getTime();
}
function bench(f) {
let datum1 = new Date(0);
let datum2 = new Date();
let začátek = Date.now();
for (let i = 0; i < 100000; i++) f(datum1, datum2);
return Date.now() - začátek;
}
let čas1 = 0;
let čas2 = 0;
// spustíme bench(rozdílOdečtením) a bench(rozdílPomocíGetTime) střídavě, každou 10krát
for (let i = 0; i < 10; i++) {
čas1 += bench(rozdílOdečtením);
čas2 += bench(rozdílPomocíGetTime);
}
alert( 'Celkový čas funkce rozdílOdečtením: ' + čas1 );
alert( 'Celkový čas funkce rozdílPomocíGetTime: ' + čas2 );
Moderní JavaScriptové motory začínají aplikovat pokročilé optimalizace jen na „horký kód“, který se provádí mnohokrát (není třeba optimalizovat to, co se provádí jen vzácně). Ve výše uvedeném příkladu tedy první spuštění nejsou dobře optimalizovaná. Můžeme chtít přidat zahřívací kolo:
// přidáno pro „zahřátí“ před hlavní smyčkou
bench(rozdílOdečtením);
bench(rozdílPomocíGetTime);
// nyní benchmark
for (let i = 0; i < 10; i++) {
čas1 += bench(rozdílOdečtením);
čas2 += bench(rozdílPomocíGetTime);
}
Moderní JavaScriptové motory provádějí množství optimalizací, které mohou vylepšit výsledky „umělých testů“ ve srovnání s „běžným použitím“, zvláště když provádíme benchmark něčeho velmi malého, např. práce operátoru nebo vestavěné funkce. Pokud tedy chcete skutečně porozumět výkonu, pak si prosíme prostudujte, jak funguje JavaScriptový motor. A pak už pravděpodobně nebudete mikrobenchmarky vůbec potřebovat.
Výborný balík článků o V8 naleznete na https://mrale.ph.
Načítání data z řetězce
Metoda Date.parse(řetězec) dokáže načíst datum z řetězce.
Řetězec by měl mít formát RRRR-MM-DDTHH:mm:ss.sssZ, kde:
RRRR-MM-DD– je datum: rok-měsíc-den.- Znak
"T"se používá jako oddělovač. HH:mm:ss.sss– je čas: hodiny, minuty, sekundy a milisekundy.- Nepovinná část
'Z'označuje časové pásmo ve formátu+-hh:mm. Samotné písmenoZznamená UTC+0.
Jsou možné i kratší varianty, např. RRRR-MM-DD nebo RRRR-MM nebo jen RRRR.
Volání Date.parse(řetězec) rozebere řetězec v uvedeném formátu a vrátí časové razítko (počet milisekund od 1. ledna 1970 UTC+0). Není-li formát správný, vrátí NaN.
Příklad:
let ms = Date.parse('2012-01-26T13:51:50.417-07:00');
alert(ms); // 1327611110417 (časové razítko)
Z tohoto časového razítka můžeme ihned vytvořit objekt pomocí new Date:
let datum = new Date( Date.parse('2012-01-26T13:51:50.417-07:00') );
alert(datum);
Shrnutí
- Datum a čas v JavaScriptu představuje objekt Date. Nemůžeme vytvořit „jenom datum“ nebo „jenom čas“: objekty
Dateobsahují vždy obojí. - Měsíce se počítají od nuly (ano, leden je nultý měsíc).
- Dny v týdnu ve funkci
getDay()se také počítají od nuly (to je neděle). Datese automaticky opravuje, když jsou nastaveny složky mimo správný rozsah. To je dobré pro přičítání/odečítání dnů/měsíců/hodin.- Data lze od sebe odečítat, výsledkem je jejich rozdíl v milisekundách. To je proto, že
Datese při konverzi na číslo převede na časové razítko. - Časové razítko času, který je právě teď, rychle získáme pomocí
Date.now().
Všimněte si, že na rozdíl od mnoha jiných systémů se časová razítka v JavaScriptu počítají v milisekundách, ne v sekundách.
Někdy potřebujeme přesnější měření času. Samotný JavaScript neobsahuje způsob, jak měřit čas v mikrosekundách (1 milióntina sekundy), ale většina prostředí jej poskytuje. Například prohlížeče mají metodu performance.now(), která vrací počet milisekund od začátku načítání stránky s přesností na mikrosekundy (3 číslice za desetinnou čárkou):
alert(`Načítání začalo před ${performance.now()}ms`);
// Něco jako: "Načítání začalo před 34731.26000000001ms"
// .26 jsou mikrosekundy (260 mikrosekund)
// více než 3 číslice za desetinnou čárkou jsou chyba přesnosti, jedině první 3 jsou správné
Node.js má modul microtime a jiné způsoby. Technicky téměř každé zařízení a každé prostředí umožňuje získat lepší přesnost, jenom ne pomocí Date.
Komentáře
<code>, pro několik řádků je obalte značkou<pre>, pro více než 10 řádků vložte odkaz na pískoviště (plnkr, jsbin, codepen…)