Als je al een tijdje aan webapplicaties werkt, heb je waarschijnlijk ooit een timestamp-bug gezien die in eerste instantie nergens op sloeg. Een datum die ineens op 1970 uitkomt. Een tijd die 52 jaar in de toekomst ligt. Een API die een timestamp afwijst die er heel normaal uitziet.

De kans is groot dat de oorzaak de seconden‑vs‑milliseconden mismatch was — één van de meest voorkomende bugs bij datum/tijd‑handling, en ook één van de makkelijkst te missen.

Het kernprobleem

Unix-timestamps horen seconden te tellen sinds 1 januari 1970 (de Unix epoch). Dat is de oorspronkelijke standaard, en vrijwel elke server-side taal en elk systeem gebruikt dat:

  • Python’s time.time() geeft seconden terug
  • PHP’s time() geeft seconden terug
  • De meeste SQL-databases slaan seconden op en geven seconden terug
  • De Unix-shell opdracht date +%s geeft seconden terug
  • In de meeste REST API-documentatie betekent “Unix timestamp” ook echt seconden

JavaScript doet iets anders. Date.now() en new Date().getTime() geven milliseconden terug — 1000× de equivalente Unix-timestamp.

Dat betekent dat een waarde als 1712534400 8 april 2024 voorstelt in seconden. Maar 1712534400000 is hetzelfde moment in milliseconden — en als je per ongeluk milliseconden gebruikt waar seconden verwacht worden (of andersom), krijg je compleet verkeerde datums.

Hoe die bugs er in de praktijk uitzien

Scenario 1: JavaScript-milliseconden naar een API die seconden verwacht

Een frontend stuurt Date.now() — bijvoorbeeld 1712534400000 — rechtstreeks naar een backend die het opslaat als Unix-timestamp in seconden. De backend slaat dus 1712534400000 op. Converteer je dat terug naar een datum, dan kom je ergens rond het jaar 56000 uit.

Dit is de “datum staat ver in de toekomst” bug.

Scenario 2: Secondenwaarde gebruiken in JavaScript

Een backend geeft een Unix-timestamp in seconden terug — bijvoorbeeld 1712534400. Een JavaScript-developer stopt die rechtstreeks in new Date(1712534400). JavaScript interpreteert dat als 1.712.534.400 milliseconden sinds de epoch — ongeveer 19,8 dagen na 1 januari 1970. De UI laat dus januari 1970 zien.

Dit is de “datum staat op 1970” bug.

Scenario 3: JWT-expiry in de verkeerde unit

Een JSON Web Token krijgt een exp claim die bedoeld is om over 24 uur te verlopen. De developer berekent Date.now() + 86400 — met het idee 86.400 seconden (24 uur) op te tellen. Maar Date.now() is milliseconden, en 86.400 is seconden. Het resultaat ligt ongeveer 86.400 milliseconden (86,4 seconden) in de toekomst. Elke token verloopt dan in minder dan 2 minuten in plaats van 24 uur.

Scenario 4: Database-timestamp verkeerd teruglezen

Een PostgreSQL-kolom bewaart een Unix-timestamp als integer (seconden). De waarde wordt opgehaald en in JavaScript “as-is” gebruikt, waardoor je weer op 1970 uitkomt. Oplossing: vermenigvuldig met 1000 voordat je het in new Date() stopt.

Met de Unix timestamp converter kun je snel checken of een getal seconden of milliseconden is en welke datum erbij hoort — handig bij debuggen.

Seconden of milliseconden herkennen

De snelste manier: tel de cijfers.

  • 10 cijfers (bijv. 1712534400) → seconden. Een plausibele datum in het heden of in de recente toekomst/verleden.
  • 13 cijfers (bijv. 1712534400000) → milliseconden.

Een Unix-timestamp in seconden voor datums tussen 2000 en 2050 ligt ongeveer tussen 946.684.800 en 2.524.608.000 — dus altijd 10 cijfers. Zie je in een tijd-context een 13-cijferig getal, dan is het bijna zeker milliseconden.

Dit werkt voor “huidige” datums. Ver in de toekomst (na het jaar 2286) krijg je 11-cijferige seconden-timestamps, maar dat is een randgeval dat je in de praktijk kunt negeren.

De fix per taal

JavaScript (naar seconden voor backend): `javascript const timestampSeconds = Math.floor(Date.now() / 1000); `

JavaScript (seconden van backend naar Date): `javascript const date = new Date(timestampSeconds * 1000); `

Python (is al seconden): `python import time timestamp_seconds = int(time.time()) `

PHP (is al seconden): `php $timestamp_seconds = time(); `

SQL (PostgreSQL, seconden extraheren): `sql SELECT EXTRACT(EPOCH FROM NOW())::int; `

Milliseconden naar seconden (elke taal): ` deel door 1000, en rond af naar beneden (floor) / truncate `

JWT-expiry: de juiste manier

JWT-claims (exp, iat, nbf) moeten volgens RFC 7519 in seconden staan. In JavaScript:

const now = Math.floor(Date.now() / 1000);   // seconden
const exp = now + 86400;                      // 24 uur vanaf nu, in seconden

Niet: `javascript const exp = Date.now() + 86400; // fout: milliseconden-basis met seconden-offset gemixt `

En niet: `javascript const exp = Date.now() + (86400 * 1000); // fout: exp zou ~28 dagen in de toekomst liggen in milliseconden `

Een JWT met een 13-cijferige exp wordt door de meeste JWT-libraries afgekeurd als ongeldig of “ver in de toekomst”. Met de Unix timestamp tool kun je controleren naar welke datum een exp waarde decodeert.

Timestamps opslaan in databases

Wees consequent in je schema’s en documenteer de unit:

  • Als je seconden opslaat, zet het erbij. Kolomnamen zoals created_at_unix of een comment “Unix timestamp in seconds” voorkomen verwarring.
  • Als je ORM of framework milliseconden opslaat (sommige JavaScript-ORM’s doen dat), wees daar ook expliciet over.
  • Vermijd mixen — sla niet sommige timestamps in seconden en andere in milliseconden op binnen hetzelfde systeem.

PostgreSQL’s TIMESTAMPTZ is vaak beter dan ruwe integers, omdat het timezones en formatting native ondersteunt. Maar als je wel integers gebruikt, kies één unit en houd je eraan.

Een noot over microseconden

Sommige systemen gaan nog verder en gebruiken microseconden (miljoensten van een seconde). Python’s time.time_ns() geeft nanoseconden. High‑frequency trading, profiling tools en sommige database-internals werken met microseconde‑precisie.

Zie je een 16-cijferig getal in een tijd-context, dan kan het microseconden zijn. Een 19-cijferig getal kan nanoseconden zijn. Dezelfde heuristiek geldt: het aantal cijfers vertelt je de schaal.

Voor webapplicaties is milliseconden meestal al preciezer dan je ooit nodig hebt. Microseconden zijn vooral relevant in performance‑kritische, high‑frequency systemen.

De praktische regel

Zodra een timestamp een grens overgaat — JavaScript frontend naar Python/PHP/Go/Java backend, app naar database, app naar externe API — stel jezelf de vraag: gebruikt deze kant seconden of milliseconden? Voeg de conversie toe op de grens, documenteer het en test met een bekende datum om te checken of het klopt.

De test is simpel: converteer de timestamp die je maakt en kijk of er een plausibele datum uitkomt. Zie je 1970 of 55000, dan weet je meteen dat er iets mis zit.