Skip to content
native-dates

Your browser’s native dates in plain English

Temporal gave the browser real time zones, daylight saving and calendar math. Native Dates gives it an API to match: four nouns, verbs you can read aloud and values that never change under you, in 7.3 kB br.

npm i native-dates
You sayYou writeYou gettodaytoday()CalendarDateNov 11, 2026.next('monday')CalendarDateNov 16, 2026.at('9:30am', 'America/Los_Angeles')DateTimeNov 16, 2026, 9:30 AM PST.in('Europe/Berlin')DateTimeNov 16, 2026, 6:30 PM GMT+1
Each column is one thought. Turn a phrase, and everything after it follows.

Date gets these wrong. Temporal gets them right.

Four questions every app asks. Date has answered them this way since 1995, and your code has worked around it ever since. Native Dates hands each one to Temporal, and logs a value you can read without opening it.

What day is 2026-09-01 in New York?

Date reads a bare date as midnight in UTC, which is still the evening before in New York. A calendar date has no clock to shift, so it is September 1 everywhere.

Date
const day = new Date('2026-09-01');
day.toLocaleDateString('en-US', { timeZone: 'America/New_York' });

'8/31/2026' a day early

native-dates
date('2026-09-01');

CalendarDate {text: 'Sep 1, 2026', month: 9, day: 1, year: 2026, weekday: 2}

What is one month after January 31?

Date counts to February 31 and spills into March. Temporal stops at the last day February has.

Date
const end = new Date(2026, 0, 31);
end.setMonth(1);
end.toDateString();

'Tue Mar 03 2026' three days late

native-dates
date('2026-01-31').plus(1, 'month');

CalendarDate {text: 'Feb 28, 2026', month: 2, day: 28, year: 2026, weekday: 6}

It is 9 AM on Saturday in New York. What time is it one day later?

The clocks spring forward that night, so the day is 23 hours long. Outside its own zone, Date can only add 24 hours. Temporal adds a day and keeps the clock at nine.

Date
const nine = new Date('2026-03-07T09:00-05:00');
const next = new Date(nine.getTime() + 24 * 60 * 60 * 1000);
next.toLocaleTimeString('en-US', { timeZone: 'America/New_York' });

'10:00:00 AM' an hour late

native-dates
datetime('2026-03-07T09:00', 'America/New_York').plus(1, 'day');

DateTime {text: 'Mar 8, 2026, 9:00 AM EDT', month: 3, day: 8, year: 2026, weekday: 7, …}

The due date is 30 days after the start. What is the start now?

setDate() changes the date it is called on, and due is the same object as start. A Temporal value never changes, so plus() hands back a new one.

Date
const start = new Date(2026, 8, 1);
const due = start;
due.setDate(31);
start.toDateString();

'Thu Oct 01 2026' the start moved

native-dates
const start = date('2026-09-01');
const due = start.plus(30, 'days');
start;

CalendarDate {text: 'Sep 1, 2026', month: 9, day: 1, year: 2026, weekday: 2}

Every zone and every clock change, already in your browser

Los Angeles and Berlin change their clocks on different weekends, so the hours between them change 4 times a year. Date knows two zones, yours and UTC. Temporal knows every zone on every date, so a standup set in one city lands at the right time in the other.

Los Angeles9:30 AM
9 hours apart
Berlin6:30 PM
Mar 8Mar 29Oct 25Nov 1Thu, Jan 1
A 9:30 standup in Los Angeles, read in Berlin on every day of 2026. On 28 days the two cities are 8 hours apart instead of 9, and it starts at 5:30 PM.

Code you can read aloud

Every call is one word for one thought, in the order you would say it. Switch to Temporal to read the same dates written against the platform.

  • One word per thought next('monday') finds the day, and at() gives it a time and a place. On Temporal that is weekday arithmetic and an options bag.
  • A zone is a view in() reads the same moment on another clock, and format('full') writes it the way the reader’s locale does.
  • Calendar math by name endOf('month') is the 28th, the 30th or the 31st, without asking how long the month is.
  • Durations in words until() reads both moments in one zone, and format() prints the wait in the reader’s language.
import { now, today } from 'native-dates';

!!const standup = today().next('monday').at('9:30am', 'America/Los_Angeles');
!!const invite = standup.in('Europe/Berlin').format('full');

!!const due = today().plus(1, 'month').endOf('month');
const notice = due.format('long');

!!const wait = now().until(standup).round('minute').format();

Run it as if you were in Tokyo

A small app, built and run in your browser. Pick a zone in the preview’s bar and it runs as it would there. Change the code and it rebuilds as you type.

import { tomorrow } from 'native-dates';

import { CITIES } from './cities.js';

const input = document.querySelector('input');
const summary = document.querySelector('#summary');
const list = document.querySelector('ul');

function render() {
  // tomorrow, at the time typed, where this app is running
  const meeting = tomorrow().at(input.value);
  summary.textContent = meeting.format('full');

  list.innerHTML = CITIES.map((zone) => {
    const there = meeting.in(zone);
    const sameDay = there.date.equals(meeting.date);
    return `<li>
      <code>${zone}</code>
      <b>${there.format('time')}</b>
      <span class="muted">${sameDay ? '' : there.format({ weekday: 'long' })}</span>
    </li>`;
  }).join('');
}

input.addEventListener('input', render);
render();

Starting…

See all examples

The whole core is 7.3 kB

Four nouns, every verb, formatting and zones, with no dependencies. A plugin adds one more thing when you import it. Switch them on to see what your app would ship.

7.3 kBmin+br
Browse all plugins

Start with now()

now().text

npm i native-dates
Get started