JavaScript, jako język działający w różnych środowiskach – od przeglądarek po serwery Node.js – musi oferować solidne narzędzia do pracy z datą i czasem. Obiekt Date reprezentuje konkretny moment jako liczbę milisekund od północy 1 stycznia 1970 roku (UTC), znaną jako epoka Unix.
Mimo fundamentalnej roli w ekosystemie webowym, Date bywa trudny w użyciu z powodu nieintuicyjnych zachowań i pułapek. Ten przewodnik pokazuje, jak tworzyć obiekty Date, pobierać i ustawiać ich komponenty, formatować je, wykonywać obliczenia oraz unikać typowych błędów — ze szczególnym naciskiem na strefy czasowe, wewnętrzną reprezentację czasu i praktyczne zastosowania.
Fundamenty obiektu Date w JavaScript
Jak JavaScript reprezentuje daty wewnętrznie
Obiekt Date opiera się na jednym numerze: milisekundach, które upłynęły od 1 stycznia 1970, 00:00:00 UTC. Wszystkie operacje sprowadzają się do manipulacji tym znacznikiem czasu (timestamp). Data w JavaScript to zawsze data i czas jednocześnie, a zakres możliwych wartości jest ogromny (od 271821 p.n.e. do 275760 n.e.).
Tworząc new Date() bez parametrów, otrzymujesz bieżący moment na podstawie czasu systemowego. Metody takie jak getFullYear(), getMonth() czy getTime() prezentują różne widoki tej samej liczby milisekund.
Aby szybko utrwalić kluczowe fakty o reprezentacji czasu w Date, zwróć uwagę na poniższe punkty:
- timestamp (ms od epoki) – jedyne źródło prawdy o chwili w czasie dla obiektu Date;
- data i czas – obiekt zawsze przechowuje pełny moment (data+czas), nie samą datę;
- strefy czasowe – odczyty mogą być interpretowane jako lokalne lub UTC w zależności od użytych metod;
- zakres – bardzo szeroki przedział obsługiwanych dat, praktycznie wystarczający dla aplikacji produkcyjnych.
Architektura obiektu Date i jego metody
Instancja Date jest „statyczna”: po utworzeniu jej wartość się nie zmienia. Aby uzyskać aktualny czas, musisz utworzyć nowy obiekt Date lub użyć Date.now(). API oferuje kilkadziesiąt metod, zgrupowanych na pobierające (get) i ustawiające (set), w wersjach lokalnych i UTC.
Dla każdego składnika istnieją odpowiedniki lokalne i UTC, np. getFullYear() (lokalnie) oraz getUTCFullYear() (UTC). Różnice mogą być istotne przy współpracy między systemami i użytkownikami w wielu strefach.
Tworzenie obiektów Date – konstruktory i różne formaty
Podstawowe sposoby inicjalizacji
Istnieją cztery główne sposoby tworzenia nowej daty. Oto skrótowy przegląd:
- Bez parametrów –
new Date()zwraca bieżący moment w czasie; - Z łańcuchem znaków –
new Date("2022-03-25"), najlepiej w ISO 8601 (np.YYYY-MM-DDTHH:mm:ss.sssZ); - Z komponentów –
new Date(2018, 11, 24, 10, 33, 30, 0)(uwaga: miesiące 0–11); - Z milisekund od epoki –
new Date(86400000)daje „1 stycznia 1970 + 24 h”.
Uwaga: liczba przekazanych argumentów do konstruktora wpływa na interpretację (możesz pominąć końcowe parametry, a brakujące przyjmują wartości domyślne, np. godzina 00:00:00).
Krytyczne różnice między formatami łańcuchów
Parsowanie łańcuchów dat historycznie bywało niejednoznaczne i różne silniki mogły zachowywać się odmiennie. Stosuj ISO 8601 zawsze, gdy to możliwe. Pamiętaj też, że łańcuch z samą datą w ISO (np. "2023-07-13") jest interpretowany jako UTC, nie czas lokalny.
Dla szybkiego rozeznania w zachowaniach typowych formatów spójrz na porównanie:
| Format | Przykład | Niezawodność | Strefa domyślna | Uwagi |
|---|---|---|---|---|
| ISO data+czas | 2023-07-13T10:30:00Z |
Wysoka | UTC | zalecany do serializacji i wymiany danych |
| ISO sama data | 2023-07-13 |
Wysoka | UTC | może zaskoczyć, bo nie używa lokalnej strefy |
| MM/DD/YYYY | 03/25/2015 |
Niska | Lokalna | często jako format amerykański, ale niegwarantowane |
| YYYY/MM/DD | 2015/03/25 |
Niska | Lokalna | zachowanie niezdefiniowane, zależne od silnika |
| DD-MM-YYYY | 25-03-2015 |
Niska | Lokalna | różne interpretacje, unikaj |
| Tekstowe | 25 Mar 2015 |
Średnia | Lokalna | często działa, ale nadal lepiej użyć ISO |
Metoda Date.parse() i jej zastosowanie
Metoda Date.parse() zwraca liczbę ms od epoki dla przekazanego łańcucha lub NaN przy błędzie. To szybki sposób walidacji i uzyskania znacznika czasu bez tworzenia instancji Date.
let msec = Date.parse("March 21, 2012");
console.log(msec); // liczba ms od epoki (zależna od interpretacji strefy)
Po uzyskaniu milisekund możesz przekazać je do new Date(msec) i kontynuować pracę na obiekcie.
Pobieranie komponentów daty – metody get
Metody pobierające w lokalnym czasie
getFullYear() zwraca rok (np. 1995) — preferowane zamiast przestarzałego getYear(). getMonth() zwraca miesiąc 0–11 (0 = styczeń). To częste źródło błędów. Pozostałe metody zwracają: getDate() (1–31), getDay() (0–6; 0 = niedziela), getHours() (0–23), getMinutes() (0–59), getSeconds() (0–59), getMilliseconds() (0–999).
Dla szybkiej orientacji w często używanych metodach odczytu skorzystaj z tej ściągi:
- getFullYear() – czterocyfrowy rok w czasie lokalnym;
- getMonth() – miesiąc 0–11 (0 = styczeń);
- getDate() – dzień miesiąca 1–31;
- getDay() – dzień tygodnia 0–6 (0 = niedziela);
- getHours() – godzina 0–23;
- getMinutes() – minuty 0–59;
- getSeconds() – sekundy 0–59;
- getMilliseconds() – milisekundy 0–999.
Odpowiadające metody UTC
Każda metoda ma wariant UTC, np. getUTCFullYear(). Są niezbędne w aplikacjach globalnych, gdzie standaryzacja do UTC zapobiega błędom. getTimezoneOffset() zwraca przesunięcie w minutach względem UTC (UTC−8 → 480, UTC+3 → −180).
Praktyczne przykłady pobierania komponentów
Poniżej pokazano, jak pobrać numer dnia tygodnia:
const now = new Date();
const element = document.querySelector(".element");
element.innerText = `Dzisiaj jest dzień tygodnia: ${now.getDay()}`; // dni tygodnia są liczone od 0
Aby wyświetlić nazwę dnia tygodnia, użyj tablicy nazw:
const now = new Date();
const days = ["niedziela", "poniedziałek", "wtorek", "środa", "czwartek", "piątek", "sobota"];
const element = document.querySelector(".element");
element.innerText = `Dzisiaj jest: ${days[now.getDay()]}`;
Aby wyświetlić kompletną datę i czas z zerami wiodącymi, możesz użyć prostej funkcji pomocniczej:
const now = new Date();
function lz(i) {
return `${i}`.padStart(2, "0");
}
const textDate = `${lz(now.getDate())}.${lz(now.getMonth() + 1)}.${now.getFullYear()}`;
const textTime = `${lz(now.getHours())}:${lz(now.getMinutes())}:${lz(now.getSeconds())}`;
console.log(textDate); // np. "10.02.2026"
console.log(textTime); // np. "19:30:45"
Ustawianie komponentów daty – metody set
Podstawowe metody ustawiające
Zmiany można wprowadzać metodami: setFullYear() (opcjonalnie także miesiąc i dzień), setMonth() (0–11), setDate() (1–31), setHours() (0–23), setMinutes() (0–59), setSeconds() (0–59), setMilliseconds() (0–999). Wszystkie modyfikują obiekt in place i zwracają nowy znacznik czasu (ms).
Przykład sekwencji zmian komponentów:
const d = new Date("January 01, 2025");
d.setFullYear(2020); // 1 stycznia 2020
d.setMonth(11); // 1 grudnia 2020
d.setDate(15); // 15 grudnia 2020
d.setHours(22, 10, 20); // 22:10:20
Dla szybkiego przeglądu metod ustawiających użyj listy poniżej:
- setFullYear(year[, month, date]) – zmienia rok (i opcjonalnie miesiąc oraz dzień);
- setMonth(month[, date]) – zmienia miesiąc 0–11 (opcjonalnie dzień miesiąca);
- setDate(date) – zmienia dzień miesiąca 1–31 (obsługuje wartości poza zakresem);
- setHours(h[, m, s, ms]) – zmienia godzinę (i opcjonalnie minuty, sekundy, ms);
- setMinutes(m[, s, ms]) – zmienia minuty (i opcjonalnie sekundy, ms);
- setSeconds(s[, ms]) – zmienia sekundy (i opcjonalnie ms);
- setMilliseconds(ms) – zmienia milisekundy 0–999;
- setTime(ms) – ustawia bezpośrednio timestamp w ms.
Dodawanie i odejmowanie czasu
Wygodny sposób dodawania dni to użycie setDate() i automatycznego przenoszenia do kolejnych miesięcy:
const d = new Date("January 01, 2025");
d.setDate(d.getDate() + 50); // dodaje 50 dni
// Wynik: 20 lutego 2025
Odejmowanie działa analogicznie:
const d = new Date("March 15, 2025");
d.setDate(d.getDate() - 10); // odejmuje 10 dni
// Wynik: 5 marca 2025
Dodawanie miesięcy:
const d = new Date("January 15, 2025");
d.setMonth(d.getMonth() + 6); // dodaje 6 miesięcy
// Wynik: 15 lipca 2025
Gdy potrzebujesz operować na milisekundach, użyj setTime():
const d = new Date();
const ONE_WEEK_IN_MS = 7 * 24 * 60 * 60 * 1000;
d.setTime(d.getTime() + ONE_WEEK_IN_MS); // dodaje dokładnie jeden tydzień
Zwracanie wartości i automatyczne dostosowywanie
Metody set zwracają nowy timestamp (ms), a zmieniają istniejący obiekt. Przy wartościach poza zakresem następuje automatyczne dostosowanie (np. 32 czerwca → 2 lipca). Dla nieprawidłowych argumentów (NaN) powstaje Invalid Date.
Oto przykłady automatycznych korekt:
const d1 = new Date("June 15, 2025");
d1.setDate(32); // Wynik: 2 lipca 2025
const d2 = new Date("June 15, 2025");
d2.setDate(0); // Wynik: 31 maja 2025 (0 = ostatni dzień poprzedniego miesiąca)
Formatowanie i konwertowanie dat – różne metody reprezentacji
Wbudowane metody toString
toString() zwraca pełną reprezentację daty wraz ze strefą, np. Tue Aug 19 1975 23:15:30 GMT+0200 (CEST). toDateString() zwraca samą datę, a toTimeString() — sam czas (wraz z oznaczeniem strefy). toUTCString() zawsze formatuje w UTC.
Format ISO 8601 i toISOString()
Metoda toISOString() zwraca łańcuch w ISO 8601 (YYYY-MM-DDTHH:mm:ss.sssZ) i zawsze w UTC. To preferowana metoda serializacji do JSON i integracji z API.
Przykład użycia toISOString():
const event = new Date("05 October 2011 14:48 UTC");
console.log(event.toISOString()); // "2011-10-05T14:48:00.000Z"
Metody locale – formatowanie wrażliwe na język
toLocaleString(), toLocaleDateString() i toLocaleTimeString() formatują wynik zgodnie z lokalizacją i opcjami, np. styl daty, język, strefa czasowa.
Przykłady formatowania lokalnego:
const d = new Date();
console.log(d.toLocaleDateString()); // Np. w USA: "12/11/2012", w Niemczech: "11.12.2012"
const date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));
const options = { weekday: "long", year: "numeric", month: "long", day: "numeric" };
console.log(date.toLocaleDateString("de-DE", options)); // "Donnerstag, 20. Dezember 2012"
Porównanie popularnych metod formatowania
Poniższa tabela ułatwia szybki dobór metody do celu wyświetlania:
| Metoda | Zakres | Strefa | Przykładowy wynik | Typowe użycie |
|---|---|---|---|---|
toString() |
data + czas | lokalna | Tue Aug 19 1975 23:15:30 GMT+0200 (CEST) | debug, szybki podgląd |
toDateString() |
data | lokalna | Thu Apr 12 2018 | wyświetlanie samej daty |
toTimeString() |
czas | lokalna | 23:15:30 GMT+0200 (CEST) | wyświetlanie samego czasu |
toUTCString() |
data + czas | UTC | Wed Aug 19 1975 15:15:30 GMT+0000 (UTC) | standaryzacja, logi |
toISOString() |
data + czas | UTC | 2011-10-05T14:48:00.000Z | serializacja, API, JSON |
toLocaleDateString() |
data | lokalna/wybrana | 11.12.2012 | UI zależne od języka |
Intl.DateTimeFormat – zaawansowane formatowanie
Intl.DateTimeFormat daje maksymalną kontrolę nad składnikami i strefą czasową, z zachowaniem reguł lokalizacji.
Przykłady użycia Intl.DateTimeFormat:
const date = new Date(Date.UTC(2020, 11, 20, 3, 23, 16, 738));
// Formatowanie dla "en-US"
console.log(new Intl.DateTimeFormat("en-US").format(date)); // "12/20/2020"
// Formatowanie dla "de-DE" z inną strefą czasową
console.log(new Intl.DateTimeFormat("de-DE", {
dateStyle: "full",
timeStyle: "long",
timeZone: "Australia/Sydney",
}).format(date)); // "Sonntag, 20. Dezember 2020 um 14:23:16 GMT+11"
Operacje na datach – porównywanie, odejmowanie i obliczenia
Porównywanie dat przy użyciu operatorów
Operatory relacyjne (<, >, <=, >=) działają „wartościowo”, bo Date konwertuje się do timestampu. Operatory równości (===) porównują referencje obiektów, więc dla równości porównuj getTime().
Przykładowe porównanie relacyjne:
let date1 = new Date();
let date2 = new Date("6/01/2022");
if (date1 > date2) {
console.log("Data 1 jest późniejsza niż data 2");
} else if (date1 < date2) {
console.log("Data 1 jest wcześniejsza niż data 2");
} else {
console.log("Obie daty są takie same");
}
Metoda getTime() dla porównań równości
Aby sprawdzić, czy dwie daty oznaczają ten sam moment, porównuj wartości getTime().
Przykład porównania równości:
let date1 = new Date("12/01/2021");
let date2 = new Date("12/01/2021");
console.log(date1.getTime() === date2.getTime()); // true
Obliczanie różnic między datami
Odejmij timestampy, a następnie przelicz jednostki (ms → sekundy → minuty → godziny → dni).
Przykład obliczania pozostałego czasu do konkretnej daty:
const dateNow = new Date();
const dateInTheFuture = new Date("2024-12-31");
const msRemaining = dateInTheFuture.getTime() - dateNow.getTime();
Konwersja różnicy na czytelne jednostki:
// Dni
const days = Math.floor(msRemaining / (1000 * 60 * 60 * 24));
console.log(`Pozostało dni: ${days}`);
// Godziny
const hours = Math.floor((msRemaining % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
console.log(`Pozostało godzin: ${hours}`);
// Minuty
const minutes = Math.floor((msRemaining % (1000 * 60 * 60)) / (1000 * 60));
console.log(`Pozostało minut: ${minutes}`);
86400000 to liczba milisekund w 24 godzinach (24 × 60 × 60 × 1000).
Praktyczne przykłady – obliczanie wieku
Przykład uwzględniający, czy urodziny już były w bieżącym roku:
function calculateAge(birthDateString) {
const birthDate = new Date(birthDateString);
const today = new Date();
let age = today.getFullYear() - birthDate.getFullYear();
const monthDifference = today.getMonth() - birthDate.getMonth();
if (monthDifference < 0 || (monthDifference === 0 && today.getDate() < birthDate.getDate())) {
age--;
}
return age;
}
console.log(calculateAge("1990-05-15"));
Pułapki, problemy i najlepsze praktyki
Problem zerowo indeksowanych miesięcy
Miesiące są indeksowane od zera (styczeń = 0, grudzień = 11). Przy wyświetlaniu dodawaj 1 do getMonth(), a przy tworzeniu z komponentów pamiętaj o indeksacji.
Przykłady użycia miesiąca w odczycie i tworzeniu:
const now = new Date();
const humanReadableMonth = now.getMonth() + 1;
console.log(`Bieżący miesiąc: ${humanReadableMonth}`);
// To tworzy 15 marca (nie kwietnia), ponieważ marzec = 2
const date = new Date(2025, 2, 15);
Problemy ze strefami czasowymi
Daty są domyślnie interpretowane w lokalnej strefie systemu. Najlepsza praktyka: pracuj wewnętrznie w UTC, a do lokalnego czasu konwertuj tylko przy wyświetlaniu.
Przykład bezpiecznej pracy z UTC i prezentacji lokalnej:
const date = new Date("2025-01-15T12:30:00Z"); // Z = UTC
console.log(date.toUTCString()); // zawsze UTC
console.log(date.toLocaleString()); // lokalna prezentacja
Nieprawidłowe daty i automatyczne dostosowanie
JavaScript nie zgłasza błędu dla wielu „nieistniejących” dat, tylko je koryguje. Może to być przydatne lub zdradliwe — zależnie od kontekstu.
Przykład automatycznej korekty 31 lutego:
const date = new Date(2022, 1, 31); // luty nie ma 31 dni
console.log(date); // Wed Mar 03 2022 00:00:00 GMT+0100
Pułapka new Date(null)
new Date(null) zwraca epokę Unix (1970-01-01T00:00:00.000Z), a nie błąd. Natomiast new Date(undefined) daje Invalid Date. Zawsze jawnie sprawdzaj null i undefined przed tworzeniem daty.
Przykłady zachowania konstruktora:
new Date(null); // Thu Jan 01 1970 00:00:00 GMT+0000
new Date(undefined); // Invalid Date
Date.now() vs new Date()
Date.now() jest szybsze i zwraca sam timestamp (przydatne do pomiarów), a new Date() daje obiekt z bogatszym API.
Przykład pomiaru czasu wykonania:
const start = Date.now();
doSomethingForALongTime();
const end = Date.now();
const elapsed = end - start; // różnica w ms
Dla wygodnej nawigacji po typowych pułapkach i rekomendacjach zwróć uwagę na tę listę:
- miesiące 0–11 – zawsze pamiętaj o indeksacji przy tworzeniu i odczycie;
- UTC vs lokalny – do przechowywania/serializacji używaj UTC, do prezentacji lokalnych ustawień;
- parsowanie łańcuchów – preferuj ISO 8601, unikaj niejednoznacznych formatów;
- automatyczne korekty – JavaScript „przenosi” wartości poza zakresem na kolejne/poprzednie miesiące;
- null vs undefined –
new Date(null)≠ błąd,new Date(undefined)→Invalid Date; - wydajność – do znaczników czasu używaj
Date.now(), do pracy na komponentach –new Date().
Nowoczesne alternatywy – date-fns i inne biblioteki
Ograniczenia natywnego obiektu Date
Natywne API bywa niewygodne (mutowalne obiekty, brak deklaratywnych operacji na datach). Brakuje ergonomicznych funkcji „dodaj dni/miesiące”, „różnica w słowach” itp.
Wprowadzenie date-fns
date-fns to modularna, lekka i niemutowalna biblioteka z ponad 200 funkcjami. Każda operacja zwraca nową datę, co ogranicza błędy stanu. Posiada szerokie wsparcie lokalizacji i jest znacznie mniejsza niż Moment.js.
Przykładowe użycie date-fns:
import { format, addDays, subDays } from "date-fns";
const today = new Date();
const inFiveDays = addDays(today, 5);
const formatted = format(inFiveDays, "yyyy-MM-dd");
console.log(formatted);
Inne alternatywy – Day.js i Temporal
Day.js to bardzo lekka biblioteka (ok. 2 kB) z API podobnym do Moment.js, ale szybsza i modularna. Temporal to nowe API JavaScript (osobna przestrzeń nazw), które rozwiązuje wiele problemów Date — w trakcie wdrożeń w środowiskach.
Dla szybkiego porównania najpopularniejszych opcji spójrz na tabelę:
| Rozwiązanie | Rozmiar | Mutowalność | Obsługa i18n | Status |
|---|---|---|---|---|
| date-fns | niski (funkcje per import) | niemutowalne | tak (wiele lokalizacji) | stabilne, aktywnie rozwijane |
| Day.js | bardzo niski (~2 kB) | niemutowalne (chainable) | tak (pluginy) | stabilne, popularne |
| Moment.js | wysoki (~232 kB) | mutowalne | tak | legacy, maintenance mode |
| Temporal | — | niemutowalne | standardowe API | nowe API JS (wdrożenia trwają) |
Zaawansowane operacje – harmonogram i integracja z API
Harmonogramowanie zdarzeń z setTimeout i setInterval
setTimeout() uruchamia funkcję po określonej liczbie ms, a setInterval() powtarza ją periodycznie.
Przykłady ustawiania opóźnienia i interwału:
// Ustaw alarm na 5 sekund od teraz
setTimeout(() => {
console.log("Minęło 5 sekund");
}, 5000);
// Wyświetl godzinę co sekundę
const timerId = setInterval(() => {
const d = new Date();
document.getElementById("demo").innerHTML = d.toLocaleTimeString();
}, 1000);
// Aby zatrzymać, użyj clearInterval(timerId)
Serializacja dat do JSON
Przy JSON.stringify() obiekty Date konwertują się do ISO 8601 przez toJSON(). Przy deserializacji musisz ręcznie utworzyć Date z łańcucha.
Przykłady serializacji i deserializacji:
const date = new Date(2006, 0, 2, 15, 4, 5);
const jsonString = JSON.stringify(date);
console.log(jsonString); // "2006-01-02T15:04:05.000Z"
const iso = JSON.parse(jsonString); // "2006-01-02T15:04:05.000Z"
const parsed = new Date(iso);
console.log(parsed); // Mon Jan 02 2006 16:04:05 GMT+0100 (w zależności od strefy)
Konwersja milisekund na datę
API często zwracają timestamp w milisekundach. Wystarczy przekazać liczbę do konstruktora, aby uzyskać Date.
Przykład konwersji ms → Date:
let milliseconds = 1578567991011;
let dateFromMs = new Date(milliseconds);
console.log(dateFromMs.toString()); // np. "Thu Jan 09 2020 11:06:31 GMT+0000"
Walidacja i sprawdzanie poprawności dat
Sprawdzanie, czy data jest ważna
Najprostsza walidacja to sprawdzenie, czy getTime() nie zwraca NaN. Alternatywnie użyj Date.parse(), pamiętając o różnicach w parsowaniu nie-ISO.
Przykłady funkcji walidujących:
function isValidDate(dateString) {
const date = new Date(dateString);
return !isNaN(date.getTime());
}
console.log(isValidDate("2022-01-01")); // true
console.log(isValidDate("invalid")); // false
function isValidDateParse(dateString) {
return !isNaN(Date.parse(dateString));
}