[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"verticals":3,"quiz-temporal-api-date-mutation-trap":44,"search-suggestions":60,"quiz-article-temporal-api-date-mutation-trap":106},[4,20,32],{"id":5,"slug":6,"name":7,"tagline":8,"description":9,"accentFrom":10,"accentTo":11,"icon":12,"defaultLocale":13,"locales":14,"features":16,"position":19},"019fe637-3d33-714b-b57f-23e163ffca0c","dev","Web Development","Read it. Run it. Prove it.","A post a day on modern web development — most with an editable playground and a quiz that explains every answer. Free, no account needed.","violet-500","cyan-400","◇","en",[13,15],"fa",{"courses":17,"paths":17,"articles":17,"exams":18,"flashcards":18,"packages":17,"community":17,"certificates":17,"teams":17,"commerce":17},true,false,0,{"id":21,"slug":22,"name":23,"tagline":24,"description":25,"accentFrom":26,"accentTo":10,"icon":27,"defaultLocale":13,"locales":28,"features":30,"position":31},"019fe637-3dc2-754c-8657-0f175bfee7c6","lang","Languages","Learn a language the way you learn a codebase.","Grammar explained the way good documentation explains an API — one idea at a time, each with a quiz.","amber-400","⌘",[13,15,29],"es",{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":17,"packages":18,"community":17,"certificates":17,"teams":18,"commerce":18},2,{"id":33,"slug":34,"name":35,"tagline":36,"description":37,"accentFrom":38,"accentTo":39,"icon":40,"defaultLocale":13,"locales":41,"features":42,"position":43},"7b3c16f2-931d-410e-802e-e1fa4edab7de","soft","Soft Skills","The half of the job nobody wrote documentation for.","Weekly, on the parts of working life that decide more than your code does — first weeks, meetings, interviews, promotions, and the people around you. Written from what actually happens, and recorded as a podcast you can listen to on the walk.","emerald-400","teal-300","◉",[13],{"courses":18,"paths":18,"articles":17,"exams":18,"flashcards":18,"packages":18,"community":17,"certificates":18,"teams":18,"commerce":18},3,{"id":45,"slug":46,"kind":47,"title":48,"description":49,"config":50,"verticalId":5,"vertical":55,"course":52,"_count":56,"access":57,"attempts":59,"questionCount":51},"01a07bbf-a20f-7298-b529-e9280d9e5bd3","temporal-api-date-mutation-trap","PRACTICE_QUIZ","Date's mutation trap, and what Temporal fixes","Check what you took from the post: why a Date-mutating helper corrupts data its caller never expected to change, what Temporal's immutability actually guarantees, and where Temporal stands in real browsers today.",{"questionCount":51,"timeLimitSec":52,"shuffleQuestions":18,"shuffleOptions":17,"negativeMarking":19,"passScorePct":53,"maxAttempts":52,"revealAnswers":54,"allowFlagging":18,"allowBacktracking":17},7,null,70,"AFTER_SUBMIT",{"slug":6,"name":7},{"questions":51},{"allowed":17,"reason":58},"FREE",[],[61,65,69,73,77,81,85,88,92,95,99,103],{"slug":62,"name":63,"articles":64},"webdev","Webdev",90,{"slug":66,"name":67,"articles":68},"javascript","Javascript",74,{"slug":70,"name":71,"articles":72},"frontend","Frontend",69,{"slug":74,"name":75,"articles":76},"css","Css",31,{"slug":78,"name":79,"articles":80},"tutorial","Tutorial",20,{"slug":82,"name":83,"articles":84},"typescript","Typescript",12,{"slug":86,"name":87,"articles":84},"performance","Performance",{"slug":89,"name":90,"articles":91},"react","React",11,{"slug":93,"name":94,"articles":51},"browser","Browser",{"slug":96,"name":97,"articles":98},"grammar","Grammar",6,{"slug":100,"name":101,"articles":102},"html","Html",5,{"slug":104,"name":105,"articles":102},"programming","Programming",{"id":107,"slug":46,"title":108,"subtitle":52,"excerpt":109,"coverUrl":110,"locale":13,"readingMinutes":102,"publishedAt":111,"viewCount":112,"likeCount":19,"commentCount":19,"author":113,"vertical":118,"topic":119,"tags":121,"_count":126,"playground":128,"body":130,"bodyMd":266,"seo":267,"translationGroupId":270,"series":52,"podcastUrl":52,"verticalId":5,"thread":271,"assessments":273,"translations":276,"quiz":278},"01a07bbf-a18a-766f-9824-295cdf509230","addDays() Mutated a Date Three Components Away From Where I Called It","A date utility that calls .setDate() and returns the same object looks pure. It isn't — Date is mutable, so the caller and everyone else holding that reference get changed out from under them. Temporal, now shipping in real browsers, fixes it by making dates immutable.","\u002Fmedia\u002Fcovers\u002Ftemporal-api-date-mutation-trap.png","2026-09-12T10:24:23.676Z",24,{"id":114,"name":115,"username":116,"avatarUrl":52,"headline":117},"019fe637-3c25-7088-9034-39c9f15dc3c8","Parsa Jiravand","parsa","Frontend engineer · building bestpractic",{"slug":6,"name":7,"accentFrom":10,"accentTo":11},{"slug":66,"name":120},"JavaScript",[122,123,124,125],{"slug":66,"name":67,"color":52},{"slug":62,"name":63,"color":52},{"slug":104,"name":105,"color":52},{"slug":82,"name":83,"color":52},{"assessments":127},1,{"slug":46,"title":129},"Date vs Temporal — the mutation trap",{"blocks":131,"version":127},[132,136,139,145,148,153,156,159,163,166,169,172,176,179,182,185,188,191,195,198,201,204,207,210,213,216,220,223,226,229,232,235,238,241,244,247,250,253,256,259],{"id":133,"html":134,"type":135},"b1","\u003Cp>Someone on the team clicked &quot;remind me&quot; on a Monday standup. Ten minutes later, someone else reported the standup card itself had silently moved to Sunday — on a completely different page, rendered by a completely different component, nowhere near the reminder code.\u003C\u002Fp>","paragraph",{"id":137,"html":138,"type":135},"b2","\u003Cp>Nobody had touched the standup&#39;s date. The bug was three function calls upstream, in a helper that had shipped, unremarkably, eight months earlier:\u003C\u002Fp>",{"id":140,"code":141,"type":142,"language":143,"highlight":144},"b3","function nextOccurrence(date, days) {\n  date.setDate(date.getDate() + days);\n  return date;\n}","code","js",[],{"id":146,"html":147,"type":135},"b4","\u003Cp>It looks pure. Takes a date, takes a number, returns a date. It is not pure, and the reason is \u003Ccode>Date\u003C\u002Fcode>.\u003C\u002Fp>",{"id":149,"html":150,"text":151,"type":152,"level":31},"b5","Where&#39;s the bug? Look before you scroll","Where's the bug? Look before you scroll","heading",{"id":154,"html":155,"type":135},"b6","\u003Cp>\u003Ccode>nextOccurrence\u003C\u002Fcode> takes a \u003Ccode>Date\u003C\u002Fcode> object and returns one. Nothing about the signature says &quot;and also, permanently, edits the object you handed me.&quot; But that&#39;s exactly what \u003Ccode>.setDate()\u003C\u002Fcode> does — it mutates the receiver in place and returns a plain number (the new timestamp), which this function throws away before returning \u003Ccode>date\u003C\u002Fcode> itself.\u003C\u002Fp>",{"id":157,"html":158,"type":135},"b7","\u003Cp>Here&#39;s the shape that broke:\u003C\u002Fp>",{"id":160,"code":161,"type":142,"language":143,"highlight":162},"b8","const standup = new Date(\"2026-09-14\"); \u002F\u002F Monday\nconst upcomingEvents = [standup];       \u002F\u002F rendered on the events page\n\nfunction reminderFor(event) {\n  return nextOccurrence(event, -1); \u002F\u002F \"remind a day before\"\n}\n\nconst reminder = reminderFor(standup);\n\nconsole.log(reminder.toDateString());        \u002F\u002F Sun Sep 13 2026 — correct\nconsole.log(upcomingEvents[0].toDateString()); \u002F\u002F Sun Sep 13 2026 — also changed!",[],{"id":164,"html":165,"type":135},"b9","\u003Cp>\u003Ccode>reminderFor(standup)\u003C\u002Fcode> passes the \u003Cem>same object reference\u003C\u002Fem> into \u003Ccode>nextOccurrence\u003C\u002Fcode>. The \u003Ccode>.setDate()\u003C\u002Fcode> call inside it edits that object directly. \u003Ccode>standup\u003C\u002Fcode> and \u003Ccode>upcomingEvents[0]\u003C\u002Fcode> were never two dates — they were one \u003Ccode>Date\u003C\u002Fcode> object with two names, and \u003Ccode>nextOccurrence\u003C\u002Fcode> rewrote it out from under everything else holding a reference. The events page didn&#39;t have a rendering bug. It rendered the mutated object perfectly.\u003C\u002Fp>",{"id":167,"html":168,"text":168,"type":152,"level":31},"b10","The fix everyone reaches for — and where it stops scaling",{"id":170,"html":171,"type":135},"b11","\u003Cp>The immediate patch is to clone before mutating:\u003C\u002Fp>",{"id":173,"code":174,"type":142,"language":143,"highlight":175},"b12","function nextOccurrence(date, days) {\n  const copy = new Date(date); \u002F\u002F clone first\n  copy.setDate(copy.getDate() + days);\n  return copy;\n}",[],{"id":177,"html":178,"type":135},"b13","\u003Cp>This works, for this function. It doesn&#39;t work as a policy. Every date helper in the codebase now has to remember to clone before it calls any \u003Ccode>set*\u003C\u002Fcode> method, and there&#39;s no error, lint rule, or type that catches the one you forget. \u003Ccode>setDate\u003C\u002Fcode>, \u003Ccode>setMonth\u003C\u002Fcode>, \u003Ccode>setHours\u003C\u002Fcode>, \u003Ccode>setFullYear\u003C\u002Fcode>, \u003Ccode>setMinutes\u003C\u002Fcode> — eight mutator methods, and each one is a place a future contributor can reintroduce this exact bug without knowing the convention exists.\u003C\u002Fp>",{"id":180,"html":181,"type":135},"b14","\u003Cp>And cloning doesn&#39;t touch the other classic \u003Ccode>Date\u003C\u002Fcode> trap hiding one line up: \u003Ccode>new Date(&quot;2026-09-14&quot;)\u003C\u002Fcode> — a date-only ISO string — is parsed as \u003Cstrong>UTC midnight\u003C\u002Fstrong>. Call \u003Ccode>.getDate()\u003C\u002Fcode> on it in a browser west of UTC and, depending on the reader&#39;s local timezone, you can get the day \u003Cem>before\u003C\u002Fem> the one in the string. \u003Ccode>Date\u003C\u002Fcode> doesn&#39;t distinguish &quot;a calendar date with no timezone&quot; from &quot;a specific instant in UTC&quot; — it collapses both into the same object and makes you guess which one you meant.\u003C\u002Fp>",{"id":183,"html":184,"text":184,"type":152,"level":31},"b15","What actually fixes the class of bug",{"id":186,"html":187,"type":135},"b16","\u003Cp>\u003Ccode>Temporal\u003C\u002Fcode> — now part of the language, Stage 4 in TC39 and folded into the ES2026 spec — replaces \u003Ccode>Date\u003C\u002Fcode> with a set of value types that are \u003Cstrong>immutable by construction\u003C\u002Fstrong>. Every method that &quot;changes&quot; a date returns a new one; none of them can touch the object you handed in.\u003C\u002Fp>",{"id":189,"html":190,"type":135},"b17","\u003Cp>Same helper, same reminder logic, rewritten on \u003Ccode>Temporal.PlainDate\u003C\u002Fcode>:\u003C\u002Fp>",{"id":192,"code":193,"type":142,"language":143,"highlight":194},"b18","const standup = Temporal.PlainDate.from(\"2026-09-14\"); \u002F\u002F Monday, no timezone at all\nconst upcomingEvents = [standup];\n\nfunction nextOccurrence(date, days) {\n  return date.add({ days }); \u002F\u002F always a NEW PlainDate — date itself can't change\n}\n\nconst reminder = nextOccurrence(standup, -1);\n\nconsole.log(reminder.toString());          \u002F\u002F 2026-09-13\nconsole.log(upcomingEvents[0].toString()); \u002F\u002F 2026-09-14 — untouched",[],{"id":196,"html":197,"type":135},"b19","\u003Cp>\u003Ccode>.add()\u003C\u002Fcode> (and \u003Ccode>.subtract()\u003C\u002Fcode>, \u003Ccode>.with()\u003C\u002Fcode>) hand back a brand-new \u003Ccode>Temporal.PlainDate\u003C\u002Fcode> and leave \u003Ccode>standup\u003C\u002Fcode> exactly as it was. There&#39;s no clone-before-mutating convention to forget, because there&#39;s no mutation to forget it before. And \u003Ccode>Temporal.PlainDate\u003C\u002Fcode> has no timezone at all — it&#39;s a calendar date, full stop — so the UTC-midnight-versus-local-day ambiguity that bit \u003Ccode>new Date(&quot;2026-09-14&quot;)\u003C\u002Fcode> doesn&#39;t exist here; there was never a timezone to disagree about.\u003C\u002Fp>",{"id":199,"html":200,"type":135},"b20","\u003C!-- playground:start -->",{"id":202,"html":203,"text":203,"type":152,"level":31},"b21","🎮 Try it yourself",{"id":205,"html":206,"type":135},"b22","\u003Cp>\u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Ftemporal-api-date-mutation-trap\u002Fplayground\">▶️ Open the interactive playground →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":208,"html":209,"type":135},"b23","\u003Cp>\u003Cem>Runs right in your browser — poke at it and watch the concept react live.\u003C\u002Fem>\u003C\u002Fp>",{"id":211,"html":212,"type":135},"b24","\u003C!-- playground:end -->",{"id":214,"html":215,"type":135},"b25","\u003Cp>When you do need to compare two dates rather than reach into their fields by hand, \u003Ccode>Temporal.PlainDate.compare()\u003C\u002Fcode> gives you a real answer instead of falling back to arithmetic on timestamps:\u003C\u002Fp>",{"id":217,"code":218,"type":142,"language":143,"highlight":219},"b26","Temporal.PlainDate.compare(\n  Temporal.PlainDate.from(\"2026-09-14\"),\n  Temporal.PlainDate.from(\"2026-09-21\"),\n); \u002F\u002F -1 — the first date is earlier",[],{"id":221,"html":222,"text":222,"type":152,"level":31},"b27","Where Temporal actually stands today",{"id":224,"html":225,"type":135},"b28","\u003Cp>This isn&#39;t a future-tense proposal anymore. Temporal reached \u003Cstrong>TC39 Stage 4\u003C\u002Fstrong> in March 2026 and shipped as part of \u003Cstrong>ES2026\u003C\u002Fstrong>. It&#39;s already running natively: \u003Cstrong>Chrome and Edge 144\u003C\u002Fstrong> (January 2026) and \u003Cstrong>Firefox 139+\u003C\u002Fstrong> ship it without a flag. Safari doesn&#39;t yet — it&#39;s behind a flag in Technology Preview, not in a stable release, which is also why MDN can&#39;t mark it &quot;Baseline&quot; yet: that label needs the widely-used browsers in agreement, and Safari hasn&#39;t caught up.\u003C\u002Fp>",{"id":227,"html":228,"type":135},"b29","\u003Cp>For anything shipping today, that means: reach for it directly if your audience is Chromium- or Firefox-heavy, or pull in \u003Ccode>temporal-polyfill\u003C\u002Fcode> (or the TC39-maintained \u003Ccode>@js-temporal\u002Fpolyfill\u003C\u002Fcode>) for the rest — both implement the same spec, so the code you write against the polyfill today keeps working unchanged once Safari catches up and you drop it.\u003C\u002Fp>",{"id":230,"html":231,"type":135},"b30","\u003C!-- quiz:start -->",{"id":233,"html":234,"text":234,"type":152,"level":31},"b31","🧠 Test yourself",{"id":236,"html":237,"type":135},"b32","\u003Cp>Think it clicked? \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Ftemporal-api-date-mutation-trap\u002Fquiz\">Take the 7-question quiz →\u003C\u002Fa>\u003C\u002Fstrong>\u003C\u002Fp>",{"id":239,"html":240,"type":135},"b33","\u003Cp>\u003Cem>Instant feedback, a hint on every question, and an explanation for each answer — right or wrong.\u003C\u002Fem>\u003C\u002Fp>",{"id":242,"html":243,"type":135},"b34","\u003C!-- quiz:end -->",{"id":245,"html":246,"type":135},"b35","\u003Cp>The standup card wasn&#39;t a rendering bug, and \u003Ccode>nextOccurrence\u003C\u002Fcode> wasn&#39;t a badly written function — it was a completely reasonable function written against an object model that lets \u003Cem>any\u003C\u002Fem> caller rewrite \u003Cem>any\u003C\u002Fem> other caller&#39;s data by accident. That&#39;s not a bug you fix once. It&#39;s a bug class you either keep guarding against by hand, function by function, forever — or hand to a type that structurally can&#39;t have it.\u003C\u002Fp>",{"id":248,"html":249,"type":135},"b36","\u003Cp>Grep your own codebase for a helper that calls \u003Ccode>setDate\u003C\u002Fcode>, \u003Ccode>setMonth\u003C\u002Fcode>, \u003Ccode>setHours\u003C\u002Fcode>, \u003Ccode>setFullYear\u003C\u002Fcode>, or \u003Ccode>setMinutes\u003C\u002Fcode> on an argument and then returns it. What did you find?\u003C\u002Fp>",{"id":251,"type":252},"b37","divider",{"id":254,"html":255,"type":135},"b38","\u003Cp>🚀 \u003Cstrong>Want more like this?\u003C\u002Fstrong> Every guide, playground, and quiz lives on \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">bestpractic.org\u003C\u002Fa>\u003C\u002Fstrong> — open it and \u003Cstrong>\u003Ca href=\"https:\u002F\u002Fbestpractic.org\u002F\">sign up free\u003C\u002Fa>\u003C\u002Fstrong> so the next one finds you.\u003C\u002Fp>",{"id":257,"html":258,"type":135},"b39","\u003Cp>\u003Cem>Thanks for reading! Let&#39;s stay connected:\u003C\u002Fem>\u003C\u002Fp>",{"id":260,"type":261,"items":262,"ordered":18},"b40","list",[263,264,265],"⭐ \u003Cstrong>GitHub\u003C\u002Fstrong> — follow me and star the projects: \u003Ca href=\"https:\u002F\u002Fgithub.com\u002Fparsajiravand\">github.com\u002Fparsajiravand\u003C\u002Fa>","💬 \u003Cstrong>Discord\u003C\u002Fstrong> — join the frontend best-practices community: \u003Ca href=\"https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ\">discord.gg\u002Fd9KRhuAwQ\u003C\u002Fa>","📸 \u003Cstrong>Instagram\u003C\u002Fstrong> — frontend best practices, daily: \u003Ca href=\"https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F\">@bestpractice___\u003C\u002Fa>","Someone on the team clicked \"remind me\" on a Monday standup. Ten minutes later, someone else reported the standup card itself had silently moved to Sunday — on a completely different page, rendered by a completely different component, nowhere near the reminder code.\n\nNobody had touched the standup's date. The bug was three function calls upstream, in a helper that had shipped, unremarkably, eight months earlier:\n\n```js\nfunction nextOccurrence(date, days) {\n  date.setDate(date.getDate() + days);\n  return date;\n}\n```\n\nIt looks pure. Takes a date, takes a number, returns a date. It is not pure, and the reason is `Date`.\n\n## Where's the bug? Look before you scroll\n\n`nextOccurrence` takes a `Date` object and returns one. Nothing about the signature says \"and also, permanently, edits the object you handed me.\" But that's exactly what `.setDate()` does — it mutates the receiver in place and returns a plain number (the new timestamp), which this function throws away before returning `date` itself.\n\nHere's the shape that broke:\n\n```js\nconst standup = new Date(\"2026-09-14\"); \u002F\u002F Monday\nconst upcomingEvents = [standup];       \u002F\u002F rendered on the events page\n\nfunction reminderFor(event) {\n  return nextOccurrence(event, -1); \u002F\u002F \"remind a day before\"\n}\n\nconst reminder = reminderFor(standup);\n\nconsole.log(reminder.toDateString());        \u002F\u002F Sun Sep 13 2026 — correct\nconsole.log(upcomingEvents[0].toDateString()); \u002F\u002F Sun Sep 13 2026 — also changed!\n```\n\n`reminderFor(standup)` passes the *same object reference* into `nextOccurrence`. The `.setDate()` call inside it edits that object directly. `standup` and `upcomingEvents[0]` were never two dates — they were one `Date` object with two names, and `nextOccurrence` rewrote it out from under everything else holding a reference. The events page didn't have a rendering bug. It rendered the mutated object perfectly.\n\n## The fix everyone reaches for — and where it stops scaling\n\nThe immediate patch is to clone before mutating:\n\n```js\nfunction nextOccurrence(date, days) {\n  const copy = new Date(date); \u002F\u002F clone first\n  copy.setDate(copy.getDate() + days);\n  return copy;\n}\n```\n\nThis works, for this function. It doesn't work as a policy. Every date helper in the codebase now has to remember to clone before it calls any `set*` method, and there's no error, lint rule, or type that catches the one you forget. `setDate`, `setMonth`, `setHours`, `setFullYear`, `setMinutes` — eight mutator methods, and each one is a place a future contributor can reintroduce this exact bug without knowing the convention exists.\n\nAnd cloning doesn't touch the other classic `Date` trap hiding one line up: `new Date(\"2026-09-14\")` — a date-only ISO string — is parsed as **UTC midnight**. Call `.getDate()` on it in a browser west of UTC and, depending on the reader's local timezone, you can get the day *before* the one in the string. `Date` doesn't distinguish \"a calendar date with no timezone\" from \"a specific instant in UTC\" — it collapses both into the same object and makes you guess which one you meant.\n\n## What actually fixes the class of bug\n\n`Temporal` — now part of the language, Stage 4 in TC39 and folded into the ES2026 spec — replaces `Date` with a set of value types that are **immutable by construction**. Every method that \"changes\" a date returns a new one; none of them can touch the object you handed in.\n\nSame helper, same reminder logic, rewritten on `Temporal.PlainDate`:\n\n```js\nconst standup = Temporal.PlainDate.from(\"2026-09-14\"); \u002F\u002F Monday, no timezone at all\nconst upcomingEvents = [standup];\n\nfunction nextOccurrence(date, days) {\n  return date.add({ days }); \u002F\u002F always a NEW PlainDate — date itself can't change\n}\n\nconst reminder = nextOccurrence(standup, -1);\n\nconsole.log(reminder.toString());          \u002F\u002F 2026-09-13\nconsole.log(upcomingEvents[0].toString()); \u002F\u002F 2026-09-14 — untouched\n```\n\n`.add()` (and `.subtract()`, `.with()`) hand back a brand-new `Temporal.PlainDate` and leave `standup` exactly as it was. There's no clone-before-mutating convention to forget, because there's no mutation to forget it before. And `Temporal.PlainDate` has no timezone at all — it's a calendar date, full stop — so the UTC-midnight-versus-local-day ambiguity that bit `new Date(\"2026-09-14\")` doesn't exist here; there was never a timezone to disagree about.\n\n\u003C!-- playground:start -->\n\n## 🎮 Try it yourself\n\n**[▶️ Open the interactive playground →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Ftemporal-api-date-mutation-trap\u002Fplayground)**\n\n_Runs right in your browser — poke at it and watch the concept react live._\n\n\u003C!-- playground:end -->\n\nWhen you do need to compare two dates rather than reach into their fields by hand, `Temporal.PlainDate.compare()` gives you a real answer instead of falling back to arithmetic on timestamps:\n\n```js\nTemporal.PlainDate.compare(\n  Temporal.PlainDate.from(\"2026-09-14\"),\n  Temporal.PlainDate.from(\"2026-09-21\"),\n); \u002F\u002F -1 — the first date is earlier\n```\n\n## Where Temporal actually stands today\n\nThis isn't a future-tense proposal anymore. Temporal reached **TC39 Stage 4** in March 2026 and shipped as part of **ES2026**. It's already running natively: **Chrome and Edge 144** (January 2026) and **Firefox 139+** ship it without a flag. Safari doesn't yet — it's behind a flag in Technology Preview, not in a stable release, which is also why MDN can't mark it \"Baseline\" yet: that label needs the widely-used browsers in agreement, and Safari hasn't caught up.\n\nFor anything shipping today, that means: reach for it directly if your audience is Chromium- or Firefox-heavy, or pull in `temporal-polyfill` (or the TC39-maintained `@js-temporal\u002Fpolyfill`) for the rest — both implement the same spec, so the code you write against the polyfill today keeps working unchanged once Safari catches up and you drop it.\n\n\u003C!-- quiz:start -->\n\n## 🧠 Test yourself\n\nThink it clicked? **[Take the 7-question quiz →](https:\u002F\u002Fbestpractic.org\u002Fblog\u002Ftemporal-api-date-mutation-trap\u002Fquiz)**\n\n_Instant feedback, a hint on every question, and an explanation for each answer — right or wrong._\n\n\u003C!-- quiz:end -->\n\nThe standup card wasn't a rendering bug, and `nextOccurrence` wasn't a badly written function — it was a completely reasonable function written against an object model that lets *any* caller rewrite *any* other caller's data by accident. That's not a bug you fix once. It's a bug class you either keep guarding against by hand, function by function, forever — or hand to a type that structurally can't have it.\n\nGrep your own codebase for a helper that calls `setDate`, `setMonth`, `setHours`, `setFullYear`, or `setMinutes` on an argument and then returns it. What did you find?\n\n---\n\n🚀 **Want more like this?** Every guide, playground, and quiz lives on **[bestpractic.org](https:\u002F\u002Fbestpractic.org\u002F)** — open it and **[sign up free](https:\u002F\u002Fbestpractic.org\u002F)** so the next one finds you.\n\n*Thanks for reading! Let's stay connected:*\n\n- ⭐ **GitHub** — follow me and star the projects: [github.com\u002Fparsajiravand](https:\u002F\u002Fgithub.com\u002Fparsajiravand)\n- 💬 **Discord** — join the frontend best-practices community: [discord.gg\u002Fd9KRhuAwQ](https:\u002F\u002Fdiscord.gg\u002Fd9KRhuAwQ)\n- 📸 **Instagram** — frontend best practices, daily: [@bestpractice___](https:\u002F\u002Fwww.instagram.com\u002Fbestpractice___\u002F)",{"title":108,"canonical":268,"description":269},"https:\u002F\u002Fbestpractic.org\u002Fblog\u002Ftemporal-api-date-mutation-trap","A date utility that calls .setDate() and returns the same object looks pure. It isn't — Date is mutable, so the caller and everyone else holding that reference get changed out from","01a07bbf-a18a-766f-9824-2db8e4a0dfbb",{"id":272,"locked":18},"01a07bbf-a1ed-72ab-b0ad-5cbb1c596bd9",[274],{"id":45,"slug":46,"title":48,"_count":275},{"questions":51},[277],{"locale":13,"slug":46},{"id":45,"slug":46,"title":48,"_count":279,"questionCount":51},{"questions":51}]