The JavaScript Temporal API is the long-awaited replacement for the broken Date object — it provides immutable date/time objects, correct timezone handling, calendar arithmetic, and duration comparisons that are impossible to get right with Date.
Problem: JavaScript's Date object is mutable, has inconsistent month indexing (0-based), unreliable timezone handling, and no built-in support for duration arithmetic or calendar systems — making date logic in WordPress admin SPAs error-prone.
Solution: Use the TC39 Temporal API (stage 3) — available behind a polyfill today via @js-temporal/polyfill. Use Temporal.PlainDate for calendar dates, Temporal.ZonedDateTime for timezone-aware instants, and Temporal.Duration for arithmetic. All objects are immutable and operations are explicit about calendar and timezone.
The examples below use Temporal.PlainDate for date-only operations, Temporal.ZonedDateTime for timezone-aware scheduling, and Temporal.Duration for relative time display in WordPress admin JavaScript.
// Temporal API — available natively in Chrome 127+ (behind flag earlier)
// Polyfill: npm install @js-temporal/polyfill
// PlainDate — date only, no time, no timezone
const today = Temporal.Now.plainDateISO(); // e.g., 2025-01-08
const published = Temporal.PlainDate.from('2024-12-15');
const daysSince = published.until(today).days; // exact day count
console.log(`Published ${daysSince} days ago`);
// Add/subtract durations (no DST surprises — pure calendar arithmetic)
const nextMonth = today.add({ months: 1 });
const weekAgo = today.subtract({ weeks: 1 });
// Compare dates
if ( Temporal.PlainDate.compare(published, today) < 0 ) {
console.log('Post is in the past');
}
// PlainDateTime — date + time, no timezone
const scheduledAt = Temporal.PlainDateTime.from('2025-01-15T14:30:00');
const formatted = scheduledAt.toLocaleString('en-US', {
dateStyle: 'medium',
timeStyle: 'short',
});
// ZonedDateTime — full timezone-aware datetime
const wpPostDate = Temporal.ZonedDateTime.from({
year: 2025, month: 1, day: 15,
hour: 14, minute: 30,
timeZone: 'America/New_York',
});
// Convert to UTC for API calls
const utcInstant = wpPostDate.toInstant();
console.log(utcInstant.toString()); // 2025-01-15T19:30:00Z
// Convert between timezones
const londonTime = wpPostDate.withTimeZone('Europe/London');
console.log(londonTime.toString());
Duration and relative time for WordPress admin UI:
// Duration — represents a span of time
const duration = Temporal.Duration.from({ hours: 2, minutes: 30 });
const inMinutes = duration.total('minutes'); // 150
// Calculate post age for "X time ago" display
function getRelativeTime( isoString ) {
const postDate = Temporal.Instant.from( isoString );
const now = Temporal.Now.instant();
const diff = now.since( postDate );
// Intl.RelativeTimeFormat gives human-readable output
const rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' });
const totalSeconds = Math.abs( diff.total('seconds') );
if ( totalSeconds < 60 ) return rtf.format( -Math.round(totalSeconds), 'second');
if ( totalSeconds < 3600 ) return rtf.format( -Math.round(totalSeconds / 60), 'minute');
if ( totalSeconds < 86400) return rtf.format( -Math.round(totalSeconds / 3600), 'hour');
return rtf.format( -Math.round(totalSeconds / 86400), 'day');
}
// Use in Gutenberg block
import { useSelect } from '@wordpress/data';
import { store as coreDataStore } from '@wordpress/core-data';
function PostAge({ postId }) {
const post = useSelect( select =>
select( coreDataStore ).getEntityRecord('postType', 'post', postId)
);
if ( !post?.date ) return null;
return { getRelativeTime(post.date) };
}
NOTE: The Temporal API polyfill (@js-temporal/polyfill) is production-ready and matches the Stage 3 spec. Avoid mixing Date and Temporal in the same module — convert at boundaries using Temporal.Instant.fromEpochMilliseconds(date.getTime()). The Temporal API is the first major JavaScript date library baked into the language itself.