<?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: Premysl Hnevkovsky</title>
    <description>The latest articles on DEV Community by Premysl Hnevkovsky (@hnevkop).</description>
    <link>https://dev.to/hnevkop</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%2F1456722%2F4caff525-835f-47a5-b801-fde0368f0835.jpeg</url>
      <title>DEV Community: Premysl Hnevkovsky</title>
      <link>https://dev.to/hnevkop</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/hnevkop"/>
    <language>en</language>
    <item>
      <title>We're Inside the DST Gap Right Now — Your Code Might Not Be</title>
      <dc:creator>Premysl Hnevkovsky</dc:creator>
      <pubDate>Wed, 25 Mar 2026 22:53:15 +0000</pubDate>
      <link>https://dev.to/hnevkop/were-inside-the-dst-gap-right-now-your-code-might-not-be-271b</link>
      <guid>https://dev.to/hnevkop/were-inside-the-dst-gap-right-now-your-code-might-not-be-271b</guid>
      <description>&lt;p&gt;&lt;em&gt;A field guide for developers building apps that dare to cross meridians&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;You decided to build an app that tracks events worldwide. Bold move. Now let's talk about the moment you realize that &lt;strong&gt;time is not a simple integer&lt;/strong&gt; and your clever &lt;code&gt;Date.now()&lt;/code&gt; will absolutely betray you at the worst possible moment.&lt;/p&gt;

&lt;p&gt;Welcome to timezone hell. Population: every developer who ever shipped a scheduling feature.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Real-time relevance:&lt;/strong&gt; I'm writing this on &lt;strong&gt;March 24, 2026&lt;/strong&gt; — and we're currently living inside the US–Europe DST gap window. The US switched to EDT on March 8, but Europe doesn't switch to CEST until March 29. If your app hardcodes timezone offsets between New York and London (or Prague, or Paris), &lt;strong&gt;it's wrong right now&lt;/strong&gt;. &lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 1 — Know Where You Are (Spoiler: It Doesn't Matter)
&lt;/h2&gt;

&lt;p&gt;You live somewhere. You know your timezone. Congratulations, that's completely irrelevant to your backend.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Your server doesn't care about Prague. Your server speaks UTC.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;GMT vs UTC:&lt;/strong&gt; GMT is a timezone. UTC is a time standard. They happen to share the same offset (+00:00), but GMT observes DST in some edge cases and is rooted in astronomical observation. UTC is atomic-clock precise and DST-free. &lt;strong&gt;Always store UTC. Never argue about this.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;/blockquote&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Server Side - Kotlin &lt;/span&gt;

&lt;span class="c1"&gt;// ❌ Don't do this&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;now&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalDateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Whose now? YOUR now? Server's now? Tokyo's now? &lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Do this&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;now&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Universal. Unambiguous. Boring in the best way.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 2 — Know Where the Event Is
&lt;/h2&gt;

&lt;p&gt;Your app cares about Tokyo Stock Exchange opening at &lt;strong&gt;09:00 JST&lt;/strong&gt; That's Tokyo's clock. Not yours.&lt;/p&gt;

&lt;p&gt;First: get the &lt;strong&gt;canonical event time&lt;/strong&gt; in its home timezone.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Server Side - Kotlin&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tokyoZone&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZoneId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Asia/Tokyo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tokyoOpen&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZonedDateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokyoZone&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;LocalTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;tokyoZone&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// tokyoOpen = 2026-03-24T09:00+09:00[Asia/Tokyo]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now convert to UTC so you have a real anchor point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Server Side - Kotlin&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tokyoOpenUtc&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokyoOpen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toInstant&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="c1"&gt;// 2026-03-24T00:00:00Z  ← this is your ground truth&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Good. Now you have something you can actually compute with.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 3 — The Countdown Is Just Subtraction (Or Is It?)
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Server Side - Kotlin&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;countdown&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;between&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Instant&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;tokyoOpenUtc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Market opens in: ${countdown.toMinutes()} minutes"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Easy, right? Ship it.&lt;/p&gt;

&lt;p&gt;...&lt;/p&gt;

&lt;p&gt;Wait.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 4 — Did You Think About DST?
&lt;/h2&gt;

&lt;p&gt;Japan doesn't observe DST. Lucky them. But your &lt;em&gt;users&lt;/em&gt; might live somewhere that does.&lt;/p&gt;

&lt;p&gt;Here's the trap: your countdown is correct (you're comparing &lt;code&gt;Instant&lt;/code&gt; values — UTC under the hood). But the &lt;em&gt;displayed&lt;/em&gt; local time on the client side may shift by ±1 hour depending on the season.&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="c1"&gt;// Client side - TypeScript&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;winterTs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&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-01-15T00:00:00Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;winterTs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeZoneName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;short&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Prague: "15. 1. 2026, 1:00:00 CET"   ← UTC+1, winter time&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;summerTs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&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-06-01T00:00:00Z&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;summerTs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toLocaleString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;timeZoneName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;short&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// Prague: "1. 6. 2026, 2:00:00 CEST"   ← UTC+2, summer time ! &lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Same code. Different timestamp. Different hour. The browser handles the rule — but only if you let it. The moment you hardcode +01:00 you're frozen in January forever.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Use &lt;code&gt;ZoneId&lt;/code&gt;, not &lt;code&gt;ZoneOffset&lt;/code&gt;. One knows about DST. The other does not.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Server Side - Kotlin&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ Hardcoded offset - breaks in summer/winter&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;prague&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZoneOffset&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"+01:00"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Named zone - handles DST transitions automatically&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;prague&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZoneId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Europe/Prague"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Step 4.5 — The DST Overlap Problem (This Is Where It Gets Spicy) 🌐
&lt;/h2&gt;

&lt;p&gt;Here's what most tutorials skip: &lt;strong&gt;different regions switch DST on different dates&lt;/strong&gt;.&lt;br&gt;
And that gap between switches is where hardcoded timezone differences silently break.&lt;/p&gt;
&lt;h3&gt;
  
  
  The US–Europe Gap Window (happening right now)
&lt;/h3&gt;

&lt;p&gt;As of this writing — late March 2026 — we are living inside exactly this trap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🇺🇸 &lt;strong&gt;US&lt;/strong&gt; switched to EDT on &lt;strong&gt;March 8&lt;/strong&gt; (second Sunday of March)&lt;/li&gt;
&lt;li&gt;🇪🇺 &lt;strong&gt;Europe&lt;/strong&gt; switches to CEST on &lt;strong&gt;March 29&lt;/strong&gt; (last Sunday of March)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's &lt;strong&gt;3 weeks&lt;/strong&gt; where the US–Europe offset is &lt;em&gt;not&lt;/em&gt; the usual value:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Period                  | New York (ET) | Prague (CET/CEST) | Difference
------------------------|---------------|-------------------|------------
Winter (before Mar 8)   | EST  = UTC-5  | CET  = UTC+1      | 6 hours
Gap window (Mar 8–28)   | EDT  = UTC-4  | CET  = UTC+1      | 5 hours💥
Summer (after Mar 29)   | EDT  = UTC-4  | CEST = UTC+2      | 6 hours
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you hardcoded &lt;code&gt;"NYSE opens 3:30pm Prague time"&lt;/code&gt; — you're wrong for 3 weeks every spring and 3 weeks every autumn. Congratulations, your bug has a season.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Server Side - Kotlin&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ This is wrong 6 weeks per year&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;nyseOpenInPrague&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;LocalTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// "I know the offset is 6h"&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Let the library do the calendar math&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;nyseOpen&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZonedDateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nc"&gt;LocalDate&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ZoneId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"America/New_York"&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="nc"&gt;LocalTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="nc"&gt;ZoneId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"America/New_York"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;pragueView&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nyseOpen&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;withZoneSameInstant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;ZoneId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Europe/Prague"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="nf"&gt;println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"NYSE opens at: ${pragueView.toLocalTime()} Prague time"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// Correctly prints 15:30 in winter, 15:30 in summer, and 14:30 in the gap 🎯&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h3&gt;
  
  
  The Australia Wildcard — When the Hemispheres Collide
&lt;/h3&gt;

&lt;p&gt;Australia is on the &lt;strong&gt;opposite DST schedule&lt;/strong&gt; because, well, opposite hemisphere. The ASX (Sydney) observes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AEDT&lt;/strong&gt; (UTC+11) — October through April (their summer)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AEST&lt;/strong&gt; (UTC+10) — April through October (their winter)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This creates a beautiful four-state matrix with Europe alone:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Period               | Sydney        | London (GMT/BST) | Difference
---------------------|---------------|------------------|------------
Jan (EU winter,      | AEDT = UTC+11 | GMT  = UTC+0     | 11 hours
 AU summer)          |               |                  |
Apr transition week  | AEST = UTC+10 | BST  = UTC+1     | 9 hours 💥
Jul (EU summer,      | AEST = UTC+10 | BST  = UTC+1     | 9 hours
 AU winter)          |               |                  |
Oct transition week  | AEDT = UTC+11 | BST  = UTC+1     | 10 hours 💥
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Sydney–London offset swings between &lt;strong&gt;9 and 11 hours&lt;/strong&gt; across the year, with &lt;strong&gt;two transition windows&lt;/strong&gt; where it's a completely different value than either stable state.&lt;/p&gt;

&lt;p&gt;Any app that displays "London time" relative to "Sydney time" from a hardcoded diff will be wrong &lt;strong&gt;four times per year&lt;/strong&gt;, at transitions in both directions.&lt;/p&gt;

&lt;p&gt;The only correct approach:&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="c1"&gt;// Client Side - TypeScript&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getOffsetBetween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zone1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;zone2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Never hardcode. Always compute. Timezone rules do the rest.&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fmt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en&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;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;tz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;timeZoneName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shortOffset&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="nf"&gt;formatToParts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;p&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;p&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="kd"&gt;type&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;timeZoneName&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)?.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&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;parseOffset&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;m&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/GMT&lt;/span&gt;&lt;span class="se"&gt;([&lt;/span&gt;&lt;span class="sr"&gt;+-&lt;/span&gt;&lt;span class="se"&gt;])(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;)(?:&lt;/span&gt;&lt;span class="sr"&gt;:&lt;/span&gt;&lt;span class="se"&gt;(\d&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;))?&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&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="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;"&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="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;m&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;0&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="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;parseOffset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zone1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nf"&gt;parseOffset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;zone2&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Usage&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&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="nf"&gt;getOffsetBetween&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Australia/Sydney&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;Europe/London&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
&lt;span class="c1"&gt;// Returns the correct value today, tomorrow, and in October&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Summary Rule
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;If you have ever typed a number of hours as a timezone difference between two places — you have a bug. You just don't know which week it will appear yet.&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  Step 5 — Did You Consider the &lt;em&gt;Date&lt;/em&gt;?
&lt;/h2&gt;

&lt;p&gt;Tokyo opens Monday 09:00 JST. You're in New York on Sunday evening. The event is&lt;br&gt;
&lt;strong&gt;tomorrow&lt;/strong&gt; in Tokyo and &lt;strong&gt;today&lt;/strong&gt; in your database query.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Server Side - Kotlin&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;tokyoZone&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZoneId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Asia/Tokyo"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;nyZone&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZoneId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"America/New_York"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;nowInTokyo&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZonedDateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokyoZone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// Monday&lt;/span&gt;
&lt;span class="kd"&gt;val&lt;/span&gt; &lt;span class="py"&gt;nowInNY&lt;/span&gt;    &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ZonedDateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nyZone&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;      &lt;span class="c1"&gt;// Sunday&lt;/span&gt;

&lt;span class="c1"&gt;// Same Instant. Different dates. Different days of the week.&lt;/span&gt;
&lt;span class="c1"&gt;// Your "today's events" filter just silently excluded Tokyo.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Rule:&lt;/strong&gt; Always derive local dates from the event's timezone, not from your server's &lt;code&gt;LocalDate.now()&lt;/code&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6 — Architecture: Server/Client Contract
&lt;/h2&gt;

&lt;p&gt;🏗️ Here's the pattern that keeps you sane across the whole stack:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌──────────────────────────────────────────────────────┐
│  SERVER                                              │
│  • Stores everything as UTC (Instant / epoch ms)    │
│  • Stores user's IANA timezone string alongside     │
│    user-facing timestamps                           │
│  • Never stores offsets (+01:00) — they're seasonal │
└───────────────────┬──────────────────────────────────┘
                    │ JSON: { "openTime": "2026-03-24T00:00:00Z",
                    │         "timezone": "Asia/Tokyo" }
┌───────────────────▼──────────────────────────────────┐
│  CLIENT                                              │
│  • Receives UTC timestamps                          │
│  • Renders in user's local timezone (browser API)   │
│  • Sends edits back as UTC or with explicit tz info │
└──────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Client Side - TypeScript&lt;/span&gt;

&lt;span class="c1"&gt;// Server response contract&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;MarketEvent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;openTimeUtc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;    &lt;span class="c1"&gt;// ISO-8601 UTC: "2026-03-24T00:00:00Z"&lt;/span&gt;
  &lt;span class="nl"&gt;marketTimezone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// IANA: "Asia/Tokyo"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Client rendering&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;formatEventTime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MarketEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;displayZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nx"&gt;Intl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;DateTimeFormat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;en-US&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;timeZone&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;displayZone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;hour&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2-digit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;minute&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;2-digit&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;timeZoneName&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;short&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="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;event&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;openTimeUtc&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;h2&gt;
  
  
  Step 7 — What About the Cloud ☁️ ?
&lt;/h2&gt;

&lt;p&gt;"My app runs on three continents. Does my replica in Singapore care about Prague time?"&lt;/p&gt;

&lt;p&gt;No. And it shouldn't.&lt;/p&gt;

&lt;p&gt;Your databases store &lt;strong&gt;UTC-based timestamps&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Firestore / MongoDB → epoch-based UTC values (timezone is not stored at all)&lt;/li&gt;
&lt;li&gt;PostgreSQL &lt;code&gt;TIMESTAMPTZ&lt;/code&gt; → stored as UTC internally, converted on read/write&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Timezones are not a storage problem. &lt;br&gt;
They are a scheduling and interpretation problem.&lt;/strong&gt; &lt;br&gt;
And sometimes — they are your &lt;strong&gt;business logic&lt;/strong&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The only time you should actually care about timezones in your backend:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Sharding data by date (whose “today”? see Step 5)&lt;/li&gt;
&lt;li&gt;Log correlation across multi-region deployments&lt;/li&gt;
&lt;li&gt;Scheduled jobs that must fire at &lt;strong&gt;"9am local" per region&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Business rules tied to real-world time (markets, bookings, SLAs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where many systems break.&lt;/p&gt;

&lt;p&gt;Because this is NOT display logic — this is &lt;strong&gt;domain logic&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;If your app says:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;"Send email at 9:00"&lt;/li&gt;
&lt;li&gt;"Market opens at 09:30"&lt;/li&gt;
&lt;li&gt;"Booking starts at midnight"&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That time belongs to a &lt;strong&gt;specific timezone&lt;/strong&gt;, and must be modeled explicitly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight kotlin"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Server Side - Kotlin&lt;/span&gt;

&lt;span class="c1"&gt;// ❌ Ambiguous — whose 9:00?&lt;/span&gt;
&lt;span class="nc"&gt;LocalTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Explicit — tied to real-world meaning&lt;/span&gt;
&lt;span class="nc"&gt;ZonedDateTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;date&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;LocalTime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nc"&gt;ZoneId&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"America/New_York"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Never rely on your server region timezone (e.g. AWS region). It is irrelevant to your application logic.&lt;/strong&gt; &lt;br&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For everything else: &lt;strong&gt;UTC in, UTC out, convert at the edges.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Mental Model
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Event happens in the real world
        ↓
Expressed in event's local timezone  (09:00 JST)
        ↓
Stored as UTC on your server         (00:00 UTC)
        ↓
Transmitted as UTC over the wire
        ↓
Displayed in user's local timezone   (browser/client)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;🧠 That's it. The whole game. Fight anyone who breaks this pipeline.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Bonus: Real Lessons from Building TradeDialer
&lt;/h2&gt;

&lt;p&gt;I built TradeDialer, a global market hours tracker covering &lt;strong&gt;25 exchanges&lt;/strong&gt; with live countdowns and index data.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo1146cldov4np15gtlv4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo1146cldov4np15gtlv4.png" alt="Trade-Dialer markets in UTC" width="800" height="882"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Here's what actually hurt: &lt;/p&gt;

&lt;h3&gt;
  
  
  1. Countdown wording is a UX problem, not just a math problem
&lt;/h3&gt;

&lt;p&gt;A raw duration like "opens in 61 hours" is technically correct and practically useless on a Friday afternoon. The countdown needs &lt;strong&gt;contextual language&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Opens in 2h 34m          ← same day, simple case
Opens Monday 09:30       ← weekend ahead
Opens in 3 days          ← holiday closure
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every one of those branches must be computed in the &lt;strong&gt;exchange's local timezone&lt;/strong&gt;, not the server's. "Monday" in Tokyo is not "Monday" in New York.&lt;/p&gt;




&lt;h3&gt;
  
  
  2. Status thresholds need their own timezone logic
&lt;/h3&gt;

&lt;p&gt;TradeDialer uses a four-state color system to communicate urgency at a glance:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Color&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;🟢 Green&lt;/td&gt;
&lt;td&gt;Market is open&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🟡 Amber&lt;/td&gt;
&lt;td&gt;Closing in &amp;lt; 1 hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;🔵 Blue&lt;/td&gt;
&lt;td&gt;Opening in &amp;lt; 1 hour&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;⚫ Gray&lt;/td&gt;
&lt;td&gt;Closed (weekend / holiday / outside hours)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The amber and blue thresholds are computed as a diff against the exchange's session end/start — as &lt;code&gt;ZonedDateTime&lt;/code&gt;, not a raw UTC comparison. Otherwise the "closing soon" window misfires around DST transitions.&lt;/p&gt;




&lt;h3&gt;
  
  
  3. Live data fallback is a timezone-triggered state machine
&lt;/h3&gt;

&lt;p&gt;When US markets close, direct exchange feeds dry up. TradeDialer automatically switches to a Yahoo Finance fallback for foreign markets that are still open — and surfaces the source change with a visible badge so users always know what they're looking at.&lt;/p&gt;

&lt;p&gt;The trigger for that switch? Computed using exchange timezone + session hours. Not UTC midnight. Not server time. The exchange's own clock.&lt;/p&gt;




&lt;h3&gt;
  
  
  4. Holiday calendars are a maintenance tax, not a one-time task
&lt;/h3&gt;

&lt;p&gt;Holiday data looks static — until a government announces a surprise market closure three days before it happens (this is a real thing that happens). Building a market hours app means committing to keeping holiday data fresh across 25 exchange calendars, globally.&lt;/p&gt;

&lt;p&gt;Design your holiday config as &lt;strong&gt;versioned and hot-reloadable&lt;/strong&gt;, not hardcoded constants. You will need to push an update on short notice someday.&lt;/p&gt;




&lt;h3&gt;
  
  
  5. Scope decisions must be explicit, not accidental
&lt;/h3&gt;

&lt;p&gt;NYSE pre-market starts at 04:00 EST. NYSE after-hours runs until 20:00 EST. TradeDialer deliberately excludes extended hours and shows only regular session data.&lt;/p&gt;

&lt;p&gt;"We don't show extended hours" is a product decision.&lt;br&gt;
"Extended hours are broken" is a bug.&lt;br&gt;
Know which one you're shipping.&lt;/p&gt;




&lt;h2&gt;
  
  
  TL;DR — The Timezone Survival Kit
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule&lt;/th&gt;
&lt;th&gt;Why&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Store as &lt;code&gt;Instant&lt;/code&gt; / UTC epoch&lt;/td&gt;
&lt;td&gt;No offset confusion&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Use IANA zone IDs, not offsets&lt;/td&gt;
&lt;td&gt;DST is handled for you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Never hardcode hour differences between zones&lt;/td&gt;
&lt;td&gt;The gap window will find you&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Derive dates per-timezone&lt;/td&gt;
&lt;td&gt;Date line is real&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Convert only at display time&lt;/td&gt;
&lt;td&gt;Single source of truth&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Evaluate holidays in exchange timezone&lt;/td&gt;
&lt;td&gt;Local days, not UTC days&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Model data quality explicitly&lt;/td&gt;
&lt;td&gt;Trust is a feature&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Make holiday data hot-reloadable&lt;/td&gt;
&lt;td&gt;Governments are unpredictable&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Never trust &lt;code&gt;LocalDateTime&lt;/code&gt; across systems&lt;/td&gt;
&lt;td&gt;It's a lie with no context&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;p&gt;&lt;em&gt;Built while debugging why TradeDialer showed Tokyo as opening at 1am. It was. In Prague. In December. Nobody told the server.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Check it out: &lt;a href="https://www.trade-dialer.com" rel="noopener noreferrer"&gt;trade-dialer.com&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;




</description>
      <category>webdev</category>
      <category>kotlin</category>
      <category>programming</category>
      <category>showdev</category>
    </item>
    <item>
      <title>I Spent Weeks Building a Clock for Global Stock Market Hours</title>
      <dc:creator>Premysl Hnevkovsky</dc:creator>
      <pubDate>Fri, 20 Feb 2026 15:07:21 +0000</pubDate>
      <link>https://dev.to/hnevkop/i-spent-weeks-building-a-clock-for-global-stock-market-hours-2b8g</link>
      <guid>https://dev.to/hnevkop/i-spent-weeks-building-a-clock-for-global-stock-market-hours-2b8g</guid>
      <description>&lt;h2&gt;
  
  
  And a 20-Year Trader Didn't Laugh.
&lt;/h2&gt;

&lt;p&gt;We live in the era of AI agents and overnight startups, "10x your productivity" threads, Full trading platforms with heat maps, AI signals, and blinking dashboards.&lt;/p&gt;

&lt;p&gt;And I built... a clock.&lt;/p&gt;

&lt;p&gt;A global stock market clock.&lt;/p&gt;

&lt;p&gt;And it turned out to be much more complicated than I expected.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;TL;DR: I built a simple global stock market hours tracker because I was tired of noisy trading dashboards. It became a lesson in time zones, scope creep, and why shipping beats perfection.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foqy6u5ye5ncnnuzu2dce.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foqy6u5ye5ncnnuzu2dce.jpg" alt="Frankfurt skyscrapers" width="756" height="1008"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  It Started With a Simple Question
&lt;/h2&gt;

&lt;p&gt;Last year my son asked for a trading account.&lt;/p&gt;

&lt;p&gt;After some evaluation (and strict limits), I opened one for him.&lt;br&gt;
I tried mentoring. Mixed results. At least I convinced him not to start with crypto.&lt;/p&gt;

&lt;p&gt;But one thing became obvious immediately:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Markets open and close.&lt;br&gt;
And if you don't know when, you're already behind.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And yes — he still has some money left on his account.&lt;br&gt;
Which I consider a success metric.&lt;/p&gt;




&lt;h2&gt;
  
  
  "Surely This Exists"
&lt;/h2&gt;

&lt;p&gt;I just wanted to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Are US markets open right now?&lt;/li&gt;
&lt;li&gt;  When does Frankfurt close?&lt;/li&gt;
&lt;li&gt;  How much time do I have before the chaos begins?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Simple questions.&lt;/p&gt;

&lt;p&gt;Surprisingly messy answers.&lt;/p&gt;

&lt;p&gt;Yes, there are websites and apps listing trading hours. Yes, there are advanced trading platforms with everything bundled.&lt;/p&gt;

&lt;p&gt;But I wanted something different. &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  A clean global overview&lt;/li&gt;
&lt;li&gt;  Clear states: &lt;strong&gt;open / closed / opens soon / closes soon&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;  A countdown&lt;/li&gt;
&lt;li&gt;  And one small extra: major indices next to each exchange&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not a trading terminal.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Not every finance tool needs to be another Bloomberg terminal. 📈&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I built TradeDialer — a global stock market hours tracker with real-time countdowns and live indices.&lt;/p&gt;

&lt;p&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fem4qgnbn7c5ohomre5le.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fem4qgnbn7c5ohomre5le.png" alt="trade-dialer.com market is about to open" width="800" height="639"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;




&lt;h3&gt;
  
  
  The First Version
&lt;/h3&gt;

&lt;p&gt;Well... there is a story of a first version built for my son.&lt;/p&gt;

&lt;p&gt;I vibe-coded it.&lt;br&gt;
Used it.&lt;br&gt;
Published it.&lt;br&gt;
Forgot about it.&lt;/p&gt;

&lt;p&gt;Classic side project. 😴&lt;/p&gt;

&lt;h3&gt;
  
  
  Then I Reopened It
&lt;/h3&gt;

&lt;p&gt;This January, I met a friend from the Prague dev community. We talked about AI, shipping fast, vibe coding, so I opened the app to show him.&lt;/p&gt;

&lt;p&gt;It was broken with time glitches and wrong market states. Edge cases everywhere.&lt;/p&gt;

&lt;p&gt;It looked simple.&lt;br&gt;
It wasn't.&lt;br&gt;
So I started fixing it.&lt;/p&gt;

&lt;p&gt;Evenings.&lt;br&gt;
Nights.&lt;br&gt;
"Just one more bug."&lt;/p&gt;

&lt;p&gt;You know the pattern. 🙄&lt;/p&gt;




&lt;h2&gt;
  
  
  "It's Just Time."
&lt;/h2&gt;

&lt;p&gt;Narrator: It was not just time.&lt;/p&gt;

&lt;p&gt;I've worked in software for years --- including finance.&lt;/p&gt;

&lt;p&gt;So I confidently thought:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Handling time zones and trading hours? Weekend project."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Here's what "just time" actually meant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Global daylight saving chaos&lt;/li&gt;
&lt;li&gt;  Exchange-specific holidays&lt;/li&gt;
&lt;li&gt;  Partial trading days&lt;/li&gt;
&lt;li&gt;  Index hours ≠ exchange hours&lt;/li&gt;
&lt;li&gt;  API quirks&lt;/li&gt;
&lt;li&gt;  Rate limits&lt;/li&gt;
&lt;li&gt;  Caching strategies&lt;/li&gt;
&lt;li&gt;  Synchronization edge cases&lt;/li&gt;
&lt;li&gt;  Desktop vs. mobile version&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Time is simple.&lt;/p&gt;

&lt;p&gt;Until it spans the planet.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy2bio6bxe3qinm60yzo9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy2bio6bxe3qinm60yzo9.png" alt="trade -dialer London Exchange trading hour" width="412" height="793"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  Backend Confidence vs Frontend Humility
&lt;/h2&gt;

&lt;p&gt;I'm a backend developer, thus I took Kotlin &amp;amp; Spring Boot &amp;amp; microservices containerized stack. &lt;/p&gt;

&lt;p&gt;Frontend?&lt;/p&gt;

&lt;p&gt;Let's call it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Vibe coding&lt;/li&gt;
&lt;li&gt;  Reviewing&lt;/li&gt;
&lt;li&gt;  Refactoring&lt;/li&gt;
&lt;li&gt;  Learning the hard way&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Respect to frontend engineers. 👍&lt;/p&gt;

&lt;h2&gt;
  
  
  Infrastructure (Because Developers)
&lt;/h2&gt;

&lt;p&gt;I like the proper setup.&lt;/p&gt;

&lt;p&gt;Terraform.&lt;br&gt;
GitHub Actions.&lt;br&gt;
AWS (ECS + CloudFront).&lt;br&gt;
Fully automated deployment.&lt;/p&gt;

&lt;p&gt;For a clock.&lt;/p&gt;

&lt;p&gt;Yes. I see the irony. &lt;/p&gt;




&lt;h2&gt;
  
  
  The "Reality Check" Moment
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;Then came the reality check.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I showed it to a friend who has been trading for decades.&lt;/p&gt;

&lt;p&gt;He didn't laugh.&lt;/p&gt;

&lt;p&gt;He didn't say "this is useless."&lt;/p&gt;

&lt;p&gt;He calmly opened professional platforms with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Heat maps&lt;/li&gt;
&lt;li&gt;  Real-time flows&lt;/li&gt;
&lt;li&gt;  Advanced dashboards&lt;/li&gt;
&lt;li&gt;  Enough information to overload three brains&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I looked at him and said:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I'm not fighting those platforms.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I'm building for the person who opens a trading app and feels cognitively exhausted.&lt;/p&gt;

&lt;p&gt;Too many colors.&lt;br&gt;
Too many numbers.&lt;br&gt;
Too many blinking things.&lt;/p&gt;

&lt;p&gt;Maybe you don't need another dashboard.&lt;br&gt;
Maybe you just need to know:&lt;/p&gt;

&lt;p&gt;Is the market open?&lt;br&gt;
When does it close?&lt;br&gt;
What's the index doing?&lt;/p&gt;

&lt;p&gt;That's it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Change this:&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6hd521p9b398yd2sycnm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6hd521p9b398yd2sycnm.png" alt="Usual dashboard" width="800" height="485"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Into this:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmh2qxnigupbo32qo3n3v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fmh2qxnigupbo32qo3n3v.png" alt="TradeDialer showing global stock market hours and countdown timers" width="800" height="819"&gt;&lt;/a&gt;&lt;br&gt;
&lt;/p&gt;




&lt;h2&gt;
  
  
  What I Learned so far
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;  Small tools are never small.&lt;/li&gt;
&lt;li&gt;  Time is harder than distributed systems sometimes.&lt;/li&gt;
&lt;li&gt;  Estimation is fiction.&lt;/li&gt;
&lt;li&gt;  AI helps a lot --- but experience still matters.&lt;/li&gt;
&lt;li&gt;  Perfection is the enemy of shipping.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At some point, you push it live.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Result
&lt;/h2&gt;

&lt;p&gt;TradeDialer&lt;/p&gt;

&lt;p&gt;A global stock market hours dashboard with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  Live open/close countdowns&lt;/li&gt;
&lt;li&gt;  Major indices&lt;/li&gt;
&lt;li&gt;  Clear filtering (active, sleeping, opens soon, closes soon)&lt;/li&gt;
&lt;li&gt;  Minimal cognitive load&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No predictions.&lt;br&gt;
No hype.&lt;br&gt;
No trading signals.&lt;/p&gt;

&lt;p&gt;Just time.&lt;/p&gt;




&lt;h2&gt;
  
  
  Maybe You're That Person
&lt;/h2&gt;

&lt;p&gt;If you’ve ever Googled:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Is NASDAQ open right now?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;ul&gt;
&lt;li&gt;  Or you feel slightly overwhelmed by full trading dashboards…&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Maybe this is for you.&lt;/p&gt;

&lt;p&gt;I’m not trying to replace professional platforms.&lt;/p&gt;

&lt;p&gt;I just wanted clarity.&lt;/p&gt;

&lt;p&gt;If that resonates, you can check it out here:&lt;/p&gt;

&lt;p&gt;👉 &lt;a href="https://trade-dialer.com" rel="noopener noreferrer"&gt;https://trade-dialer.com&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And if you think it’s useless — tell me why.&lt;/p&gt;

&lt;p&gt;That’s how small tools get better.&lt;/p&gt;

&lt;p&gt;But I did spend weeks building a very sophisticated clock. ⏰ &lt;/p&gt;

</description>
      <category>indiehackers</category>
      <category>startup</category>
      <category>webdev</category>
      <category>fintech</category>
    </item>
  </channel>
</rss>
