Unix-aikaleima: sekunnit vs millisekunnit ja niiden aiheuttamat virheet

Jos olet työskennellyt web-sovelluksissa pidempään, olet varmasti törmännyt aikaleima-virheeseen, joka ei aluksi ollut mitään järkeä. Päivämäärä näkyi vuonna 1970. Aika näkyi 52 vuotta tulevaisuudessa. API hylkäsi aikaleiman, joka näytti täysin järkevältä.

Todennäköinen syyllinen oli sekuntien ja millisekuntien ristiriita — yksi yleisimmistä päivämäärän käsittelyvirheistä ohjelmistoissa ja yksi niistä, joita on helpoin jäädä huomaamatta.

Perusongelma

Unix-aikaleimoja oletetaan laskettavan sekunneissa 1.1.1970 lähtien (Unix-epookki). Se on alkuperäinen standardi ja sitä käyttävät käytännöllisesti katsoen kaikki palvelinpuolen kielet ja järjestelmät:

  • Pythonin time.time() palauttaa sekunnit
  • PHPin time() palauttaa sekunnit
  • Useimmat SQL-tietokannat tallentavat ja palauttavat sekunnit
  • Unix-komentorivin date +%s palauttaa sekunnit
  • Suurin osa REST API -dokumentaatiosta, kun se sanoo "Unix-aikaleima", tarkoittaa sekunteja

JavaScript toimii eri tavalla. Date.now() ja new Date().getTime() palauttavat millisekunnit — 1000 kertaa vastaavan Unix-aikaleiman.

Tämä tarkoittaa, että aikaleiman arvo 1712534400 edustaa 8. huhtikuuta 2024 sekunneissa. Mutta 1712534400000 edustaa samaa hetkeä millisekunteissa — ja jos vahingossa käytät millisekuntien arvoa, kun sekunteja odotetaan, tai päinvastoin, päivämäärät menevät pahasti pieleen.

Millaisia nämä virheet tosiasiassa ovat

Tilanne 1: JavaScriptin millisekunnit lähetetään sekunteja odottavalle API:lle

Frontend lähettää Date.now() -arvon — esimerkiksi 1712534400000 — suoraan backendiin, joka tallentaa sen Unix-aikaleimana sekunneissa. Backend tallentaa 1712534400000 aikaleimaksi. Muuta se takaisin päivämääräksi ja saat jonkin vuoden 56000 tietämille.

Tämä on "päivämäärä näkyy kaukaisessa tulevaisuudessa" -virhe.

Tilanne 2: Sekuntien arvo käytetään JavaScriptissä

Backend palauttaa Unix-aikaleiman sekunneissa — esimerkiksi 1712534400. JavaScriptin kehittäjä välittää sen suoraan new Date(1712534400) -funktiolle. JavaScript tulkitsee tämän 1 712 534 400 millisekunnin ajaksi epookista — noin 19,8 päivää epookin jälkeen. UI näyttää päivämäärän tammikuussa 1970.

Tämä on "päivämäärä näkyy vuonna 1970" -virhe.

Tilanne 3: JWT:n voimassaolon päättyminen väärässä yksikössä

JSON Web Token myönnetään exp-väitteellä, jonka on tarkoitus vanhentua 24 tunnissa. Kehittäjä laskee Date.now() + 86400 — tarkoituksenaan lisätä 86 400 sekuntia (24 tuntia). Mutta Date.now() on millisekunteissa ja 86 400 on sekunneissa. Tulos on aikaleima noin 86 400 millisekuntia (86,4 sekuntia) tulevaisuudessa. Jokainen token vanhentuu alle 2 minuutin kuluessa 24 tunnin sijaan.

Tilanne 4: Tietokannan aikaleima luetaan väärin

PostgreSQL-sarake tallentaa Unix-aikaleiman kokonaislukuna (sekunnit). Arvo haetaan ja käytetään JavaScriptissä sellaisenaan, mikä tuottaa vuonna 1970 olevan päivämäärän. Korjaus: kerro 1000:lla ennen kuin välität new Date() -funktiolle.

Voit käyttää Unix-aikaleiman muuntajaa nopeasti tarkistaaksesi, onko näkemäsi luku sekuntien vai millisekuntien aikaleima ja mihin päivämäärään se vastaa — hyödyllinen virheenetsintään.

Sekuntien ja millisekuntien erottaminen

Nopein keino: laske numerot.

  • 10 numeroa (esimerkiksi 1712534400) → sekuntien aikaleima. Edustaa todellista tai lähimenneisyyttä tai lähitulevaisuutta.
  • 13 numeroa (esimerkiksi 1712534400000) → millisekuntien aikaleima.

Sekunteihin perustuva Unix-aikaleima vuosille 2000–2050 on noin 946 684 800 - 2 524 608 000 — aina 10 numeroa. Jos näet 13-numeroisen kokonaisluvun aikaan liittyvässä kontekstissa, se on lähes varmasti millisekunteja.

Tämä sääntö toimii nykyisille päivämäärille. Kaukaiset tulevaisuuden päivämäärät (vuoden 2286 jälkeen) olisivat 11-numeroisia sekuntien aikaleimoja, mutta se on käytännöllisesti katsoen poikkeama, jonka voit jättää huomiotta.

Korjaus jokaisessa kielessä

JavaScript (muunna sekunneiksi lähettäessä backendiin): `javascript const timestampSeconds = Math.floor(Date.now() / 1000); `

JavaScript (muunna backendista tulevat sekunnit käyttöä varten Date-oliossa): `javascript const date = new Date(timestampSeconds * 1000); `

Python (jo sekunneissa): `python import time timestamp_seconds = int(time.time()) `

PHP (jo sekunneissa): `php $timestamp_seconds = time(); `

SQL (PostgreSQL, pura sekunnit): `sql SELECT EXTRACT(EPOCH FROM NOW())::int; `

Muunna millisekuntien aikaleima sekunneiksi (mikä tahansa kieli): ` jaa 1000:lla, sitten pyöristä alaspäin/katkaise `

JWT:n voimassaolon päättyminen oikein

JWT-väitteet (exp, iat, nbf) on oltava sekunneissa RFC 7519 -määrityksen mukaisesti. JavaScriptissä:

const now = Math.floor(Date.now() / 1000);   // sekunnit
const exp = now + 86400;                      // 24 tunnin päästä, sekunneissa

Ei näin: `javascript const exp = Date.now() + 86400; // väärin: sekoittaa millisekuntien perustan ja sekuntien poikkeaman `

Eikä näin: `javascript const exp = Date.now() + (86400 * 1000); // väärin: exp olisi noin 28 päivää tulevaisuudessa millisekunteissa `

JWT-token, jonka exp-arvo on 13 numeroa, hylätään useimpien JWT-kirjastojen toimesta virheellisenä tai kaukaisena tulevaisuutena. Unix-aikaleiman työkalu voi auttaa sinua varmentamaan, mihin päivämäärään tietty exp-arvo purkautuu.

Aikaleimien tallentaminen tietokantoihin

Kun suunnittelet skeemoja, ole johdonmukainen yksikön suhteen:

  • Jos tallennat sekunteja, dokumentoi se. Sarakkeisiin kuten created_at_unix tai kommentilla, joka selventää "Unix-aikaleima sekunneissa", vältetään epäselvyys.
  • Jos ORM tai kehykselläsi tallennetaan millisekunteja (jotkut JavaScript ORM:t tekevät niin), ole selkeä myös siitä.
  • Vältä sekoittamista — älä tallenna joitain aikaleimoja sekunneissa ja muita millisekunteissa samassa järjestelmässä.

PostgreSQL:n natiivi TIMESTAMPTZ-tyyppi on usein parempi valinta kuin raakojen kokonaislukujen tallentaminen, sillä se käsittelee aikavyöhykkeen muuntamisen ja muotoilun natiivisti. Mutta kun käytät kokonaislukuja aikaleimille, valitse yksi yksikkö ja pysy siinä.

Huomautus mikrosekunneista

Jotkut järjestelmät menevät vieläkin pidemmälle ja käyttävät mikrosekunteja (sekunnin miljoonasosaa). Pythonin time.time_ns() palauttaa nanosekunteja. Korkean taajuuden kaupankäyntijärjestelmät, profilointityökalut ja jotkut tietokannan sisäosat käyttävät mikrosekuntien tarkkuutta.

Jos näet 16-numeroisen kokonaisluvun aikakontekstissa, se saattaa olla mikrosekunteja. 19-numeroinen kokonaisluku voi olla nanosekunteja. Sama havaitsemiskeino pätee — numeroiden määrä kertoo likimääräisen mittakaavan.

Web-sovelluksien aikaleimille millisekuntien tarkkuus on aina riittävä. Mikrosekuntien tarkkuus on merkitystä vain suorituskykykriittisissä, korkean taajuuden järjestelmissä.

Käytännöllinen sääntö

Aina kun aikaleima ylittää kielten tai järjestelmien rajan — JavaScript-frontend ja Python/PHP/Go/Java-backend, sovellus tietokantaan, sovellus ulkoiseen API:iin — kysy: kumpi puoli käyttää sekunteja ja kumpi millisekunteja? Lisää muunnos rajalle, dokumentoi se ja testaa tunnetulla päivämäärällä varmistaaksesi, että tulos on järkevä.

Testi on yksinkertainen: muunna luomasi aikaleima ja varmista, että se näyttää todellisen päivämäärän. Jos se näyttää vuotta 1970 tai 55000, tiedät heti, että jotain on vialla.