Back to News & Insights
Web Development October 15, 2026 · 10 min read

Node 26 Ships Temporal by Default. A Carbon User's Guide to Dates in TypeScript

Node 26 runs Temporal, no flag. Your daily Carbon and DateTimeImmutable habits beside their TypeScript twins, plus two PHP reflexes that break.

Node 26 Ships Temporal by Default. A Carbon User's Guide to Dates in TypeScript

Book: PHP to TypeScript Also by me: The TypeScript Library — the 5-book collection My project: Hermes IDE | GitHub — an IDE for developers who ship with Claude Code and other AI coding tools Me: xgabriel.com | GitHub

You write new Date(2026, 9, 25) for September 25 in your first Node service and get October 25, because the Date constructor counts months from zero. Then you add a day by calling setDate(), which changes the object in place, in whatever time zone the server happens to run in. If you came from PHP with Carbon in your fingers, this is the moment you start missing it.

You can stop missing it. Temporal reached Stage 4 at TC39's 113th plenary in March 2026, and Node.js 26.0.0, released on 2026-05-05, turns it on by default. No flag, no polyfill. Node 26 is still the "Current" line today, and the Node.js release schedule plans its move to Active LTS for 2026-10-28, a common point for teams to upgrade.

Almost every Carbon habit has a direct Temporal twin. Two of your reflexes don't, and they get their own section at the end. Below, each PHP snippet sits next to the TypeScript that does the same job. Run the PHP with php and the TypeScript with node.

Node 26 runs .ts files directly. Type stripping has been on by default since v23.6.0 and stable since v25.2.0, so node dates.ts works as long as you stick to erasable syntax (no enum, no parameter properties). Node does no type checking at run time; your editor and tsc do.

For the types, TypeScript 6.0 ships built-in Temporal declarations. Enable them with esnext or the narrower esnext.temporal lib. The same release changes the default types to [], so @types/node (and with it console) is no longer picked up on its own. Install it with npm i -D @types/node and list it:

erasableSyntaxOnly makes tsc reject the syntax Node can't strip, so the "no enum" rule is enforced for you.

On the PHP side, the snippets use the built-in DateTimeImmutable and Carbon 3 (composer require nesbot/carbon). If you are still on Node 24, the @js-temporal/polyfill package provides the same API, with two caveats: it does not install a global, so add import { Temporal } from '@js-temporal/polyfill'; at the top of each file, and its README still marks it as not yet production-ready.

PHP has no date-only type. new DateTimeImmutable('2026-10-15') is midnight in your default time zone, and Carbon extends DateTime, so it carries the same baggage. Temporal splits the concepts: PlainDate is a calendar date with no time and no zone, which is what a birthday, an invoice due date or a holiday actually is.

Month ends are where the two part ways. PHP lets day numbers overflow into the next month: "2008-06-31" means July 1. So January 31 plus one month becomes February 31, which rolls over to March 3 in 2026. Temporal's default is to clamp to the last valid day instead.

Pass { overflow: 'reject' } as the second argument and Temporal throws a RangeError rather than clamping. from() takes the same option, and that is the one you want for anything a user typed.

Parsing is the other big difference. createFromFormat() has no Temporal twin: PlainDate.from() takes RFC 9557 strings (ISO-style 2026-10-15) or an object. For a d/m/Y string, split it yourself and hand Temporal the parts:

Note the ! in the PHP format. Without it, every field you don't parse is filled from the current system time, so your "date" quietly carries the time the request ran. A PlainDate has no time to leak.

In 2026, Europe falls back on Sunday, October 25, and the US on Sunday, November 1. That makes October 25 in Berlin a 25-hour day, and it is the classic place where "add one day" and "add 24 hours" stop meaning the same thing.

PHP has both behaviours, spread across two methods that look alike: modify('+1 day') keeps the wall-clock time, while add(new DateInterval('PT24H')) adds elapsed time. The PHP manual's arithmetic examples walk through the same split between modify() and add():

Temporal has one rule, and it is easy to remember. Days use calendar arithmetic and keep the clock time; hours use exact elapsed time:

The zone lives inside the value as a bracketed IANA name, so the result never depends on what TZ the container was started with. hoursInDay is a nice bonus for billing or shift code that needs to know how long "today" is.

Time zone conversion is setTimezone() in PHP and withTimeZone() in Temporal. Both keep the instant and change the zone:

Want to discuss this further?

Book a free strategy call with our team to see how these insights apply to your specific business goals.

Book a consultation