Het vastleggen van chronologische gegevens die hetzelfde moeten blijven, ongeacht de locatie van de kijker (zoals geboortedatums, dagelijkse wekkers, vervaldatums van creditcards of maandelijkse betaalcycli), was in het verleden foutgevoelig met het verouderde Date-object. Omdat een Date altijd een specifiek moment in de tijd vertegenwoordigt en wordt getoond in de lokale tijdzone van de kijker, kan het opslaan van een datum zoals "1990-01-01" ertoe leiden dat gebruikers ten westen van UTC "1989-12-31" zien. Als u gedeeltelijke datums modelleert (zoals een kaart die alleen een maand verloopt), worden er willekeurige tijdelijke aanduidingen voor dagen gebruikt die overloopfouten veroorzaken.
De Temporal API biedt een reeks locatie-onafhankelijke 'Plain'-typen die geen tijdzoneverschil hebben en kalenderdatums en kloktijden vertegenwoordigen zoals ze op een kalender of klok worden afgelezen:
| Type | Wat het modelleert | Typische use cases |
|---|---|---|
Temporal.PlainDate |
Volledige kalenderdatum (year, month, day) |
Geboortedatums, evenementdatums, factureringsdatums voor abonnementen |
Temporal.PlainTime |
Tijd op de klok (hour, minute, second, ...) |
Dagelijkse wekkers (09:00 uur, waar de gebruiker zich ook bevindt), openingstijden van winkels |
Temporal.PlainDateTime |
Kalenderdatum + kloktijd zonder tijdzone | Zwevende lokale schema's, waarbij een PlainDate en PlainTime worden gecombineerd voordat een zone wordt gekoppeld |
Temporal.PlainMonthDay |
Maand en dag zonder jaar | Terugkerende jaarlijkse verjaardagen, jubileums, vaste feestdagen |
Temporal.PlainYearMonth |
Jaar en maand zonder dag | Vervaldatums van creditcards (2027-12), maandelijkse factureringsperioden |
Implementeren
- Kies het meest specifieke type
Temporal.Plain*dat overeenkomt met uw gegevens:- Gebruik
Temporal.PlainDate.from()voor volledige kalenderdatums enTemporal.PlainTime.from()voor kloktijden. - Gebruik
Temporal.PlainDateTime.from()(ofplainDate.toPlainDateTime(plainTime)) als je een kalenderdatum en een tijd combineert zonder deze aan een tijdzone te binden. - Gebruik
Temporal.PlainMonthDay.from()voor jaarlijkse datums zonder jaar en converteer naar eenPlainDatemet.toPlainDate({ year })voordat je datumberekeningen uitvoert. - Gebruik
Temporal.PlainYearMonth.from()voor jaar-en-maandwaarden, zodat willekeurige dagnummers nooit in berekeningen terechtkomen.
- Gebruik
- Let op de parameter
calendarals je Plain-typen maakt: ISO 8601 ('iso8601') is de standaardkalender als je ISO-tekenreeksen parseert of maakt metPlainDate.from({ year, month, day }). Als jePlainMonthDayofPlainYearMonthmaakt op basis van property bags of als je met niet-ISO-kalenders werkt, geef jecalendarexpliciet door (bijvoorbeeld{ month: 10, day: 31, calendar: 'iso8601' }). - Gebruik
.add()metoverflow-opties voor terugkerende kalenderintervallen: Als jeplainDate.add({ months: 1 })aanroept, wordt standaard{ overflow: 'constrain' }gebruikt. Datums aan het einde van de maand, zoals 31 januari, worden dan vastgezet op 28/29 februari. Geef{ overflow: 'reject' }door als ongeldige doeldatums eenRangeErrormoeten opleveren in plaats van vastzetten.
Voorbeeld: Volledige datums, kloktijden en PlainDateTime
// 1. Parse a calendar date and a wall-clock time from input strings
const birthdate = Temporal.PlainDate.from('1990-01-01');
const reminderTime = Temporal.PlainTime.from('09:00');
// Combine into a location-agnostic PlainDateTime without string interpolation
const localAppointment = birthdate.toPlainDateTime(reminderTime);
// 2. Display without time zone conversion; outputs the same date in any time zone
console.log(birthdate.toLocaleString('en-GB'));
console.log(localAppointment.toString()); // "1990-01-01T09:00:00"
// 3. Read calendar fields directly from the Plain type. A PlainDate already *is*
// a calendar date, so there is no need for the formatToParts() workaround that
// legacy Date code uses to recover year/month/day from an instant.
const { year, month, day } = birthdate;
Voorbeeld: Gedeeltelijke datums (PlainMonthDay en PlainYearMonth)
// Annual recurring event (no year component)
// Include explicit calendar when constructing from an object
const annualHoliday = Temporal.PlainMonthDay.from({
month: 10,
day: 31,
calendar: 'iso8601',
});
const today = Temporal.Now.plainDateISO();
const isToday = annualHoliday.equals(today.toPlainMonthDay());
// Convert PlainMonthDay to a PlainDate with a specific year before doing arithmetic,
// because month lengths (such as February) depend on whether the year is a leap year.
const occurrenceThisYear = annualHoliday.toPlainDate({ year: today.year });
// Monthly expiration (no day component, preventing day-of-month overflow bugs)
const cardExpiry = Temporal.PlainYearMonth.from({
year: 2027,
month: 12,
calendar: 'iso8601',
});
const currentMonth = today.toPlainYearMonth();
const isExpired = Temporal.PlainYearMonth.compare(cardExpiry, currentMonth) < 0;
Voorbeeld: Terugkerende maandelijkse intervallen (overflow: 'constrain' vs. 'reject')
const billingStart = Temporal.PlainDate.from('2024-01-31');
// Default overflow: 'constrain' clamps Jan 31 + 1 month to the last valid day of February
const nextBilling = billingStart.add({ months: 1 }, { overflow: 'constrain' });
console.log(nextBilling.toString()); // "2024-02-29" (2024 is a leap year)
// Use overflow: 'reject' to throw a RangeError when the target day does not exist
try {
billingStart.add({ months: 1 }, { overflow: 'reject' });
} catch (err) {
// RangeError: day 31 does not exist in February
}
Voorbeeld: Samenwerken met verouderde Date
Een verouderde Date is een tijdstempel (een moment), geen kalenderdatum. Als je "1990-01-01" opslaat als Date, moet je een tijdstip kiezen (meestal middernacht UTC). Als je dit terugleest in een tijdzone ten westen van UTC, krijg je "31/12/1989". Als een bibliotheek of API nog steeds een Date vereist, converteer je expliciet via Temporal.Instant in plaats van handmatig ISO-tekenreeksen te maken:
// PlainDate -> Date: attach a time zone (time of day defaults to the start of
// that day) to get an Instant, then hand the epoch milliseconds to Date.
// 'UTC' is a deliberate choice and must be documented; any zone would do,
// but the stored instant depends on it.
const instant = birthdate.toZonedDateTime('UTC').toInstant();
const legacyDate = new Date(instant.epochMilliseconds);
// Date -> PlainDate: go back through Instant (legacyDate.toTemporalInstant()
// is equivalent), then project into an explicit time zone. The result depends
// on the zone, which is exactly the drift risk.
const viewedDate = Temporal.Instant.fromEpochMilliseconds(legacyDate.getTime())
.toZonedDateTimeISO('America/New_York')
.toPlainDate();
// Compare Plain types with .equals() (or Temporal.PlainDate.compare), not strings.
console.log(viewedDate.equals(birthdate)); // false: the calendar date drifted to 1989-12-31
Strategische implementatie en best practices
- Gebruik WEL
Temporal.PlainDatevoor volledige kalenderdatums,Temporal.PlainTimevoor kloktijden,Temporal.PlainDateTimevoor lokale datum-tijdparen zonder zone,Temporal.PlainMonthDayvoor terugkerende jaarlijkse datums enTemporal.PlainYearMonthvoor jaar-maand-vervaldatums of factureringsperioden. - Voer GEEN rekenkundige bewerkingen rechtstreeks uit op
Temporal.PlainMonthDay. Converteer deze eerst naar eenTemporal.PlainDatevia.toPlainDate({ year }), omdat de maandlengte per jaar verschilt. - Gebruik WEL
.add()opTemporal.PlainDatevoor terugkerende kalenderintervallen. Vertrouw opoverflow: 'constrain'(de standaardinstelling) om datums aan het einde van de maand vast te zetten of opoverflow: 'reject'om eenRangeErrorte genereren voor niet-bestaande datums. - WEL: Geef
calendar(bijv.calendar: 'iso8601') op als jePlainMonthDayof niet-ISO-typen zonder opmaak maakt op basis van objecten. - Gebruik GEEN gewone typen als u een specifiek moment in de fysieke tijd (een moment) wilt weergeven. Gebruik
Temporal.InstantofTemporal.ZonedDateTimevoor logboeken, tijdstempels van gebeurtenissen of planningen voor meerdere tijdzones. - Maak GEEN
Date-objecten op basis van handmatig gemaakte ISO-tekenreeksen (zoals`${date}T00:00:00Z`). Maak eenDateop basis van deepochMillisecondsvan eenTemporal.InstantofTemporal.ZonedDateTimeen lees er een terug metTemporal.Instant.fromEpochMilliseconds(date.getTime())ofdate.toTemporalInstant(), zodat de aanname van de tijdzone expliciet is. - Gebruik
Intl.DateTimeFormat.prototype.formatToParts()NIET om jaar-, maand- of dagwaarden te extraheren of te vergelijken. Lees.year,.monthen.dayrechtstreeks vanuit een Plain-type en vergelijk met.equals(). - WEL: Identificeer tijdzones met de IANA-naam (bijv.
America/New_York) en nooit met een vaste UTC-afwijking. Verschillen veranderen met de zomertijd en wetgeving, dus labels zoals"UTC-5"worden een deel van het jaar onjuist. - WEL onthouden dat
Temporal-objecten onveranderlijk zijn. Methoden zoals.add(),.subtract()of.with()retourneren altijd een nieuwe instantie met een nieuwe verwijzing in plaats van het oorspronkelijke object ter plekke te muteren. Hierdoor kunnen ze veilig rechtstreeks in de reactieve UI-status worden opgeslagen.
Reservestrategie
Fallbacks en browserondersteuning voor Temporal
Voor browsers die de native Temporal API nog niet ondersteunen, gebruik je functiedetectie en laad je temporal-polyfill dynamisch (~15 KB min+gz).
Houd er rekening mee dat als je temporal-polyfill importeert, het Temporal-object niet automatisch wordt toegewezen aan het globale bereik. Wijs de waarde expliciet toe als je code afhankelijk is van globalThis.Temporal.
// Check if Temporal is supported natively
(async () => {
if (typeof Temporal === 'undefined') {
// Load the polyfill conditionally
const module = await import('https://esm.sh/temporal-polyfill');
globalThis.Temporal = module.Temporal;
// Extend Date.prototype if needed
Date.prototype.toTemporalInstant = module.toTemporalInstant;
}
initializeApp();
})();