<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Michael Maitland</title>
    <description>The latest articles on DEV Community by Michael Maitland (@mike-mait).</description>
    <link>https://dev.to/mike-mait</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3904470%2F4c3b7505-40b3-48ed-a95a-e0c1977ab7ae.png</url>
      <title>DEV Community: Michael Maitland</title>
      <link>https://dev.to/mike-mait</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mike-mait"/>
    <language>en</language>
    <item>
      <title>The timezone bugs you don't know you have (and how to find them)</title>
      <dc:creator>Michael Maitland</dc:creator>
      <pubDate>Wed, 29 Apr 2026 13:48:16 +0000</pubDate>
      <link>https://dev.to/mike-mait/the-timezone-bugs-you-dont-know-you-have-and-how-to-find-them-gd5</link>
      <guid>https://dev.to/mike-mait/the-timezone-bugs-you-dont-know-you-have-and-how-to-find-them-gd5</guid>
      <description>&lt;p&gt;A user reports that a meeting fired an hour late. You check the database — the timestamp looks fine. You check the UI — the display matches what the user entered. Then you realize: today was DST transition day.&lt;/p&gt;

&lt;p&gt;This post is about the timezone bugs that 90% of production apps have and don't know about. None of these are exotic. All of them are quietly corrupting data in some app you've shipped.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug #1: The Spring-Forward Gap
&lt;/h2&gt;

&lt;p&gt;On March 8, 2026 in &lt;code&gt;America/New_York&lt;/code&gt;, clocks jump from 1:59:59 directly to 3:00:00. The entire 2:00–2:59 hour does not exist.&lt;/p&gt;

&lt;p&gt;If a user enters "2:30am" in your scheduler:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromISO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-03-08T02:30:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;America/New_York&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toISO&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="c1"&gt;// "2026-03-08T03:30:00.000-04:00"  ← Luxon silently shifted it to 3:30&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Luxon "helpfully" resolved the invalid time forward. Your user wanted 2:30am. You stored 3:30am. They have no idea. The bug surfaces 8 months later when their recurring event fires an hour late.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; explicitly check whether a parsed datetime equals what you asked for, and reject (or escalate to the user) if it doesn't. Don't trust your library to "just handle it."&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug #2: The Fall-Back Overlap
&lt;/h2&gt;

&lt;p&gt;On November 1, 2026 in &lt;code&gt;America/New_York&lt;/code&gt;, clocks go from 1:59:59 back to 1:00:00. The 1:00–1:59 hour happens twice. "1:30am" is genuinely ambiguous.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromISO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2026-11-01T01:30:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;America/New_York&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="c1"&gt;// -240 (EDT) — but it could just as legitimately be -300 (EST)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Most libraries pick one without telling you. Picking "the first occurrence" is a real business decision (your user might mean the second). It should not be a silent default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; when you detect an ambiguous local time, force a policy decision in your domain layer. Log it. Don't let the library guess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug #3: Non-Whole-Hour Offsets
&lt;/h2&gt;

&lt;p&gt;Pop quiz — what's the offset for &lt;code&gt;Asia/Kathmandu&lt;/code&gt;? Most developers say "+5:30." Wrong, that's India.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nepal: &lt;strong&gt;UTC+5:45&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Chatham Islands: &lt;strong&gt;UTC+12:45&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Lord Howe Island uses a &lt;strong&gt;30-minute&lt;/strong&gt; DST shift, not 60&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your code anywhere does &lt;code&gt;offset / 60&lt;/code&gt; to get hours, or assumes the minutes component is always 0 or 30, you have a bug. It might never fire, because you might never have a Nepali user. Until you do.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; treat offsets as minutes-from-UTC, never as hours. Format defensively. Test with Kathmandu in your suite.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug #4: Historical Zone Rules
&lt;/h2&gt;

&lt;p&gt;Germany on 1945-05-24 had a 23:00 → 01:00 jump (the occupation forces synchronized clocks). Russia in 2011 abolished DST permanently. Iran abolished DST in 2022. Egypt re-instated it in 2023. Samoa literally skipped December 30, 2011 to switch date lines.&lt;/p&gt;

&lt;p&gt;If you're storing or computing historical timestamps — billing periods that started years ago, audit trails, IoT data from old devices — your results may be wrong by an hour for any user in an affected zone.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; if you're computing across years, pin your tzdata version explicitly and document it. Don't assume the runtime's bundled tzdata is up to date.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug #5: tzdb Update Drift
&lt;/h2&gt;

&lt;p&gt;The IANA tzdata gets updated 3-6 times per year. Every release fixes historical errors and adds new rule changes (countries change DST policy all the time — Mexico in 2022, Egypt in 2023, BC's pending change in 2026).&lt;/p&gt;

&lt;p&gt;Your Node.js version was built with a snapshot of tzdata from whenever it was released. If you're on Node 20 from 2023, you're on tzdata from 2023. You're missing every IANA update since then.&lt;/p&gt;

&lt;p&gt;This means &lt;strong&gt;your app gives wrong answers for any zone whose rules changed after your Node version's release date&lt;/strong&gt;. Often by an hour, often silently.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The fix:&lt;/strong&gt; decouple tzdata from your runtime version. Use a userland tzdb package you can update independently. I just did this for my production system and wrote up the migration if you want details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug #6: AI agents silently scheduling impossible times
&lt;/h2&gt;

&lt;p&gt;You'd think that with five named bug classes documented above, AI tools would handle them carefully. They don't. The opposite, actually — AI agents make the prior five bugs &lt;em&gt;worse&lt;/em&gt;, because they:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Confidently parse natural language ("schedule me for 2:30 AM on March 8 in New York")&lt;/li&gt;
&lt;li&gt;Pass the parsed local time to a calendar, booking, or billing API without verification&lt;/li&gt;
&lt;li&gt;Get an answer back that looks fine&lt;/li&gt;
&lt;li&gt;Tell the user "done, I scheduled it for 2:30 AM"&lt;/li&gt;
&lt;li&gt;The downstream system silently shifts to 3:30 AM (Bug #1) or picks the wrong UTC instant (Bug #2)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There's no error. The agent doesn't know to second-guess itself. And the user trusts the agent's confirmation. By the time anyone notices, the meeting has already fired at the wrong hour, the cron job has already double-charged the customer, or the reminder has already failed to land.&lt;/p&gt;

&lt;p&gt;I've watched Claude do exactly this. So has GPT-4. Both are perfectly happy to say "scheduled for 2:30 AM March 8, 2026" — a moment that does not exist in &lt;code&gt;America/New_York&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  The fix: agent preflight
&lt;/h3&gt;

&lt;p&gt;The same validation pattern that works for human-driven UIs (&lt;code&gt;validate&lt;/code&gt; before storing) works for agents — you just expose it as a tool the model can call before it acts.&lt;/p&gt;

&lt;p&gt;For the OpenAI or Anthropic SDK route, the tool definition is a few lines:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validateLocalDatetime&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;function&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;function&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;validate_local_datetime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Preflight check for a user-entered local datetime. Call BEFORE scheduling, booking, billing, or reminding from natural-language time references. Returns DST_GAP / DST_OVERLAP / INVALID_TIMEZONE so the agent knows when to ask the user to disambiguate.&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;parameters&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;object&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;properties&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;local_datetime&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;ISO 8601 local datetime, e.g. 2026-03-08T02:30:00&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;time_zone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;      &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;IANA timezone, e.g. America/New_York&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;required&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;local_datetime&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;time_zone&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Bind that to a &lt;code&gt;fetch&lt;/code&gt; call to &lt;code&gt;https://chronoshieldapi.com/v1/datetime/validate&lt;/code&gt; and the model has a deterministic preflight it can run on any user-entered time.&lt;/p&gt;

&lt;p&gt;For Claude Desktop, Cursor, Windsurf, or any MCP-compatible client, there's an even shorter path — install the &lt;a href="https://modelcontextprotocol.io" rel="noopener noreferrer"&gt;Model Context Protocol&lt;/a&gt; server and the model gets the tool natively, no integration code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"mcpServers"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"chronoshield"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"npx"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"args"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"-y"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"chronoshield-mcp"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"CHRONOSHIELD_API_KEY"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"your_key"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After restart, ask Claude: &lt;em&gt;"Schedule a reminder for 2:30 AM on March 8, 2026 in New York."&lt;/em&gt; It calls the tool, sees &lt;code&gt;DST_GAP&lt;/code&gt;, and asks you whether to use 3:00 AM instead — instead of silently confirming a moment that won't exist.&lt;/p&gt;

&lt;p&gt;Free key at &lt;a href="https://chronoshieldapi.com" rel="noopener noreferrer"&gt;chronoshieldapi.com&lt;/a&gt; (no card). Full agent integration guide for OpenAI / Claude / LangChain / Vercel AI SDK at &lt;a href="https://chronoshieldapi.com/docs/ai-agents" rel="noopener noreferrer"&gt;chronoshieldapi.com/docs/ai-agents&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to test for these in your own code
&lt;/h2&gt;

&lt;p&gt;For each of the bugs above, here's a one-liner test:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Bug #1 detection&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isValidLocalDatetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromISO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;zone&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="c1"&gt;// Compare requested local time vs what the lib gave back&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;yyyy-MM-dd'T'HH:mm:ss&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Bug #2 detection&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;isAmbiguousLocalDatetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;zone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;DateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromISO&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;input&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;zone&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;before&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;minus&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;dt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;plus&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;hours&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;before&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;after&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;offset&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run these against your top 50 customers' time zones for the next 5 years worth of DST transition dates. If you find a bug, congrats — you found one that probably already cost you a support ticket.&lt;/p&gt;

&lt;h2&gt;
  
  
  My take
&lt;/h2&gt;

&lt;p&gt;I got tired of writing this code in every project. So I built &lt;a href="https://chronoshieldapi.com" rel="noopener noreferrer"&gt;ChronoShield API&lt;/a&gt; — a REST API that handles all of these cases as a service. Today's launch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;npm install chronoshield&lt;/code&gt; / &lt;code&gt;pip install chronoshield&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Free tier: 1k req/mo&lt;/li&gt;
&lt;li&gt;Currently serving IANA tzdata 2026b (latest), with the BC permanent-DST projection already live&lt;/li&gt;
&lt;li&gt;Endpoints: &lt;code&gt;/validate&lt;/code&gt;, &lt;code&gt;/resolve&lt;/code&gt;, &lt;code&gt;/convert&lt;/code&gt;, &lt;code&gt;/version&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But the test cases above work whether or not you use the API. If you take nothing else from this post — write the tests. They'll find bugs you didn't know you had.&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>api</category>
      <category>ai</category>
    </item>
  </channel>
</rss>
