Leer hoe je tijd in JavaScript formatteert en converteert zonder verrassingen: timestamps, ISO-strings, tijdzones, zomertijd, parse-regels en betrouwbare patronen.

JavaScript-tijdbugs lijken zelden op "de klok staat verkeerd." Ze verschijnen als kleine, verwarrende verschuivingen: een datum die op jouw laptop goed lijkt maar op die van een collega fout is, een API-respons die prima lijkt totdat die in een andere tijdzone wordt weergegeven, of een rapport dat "een dag teveel of te weinig" aangeeft rond een seizoensgebonden tijdwijziging.
Je ziet meestal één (of meer) van deze situaties:
+02:00) is anders dan verwacht.Een grote oorzaak van pijn is dat het woord tijd naar verschillende concepten kan verwijzen:
De ingebouwde Date van JavaScript probeert al deze gevallen te dekken, maar het representeert voornamelijk een instant in de tijd terwijl het je constant naar lokale weergave duwt — waardoor onbedoelde conversies makkelijk ontstaan.
Deze gids is bewust praktisch: hoe krijg je voorspelbare conversies tussen browsers en servers, hoe kies je veiliger formaten (zoals ISO 8601), en hoe spot je de klassieke valkuilen (seconden vs milliseconden, UTC vs lokaal, en parse-verschillen). Het doel is niet meer theorie — het zijn minder "waarom is het verschoven?"-verrassingen.
JavaScript-tijdbugs beginnen vaak door het mixen van representaties die er uitwisselbaar uitzien, maar dat niet zijn.
1) Epoch milliseconden (nummer)
Een gewoon nummer zoals 1735689600000 is meestal "milliseconden sinds 1970-01-01T00:00:00Z". Het representeert een instant in de tijd zonder formattering of tijdzone.
2) Date-object (wrapper rond een instant)
Een Date slaat hetzelfde soort instant op als een timestamp. Het verwarrende deel: wanneer je een Date afdrukt, formatteert JavaScript die met de lokale regels van de omgeving, tenzij je anders vraagt.
3) Geformatteerde string (menselijke weergave)
Strings zoals "2025-01-01", "01/01/2025 10:00" of "2025-01-01T00:00:00Z" zijn niet één ding. Sommige zijn eenduidig (ISO 8601 met Z), andere hangen af van locale, en sommige bevatten helemaal geen tijdzone.
Dezelfde instant kan er per tijdzone verschillend uitzien:
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" (vorige dag)
Kies één interne representatie (veelal epoch milliseconden of UTC ISO 8601) en houd je daar aan in je app en API's. Converteer naar/van Date en geformatteerde strings alleen aan de randen: bij input-parsing en UI-weergave.
Een "timestamp" betekent meestal epoch time (ook Unix time genoemd): de telling van tijd sinds 1970-01-01 00:00:00 UTC. Het probleem: verschillende systemen tellen in verschillende eenheden.
JavaScript's Date is de bron van de meeste verwarring omdat het milliseconden gebruikt. Veel API's, databases en logs gebruiken seconden.
17040672001704067200000Zelfde moment, maar de milliseconde-versie heeft drie extra cijfers.
Gebruik expliciete vermenigvuldiging/deling zodat de eenheid duidelijk is:
// 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();
Date()Dit lijkt logisch, maar is fout wanneer ts in seconden is:
const ts = 1704067200; // seconds
const d = new Date(ts); // WRONG: treated as milliseconds
Het resultaat zal een datum in 1970 zijn, omdat 1,704,067,200 milliseconden slechts ~19 dagen na de epoch is.
Wanneer je niet zeker weet welke eenheid je hebt, voeg dan snelle vangrails toe:
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());
Als de “digits” telling ~10 is, is het waarschijnlijk seconden. Als het ~13 is, waarschijnlijk milliseconden. Print ook toISOString() tijdens debuggen: het is eenduidig en helpt je eenhedenfouten direct te zien.
JavaScript's Date kan verwarrend zijn omdat het een enkele instant in de tijd opslaat, maar die instant in verschillende tijdzones kan presenteren.
Intern is een Date in feite "milliseconden sinds de Unix epoch (1970-01-01T00:00:00Z)". Dat getal representeert een moment in UTC. De "verschuiving" ontstaat wanneer je JavaScript vraagt dat moment te formatteren als lokale tijd (gebaseerd op de instellingen van de computer/server) versus UTC.
Veel Date-API's hebben zowel lokale als UTC-varianten. Ze geven verschillende getallen voor dezelfde instant:
const d = new Date('2025-01-01T00:30:00Z');
d.getHours(); // uur in *lokale* tijdzone
d.getUTCHours(); // uur in UTC
d.toString(); // lokale tijdstring
d.toISOString(); // UTC (eindigt altijd met Z)
Als jouw machine in New York zit (UTC-5), kan die UTC-tijd lokaal als "19:30" op de vorige dag verschijnen. Op een server ingesteld op UTC verschijnt het als "00:30". Zelfde instant, andere weergave.
Logs gebruiken vaak Date#toString() of interpoleren een Date impliciet, wat de lokale tijdzone van de omgeving gebruikt. Dat betekent dat dezelfde code andere timestamps kan afdrukken op jouw laptop, in CI en in productie.
Sla en verstuur tijd in UTC (bijv. epoch milliseconden of ISO 8601 met Z). Converteer naar de gebruiker zijn locale alleen bij weergave:
toISOString() of verstuur epoch millisecondenIntl.DateTimeFormatAls je snel een app bouwt (bijvoorbeeld met een vibe-coding workflow in Koder.ai), helpt het dit vroeg in je gegenereerde API-contracten in te bakken: benoem velden duidelijk (createdAtMs, createdAtIso) en houd server (Go + PostgreSQL) en client (React) consistent over wat elk veld representeert.
Als je datums/tijden tussen browser, server en database moet sturen, zijn ISO 8601-strings de veiligste standaard. Ze zijn expliciet, breed ondersteund en dragen (belangrijk) tijdzone-informatie.
Twee goede uitwisselingsformaten:
2025-03-04T12:30:00Z2025-03-04T12:30:00+02:00Wat betekent “Z”?
Z staat voor Zulu time, een andere naam voor UTC. Dus 2025-03-04T12:30:00Z is "12:30 UTC."
Wanneer zijn offsets zoals +02:00 belangrijk?
Offsets zijn cruciaal wanneer een event aan een lokale context is gebonden (afspraken, boekingen, winkeltijden). 2025-03-04T12:30:00+02:00 beschrijft een moment dat twee uur voorloopt op UTC, en het is niet hetzelfde instant als 2025-03-04T12:30:00Z.
Strings zoals 03/04/2025 zijn een valkuil: is het 4 maart of 3 april? Verschillende gebruikers en omgevingen interpreteren het verschillend. Geef de voorkeur aan 2025-03-04 (ISO-datum) of een volledige ISO-datetime.
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
Dat "round-trip"-gedrag is precies wat je wilt voor API's: consistent, voorspelbaar en tijdzonebewust.
Date.parse() voelt handig: geef het een string, krijg een timestamp. Het probleem is dat voor alles wat geen duidelijke ISO 8601 is, parsing kan vertrouwen op browserheuristieken. Die heuristieken verschillen tussen engines en versies, wat betekent dat dezelfde input anders (of helemaal niet) kan parsen afhankelijk van waar je code draait.
Date.parse() kan variërenJavaScript standaardiseert parsing betrouwbaar alleen voor ISO 8601-achtige strings (en zelfs dan kunnen details zoals tijdzone belangrijk zijn). Voor "vriendelijke" formaten — zoals "03/04/2025", "March 4, 2025" of "2025-3-4" — kunnen browsers interpreteren:
Als je de exacte stringvorm niet kunt voorspellen, kun je het resultaat niet voorspellen.
YYYY-MM-DDEen veelgemaakte valkuil is de kale datumvorm "YYYY-MM-DD" (bijv. "2025-01-15"). Veel ontwikkelaars verwachten dat dit als lokale middernacht wordt geïnterpreteerd. In de praktijk behandelen sommige omgevingen dit als UTC-middernacht.
Dat verschil doet ertoe: UTC-middernacht omgezet naar lokale tijd kan de vorige dag worden in negatieve tijdzones (bv. Amerika) of het uur onverwacht verschuiven. Het is een makkelijke manier om "waarom is mijn datum een dag af?"-bugs te krijgen.
Voor server/API-input:
2025-01-15T13:45:00Z of 2025-01-15T13:45:00+02:00."YYYY-MM-DD") en converteer het niet naar een Date tenzij je ook de bedoelde tijdzone definieert.Voor gebruikersinput:
03/04/2025 tenzij je UI de betekenis afdwingt.In plaats van Date.parse() te laten "uitzoeken", kies een van deze patronen:
new Date(year, monthIndex, day) voor lokale data).Wanneer tijddata kritisch is, is "het parse op mijn machine" niet genoeg — maak je parse-regels expliciet en consistent.
Als je doel is "een datum/tijd tonen zoals mensen verwachten", is het beste instrument in JavaScript Intl.DateTimeFormat. Het gebruikt de lokale regels van de gebruiker (volgorde, scheidingstekens, maandnamen) en vermijdt de breekbare aanpak van zelf strings aan elkaar plakken zoals month + '/' + day.
Handmatige formatting hard-codeert vaak US-stijl output, vergeet voorloopnullen, of produceert verwarrende 24/12-uursresultaten. Intl.DateTimeFormat maakt ook expliciet in welke tijdzone je toont — cruciaal wanneer je data in UTC is opgeslagen maar de UI de lokale tijd van de gebruiker moet tonen.
Voor "gewoon mooi formatteren", zijn dateStyle en timeStyle het eenvoudigst:
const d = new Date('2025-01-05T16:30:00Z');
// Locale van de gebruiker + lokale tijdzone van de gebruiker
console.log(new Intl.DateTimeFormat(undefined, {
dateStyle: 'medium',
timeStyle: 'short'
}).format(d));
// Forceer een specifieke tijdzone (handig voor event-tijden)
console.log(new Intl.DateTimeFormat('en-GB', {
dateStyle: 'full',
timeStyle: 'short',
timeZone: 'UTC'
}).format(d));
Als je consistente uurcycli nodig hebt (bijv. een voorkeurinstelling), gebruik hour12:
console.log(new Intl.DateTimeFormat('en-US', {
hour: 'numeric',
minute: '2-digit',
hour12: true
}).format(d));
Kies één formatteringsfunctie per "type" timestamp in je UI (berichttijd, log-entry, event-start), en houd de timeZone-beslissing bewust:
Dat geeft consistente, locale-vriendelijke output zonder een fragiele set custom formatstrings te onderhouden.
Zomertijd (DST) is wanneer een tijdzone haar UTC-offset wijzigt (meestal met één uur) op specifieke datums. Het lastige is: DST verandert niet alleen de offset — het verandert het bestaan van bepaalde lokale tijden.
Wanneer de klok vooruit wordt gezet, gebeurt een reeks lokale tijden nooit. Bijvoorbeeld: de klok springt van 01:59 naar 03:00, dus 02:30 lokale tijd bestaat niet.
Wanneer de klok achteruit wordt gezet, gebeurt een reeks lokale tijden twee keer. Bijvoorbeeld, 01:30 kan één keer voor de verschuiving en één keer erna voorkomen; dezelfde kloktijd kan dus naar twee verschillende instants verwijzen.
Deze zijn niet gelijk rond DST-grenzen:
Als DST vanavond ingaat, kan "morgen om 9:00" slechts 23 uur weg zijn. Als DST vanavond eindigt, kan het 25 uur zijn.
// Scenario: schedule “same local time tomorrow”
const d = new Date(2025, 2, 8, 9, 0); // Mar 8, 9:00 lokale tijd
const plus24h = new Date(d.getTime() + 24 * 60 * 60 * 1000);
const nextDaySameLocal = new Date(d);
nextDaySameLocal.setDate(d.getDate() + 1);
// Rond DST kunnen plus24h en nextDaySameLocal 1 uur verschillen.
setHours je kan verrassenAls je iets doet als date.setHours(2, 30, 0, 0) op een dag dat de klok vooruit gaat, kan JavaScript het normaliseren naar een andere geldige tijd (vaak 03:30), omdat 02:30 niet bestaat in lokale tijd.
setDate) gebruiken in plaats van milliseconden optellen.Z zodat het instant eenduidig is.Een veelvoorkomende bug is Date gebruiken om iets te representeren dat geen kalendermoment is.
Een timestamp antwoordt op "wanneer gebeurde dit?" (een specifiek instant zoals 2025-12-23T10:00:00Z). Een duur antwoordt op "hoe lang?" (bijv. "3 minuten 12 seconden"). Dit zijn verschillende concepten, en het mixen ervan leidt tot verwarrende rekenkunde en onverwachte tijdzone-/DST-effecten.
Date het verkeerde gereedschap is voor duurDate representeert altijd een punt op de tijdlijn relatief aan een epoch. Als je "90 seconden" opslaat als een Date, sla je eigenlijk "1970-01-01 plus 90 seconden" op in een bepaalde tijdzone. Formatteren kan dan plots 01:01:30 tonen, een uur opschuiven of een datum tonen die je nooit bedoelde.
Voor duur geef de voorkeur aan platte nummers:
HH:mm:ss converterenEen eenvoudige formatter die werkt voor countdown-timers en mediaspeler-duur:
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)
Als je van minuten converteert, vermenigvuldig eerst (minutes * 60) en houd de waarde numeriek totdat je het rendert.
Wanneer je tijden in JavaScript vergelijkt, is de veiligste aanpak om nummers te vergelijken, niet geformatteerde tekst. Een Date-object is in essentie een wrapper rond een numerieke timestamp (epoch milliseconden), dus wil je dat vergelijkingen uiteindelijk "nummer vs nummer" worden.
Gebruik getTime() (of Date.valueOf(), wat hetzelfde getal teruggeeft) om betrouwbaar te vergelijken:
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
}
// Werkt ook:
if (+a < +b) {
// unary + calls valueOf()
}
Vermijd het vergelijken van geformatteerde strings zoals "1/10/2025, 12:00 PM" — deze zijn locale-afhankelijk en sorteren niet correct. De belangrijkste uitzondering zijn ISO 8601-strings in hetzelfde formaat en dezelfde tijdzone (bv. allemaal ...Z), die lexicografisch sorteervriendelijk zijn.
Sorteren op tijd is eenvoudig als je sorteert op epoch milliseconden:
items.sort((x, y) => new Date(x.createdAt).getTime() - new Date(y.createdAt).getTime());
Filteren binnen een bereik is hetzelfde idee:
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;
});
"Begin van de dag" hangt af van of je lokale tijd of UTC bedoelt:
// Lokale begin/einde van de dag
const d = new Date(2025, 0, 10); // 10 jan in lokale tijd
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 begin/einde van de dag
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));
Kies één definitie vroeg en houd je eraan in vergelijkingen en range-logica.
Tijdbugs voelen willekeurig totdat je vaststelt wat je hebt (timestamp? string? Date?) en waar de verschuiving wordt geïntroduceerd (parsing, tijdzoneconversie, formattering).
Begin met het loggen van dezelfde waarde op drie manieren. Dat onthult snel of het probleem seconden vs milliseconden is, lokaal vs UTC, of string-parsing.
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());
Waar je op moet letten:
toISOString() erg afwijkt (bv. jaar 1970 of ver in de toekomst), verdenk seconden vs milliseconden.toISOString() er goed uitziet maar toString() "verschoven" is, zie je een lokale tijdzone-weergave-probleem.getTimezoneOffset() verandert afhankelijk van de datum, kruis je een zomertijd.Veel "het werkt op mijn machine"-meldingen zijn gewoon verschillende omgevingsinstellingen.
console.log(Intl.DateTimeFormat().resolvedOptions());
console.log('TZ:', process.env.TZ);
console.log(Intl.DateTimeFormat().resolvedOptions().timeZone);
Als je server op UTC draait maar je laptop in een lokale zone, zal geformatteerde output verschillen tenzij je expliciet timeZone opgeeft.
Maak unittests rond DST-grenzen en "edge"-tijden:
23:30 → 00:30 crossoversAls je snel iterereert, overweeg deze tests onderdeel te maken van je scaffolding. Bijvoorbeeld: bij het genereren van een React + Go app in Koder.ai kun je een kleine "time contract" testsuite toevoegen (API payload voorbeelden + parsing/formatting assertions) zodat regressies vroeg worden ontdekt.
"2025-03-02 10:00".locale en (wanneer nodig) timeZone.Betrouwbare tijdafhandeling in JavaScript draait vooral om het kiezen van een "source of truth" en consistent zijn van opslag tot weergave.
Sla en bereken in UTC. Behandel gebruikersgerichte lokale tijd als presentatie-detail.
Transmiteer datums tussen systemen als ISO 8601-strings met een expliciete offset (bij voorkeur Z). Als je numerieke epochs moet sturen, documenteer de eenheid en houd het consistent (milliseconden is de gebruikelijke default in JS).
Formatteer voor mensen met Intl.DateTimeFormat (of toLocaleString) en geef een expliciete timeZone mee wanneer je deterministische output nodig hebt (bijv. altijd in UTC of een specifieke bedrijfsregio).
Z (bv. 2025-12-23T10:15:00Z). Als je epochs gebruikt, gebruik veldnamen zoals createdAtMs om de eenheden duidelijk te maken.Overweeg een dedicated date-time library als je recurrerende events, complexe tijdzone-regels, DST-veilige rekenkunde ("zelfde lokale tijd morgen") of veel parsing van inconsistente inputs nodig hebt. De waarde zit in duidelijkere API's en minder edge-case bugs.
Als je dieper wilt gaan, bekijk meer tijd-gerelateerde gidsen op /blog. Als je tooling of support-opties evalueert, zie /pricing.