7 min

Formatowanie i konwersja czasu w JavaScript — typowe pułapki

Dowiedz się, jak formatować i konwertować czas w JavaScript bez niespodzianek: znaczniki czasu, ciągi ISO, strefy czasowe, DST, zasady parsowania i sprawdzone wzorce.

Formatowanie i konwersja czasu w JavaScript — typowe pułapki

Co zwykle idzie nie tak z czasem w JavaScript

Błędy związane z czasem w JavaScript rzadko wyglądają jak „zegar jest zepsuty”. Pojawiają się jako mylące przesunięcia: data poprawna na twoim laptopie, ale nie na komputerze kolegi, odpowiedź API, która wydaje się prawidłowa, dopóki nie zostanie wyrenderowana w innej strefie czasowej, albo raport „o dzień w bok” wokół zmiany sezonowej.

Najczęstsze objawy

Zazwyczaj zauważysz jedno (lub więcej) z poniższych:

  • Błąd o jedną godzinę: zwłaszcza wokół czasu letniego (DST) lub gdy wartość jest nieumyślnie konwertowana między czasem lokalnym a UTC.
  • Błąd o jeden dzień: wartość będąca tylko datą (np. „2025-12-23”) pojawia się jako poprzedni/następny dzień w zależności od strefy czasowej.
  • Zła strefa czasowa: godziny wyglądają dobrze, ale przesunięcie (np. +02:00) jest inne niż oczekiwałeś.
  • Niespójne formatowanie: „działa w Chrome”, ale wygląda inaczej w Safari, albo serwer i przeglądarka różnie parsują ten sam ciąg.

Dlaczego tak się dzieje: „czas” może znaczyć różne rzeczy

Dużym źródłem problemów jest to, że słowo czas może odnosić się do różnych pojęć:

  • Moment (instant): konkretny moment na całym świecie (np. „2025-12-23T10:00:00Z”). To zwykle to, czego chcesz do logów, zdarzeń i przechowywania w API.
  • Data kalendarzowa: dzień w kalendarzu bez strefy czasowej (np. urodziny, data faktury). Traktowanie jej jak momentu może przesunąć datę na inną dobę.
  • Czas wskazówkowy (wall-clock): „9:00 w Berlinie”, który zależy od zasad strefy czasowej i zmian DST.

Wbudowany Date w JavaScript próbuje obsłużyć wszystkie te przypadki, ale przede wszystkim reprezentuje moment w czasie, jednocześnie skłaniając cię do wyświetlania w czasie lokalnym, co ułatwia przypadkowe konwersje.

Na czym skupia się ten artykuł

Ten przewodnik jest praktyczny: jak uzyskać przewidywalne konwersje między przeglądarkami i serwerami, jak wybierać bezpieczniejsze formaty (np. ISO 8601) i jak wykrywać klasyczne pułapki (sekundy vs milisekundy, UTC vs lokalny, różnice w parsowaniu). Celem nie jest teoria — lecz mniej pytań „dlaczego się przesunęło?”.

Typy danych czasu: znacznik czasu, Date i ciąg znaków

Błędy związane z czasem w JavaScript często zaczynają się od mieszania reprezentacji, które wyglądają na wymienne, ale takie nie są.

Trzy reprezentacje, które najczęściej zobaczysz

1) Milisekundy epoki (number)

Zwykła liczba jak 1735689600000 to zazwyczaj „milisekundy od 1970-01-01T00:00:00Z”. Reprezentuje moment w czasie bez formatu i bez strefy czasowej.

2) Obiekt Date (opakowanie wokół momentu)

Date przechowuje ten sam typ informacji co znacznik czasu. Mylące jest to, że gdy drukujesz Date, JavaScript formatuje go używając lokalnych reguł środowiska, o ile nie poprosisz inaczej.

3) Sformatowany ciąg (do wyświetlenia)

Ciągi jak "2025-01-01", "01/01/2025 10:00" czy "2025-01-01T00:00:00Z" to różne rzeczy. Niektóre są jednoznaczne (ISO 8601 z Z), inne zależą od lokalizacji, a niektóre nie zawierają strefy czasowej wcale.

„Moment w czasie” kontra „czas do wyświetlenia”

  • Moment w czasie: „ten dokładny moment globalnie” (najlepiej przechowywać jako ms epoki lub ISO UTC).
  • Czas do wyświetlenia: „co użytkownik powinien zobaczyć” (zależy od lokalizacji i strefy czasowej).

Ten sam moment może być wyświetlany inaczej w różnych strefach czasowych:

const instant = new Date("2025-01-01T00:00:00Z");

instant.toLocaleString("en-US", { timeZone: "UTC" });
// "1/1/2025, 12:00:00 AM"

instant.toLocaleString("en-US", { timeZone: "America/Los_Angeles" });
// "12/31/2024, 4:00:00 PM" (poprzedni dzień)

Wybierz jedno „źródło prawdy”

Wybierz jedną wewnętrzną reprezentację (często milisekundy epoki lub UTC ISO 8601) i trzymaj się jej w całej aplikacji i API. Konwertuj do/z Date i sformatowanych ciągów tylko na granicach: przy parsowaniu wejścia i przy wyświetlaniu w UI.

Znaczniki czasu: sekundy kontra milisekundy (łatwo pomylić)

„Znacznik czasu” zwykle oznacza czas epoki (Unix time): liczbę sekund/miilisekund od 1970-01-01 00:00:00 UTC. Pułapka: różne systemy liczą w różnych jednostkach.

Date w JavaScript powoduje najwięcej nieporozumień, bo używa milisekund. Wiele API, baz danych i logów używa sekund.

Zasadnicza wskazówka

  • Unix timestamp (sekundy): 1704067200
  • Timestamp JS (milisekundy): 1704067200000

Ten sam moment, ale wersja w milisekundach ma trzy dodatkowe cyfry.

Bezpieczne konwersje (sekundy ↔ milisekundy)

Używaj explicite mnożenia/dzielenia, żeby jednostka była oczywista:

// seconds -> Date
const seconds = 1704067200;
const d1 = new Date(seconds * 1000);

// milliseconds -> Date
const ms = 1704067200000;
const d2 = new Date(ms);

// Date -> seconds
const secondsOut = Math.floor(d2.getTime() / 1000);

// Date -> milliseconds
const msOut = d2.getTime();

Klasyczny błąd: przekazanie sekund do Date()

To wygląda rozsądnie, ale jest błędne, gdy ts są w sekundach:

const ts = 1704067200;      // seconds
const d = new Date(ts);     // WRONG: treated as milliseconds

Wynik będzie datą w 1970, ponieważ 1,704,067,200 milisekund to tylko około 19 dni po epoce.

Szybkie walidacje i debugowanie

Kiedy nie jesteś pewny, jakiej jednostki masz, dodaj proste zabezpieczenia:

function asDateFromUnknownEpoch(x) {
  // crude heuristic: seconds are ~1e9-1e10, milliseconds are ~1e12-1e13
  if (x < 1e11) return new Date(x * 1000); // assume seconds
  return new Date(x);                      // assume milliseconds
}

const input = Number(valueFromApi);
console.log({ input, digits: String(Math.trunc(input)).length });
console.log('as ISO:', asDateFromUnknownEpoch(input).toISOString());

Jeśli liczba cyfr to ~10, prawdopodobnie są to sekundy. Jeśli ~13, to milisekundy. Drukowanie toISOString() podczas debugowania szybko ujawnia błędy jednostek.

Czas lokalny kontra UTC: dlaczego wynik się przesuwa

Date w JavaScript może być mylący, bo przechowuje pojedynczy moment w czasie, ale może prezentować ten moment w różnych strefach czasowych.

Wewnątrz Date to w zasadzie „milisekundy od epoki Unix (1970-01-01T00:00:00Z)”. Ta liczba reprezentuje moment w UTC. „Przesunięcie” pojawia się, gdy prosisz JavaScript o sformatowanie tego momentu jako czas lokalny (na podstawie ustawień komputera/serwera) zamiast UTC.

Gettery lokalne kontra UTC

Wiele API Date ma zarówno warianty lokalne, jak i UTC. Zwracają różne liczby dla tego samego momentu:

const d = new Date('2025-01-01T00:30:00Z');

d.getHours();      // hour in *local* time zone
d.getUTCHours();   // hour in UTC

d.toString();      // local time string
d.toISOString();   // UTC (always ends with Z)

Jeśli twój komputer jest w Nowym Jorku (UTC-5), ten czas UTC może pojawić się lokalnie jako „19:30” poprzedniego dnia. Na serwerze ustawionym na UTC będzie to „00:30”. Ten sam moment, różne wyświetlenie.

Dlaczego logi wyglądają „źle”

Logi często używają Date#toString() lub interpolują Date implicite, co używa lokalnej strefy czasowej środowiska. To oznacza, że ten sam kod może wydrukować różne znaczniki czasu na twoim laptopie, w CI i w produkcji.

Praktyczne wskazówki

Przechowuj i przesyłaj czas w UTC (np. ms epoki lub ISO 8601 z Z). Konwertuj na strefę użytkownika tylko przy wyświetlaniu:

  • Dla API: preferuj toISOString() albo przesyłaj milisekundy epoki
  • Dla UI: sformatuj w strefie użytkownika używając Intl.DateTimeFormat

Jeśli szybko tworzysz aplikację (np. w workflow vibe-coding w Koder.ai), warto wcześnie ustalić kontrakty API: nazwij pola wprost (createdAtMs, createdAtIso) i trzymaj spójność między serwerem (Go + PostgreSQL) a klientem (React).

Ciągi ISO 8601: najbezpieczniejszy format dla API

Jeśli musisz wysyłać daty/czasy między przeglądarką, serwerem i bazą danych, ciągi ISO 8601 to najbezpieczniejszy wybór. Są jawne, szeroko wspierane i (co najważniejsze) niosą informacje o strefie czasowej.

Używaj jawnego UTC lub jawnego offsetu

Dwa dobre formaty wymiany:

  • Czas UTC (zalecane, jeśli strefa lokalna nie ma znaczenia): 2025-03-04T12:30:00Z
  • Czas lokalny z offsetem (gdy liczy się lokalny czas): 2025-03-04T12:30:00+02:00

Co oznacza „Z”?

Z to skrót od Zulu time, inna nazwa dla UTC. Więc 2025-03-04T12:30:00Z to „12:30 w UTC”.

Kiedy offsety jak +02:00 mają znaczenie?

Offsety są kluczowe, gdy zdarzenie jest powiązane z kontekstem lokalnej strefy (wizyty, rezerwacje, godziny otwarcia). 2025-03-04T12:30:00+02:00 opisuje moment dwie godziny przed UTC, i to nie jest ten sam moment co 2025-03-04T12:30:00Z.

Unikaj niejednoznacznych ciągów

Ciągi jak 03/04/2025 to pułapka: czy to 4 marca czy 3 kwietnia? Różni użytkownicy i środowiska interpretują to inaczej. Preferuj 2025-03-04 (data w ISO) lub pełny datetime w ISO.

Bezpieczne round-trip (string → Date → string)

const iso = "2025-03-04T12:30:00Z";
const d = new Date(iso);
const back = d.toISOString();

console.log(iso);  // 2025-03-04T12:30:00Z
console.log(back); // 2025-03-04T12:30:00.000Z

To „round-trip” to dokładnie to, czego chcesz dla API: spójne, przewidywalne i świadome strefy czasowej.

Pułapki parsowania: Date.parse i różnice między przeglądarkami

Build a time-safe API
Generuj endpointy React + Go z jasnymi polami createdAtMs i payloadami w ISO 8601.

Date.parse() wydaje się wygodne: podaj ciąg, dostaniesz znacznik czasu. Problem w tym, że dla wszystkiego poza jednoznacznym ISO 8601 parsowanie może zależeć od heurystyk przeglądarki. Te heurystyki różniły się między silnikami i wersjami, co oznacza, że ten sam input może zostać sparsowany inaczej (lub wcale) w zależności od środowiska.

Dlaczego Date.parse() może się różnić

JavaScript standaryzuje parsowanie pewnych formatów ISO 8601 (i nawet wtedy szczegóły strefy czasowej mają znaczenie). Dla „przyjaznych” formatów — jak "03/04/2025", "March 4, 2025" czy "2025-3-4" — przeglądarki mogą interpretować:

  • miesiąc/dzień kontra dzień/miesiąc w zależności od ustawień lokalnych
  • brakująca strefa czasowa jako czas lokalny w jednym silniku, a jako błąd w innym
  • lekko niepoprawne ciągi jako „wystarczająco bliskie”… aż przestaną być

Jeśli nie możesz przewidzieć dokładnego kształtu ciągu, nie możesz przewidzieć wyniku.

Zaskakujący przypadek: YYYY-MM-DD

Powszechna pułapka to prosty format "YYYY-MM-DD" (np. "2025-01-15"). Wielu programistów oczekuje, że będzie traktowany jako lokalne północ. W praktyce niektóre środowiska traktują taką formę jako UTC midnight.

Ta różnica ma znaczenie: północ UTC przekonwertowana na czas lokalny może stać się poprzednim dniem w strefach ujemnych (np. Ameryki) lub przesunąć godzinę. To łatwy sposób na pojawienie się błędu „dlaczego moja data jest przesunięta o jeden dzień?”.

Lista kontrolna parsowania: dane użytkownika kontra dane serwera

Dla wejścia do serwera/API:

  • Preferuj pełne ISO 8601 z jawnie podaną strefą, np. 2025-01-15T13:45:00Z lub 2025-01-15T13:45:00+02:00.
  • Traktuj wartości zawierające tylko datę jako dane, nie jako moment w czasie. Jeśli to urodziny lub termin, trzymaj to jako zwykły ciąg ("YYYY-MM-DD") i unikaj konwersji na Date, chyba że zdefiniujesz zamierzoną strefę czasową.

Dla wejścia od użytkownika:

  • Nie akceptuj niejednoznacznych formatów jak 03/04/2025, chyba że UI narzuca znaczenie.
  • Preferuj kontrolowane wejścia (date pickery), które generują znany format.
  • Jeśli musisz parsować wolny tekst, zdefiniuj i wymuś akceptowane formaty z góry.

Używaj jawnych reguł (nie heurystyk)

Zamiast polegać na Date.parse(), aby „domyślić się”, wybierz jedną z tych strategii:

  • Akceptuj tylko ISO 8601 od serwerów; odrzucaj inne formy.
  • Ręcznie parsuj znane formaty (rozkładając ciąg i używając new Date(year, monthIndex, day) dla dat lokalnych).
  • Przechowuj i przesyłaj znaczniki czasu (milisekundy epoki) dla precyzyjnych momentów i formatuj dopiero przy wyświetlaniu.

Gdy dane czasowe są krytyczne, „u mnie parsuje” nie wystarczy — zdefiniuj reguły parsowania jawnie i konsekwentnie.

Formatowanie dla ludzi z Intl.DateTimeFormat

Jeśli celem jest „pokazać datę/czas tak, jak ludzie tego oczekują”, najlepszym narzędziem w JavaScript jest Intl.DateTimeFormat. Używa reguł lokalnych użytkownika (kolejność, separatory, nazwy miesięcy) i unika kruchego składania ciągów jak month + '/' + day.

Dlaczego bije ręczne budowanie ciągów

Ręczne formatowanie często twardo koduje amerykański styl, zapomina o zerach wiodących lub daje mylący efekt 24/12 godzin. Intl.DateTimeFormat pozwala też jawnie określić, w której strefie czasowej wyświetlasz — kluczowe, gdy dane przechowywane są w UTC, a UI ma pokazywać czas użytkownika.

Opcje, których faktycznie będziesz używać

Dla „ładnego” formatowania dateStyle i timeStyle są najprostsze:

const d = new Date('2025-01-05T16:30:00Z');

// User’s locale + user’s local time zone
console.log(new Intl.DateTimeFormat(undefined, {
  dateStyle: 'medium',
  timeStyle: 'short'
}).format(d));

// Force a specific time zone (great for event times)
console.log(new Intl.DateTimeFormat('en-GB', {
  dateStyle: 'full',
  timeStyle: 'short',
  timeZone: 'UTC'
}).format(d));

Jeśli potrzebujesz spójnego cyklu godzin (np. ustawienie w preferencjach), użyj hour12:

console.log(new Intl.DateTimeFormat('en-US', {
  hour: 'numeric',
  minute: '2-digit',
  hour12: true
}).format(d));

Praktyczny wzorzec UI

Wybierz jedną funkcję formatowania dla każdego „typu” znacznika czasu w UI (czas wiadomości, wpisu w logu, początek wydarzenia) i miej świadomą decyzję o timeZone:

  • Używaj czasu lokalnego dla „kiedy to się zdarzyło dla mnie”.
  • Używaj stałej strefy (często UTC) dla logów audytowych, zdarzeń serwera lub koordynacji między zespołami.

To daje spójny, przyjazny użytkownikowi output bez utrzymywania kruchego zestawu własnych formatów.

Czas letni (DST): ukryty błąd o jedną godzinę

Fix date parsing bugs
Opisz swoje ciągi wejściowe i otrzymaj bezpieczniejszy kod parsujący bez niespodzianek Date.parse.

Czas letni (DST) to moment, gdy strefa czasowa zmienia swoje przesunięcie UTC (zwykle o jedną godzinę) w określonych datach. Trudność polega na tym, że DST nie tylko „zmienia przesunięcie” — zmienia też istnienie pewnych lokalnych godzin.

Brakujące i zduplikowane czasy wskazówkowe

Gdy zegary przyspieszają (spring forward), zakres lokalnych godzin nigdy nie występuje. Na przykład zegar może przeskoczyć z 01:59 do 03:00, więc 02:30 lokalnego czasu nie ma.

Gdy zegary cofają się (fall back), zakres godzin występuje dwukrotnie. Na przykład 01:30 może wystąpić raz przed przesunięciem i raz po, co oznacza, że ta sama godzina wskazówkowa może odnosić się do dwóch różnych momentów.

Dodanie 24 godzin kontra „ta sama godzina jutro”

To nie to samo przy granicach DST:

  • Dodaj 24 godziny: „dokładnie 24 * 60 * 60 sekund później”
  • Jutro o tej samej godzinie lokalnej: „jutro o 9:00 w tej strefie”

Jeśli dziś jest rozpoczęcie DST, „jutro o 9:00” może być tylko 23 godziny dalej. Jeśli DST się zakończy, może to być 25 godzin.

// Scenario: schedule “same local time tomorrow”
const d = new Date(2025, 2, 8, 9, 0); // Mar 8, 9:00 local

const plus24h = new Date(d.getTime() + 24 * 60 * 60 * 1000);
const nextDaySameLocal = new Date(d);
nextDaySameLocal.setDate(d.getDate() + 1);

// Around DST, plus24h and nextDaySameLocal can differ by 1 hour.

Dlaczego setHours może cię zaskoczyć

Jeśli zrobisz coś w stylu date.setHours(2, 30, 0, 0) w dniu „spring forward”, JavaScript może znormalizować to do innego ważnego czasu (często 03:30), ponieważ 02:30 nie istnieje w lokalnym czasie.

Bezpieczniejsze podejścia

  • Rób arytmetykę w UTC dla upływów czasu (durations): używaj milisekund epoki i metod UTC.
  • Dla lokalnego planowania bądź explicit co do intencji: „jutro o 9:00 lokalnie” powinno używać operacji kalendarzowych (setDate) zamiast dodawania milisekund.
  • Przy wymianie czasów przez API preferuj ISO 8601 z offsetem lub Z, żeby moment był jednoznaczny.

Durations kontra daty: nie używaj Date do timera

Częstym źródłem błędów jest używanie Date do reprezentowania czegoś, co nie jest momentem kalendarzowym.

Timestamp odpowiada na pytanie „kiedy to się stało?” (konkretny moment jak 2025-12-23T10:00:00Z). Duration odpowiada na „jak długo?” (np. „3 minuty 12 sekund”). To różne pojęcia i mieszanie ich prowadzi do mylących obliczeń i niespodzianek związanych ze strefą czasową/DST.

Dlaczego Date to zły wybór dla duration

Date zawsze reprezentuje punkt na osi czasu względem epoki. Jeśli przechowasz „90 sekund” jako Date, tak naprawdę przechowujesz „1970-01-01 plus 90 sekund” w określonej strefie. Formatowanie może wtedy pokazać 01:01:30, przesunąć się o godzinę lub pokazać datę, której nie zamierzałeś.

Dla duration lepiej używać zwykłych liczb:

  • Przechowuj duration w sekundach lub milisekundach (wybierz jedną jednostkę i trzymaj się jej).
  • Rób obliczenia na liczbach.
  • Konwertuj do tekstu wyświetlanego dopiero na końcu.

Konwersja sekund do HH:mm:ss

Prosty formatter, który działa dla timerów i długości mediów:

function formatHMS(totalSeconds) {
  const s = Math.max(0, Math.floor(totalSeconds));
  const hh = String(Math.floor(s / 3600)).padStart(2, "0");
  const mm = String(Math.floor((s % 3600) / 60)).padStart(2, "0");
  const ss = String(s % 60).padStart(2, "0");
  return `${hh}:${mm}:${ss}`;
}

formatHMS(75);    // "00:01:15" (countdown timer)
formatHMS(5423);  // "01:30:23" (media duration)

Jeśli konwertujesz z minut, najpierw mnoż przez 60 (minutes * 60) i trzymaj wartość jako liczbę aż do renderowania.

Porównywanie, sortowanie i zakresy bez niespodzianek

Gdy porównujesz czasy w JavaScript, najbezpieczniej porównywać liczby, nie sformatowany tekst. Obiekt Date jest opakowaniem wokół liczby (milisekundy epoki), więc porównania powinny sprowadzać się do „liczba vs liczba”.

Bezpieczne porównania (wygrywają znaczniki czasu)

Używaj getTime() (lub Date.valueOf(), który zwraca tę samą liczbę), aby porównywać niezawodnie:

const a = new Date('2025-01-10T12:00:00Z');
const b = new Date('2025-01-10T12:00:01Z');

if (a.getTime() < b.getTime()) {
  // a is earlier
}

// Also works:
if (+a < +b) {
  // unary + calls valueOf()
}

Unikaj porównywania sformatowanych ciągów jak "1/10/2025, 12:00 PM" — są zależne od lokalizacji i nie będą poprawnie sortować. Głównym wyjątkiem są ciągi ISO 8601 w tym samym formacie i strefie (np. wszystkie ...Z), które sortują się leksykograficznie.

Sortowanie i filtrowanie po zakresie

Sortowanie po czasie jest proste, jeśli sortujesz po milisekundach epoki:

items.sort((x, y) => new Date(x.createdAt).getTime() - new Date(y.createdAt).getTime());

Filtrowanie elementów w zakresie to ta sama idea:

const start = new Date('2025-01-01T00:00:00Z').getTime();
const end   = new Date('2025-02-01T00:00:00Z').getTime();

const inRange = items.filter(i => {
  const t = new Date(i.createdAt).getTime();
  return t >= start && t < end;
});

„Początek/koniec dnia” (lokalnie kontra UTC)

„Początek dnia” zależy od tego, czy masz na myśli czas lokalny czy UTC:

// Local start/end of day
const d = new Date(2025, 0, 10); // Jan 10 in local time
const localStart = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
const localEnd   = new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999);

// UTC start/end of day
const utcStart = new Date(Date.UTC(2025, 0, 10, 0, 0, 0, 0));
const utcEnd   = new Date(Date.UTC(2025, 0, 10, 23, 59, 59, 999));

Wybierz jedną definicję wcześnie i trzymaj się jej w porównaniach i logice zakresów.

Lista kontrolna do debugowania: jak zdiagnozować błąd konwersji czasu

Test conversions in a live app
Generuj, wdrażaj i hostuj viewer stref czasowych, aby zweryfikować formatowanie end-to-end.

Błędy czasowe wydają się losowe, dopóki nie wyznaczysz czego dokładnie masz (znacznik czasu? ciąg? Date?) i gdzie wprowadzono przesunięcie (parsowanie, konwersja strefy, formatowanie).

1) Zapisz „trzy widoki” tego samego momentu

Zacznij od logowania tej samej wartości na trzy różne sposoby. To szybko pokaże, czy problem to sekundy vs milisekundy, lokalne vs UTC, czy parsowanie ciągu.

console.log('raw input:', input);

const d = new Date(input);
console.log('toISOString (UTC):', d.toISOString());
console.log('toString (local):', d.toString());
console.log('timezone offset (min):', d.getTimezoneOffset());

Na co zwrócić uwagę:

  • Jeśli toISOString() jest bardzo „od czapy” (np. rok 1970 lub bardzo daleka przyszłość), podejrzewaj sekundy vs milisekundy.
  • Jeśli toISOString() wygląda dobrze, ale toString() jest „przesunięty”, widzisz wyświetlanie w strefie lokalnej.
  • Jeśli getTimezoneOffset() zmienia się w zależności od daty, przekraczasz czas letni.

2) Zweryfikuj środowisko: strefa czasowa i locale

Wiele „u mnie działa” to po prostu różne domyślne ustawienia środowiska.

  • Przeglądarka: sprawdź ustawienia strefy systemowej i język przeglądarki. Potem zaloguj:
console.log(Intl.DateTimeFormat().resolvedOptions());
  • Node.js / serwer: potwierdź strefę procesu:
console.log('TZ:', process.env.TZ);
console.log(Intl.DateTimeFormat().resolvedOptions().timeZone);

Jeśli twój serwer działa w UTC, a laptop w strefie lokalnej, sformatowane wyjście będzie inne, chyba że jawnie określisz timeZone.

3) Dodaj testy tam, gdzie czas najczęściej zawodzi

Stwórz testy jednostkowe wokół granic DST i „krawędziowych” czasów:

  • Godzina przed i po przełączeniu DST w danej strefie
  • Koniec miesiąca/roku oraz przejścia 23:3000:30
  • Wiele stref czasowych, jeśli produkt je wspiera

Jeśli szybko iterujesz, rozważ włączenie tych testów do scaffoldu. Na przykład przy generowaniu React + Go app w Koder.ai możesz dodać mały zestaw testów „time contract” od razu (przykładowe payloady API + asercje parsowania/formatowania), żeby regresje były łapane przed deploymentem.

Szybka lista przed wypuszczeniem

  • Wejścia mają jasny kontrakt: milisekundy epoki lub ISO 8601 z offsetem.
  • Brak niejednoznacznych ciągów jak "2025-03-02 10:00".
  • Formatowanie zawsze określa locale i (gdy potrzeba) timeZone.
  • Testy obejmują granice DST dla twoich regionów celowych.

Rekomendowane wzorce dla niezawodnej obsługi czasu

Niezawodna obsługa czasu w JavaScript to w dużej mierze wybór „źródła prawdy” i konsekwencja od przechowywania do wyświetlania.

Prosty zestaw najlepszych praktyk

Przechowuj i obliczaj w UTC. Traktuj lokalny czas użytkownika jako szczegół prezentacji.

Przesyłaj daty między systemami jako ciągi ISO 8601 z jawnie podanym offsetem (najlepiej Z). Jeśli musisz wysyłać epoki liczbowe, udokumentuj jednostkę i trzymaj ją spójną (milisekundy to domyślna i powszechna opcja w JS).

Formatuj dla ludzi za pomocą Intl.DateTimeFormat (lub toLocaleString) i podawaj jawny timeZone, gdy potrzebujesz deterministycznego wyjścia (np. zawsze pokazywać czasy w UTC lub w konkretnej strefie biznesowej).

Przewodnik decyzyjny: DB vs API vs UI

  • Baza danych: przechowuj moment w czasie w UTC (milisekundy epoki lub typ datetime w UTC). Unikaj dat lokalnych, chyba że domena rzeczywiście przechowuje czasy wskazówkowe (np. „sklep otwarty o 09:00”).
  • API: preferuj ISO 8601 z Z (np. 2025-12-23T10:15:00Z). Jeśli używasz epok, dodaj nazwę pola jak createdAtMs, żeby jednostki były jasne.
  • UI: weź przechowany moment UTC i sformatuj go dla lokalizacji użytkownika. W interfejsach planowania jasno oznacz strefę i trzymaj konwersje jawnie.

Kiedy warto sięgnąć po bibliotekę

Rozważ dedykowaną bibliotekę daty-czasu, jeśli potrzebujesz zdarzeń cyklicznych, złożonych reguł stref czasowych, arytmetyki bezpiecznej względem DST („ta sama godzina jutro”) lub dużej ilości parsowania niespójnych wejść. Wartość to czytelniejsze API i mniej edge-case'ów.

Jeśli chcesz zgłębić temat, przejrzyj więcej przewodników o czasie pod tekstem „/blog”. Jeśli oceniasz narzędzia lub opcje wsparcia, zobacz „/pricing”.

Często zadawane pytania

Jakiego formatu znaczników czasu używać w JavaScript?

Używaj milisekund epoki lub ciągu UTC ISO 8601 jako formatu wewnętrznego. Konwertuj go na format lokalny tylko wtedy, gdy wyświetlasz wartość użytkownikowi.

Dlaczego mój znacznik czasu Unix zamienia się w datę z 1970 roku?

JavaScriptowy Date oczekuje milisekund. Pomnóż znacznik czasu Unix w sekundach przez 1000, zanim przekażesz go do new Date().

Dlaczego ta sama data pokazuje różne godziny na różnych komputerach?

Date przechowuje jeden moment w czasie, ale JavaScript często wyświetla go w lokalnej strefie czasowej komputera. Porównaj toISOString() z toString(), aby sprawdzić, czy zmieniły się dane, czy tylko sposób wyświetlania.

Jak zapobiec przesunięciu daty o jeden dzień?

Przechowuj wartość zawierającą tylko datę, taką jak data urodzin lub data faktury, jako YYYY-MM-DD. Nie zamieniaj jej na Date, chyba że określisz też właściwą strefę czasową.

Jaki format daty jest najbezpieczniejszy dla API?

Wysyłaj pełną wartość ISO 8601 z Z dla UTC, na przykład 2025-03-04T12:30:00Z, albo podaj jawne przesunięcie. Unikaj ciągów takich jak 03/04/2025, ponieważ ludzie i przeglądarki mogą odczytać je inaczej.

Jak formatować daty dla użytkowników?

Używaj Intl.DateTimeFormat i określ timeZone, gdy widok wymaga stałej strefy. Formatuje daty zgodnie z ustawieniami regionalnymi użytkownika, bez ręcznego budowania ciągów.

Czy używać Date.parse do dat wprowadzanych przez użytkowników?

Nie polegaj na nim w przypadku nieformalnych lub niejednoznacznych ciągów. Akceptuj znany format ISO z API albo analizuj kontrolowane dane wprowadzane przez użytkownika według zasad, które sam określisz.

Dlaczego nie warto używać Date do odliczania lub czasu trwania?

Date oznacza moment w kalendarzu, a czas trwania to tylko ilość czasu. Przechowuj czasy trwania w sekundach lub milisekundach, a następnie formatuj liczbę podczas wyświetlania.

Jak czas letni wpływa na daty w JavaScript?

Do obliczeń czasu, który upłynął, używaj milisekund epoki. W przypadku harmonogramów, takich jak „jutro o 9:00” w nazwanej strefie, korzystaj z operacji opartych na kalendarzu i testuj daty przejść na czas letni.

Jaki jest najszybszy sposób debugowania błędu czasu w JavaScript?

Zapisuj surowe dane wejściowe, toISOString(), toString() i getTimezoneOffset(). Błędna wartość ISO często oznacza problem z analizowaniem lub jednostkami, natomiast inny ciąg lokalny zwykle wskazuje na różnicę w wyświetlaniu strefy czasowej.

Related posts