<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[TradingLookup Build Log]]></title><description><![CDATA[Engineering notes on collecting, checking and publishing open trading data: source pages, schemas, re-reads and the mistakes we caught.]]></description><link>https://tradinglookup.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6ad268295ed9a039e34bb943/4b76f979-e7b3-478b-b877-3f695c1120b5.png</url><title>TradingLookup Build Log</title><link>https://tradinglookup.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sun, 01 Nov 2026 12:52:51 GMT</lastBuildDate><atom:link href="https://tradinglookup.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Is the exchange open? Handling trading hours, holidays and DST across six markets]]></title><description><![CDATA[Exchange hours in code: DST gaps, half days, split Friday sessions and holidays for NYSE, LSE, NSE, PSX, NGX and JSE, with zoneinfo and Intl examples.]]></description><link>https://tradinglookup.hashnode.dev/is-the-exchange-open-handling-trading-hours-holidays-and-dst-across-six-markets</link><guid isPermaLink="true">https://tradinglookup.hashnode.dev/is-the-exchange-open-handling-trading-hours-holidays-and-dst-across-six-markets</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Python]]></category><category><![CDATA[timezone]]></category><category><![CDATA[datetime]]></category><category><![CDATA[fintech]]></category><dc:creator><![CDATA[TradingLookup]]></dc:creator><pubDate>Sat, 17 Oct 2026 16:09:05 GMT</pubDate><content:encoded><![CDATA[<p><em>By Alexander Burnett</em></p>
<p>"Is the market open?" sounds like a one-line function. Opening time, closing time, weekday check, done.</p>
<p>Then you add a second exchange in another country and it falls apart. Clocks change on different dates. Holidays follow different calendars. Some days close at lunchtime. One exchange splits Friday in two. One holds a session on a Sunday.</p>
<p>We keep a small dataset of exchange hours and holidays, read from each exchange's own pages, and a market clock on top of it. These are the problems we hit building it, with real dates from the file (as of 15 October 2026) and code you can lift.</p>
<h2>Rule 1: store local wall time plus an IANA zone, never a UTC offset</h2>
<p>Every exchange publishes its hours in local time, so store them that way:</p>
<table>
<thead>
<tr>
<th>Exchange</th>
<th>Zone</th>
<th>Regular hours (local)</th>
<th>Clock changes?</th>
</tr>
</thead>
<tbody><tr>
<td>NYSE</td>
<td>America/New_York</td>
<td>09:30–16:00</td>
<td>Yes (US rules)</td>
</tr>
<tr>
<td>LSE</td>
<td>Europe/London</td>
<td>not in our file yet</td>
<td>Yes (UK rules)</td>
</tr>
<tr>
<td>NSE</td>
<td>Asia/Kolkata</td>
<td>09:15–15:30</td>
<td>No</td>
</tr>
<tr>
<td>PSX</td>
<td>Asia/Karachi</td>
<td>Mon–Thu 09:32–15:30; Fri 09:17–12:00 and 14:32–16:30</td>
<td>No</td>
</tr>
<tr>
<td>NGX</td>
<td>Africa/Lagos</td>
<td>09:30–16:00</td>
<td>No</td>
</tr>
<tr>
<td>JSE</td>
<td>Africa/Johannesburg</td>
<td>not in our file yet</td>
<td>No</td>
</tr>
</tbody></table>
<p>The LSE and JSE gaps are deliberate. Our script keeps an hour only if the exchange's own page states it in words we can match, and for those two it didn't (the JSE's pages blocked our script or didn't state hours). We do hold LSE holidays and half days. An honest "unknown" beats a guessed open.</p>
<p>If you store "NYSE opens at 14:30 UTC", you're right for about seven months a year and wrong for the rest.</p>
<h2>Rule 2: the DST gap weeks are where bugs live</h2>
<p>The US and UK don't change clocks on the same day. In 2026, the UK falls back on Sunday 25 October and the US on Sunday 1 November. For one week the gap between London and New York is four hours, not five.</p>
<p>Here's the NYSE 09:30 open converted with Python's <code>zoneinfo</code> (run on 16 October 2026):</p>
<table>
<thead>
<tr>
<th>Date (2026)</th>
<th>UTC</th>
<th>London</th>
<th>Mumbai (IST)</th>
<th>Karachi (PKT)</th>
<th>Lagos (WAT)</th>
<th>Johannesburg (SAST)</th>
</tr>
</thead>
<tbody><tr>
<td>Fri 23 Oct</td>
<td>13:30</td>
<td>14:30 BST</td>
<td>19:00</td>
<td>18:30</td>
<td>14:30</td>
<td>15:30</td>
</tr>
<tr>
<td>Mon 26 Oct</td>
<td>13:30</td>
<td>13:30 GMT</td>
<td>19:00</td>
<td>18:30</td>
<td>14:30</td>
<td>15:30</td>
</tr>
<tr>
<td>Mon 2 Nov</td>
<td>14:30</td>
<td>14:30 GMT</td>
<td>20:00</td>
<td>19:30</td>
<td>15:30</td>
<td>16:30</td>
</tr>
</tbody></table>
<p>Look at the columns, not the rows. Between 23 and 26 October only London moves. Between 26 October and 2 November, London stays put and everyone else's NYSE open moves an hour later, because India, Pakistan, Nigeria and South Africa don't observe daylight saving. A trader in Lagos sees the US open at 14:30 in October and 15:30 in November without their own clock changing at all.</p>
<p>The fix is to do every conversion from the exchange's zone at the specific date, never by adding a fixed offset.</p>
<h2>Rule 3: JavaScript can format into a zone, but not out of one</h2>
<p><code>Intl.DateTimeFormat</code> will happily show a UTC instant in any IANA zone. What it won't do is the reverse: "09:30 in New York on 2 November, as UTC". You have to derive the offset yourself:</p>
<pre><code class="language-js">function zonedToUtc(dateStr, hhmm, tz) {
  const guess = new Date(`${dateStr}T${hhmm}:00Z`);
  const p = Object.fromEntries(
    new Intl.DateTimeFormat("en-US", {
      timeZone: tz, hourCycle: "h23",
      year: "numeric", month: "2-digit", day: "2-digit",
      hour: "2-digit", minute: "2-digit",
    }).formatToParts(guess).map(({ type, value }) =&gt; [type, value])
  );
  const wallAsUtc = Date.UTC(p.year, p.month - 1, p.day, p.hour, p.minute);
  return new Date(guess.getTime() - (wallAsUtc - guess.getTime()));
}

zonedToUtc("2026-10-26", "09:30", "America/New_York").toISOString();
// "2026-10-26T13:30:00.000Z"
zonedToUtc("2026-11-02", "09:30", "America/New_York").toISOString();
// "2026-11-02T14:30:00.000Z"
</code></pre>
<p>This one-pass version is fine for exchange opens, because none of them sit inside the 01:00–03:00 window where clocks jump. Don't reuse it for arbitrary times without a second pass. Once the Temporal API is available in your runtime, <code>Temporal.ZonedDateTime</code> replaces all of this.</p>
<h2>Rule 4: a holiday belongs to a market, not a country</h2>
<p>A US federal holiday doesn't close US stocks. On Monday 12 October 2026, Columbus Day, the US Office of Personnel Management listed a federal holiday and SIFMA recommended a full bond-market close, but the NYSE was open as normal. Veterans Day, 11 November 2026, is the same story.</p>
<p>Thanksgiving shows how far apart related markets can drift on one day. On Thursday 26 November 2026 the NYSE is closed, while CME Globex equity and interest-rate futures halt at 12:00 Chicago time. On Friday 27 November the NYSE closes at 13:00, CME's equity and rate futures at 12:15 Chicago time, and SIFMA's bond-market recommendation is a 14:00 close.</p>
<p>So key holidays by venue, and resist one shared <code>is_holiday(country)</code> helper.</p>
<h2>Rule 5: half days and different calendars</h2>
<p>Early closes are just a different close time on a known date, but they land at awkward moments across zones. On Thursday 24 December 2026:</p>
<ul>
<li>The LSE closes at 12:30 London time, which is 07:30 in New York, two hours before the NYSE opens.</li>
<li>The NYSE closes at 13:00 New York time, which is 18:00 in London and 23:30 in Mumbai.</li>
</ul>
<p>The LSE also has 12:30 half days on 31 December 2026 and on 24 and 31 December 2027. Then on Monday 28 December 2026 London and Lagos are both shut (Boxing Day, and its substitute in London) while New York trades.</p>
<p>Then there are calendars that aren't Gregorian at all. PSX and NGX holidays include Eid dates, which follow the moon. Our file lists Eid-ul-Fitr on 23 March 2026 at PSX and 20 and 23 March at NGX. Dates like these are announced close to the day and can shift, so treat any far-future Eid entry as provisional and re-read the exchange's notices.</p>
<h2>Rule 6: weekday logic has exceptions both ways</h2>
<p>Two from our file:</p>
<ul>
<li><strong>PSX splits Friday.</strong> Monday to Thursday is one session; Friday has a morning session (09:17–12:00) and an afternoon one (14:32–16:30) around midday prayers. In our JSON, those are separate entries in <code>sessions</code>, so code can't assume <code>sessions[0]</code> covers every day. PSX's hours page also shows this table under an old Ramadan heading, and its 2026 Ramadan notice gave different hours. Our reading that it's the standard timetable is ours, not PSX's, so check its notices.</li>
<li><strong>NSE trades on a Sunday.</strong> Muhurat Trading for Diwali falls on Sunday 8 November 2026, and the exchange hadn't published the session time when we read its page. Two days later, Tuesday 10 November (Diwali-Balipratipada), is a full holiday. See <a href="https://tradinglookup.com/market-hours/nse/">NSE's 2026 holiday list and session times</a> for the rest of the year.</li>
</ul>
<p>Another wrinkle: NSE's 09:15 open is 23:45 the previous evening in New York (on 16 October 2026). A date stamped by a US server isn't the trading date in Mumbai.</p>
<h2>Putting it together in Python</h2>
<p>Here's a small status check against the <code>market-hours.json</code> structure (<code>exchanges[]</code>, each with <code>tz</code>, <code>sessions</code>, <code>holidays</code> and <code>early_closes</code>):</p>
<pre><code class="language-python">import json
from datetime import datetime, time, timezone
from zoneinfo import ZoneInfo

data = json.load(open("market-hours.json"))
venues = {e["slug"]: e for e in data["exchanges"]}

def status(slug, when_utc):
    ex = venues[slug]
    if not ex["sessions"]:
        return "unknown (no verified hours)"
    local = when_utc.astimezone(ZoneInfo(ex["tz"]))
    day = local.date().isoformat()
    if any(h["date"] == day for h in ex["holidays"]):
        return "closed (holiday)"
    if local.weekday() &gt;= 5:
        return "closed (weekend)"
    s = ex["sessions"][0]
    close = next((e["close"] for e in ex["early_closes"] if e["date"] == day), s["close"])
    is_open = time.fromisoformat(s["open"]) &lt;= local.time() &lt; time.fromisoformat(close)
    return "open" if is_open else "closed"

utc = lambda *a: datetime(*a, tzinfo=timezone.utc)
status("nyse", utc(2026, 12, 24, 17, 45))  # 'open'   (12:45 New York)
status("nyse", utc(2026, 12, 24, 18, 15))  # 'closed' (13:15, after the early close)
status("nse",  utc(2026, 11, 10, 5, 0))    # 'closed (holiday)'
status("lse",  utc(2026, 12, 28, 10, 0))   # 'unknown (no verified hours)'
status("ngx",  utc(2026, 12, 28, 10, 0))   # 'closed (holiday)'
</code></pre>
<p>It's deliberately incomplete: it ignores PSX's Friday split, NSE's Sunday session and weekends that aren't Saturday–Sunday (the Saudi Exchange trades Sunday to Thursday). Each needs its own field rather than another <code>if</code>.</p>
<h2>What I'd tell my past self</h2>
<ol>
<li>Store local times with an IANA zone. Convert at the moment you need to, for that date.</li>
<li>Test the DST gap weeks explicitly (in 2026: 25 October to 1 November, and the March gap).</li>
<li>Treat holidays and early closes as per-venue data with a source and a date read, and let "unknown" be a real state.</li>
<li>Re-read the sources. Exchanges change hours, and moon-dated holidays move.</li>
</ol>
<p>If you'd rather check a result than run code, there's <a href="https://tradinglookup.com/market-hours/">a live market clock built on the same file</a>, which works out open or closed in your browser from these hours.</p>
<p><em>Times and dates here come from each exchange's own pages as read on 15 October 2026. Check the exchange's notices before relying on them; this isn't trading advice.</em></p>
<hr />
<p><em><a href="https://tradinglookup.com/authors/alex/">Alexander Burnett</a> is a co-founder of TradingLookup and a former day trader.</em></p>
]]></content:encoded></item></channel></rss>