<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged temporal</title>
<link>https://coderlegion.com/tag/temporal</link>
<description>Powered by Question2Answer</description>
<item>
<title>Goodbye Date: Why JavaScript is finally getting dates right.</title>
<link>https://coderlegion.com/29825/goodbye-date-why-javascript-is-finally-getting-dates-right</link>
<description>&lt;p&gt;If you've written JavaScript for a while, you've probably fought with &lt;code&gt;Date&lt;/code&gt; at some point. It's one of the oldest parts of the language, barely changed since 1995, and honestly, it shows. Let's look at why it causes so much pain and how the new &lt;code&gt;Temporal&lt;/code&gt; API fixes it, with real, tested examples.&lt;/p&gt;
&lt;h5 id=&quot;date-is-mutable-and-that-bites-people-constantly-&quot;&gt;&lt;code&gt;Date&lt;/code&gt; is mutable, and that bites people constantly.&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;Date&lt;/code&gt; objects can be changed in place. That sounds fine until you pass one around your app and something quietly changes it behind your back.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const meeting = new Date('2024-03-01');
const followUp = meeting;
followUp.setMonth(followUp.getMonth() + 1);

console.log(meeting.toDateString()); // &quot;Mon Apr 01 2024&quot; (wait, the original changed too)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;You never touched &lt;code&gt;meeting&lt;/code&gt; directly. But &lt;code&gt;followUp&lt;/code&gt; is just a reference to the same object, so changing one changes both. This is a really easy mistake to make, and it tends to show up in bigger codebases where a date gets passed through a few layers of functions.&lt;/p&gt;
&lt;h5 id=&quot;adding-months-doesn-t-do-what-you-d-expect-&quot;&gt;Adding months doesn't do what you'd expect.&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const jan31 = new Date(2024, 0, 31); // Jan 31, 2024
jan31.setMonth(jan31.getMonth() + 1);

console.log(jan31.toDateString()); // &quot;Sat Mar 02 2024&quot; (not February at all)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;February only has 29 days that year, so &lt;code&gt;Date&lt;/code&gt; just overflows into March instead of clamping to the end of the month. If you're building anything with recurring billing or subscription renewals, this kind of bug is waiting for you, usually right around the 29th, 30th, or 31st.&lt;/p&gt;
&lt;h5 id=&quot;parsing-strings-is-a-bit-of-a-gamble-&quot;&gt;Parsing strings is a bit of a gamble.&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;console.log(new Date('01/02/2024').toDateString());
// &quot;Tue Jan 02 2024&quot; (read as MM/DD/YYYY)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Is that January 2nd or February 1st? Depends on the browser, the locale, and the exact string. ISO strings like &lt;code&gt;2024-01-02&lt;/code&gt; are safer, but plenty of real input in the wild isn't in ISO format, and &lt;code&gt;Date&lt;/code&gt; doesn't give you a good way to say &quot;parse this the way I mean.&quot;&lt;/p&gt;
&lt;h5 id=&quot;it-mixes-up-three-different-ideas-&quot;&gt;It mixes up three different ideas.&lt;/h5&gt;
&lt;p&gt;&lt;code&gt;Date&lt;/code&gt; treats these three things as if they were the same thing:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;a calendar date, like &quot;March 1st&quot;&lt;/li&gt;
&lt;li&gt;a wall-clock time, like &quot;3:00 PM&quot;&lt;/li&gt;
&lt;li&gt;an exact moment in time, tied to a specific place&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A birthday doesn't have a timezone. A 3 PM meeting only makes sense once you know whose 3 PM you mean. But under the hood, &lt;code&gt;Date&lt;/code&gt; always stores a single number (milliseconds since the Unix epoch) and quietly converts through whatever timezone your machine happens to be running in. That's a pretty easy way to introduce a bug that only shows up once your app hits a server in a different timezone.&lt;/p&gt;
&lt;p&gt;And speaking of timezones, &lt;code&gt;Date&lt;/code&gt; doesn't really support them. You get UTC, and you get &quot;whatever timezone this computer is set to,&quot; and nothing in between. Anything more than that means reaching for a library like &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/luxon&quot;&gt;&lt;code&gt;luxon&lt;/code&gt;&lt;/a&gt; or &lt;a rel=&quot;nofollow&quot; href=&quot;https://www.npmjs.com/package/@date-fns/tz&quot;&gt;&lt;code&gt;date-fns-tz&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;h4 id=&quot;so-what-s-temporal-&quot;&gt;So what's Temporal?&lt;/h4&gt;
&lt;p&gt;&lt;a rel=&quot;nofollow&quot; href=&quot;https://tc39.es/proposal-temporal/docs/&quot;&gt;&lt;code&gt;Temporal&lt;/code&gt;&lt;/a&gt; is a modern date and time API for JavaScript, built to replace &lt;code&gt;Date&lt;/code&gt; outright. It has officially reached &lt;strong&gt;TC39 Stage 4&lt;/strong&gt;, making it a permanent part of the standard ECMAScript specification. It is now supported natively across major modern runtimes, including &lt;code&gt;Chrome&lt;/code&gt;, &lt;code&gt;Firefox&lt;/code&gt;, and &lt;code&gt;Node.js&lt;/code&gt;. You can still ensure full cross-environment compatibility today by using production-ready packages like &lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/js-temporal/temporal-polyfill&quot;&gt;&lt;code&gt;@js-temporal/polyfill&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The big idea is that instead of one object trying to be everything, you get separate types for separate concepts:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Temporal.PlainDate&lt;/code&gt;: just a calendar date, no time, no timezone&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Temporal.PlainTime&lt;/code&gt;: just a time, no date&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Temporal.PlainDateTime&lt;/code&gt;: date and time together, still no timezone&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Temporal.ZonedDateTime&lt;/code&gt;: an exact moment, tied to a real timezone&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Temporal.Instant&lt;/code&gt;: an exact moment on the UTC timeline&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Temporal.Duration&lt;/code&gt;: a length of time, like &quot;2 hours 30 minutes&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Let's see how this actually plays out.&lt;/p&gt;
&lt;h5 id=&quot;it-doesn-t-let-you-mutate-by-accident-&quot;&gt;It doesn't let you mutate by accident.&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const meeting = Temporal.PlainDate.from('2024-03-01');
const followUp = meeting.add({ months: 1 });

console.log(meeting.toString());   // &quot;2024-03-01&quot; (untouched)
console.log(followUp.toString());  // &quot;2024-04-01&quot; (a brand new object)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;add()&lt;/code&gt; always hands back a new value. The original is never touched, so there's nothing to accidentally break further down the call stack.&lt;/p&gt;
&lt;h5 id=&quot;month-math-actually-makes-sense-&quot;&gt;Month math actually makes sense.&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const jan31 = Temporal.PlainDate.from('2024-01-31');
const nextMonth = jan31.add({ months: 1 });

console.log(nextMonth.toString()); // &quot;2024-02-29&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Since February doesn't have a 31st, Temporal clamps to the last real day of that month instead of spilling over into March. (2024 is a leap year, so it lands on the 29th rather than the 28th, nice.)&lt;/p&gt;
&lt;h5 id=&quot;parsing-is-boring-in-a-good-way-&quot;&gt;Parsing is boring, in a good way.&lt;/h5&gt;
&lt;p&gt;Temporal only parses ISO 8601 by default, so there's no guessing between MM/DD and DD/MM:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const date = Temporal.PlainDate.from('2024-01-02');
console.log(date.toString()); // &quot;2024-01-02&quot; (always year-month-day)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;If you need to handle something like &lt;code&gt;&quot;1 Feb 2024&quot;&lt;/code&gt;, you convert it explicitly yourself, rather than hoping the engine guesses right.&lt;/p&gt;
&lt;h5 id=&quot;timezones-are-handled-properly-dst-and-all-&quot;&gt;Timezones are handled properly, DST and all.&lt;/h5&gt;
&lt;p&gt;This is probably the best part. Here's a time in New York, right as Daylight Saving kicks in:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const beforeDST = Temporal.ZonedDateTime.from(
  '2024-03-10T01:30:00[America/New_York]'
);
console.log(beforeDST.toString());
// &quot;2024-03-10T01:30:00-05:00[America/New_York]&quot;

const oneHourLater = beforeDST.add({ hours: 1 });
console.log(oneHourLater.toString());
// &quot;2024-03-10T03:30:00-04:00[America/New_York]&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;A couple of things happen here: the offset flips from &lt;code&gt;-05:00&lt;/code&gt; to &lt;code&gt;-04:00&lt;/code&gt; on its own, and the clock jumps from 1:30 to 3:30 (a full two hours) because 2:00 to 2:59 simply doesn't exist that morning in New York. Temporal knows that and handles it correctly by default. With plain &lt;code&gt;Date&lt;/code&gt;, getting this right takes careful manual offset math; here it just works.&lt;/p&gt;
&lt;h5 id=&quot;durations-aren-t-an-afterthought-either-&quot;&gt;Durations aren't an afterthought either.&lt;/h5&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;const projectStart = Temporal.PlainDate.from('2024-06-01');
const projectEnd = Temporal.PlainDate.from('2024-06-15');

const span = projectStart.until(projectEnd);
console.log(span.days); // 14

const deadline = Temporal.Duration.from({ hours: 26 });
console.log(deadline.toString()); // &quot;PT26H&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;until()&lt;/code&gt; gives you back a real &lt;code&gt;Duration&lt;/code&gt; object, and you can ask it for &lt;code&gt;.days&lt;/code&gt;, &lt;code&gt;.hours&lt;/code&gt;, or a total in whatever unit you want, no dividing by 86400000 by hand.&lt;/p&gt;
&lt;h4 id=&quot;should-you-actually-use-it-right-now-&quot;&gt;Should you actually use it right now?&lt;/h4&gt;
&lt;p&gt;Browser support is rolling out, but it's not everywhere yet, so for now you'll probably want the polyfill:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;npm install @js-temporal/polyfill
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-js&quot;&gt;import { Temporal } from '@js-temporal/polyfill';
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Worth checking &lt;a rel=&quot;nofollow&quot; href=&quot;https://caniuse.com/temporal&quot;&gt;caniuse.com/temporal&lt;/a&gt; before you decide whether you need it for your target environment.&lt;/p&gt;
&lt;h4 id=&quot;bottom-line&quot;&gt;Bottom line&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;Date&lt;/code&gt; isn't going anywhere. It's too deeply baked into the platform to remove. But for new code, Temporal is just a better tool: it doesn't mutate behind your back, it's upfront about timezones, and it doesn't have the silent overflow bugs that have probably caused a production incident or two somewhere. If you've ever chased down a mystery bug involving a date that was off by a month or an hour, Temporal was built with exactly that headache in mind.&lt;/p&gt;
&lt;p&gt;Keep building, keep vibing!&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/29825/goodbye-date-why-javascript-is-finally-getting-dates-right</guid>
<pubDate>Mon, 05 Oct 2026 08:49:38 +0000</pubDate>
</item>
</channel>
</rss>