TT Lab
Get started
Learn Learning paths Courses

Capital Markets and Settlement

What "today" means differs from system to system

Continue in TT Lab

In one line

A trading day is not a calendar day, sessions differ by market, and in a market with daylight saving time the same local time occurs twice or not at all. If you flatten these three into a fixed offset, it is quietly wrong twice a year, every transition weekend.

Why this was needed

Code that handles time is usually written twice. It runs fine when first written and gets fixed once on a transition weekend. The reason nobody knows there is a problem for the months in between is that the wrong value does not throw an exception and is simply stored an hour off.

In capital markets, this drift reaches money and reports right away. If the closing batch runs an hour early, the executions in between get pushed to the next day, and reconciliation is matched while off. And the transition dates differ by market. Every year there are several weeks in which European markets are still on winter time even after the US market has already moved to summer time, and during those weeks the gap between the two markets' closes differs from usual. There is no reason for a system not to need to know this.

How it works

First, convert by rule, not by offset. Converting local time to UTC is not "subtract nine hours." You have to look at what rules that region was under on that date. The source of those rules is the IANA time zone database, and in Python zoneinfo reads it. The background of how this module came into the standard library is written in PEP 615. If you hard-code a fixed offset in the code, every record after the transition date is off by an hour.

Second, on a transition day there are moments when the wall clock does not hold. In a market that moves the clock forward in spring, the skipped hour does not exist at all. A record bearing a time in that hour cannot be read as local time, and if you "just convert and store it," it quietly becomes a value an hour later. In a market that sets the clock back in autumn, the same wall-clock value passes twice, so from that time alone you cannot decide which of the two moments it is. Python distinguishes this second case with the fold attribute, but which one to use is a rule the business has to decide, not something the library decides for you.

Third, sessions and calendars are separate data. Around the regular session there are pre-open and post-close periods, there are half-day sessions, and there are market holidays. If you hard-code these as constants in the code, every new year needs a deployment. Keeping the calendar as data has the added benefit that the data itself can be validated. A half-day close written later than the regular close, or a day that is both a market holiday and a half-day, really does happen.

Fourth, the trading day is a label. For an overnight session that crosses midnight, an execution at one in the morning is today on the calendar but yesterday as a trading day. If you do not fix this label, which day's tally the same execution goes into differs by system, and reconciliation never matches. The rules for the notation itself are set by RFC 3339, but what counts as the trading day is the market's rule.

What it looks like in the field

The most commonly seen incident is the closing batch right after the transition weekend. If you fix a market's close in UTC, after the transition the batch runs at a time when that market is still open. Conversely, if you set it only in local time, it overlaps with another market's batch at the same moment and the two jobs collide over the same resource. In fact there is a stretch where, during summer time, the closes of two markets fall at exactly the same UTC time, and only during those weeks do batches back up.

The second most common is time strings without an offset. If a log or message has only 2025-03-30 01:30:00, you have to get which time zone it is from the documentation. And you also have to deal with the possibility that the time does not exist in that time zone.

The third is when the calendar is inside the code. If you hard-code holidays and half-days as constants, every new year needs a deployment, and in a rushed fix nobody ends up knowing where those values came from. Once you move the calendar out into data, from then on you can validate the calendar itself. A day written as both a market holiday and a half-day, or a half-day close written later than the regular close, really does happen, and one such line changes the whole tally of that day. This validation takes only a few lines, but if the calendar is inside the code, there is no place to write those few lines.

And there is one principle common to all this handling. Interpret a time once, the moment you receive it, and after that carry it only as an absolute time. If you carry a local-time string around and interpret it each time it is needed, the same value gets read differently at each layer of the system. You only need to convert it back to local time when showing it on the screen, and then which market's local time is determined by the context of that screen.

What you will do in the next lab

You build the session definitions and calendars of four markets and thirty executions, and convert them to UTC by time zone rules. You compare with the result of converting by a fixed offset to find the days that are off, separate the nonexistent times and the twice-occurring times on the transition day, and then assign the session window and the trading-day label. At the end you gather each market's close in UTC to find the days the batch windows overlap, and find two contradictions in the calendar itself.