LW IT Solutions
« Blog Overview /Digital Analytics/Tutorials / Tutorial: Pięć formatów znacznika czasu w stosie...
This post in other languages:

Tutorial: Pięć formatów znacznika czasu w stosie pomiarowym i ich przeliczanie

Tutorial: Pięć formatów znacznika czasu w stosie pomiarowym i ich przeliczanie
Spis treści
  1. Pięć formatów spotykających się w jednym stosie
  2. Rozróżnienie przez policzenie cyfr
  3. Dzień, który nie jest tym samym dniem
  4. Przeliczanie w BigQuery
  5. Okna odrzucające znacznik czasu
  6. Dwie dwuznaczności nie do naprawienia po fakcie
  7. Pytania i odpowiedzi
  8. Źródła

Jedno kliknięcie wytwarza znacznik czasu w przeglądarce, kolejny w menedżerze tagów, trzeci w eksporcie analitycznym i czwarty w platformie reklamowej, do której zostaje przekazany. Wszystkie cztery opisują tę samą chwilę. Żaden nie jest zapisany tak samo.

Największa część zamieszania rozwiązuje się przez policzenie cyfr. Część pozostała to granica dnia – a tę warto poznać, zanim jeden raport zostanie zestawiony z drugim.

Pięć formatów znacznika czasu z przykładową wartością i liczbą cyfr, pod spodem oś czasu ze zdarzeniem o wpół do drugiej w nocy, które zależnie od konwencji przypada na dwie różne daty
Ta sama chwila w pięciu zapisach. Oś poniżej pokazuje przypadek, w którym dwa z nich nie zgadzają się co do daty.

Pięć formatów spotykających się w jednym stosie

Wszystkie liczą od tego samego punktu zerowego: północy 1 stycznia 1970 roku w UTC. Różnią się jednostką i opakowaniem.

Format Przykład Gdzie występuje
Sekundy 1790465400 Meta CAPI event_time, większość interfejsów REST
Milisekundy 1790465400000 Date.now(), gtm.start, większość JavaScriptu
Mikrosekundy 1790465400000000 GA4 BigQuery event_timestamp
ISO 8601 2026-09-27T01:30:00+02:00 Dzienniki, API Search Console, większość eksportów
Sama data 20260927 GA4 event_date, sufiks tabeli BigQuery

Trzy pierwsze są jednoznaczne: to liczba jednostek od stałego punktu i nie niosą strefy czasowej, bo jej nie potrzebują. Czwarty jest jednoznaczny, dopóki niesie przesunięcie – to +02:00 na końcu czyni z niego chwilę, a nie opis.

Piąty jest innego rodzaju. Data bez godziny nie jest chwilą, lecz zakresem dwudziestu czterech godzin – a które to dwadzieścia cztery godziny, zależy od strefy czasowej, której w wartości nigdzie nie ma.

Rozróżnienie przez policzenie cyfr

Dla liczby z dziennika, eksportu albo zawartości rozstrzyga długość.

10 cyfr   sekundy       1790465400          do roku 2286
13 cyfr   milisekundy   1790465400000
16 cyfr   mikrosekundy  1790465400000000
 8 cyfr   data          20260927            wcale nie chwila

Z pominięcia tego biorą się dwa błędy, a oba dają wynik wyglądający wiarygodnie. Odczytanie milisekund jako sekund przesuwa datę do roku 58 707 – błąd oczywisty, a więc nieszkodliwy. Odczytanie sekund jako milisekund przesuwa ją na 21 stycznia 1970, co oczywiste wcale nie jest, i pojawia się w raporcie jako maleńki słupek na lewym skraju każdego wykresu.

Zabezpieczenie kosztuje trzy linie i opłaca się wszędzie tam, gdzie wartość przychodzi z zewnątrz.

def zu_sekunden(wert):
    z = len(str(int(wert)))
    if z == 16: return int(wert) / 1_000_000
    if z == 13: return int(wert) / 1_000
    if z == 10: return int(wert)
    raise ValueError(f"nieznany format czasu o {z} cyfrach: {wert}")

Dzień, który nie jest tym samym dniem

Eksport GA4 do BigQuery niesie zarówno chwilę, jak i datę, a oba trzymają się różnych konwencji.

event_timestamp to mikrosekundy od epoki, a więc UTC. event_date to ciąg postaci YYYYMMDD w strefie raportowania usługi. Przy usłudze ustawionej na Berlin każde zdarzenie między północą a drugą w nocy czasu lokalnego niesie więc datę o dzień dalszą niż to, co mówi w UTC jego własny znacznik czasu.

Zdarzenie 27.09.2026 o godzinie 01:30 czasu berlinskiego

event_date                                          20260927
DATE(TIMESTAMP_MICROS(event_timestamp))           2026-09-26
DATE(TIMESTAMP_MICROS(event_timestamp),
     "Europe/Berlin")                             2026-09-27

To nie jest usterka i nie da się tego wyłączyć. To powód, dla którego dwa zapytania po tej samej tabeli mogą dać różne sumy dzienne – a różnica ma zawsze ten sam rozmiar: zdarzenia z pierwszej jednej lub dwóch godzin każdego dnia.

Wynikająca zasada jest krótka. Grupowanie po dniach używa albo wszędzie event_date, albo wszędzie DATE(..., "Europe/Berlin"), nigdy obu naraz – a zapytanie łączące dwie tabele musi po obu stronach użyć tej samej konwencji. Sufiks tabeli _TABLE_SUFFIX podąża za event_date, przez co filtr daty na sufiksie i filtr na przeliczonym znaczniku czasu wybierają różne wiersze.

Przeliczanie w BigQuery

Trzy funkcje obejmują każdy przypadek, a każda nosi jednostkę w nazwie.

SELECT
  event_timestamp,
  TIMESTAMP_MICROS(event_timestamp)                       AS moment_utc,
  DATETIME(TIMESTAMP_MICROS(event_timestamp),
           "Europe/Berlin")                               AS ortszeit,
  DATE(TIMESTAMP_MICROS(event_timestamp), "Europe/Berlin") AS tag_lokal,
  FORMAT_TIMESTAMP("%FT%T%Ez",
                   TIMESTAMP_MICROS(event_timestamp),
                   "Europe/Berlin")                       AS iso_mit_versatz,
  UNIX_SECONDS(TIMESTAMP_MICROS(event_timestamp))         AS sekunden
FROM `projekt.analytics_123456789.events_*`
WHERE _TABLE_SUFFIX = "20260927"
LIMIT 10;

Różnicę między TIMESTAMP a DATETIME warto sobie przyswoić. TIMESTAMP jest chwilą i wie, czym jest; DATETIME jest odczytem zegara bez strefy, a zamiana znacznika czasu w taki odczyt to dokładnie miejsce, w którym informacja o strefie zostaje celowo odrzucona. Zapisany DATETIME odczytany gdzie indziej to droga, którą znika godzina.

W drugą stronę z ciągu znaków powstaje znacznik czasu wyłącznie przy podanym formacie – a podanie formatu jest miejscem, w którym przesunięcie zostaje albo odczytane, albo wymyślone.

PARSE_TIMESTAMP("%FT%T%Ez", "2026-09-27T01:30:00+02:00")   -- poprawnie
PARSE_TIMESTAMP("%FT%T",     "2026-09-27T01:30:00")        -- odczytane jako UTC

Okna odrzucające znacznik czasu

Dwa systemy odbierające sprawdzają wartość, zamiast tylko ją zapisać, i oba zawodzą w sposób łatwy do przeoczenia.

Meta Conversions API przyjmuje event_time z ostatnich siedmiu dni. Zdarzenie starsze zostaje odrzucone, a wsadowe przesłanie historycznych konwersji traci przez to po cichu wszystko poza oknem – odpowiedź melduje, ile zdarzeń odebrano, a nie ile zachowano. Sprawdzeniem jest liczba z odpowiedzi wobec liczby wysłanych.

Znacznik czasu w przyszłości to druga połowa tej samej zasady i zdarza się częściej niż przeszłość: serwer z zegarem spieszącym o kilka minut wytwarza zdarzenia, które platforma odrzuca. Nieudane przesłanie konwersji warto więc badać jako problem zegara, zanim zostanie zbadane jako problem danych.

Przesyłanie konwersji offline do platform reklamowych zwykle wymaga czasu lokalnego z wyraźnym przesunięciem, a nie wartości epoki.

2026-09-27 01:30:00+02:00

Przesunięcie nie jest ozdobą. Bez niego platforma stosuje strefę czasową konta, a konto w innej strefie niż sklep przesuwa każdą konwersję o tę różnicę – co objawia się konwersjami w błędnym dniu, a na granicy miesiąca w błędnym miesiącu.

Dwie dwuznaczności nie do naprawienia po fakcie

Wszystko powyższe jest przeliczeniem. Te dwie są stratami, a jedynym środkiem jest niedopuszczenie do ich powstania.

Pierwszą jest czas lokalny bez przesunięcia. 2026-09-27 01:30:00 nie jest chwilą, lecz chwilą w jakiejś strefie – a jeżeli strefy nie zapisano, żadne późniejsze przetwarzanie jej nie odzyska. Domysł jest możliwy i pozostaje domysłem: ten sam ciąg znaków może oznaczać dwie chwile odległe o godzinę.

Drugą jest noc zmiany czasu. Przy cofnięciu zegarów godzina między drugą a trzecią w nocy zdarza się dwa razy, a czas lokalny w jej obrębie jest naprawdę dwuznaczny nawet przy znanej strefie. Przesunięcie to rozstrzyga, bo oba przebiegi mają różne przesunięcia. Sama nazwa strefy tego nie robi.

Jedno i drugie znika, gdy wartości zapisywane są jako UTC i przeliczane wyłącznie do wyświetlenia. To zasada jednolinijkowa, brzmiąca oczywiście i łamana bez przerwy, bo czas lokalny to właśnie to, co człowiek chce przeczytać – a zaradza temu przeliczanie przy odczycie, a nie przy zapisie.

Pytania i odpowiedzi

Wartość ma 19 cyfr. Co to jest?

Najprawdopodobniej nanosekundy od epoki. Tego formatu używają na przykład OpenTelemetry oraz funkcja UnixNano w Go; w stosie pomiarowym pojawia się przede wszystkim w danych serwerowych i w dziennikach. Zabezpieczenie słusznie go odrzuca, zamiast zgadywać, a da się je rozszerzyć o jedną linię dla 19 cyfr z dzielnikiem 1_000_000_000.

Czy JavaScript przetwarza 16-cyfrowe mikrosekundy bez straty?

Dziś tak, ale na styk. JavaScript przechowuje liczby jako wartości zmiennoprzecinkowe podwójnej precyzji, a te odwzorowują liczby całkowite dokładnie tylko do 9 007 199 254 740 991 (Number.MAX_SAFE_INTEGER). Wartość w mikrosekundach z 2026 roku, 1 790 465 400 000 000, jest mniej więcej pięć razy mniejsza; w mikrosekundach ta granica odpowiada rokowi 2255.

Nanosekundy leżą natomiast daleko ponad nią. Wartość 19-cyfrowa traci przy wczytaniu przez JSON.parse ostatnie cyfry, i to bez żadnego błędu. Gdy takie wartości są potrzebne w JavaScripcie, najlepiej wczytywać je jako ciągi znaków i przekształcać przez BigInt albo już na serwerze przeliczać na milisekundy.

Dlaczego nie ustawić po prostu strefy czasowej usługi na UTC?

Usuwa to różnicę między event_date a znacznikiem czasu, ale przenosi problem w inne miejsce. Wszystkie raporty dzienne w GA4 zaczynają się wtedy o północy UTC, czyli dla sklepu w Berlinie o pierwszej w nocy zimą i o drugiej latem. Zakup o wpół do pierwszej w nocy trafia do dnia poprzedniego, a wartości dzienne przestają zgadzać się z kasą, systemem magazynowym i kontami reklamowymi, które liczą w czasie lokalnym.

Do tego dochodzi sama zmiana. Nowa strefa raportowania obowiązuje tylko dla danych od chwili przestawienia, a wcześniejsze dni zostają takie, jakie były. Dzień zmiany jest więc krótszy albo dłuższy niż dwadzieścia cztery godziny, a szereg czasowy obejmujący zmianę zestawia dni berlińskie z dniami UTC.

Lepiej sprawdza się zasada z artykułu: usługa zachowuje strefę, w której liczy firma, a każde zapytanie wybiera jedną konwencję. Grupowanie w BigQuery według dni berlińskich ma wtedy te same granice dnia co interfejs, a grupowanie według UTC jest świadomym wyborem innego dnia.

Jak potwierdzić, że za odrzuconym przesłaniem stoi zegar serwera?

Dwoma sprawdzeniami. Na samym serwerze timedatectl w Linuksie pokazuje, czy zegar jest synchronizowany z serwerem czasu; jeśli wiersz System clock synchronized ma wartość no, rozjechany zegar jest prawdopodobny. Drugie sprawdzenie nie wymaga dostępu do systemu operacyjnego: każda odpowiedź HTTP platformy niesie w nagłówku Date czas jej serwera, a porównanie z własnym czasem w tej samej chwili pokazuje odchyłkę z dokładnością do około sekundy.

Jeśli odchyłka sięga minut, znika, gdy tylko zacznie działać synchronizacja z serwerem czasu. Zdarzenia już wysłane z błędnym czasem i odrzucone należy potem wysłać ponownie z poprawionym znacznikiem czasu, dopóki mieszczą się w siedmiodniowym oknie.

Lukas Wojcik

Lukas Wojcik

Systems architect and technology enthusiast specializing in scalable tracking solutions, GMP Stack (GA4 & GTM), and robust backend architectures. Advocate for clean code and privacy-first design.

Get in Touch

Briefly describe your project or inquiry for a tailored response. This site is protected by reCAPTCHA.

Napisanie komentarza

Odmienne wyniki z innych kont i pytania o konfigurację są tu mile widziane.

Adres e-mail nie jest publikowany. Pola obowiązkowe oznaczono gwiazdką.

ALL ARTICLES & CATEGORIES

CCTV

Śledź tę kategorię przez RSS

Cloud & AI

Wszystkie artykuły w tej kategorii (11) Śledź tę kategorię przez RSS

Data Privacy

Wszystkie artykuły w tej kategorii (15) Śledź tę kategorię przez RSS

Digital Analytics

Wszystkie artykuły w tej kategorii (54) Śledź tę kategorię przez RSS

Digital Marketing

Wszystkie artykuły w tej kategorii (34) Śledź tę kategorię przez RSS

IT & Networks

Wszystkie artykuły w tej kategorii (18) Śledź tę kategorię przez RSS

Music Production

Śledź tę kategorię przez RSS

Raspberry Pi

Śledź tę kategorię przez RSS

Smart Home

Wszystkie artykuły w tej kategorii (19) Śledź tę kategorię przez RSS

Tworzenie stron internetowych

Śledź tę kategorię przez RSS

Wtyczki i triki WordPress

Śledź tę kategorię przez RSS