Odbierasz z API liczbę `1700000000` i chcesz zapisać ją jako datę w aplikacji .NET. To najpewniej liczba sekund od początku epoki uniksowej, ale ten sam opis pola bywa używany dla milisekund, a pomyłka zmienia wynik o rzędy wielkości.
Liczbę czasu trzeba czytać razem z jej jednostką
- Początek epoki: 1 stycznia 1970 o północy UTC.
- W .NET: użyj `DateTimeOffset.FromUnixTimeSeconds` albo wariantu dla milisekund.
- Strefa: liczba wskazuje chwilę, a nie lokalny zapis daty.
- Zakres: typowe wartości `DateTimeOffset` wykraczają poza ograniczenia starszych, 32-bitowych liczników.
W .NET konwersję wykonuje DateTimeOffset
Wartość `1700000000` opisuje liczbę sekund, które upłynęły od 1970-01-01T00:00:00Z. Litera `Z` oznacza UTC. Sam zapis nie zawiera informacji, jak wyświetlić tę chwilę w polskim czasie.
Do konwersji wybieram `DateTimeOffset`, bo przechowuje chwilę razem z przesunięciem względem UTC. Microsoft Learn dokumentuje metody `FromUnixTimeSeconds` i `ToUnixTimeSeconds`; pierwsza zwraca obiekt z przesunięciem UTC, a druga zamienia obiekt na liczbę pełnych sekund.
using System;
long timestamp = 1700000000;
DateTimeOffset instant =
DateTimeOffset.FromUnixTimeSeconds(timestamp);
Console.WriteLine(instant); // 11/14/2023 10:13:20 PM +00:00
long secondsAgain = instant.ToUnixTimeSeconds();
Jeśli potrzebujesz czasu warszawskiego do prezentacji, przelicz gotową chwilę przez `TimeZoneInfo`, zamiast zmieniać samą liczbę. To rozdziela dwie rzeczy, które łatwo pomylić: moment zdarzenia i jego lokalny zapis.
Jednostkę rozpoznaj po kontrakcie, nie po liczbie cyfr
Współczesna wartość w sekundach często ma 10 cyfr, a w milisekundach 13, ale to tylko wskazówka. Zakres dat, dane testowe albo inna jednostka mogą złamać tę regułę, więc sprawdzam opis pola w API lub dokumentacji formatu.
| Wartość lub źródło | Jednostka | Metoda .NET |
|---|---|---|
| `1700000000` | Sekundy | `FromUnixTimeSeconds` |
| `1700000000000` | Milisekundy | `FromUnixTimeMilliseconds` |
| `DateTimeOffset.UtcNow.ToUnixTimeMilliseconds()` | Milisekundy | `FromUnixTimeMilliseconds` przy odczycie |
Reguła jest prosta: jeśli kontrakt mówi o sekundach, użyj metody sekundowej; jeśli mówi o milisekundach, użyj milisekundowej. Nie zgaduj na podstawie samej nazwy `timestamp` i nie mnoż ani nie dziel wartości bez potwierdzenia jednostki.
Własność precyzji też zależy od metody. `ToUnixTimeSeconds()` zwraca pełne sekundy, więc część ułamkowa chwili przepada; jeśli system ma zachować milisekundy, potrzebna jest metoda milisekundowa.
Ta sama liczba daje inny zapis w Warszawie i UTC
Przy wartości `1700000000` konwersja sekundowa daje chwilę 14 listopada 2023 o 22:13:20 UTC. W Warszawie tego dnia obowiązywał czas UTC+1, więc na ekranie lokalnym będzie to 23:13:20. Liczba się nie zmieniła, zmienił się sposób jej pokazania.
Przy pracy z API zwykle zostawiam chwilę w UTC, a strefę stosuję dopiero w warstwie prezentacji. Dzięki temu dwa serwery w różnych krajach nadal porównują to samo zdarzenie, zamiast porównywać lokalne odczyty zegara.
| Sytuacja | Skutek | Co zrobić |
|---|---|---|
| Liczba wygląda jak data z 1970 roku | Milisekundy mogły zostać odczytane jako sekundy. | Sprawdź jednostkę w kontrakcie i wybierz metodę milisekundową, jeśli ją określono. |
| Data różni się o godzinę | Chwila jest poprawna, lecz wyświetlono ją w innej strefie. | Porównaj wartości UTC, a lokalną strefę zastosuj przy prezentacji. |
| `FromUnixTimeSeconds` zgłasza `ArgumentOutOfRangeException` | Liczba wykracza poza zakres obsługiwany przez `DateTimeOffset`. | Zweryfikuj jednostkę, znak wartości i zakres danych wejściowych. |
Daty sprzed początku epoki są poprawne: mają ujemne wartości. Przykładowo minuta przed północą UTC 1 stycznia 1970 odpowiada `-60`, a nie błędowi konwersji.
Licznik 32-bitowy kończy się tam, gdzie long działa dalej
Typowe przykłady w .NET używają `long`, czyli 64-bitowej liczby całkowitej ze znakiem. Kłopot z rokiem 2038 dotyczy systemów i formatów, które nadal zapisują sekundy w 32-bitowym, podpisanym `time_t`: taki licznik osiąga granicę 19 stycznia 2038 o 03:14:07 UTC.
Open Group opisuje przepełnienie implementacji `time_t` opartej na 32-bitowej liczbie ze znakiem. Dla aplikacji .NET nie oznacza to, że każda data z przyszłości jest problemem: `DateTimeOffset` przyjmuje znacznie szerszy zakres, ale ograniczenie może siedzieć w bazie, natywnym komponencie albo kontrakcie zewnętrznego systemu.
Unix time nie uwzględnia sekund przestępnych jako osobnych kolejnych wartości. Microsoft Learn zaznacza to w opisie konwersji, a POSIX opisuje pomijanie sekund przestępnych w czasie od epoki. Do typowych logów i znaczników zdarzeń to spójna konwencja; do pomiarów wymagających ścisłego śledzenia czasu astronomicznego potrzebny jest inny model.
Najpierw ustal jednostkę, potem zapisuj chwilę
W systemie produkcyjnym zapisz jednostkę w nazwie pola lub schemacie, na przykład `createdAtUnixSeconds`, i waliduj zakres przy wejściu. Sama etykieta `timestamp` pozostawia miejsce na błąd, który może wyglądać jak problem ze strefą albo uszkodzona data.
Uważam, że najbezpieczniejszy przepływ w .NET jest czytelny: parsuj jawnie sekundy lub milisekundy do `DateTimeOffset`, trzymaj chwilę w UTC i przeliczaj ją na czas lokalny dopiero wtedy, gdy ma ją zobaczyć człowiek.
