Broker API HubMenu

Stage 2 of 4, lesson 8 of 31. Beginner

Dates, times and time zones in Python

Turn timestamps from text into values you can subtract, compare and show in another time zone. It introduces datetime, timedelta, UTC, zoneinfo and the naive datetime trap.

Independent and unofficialCode run

Risk warning: CFDs and spread bets are complex instruments and come with a high risk of losing money rapidly due to leverage. You can lose money on these products, and each provider publishes its own loss figure under FCA rules. You should consider whether you understand how they work and whether you can afford to take the high risk of losing your money. Automated and API trading can amplify losses through bugs, connectivity failures and runaway orders.

You need: The lesson on variables and types. On Windows, also run pip install tzdata once. No broker account.

Every trade has a time, and time is where quiet mistakes live. A timestamp with no zone attached could mean several different moments, and a day's trades depend on whose midnight you count from.

The habit that removes most of the trouble: keep every time in UTC inside your program, attached to its zone, and convert to a local clock only when you show it to a person. A datetime that knows its zone is called aware. One that does not is called naive, and Python refuses to subtract one from the other.

The script reads two invented timestamps, works out how long a position was held, and shows the same moments on a London clock and a New York clock. Look at the two London lines: the label changes from GMT to BST between them, because the clocks moved in between. Code that adds a fixed number of hours would have got one of them wrong.

Check the documentation of the API you use for the zone its timestamps are in. Do not assume.

The code

# Dates, times and time zones: the same moment reads differently in different places.
# No broker, no account, no internet. The timestamps are invented.
# On Windows, install the time zone data once with: pip install tzdata
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo

# An API usually sends a time as text. Turn it into a datetime that knows its zone.
opened = datetime.strptime("2025-03-28T15:30:00", "%Y-%m-%dT%H:%M:%S").replace(tzinfo=timezone.utc)
closed = datetime.fromisoformat("2025-03-31T09:15:00+00:00")
print("Opened (UTC):", opened.isoformat())
print("Closed (UTC):", closed.isoformat())

# Subtracting two datetimes gives a timedelta: a length of time.
held = closed - opened
print("Held for:", held)
print("In hours:", held.total_seconds() / 3600)

# The same two moments on a clock in London and in New York.
# The clocks in London went forward between them, so the offset changes.
london = ZoneInfo("Europe/London")
new_york = ZoneInfo("America/New_York")
print("Opened in London:", opened.astimezone(london).strftime("%d %B %Y %H:%M %Z"))
print("Closed in London:", closed.astimezone(london).strftime("%d %B %Y %H:%M %Z"))
print("Closed in New York:", closed.astimezone(new_york).strftime("%d %B %Y %H:%M %Z"))

# A datetime with no zone is called naive. Python refuses to mix the two kinds.
naive = datetime(2025, 3, 31, 9, 15)
print("A naive datetime has the zone:", naive.tzinfo)
try:
    print(closed - naive)
except TypeError as problem:
    print("Refused:", problem)

# Which day a trade belongs to depends on the zone you count in.
late = datetime.fromisoformat("2025-03-31T23:30:00+00:00")
print("Date in UTC:", late.date(), "Date in London:", late.astimezone(london).date())

# Adding a timedelta moves a datetime.
print("One week after opening:", (opened + timedelta(days=7)).isoformat())
print("Opened on a", opened.strftime("%A"))

What you should see

Opened (UTC): 2025-03-28T15:30:00+00:00
Closed (UTC): 2025-03-31T09:15:00+00:00
Held for: 2 days, 17:45:00
In hours: 65.75
Opened in London: 28 March 2025 15:30 GMT
Closed in London: 31 March 2025 10:15 BST
Closed in New York: 31 March 2025 05:15 EDT
A naive datetime has the zone: None
Refused: can't subtract offset-naive and offset-aware datetimes
Date in UTC: 2025-03-31 Date in London: 2025-04-01
One week after opening: 2025-04-04T15:30:00+00:00
Opened on a Friday

Run with Python 3.12 on Windows. No network and no account.

Check yourself

Answer each question from the code above before you open it. The answers were written from the script on this page and what it printed.

Both London lines come from UTC times. Why is one labelled GMT and the other BST?

London's offset from UTC is not fixed. The output shows 15:30 UTC as 15:30 GMT on 28 March and 09:15 UTC as 10:15 BST on 31 March, so the clocks went forward between the two. ZoneInfo knows the rule, which is why the script uses it instead of adding hours by hand.

What does the script print when it subtracts the naive datetime from the aware one?

Python raises a TypeError and the script prints Refused with the message: it cannot subtract offset-naive and offset-aware datetimes. The naive one has None for its zone, so the gap between them cannot be known.

A trade closes at 23:30 UTC on 31 March. Which day is it filed under?

It depends on the zone you count in. The script prints 2025-03-31 for UTC and 2025-04-01 for London, because London was an hour ahead that night.

Exercise

Add a line that prints the closing time on a clock in Tokyo, using the zone name Asia/Tokyo, straight after the New York line.

What the changed script prints
Opened (UTC): 2025-03-28T15:30:00+00:00
Closed (UTC): 2025-03-31T09:15:00+00:00
Held for: 2 days, 17:45:00
In hours: 65.75
Opened in London: 28 March 2025 15:30 GMT
Closed in London: 31 March 2025 10:15 BST
Closed in New York: 31 March 2025 05:15 EDT
Closed in Tokyo: 31 March 2025 18:15 JST
A naive datetime has the zone: None
Refused: can't subtract offset-naive and offset-aware datetimes
Date in UTC: 2025-03-31 Date in London: 2025-04-01
One week after opening: 2025-04-04T15:30:00+00:00
Opened on a Friday

Captured from a run of the solution on . Run with Python 3.12 on Windows. No network and no account.

One solution
# Exercise solution: the lesson script with the one change the exercise asks for.
# Dates, times and time zones: the same moment reads differently in different places.
# No broker, no account, no internet. The timestamps are invented.
# On Windows, install the time zone data once with: pip install tzdata
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo

# An API usually sends a time as text. Turn it into a datetime that knows its zone.
opened = datetime.strptime("2025-03-28T15:30:00", "%Y-%m-%dT%H:%M:%S").replace(tzinfo=timezone.utc)
closed = datetime.fromisoformat("2025-03-31T09:15:00+00:00")
print("Opened (UTC):", opened.isoformat())
print("Closed (UTC):", closed.isoformat())

# Subtracting two datetimes gives a timedelta: a length of time.
held = closed - opened
print("Held for:", held)
print("In hours:", held.total_seconds() / 3600)

# The same two moments on a clock in London and in New York.
# The clocks in London went forward between them, so the offset changes.
london = ZoneInfo("Europe/London")
new_york = ZoneInfo("America/New_York")
print("Opened in London:", opened.astimezone(london).strftime("%d %B %Y %H:%M %Z"))
print("Closed in London:", closed.astimezone(london).strftime("%d %B %Y %H:%M %Z"))
print("Closed in New York:", closed.astimezone(new_york).strftime("%d %B %Y %H:%M %Z"))
print("Closed in Tokyo:", closed.astimezone(ZoneInfo("Asia/Tokyo")).strftime("%d %B %Y %H:%M %Z"))

# A datetime with no zone is called naive. Python refuses to mix the two kinds.
naive = datetime(2025, 3, 31, 9, 15)
print("A naive datetime has the zone:", naive.tzinfo)
try:
    print(closed - naive)
except TypeError as problem:
    print("Refused:", problem)

# Which day a trade belongs to depends on the zone you count in.
late = datetime.fromisoformat("2025-03-31T23:30:00+00:00")
print("Date in UTC:", late.date(), "Date in London:", late.astimezone(london).date())

# Adding a timedelta moves a datetime.
print("One week after opening:", (opened + timedelta(days=7)).isoformat())
print("Opened on a", opened.strftime("%A"))

Your progress

Next: Read and write JSON

Your marks are kept in this browser only, in its local storage. They are not sent anywhere, there is no account, and they will not follow you to another browser or device. To remove them, use Clear my progress on the learning track page, or clear this site's data in your browser's settings. Marking needs JavaScript. With scripts off the track reads the same, without the ticks.

Where this sits in the path

Stage 2 of 4: Working with data. Read prices and trades from files, get dates and money right, and turn a list of trades into a journal.

  1. Prices from a file
  2. Dates and time zonesYou are here
  3. Read and write JSON
  4. Money with Decimal
  5. Build a trade journal
  6. Measure drawdown

The whole path, every stage

Back: Prices from a fileNext: Read and write JSON