When building a forex analytics system, time handling is often treated as an afterthought. That was true for us until we noticed some historical hourly candles sitting in the wrong positions. The price data was correct, but the time axis had shifted. The cause? Daylight saving time transitions that weren’t accounted for in our data pipeline.
This post covers how we now handle DST shifts when working with a forex API and historical K-line data. We’ll walk through the problem, the schema we use, and the code that prevents future time-axis corruption.
The Problem: DST Start Day Changes UTC Mapping
On the day DST starts, the trading session doesn’t lose data. What changes is the UTC offset for local trading hours.
For U.S. markets during standard time, 09:00 New York = 14:00 UTC. During DST, 09:00 New York = 13:00 UTC.
| Time Status | Local Trading Time | UTC Time |
|---|---|---|
| Standard Time | 09:00 | 14:00 |
| DST Active | 09:00 | 13:00 |
If your K-line generator uses a fixed offset, candles around the switch date may be assigned to the wrong hourly bucket. This effect is most visible on minute and hourly charts.
Data Model: Add Time Metadata, Don’t Overwrite
Our rule is simple: keep the original timestamp, but add fields that describe the time context.
We store these four fields for every candle:
| Field | Purpose |
|---|---|
| utc_time | Unified time standard |
| local_time | Market local time |
| timezone | Timezone identifier |
| dst_status | DST active or not |
Example record:
{
"symbol": "EURUSD",
"local_time": "2026-03-08 09:00:00",
"utc_time": "2026-03-08T13:00:00Z",
"dst_status": "active"
}
With this, you always know the exact time environment for each candle.
Code: Avoid Fixed Offsets, Use Timezone Rules
Many developers convert timezones by adding or subtracting hours. That breaks on DST transition days because the offset changes.
Use timezone-aware conversion instead. Here’s a Python example:
from datetime import datetime
import pytz
timezone = pytz.timezone("US/Eastern")
time_str = "2026-03-08 09:00:00"
local_time = datetime.strptime(
time_str,
"%Y-%m-%d %H:%M:%S"
)
local_time = timezone.localize(local_time)
utc_time = local_time.astimezone(pytz.utc)
print(utc_time)
This method requires no manual DST tables and remains accurate across years.
Real-Time and Historical Data: Keep Them Consistent
If real-time ticks and historical candles use different time standards, you’ll get gaps in your analysis. We always normalize timestamps before generating K-lines.
For example, when receiving live forex data through AllTick API, we convert the tradeTime field to UTC first, then build minute and hourly bars. The following WebSocket snippet shows the normalization step:
import websocket
import json
from datetime import datetime
import pytz
def on_message(ws, message):
data = json.loads(message)
trade_time = data["tradeTime"]
tz = pytz.timezone("US/Eastern")
dt = datetime.strptime(
trade_time,
"%Y-%m-%d %H:%M:%S"
)
dt = tz.localize(dt)
utc_time = dt.astimezone(pytz.utc)
print(data["symbol"], utc_time)
ws = websocket.WebSocketApp(
"wss://api.alltick.co/stock/websocket",
on_message=on_message
)
ws.run_forever()
Practical Checklist for Developers
Based on our experience, we recommend:
- Add time metadata during ingestion, not during analysis.
- Use UTC as the internal standard; local time is for display only.
- Never use fixed hour offsets for K-line generation.
- Apply the same time-handling logic to both real-time and historical pipelines.
A forex API provides raw market data. Building a reliable system means handling timezones and DST at the storage level. Treat UTC as your source of truth and local time as presentation; this approach reduces long-term data inconsistencies.

Top comments (0)