Addaly is in open beta. Things will change, and AI answers can be wrong — check anything that matters.

Python, From Zero, For AI

From your first line of code to your first API call.

Lesson 44 of 899 min

Dates and times, and the hour that does not exist

The three types

python
from datetime import date, datetime, timedelta

date(2026, 9, 4)                       # a day, no time
datetime(2026, 9, 4, 14, 30)           # a day and a time
timedelta(days=7, hours=3)             # a duration

Arithmetic works between them:

python
d = date.today()
d + timedelta(days=30)                 # a date 30 days later
(datetime.now() - started).total_seconds()

total_seconds() is the one to remember. timedelta.seconds is the seconds component and excludes the days, so a two-day duration reports 0 seconds. This is one of the most reliable sources of wrong elapsed-time calculations.

Aware and naive

python
datetime.now()                  # naive — no timezone attached
datetime.now(timezone.utc)      # aware

A naive datetime is a number with no reference point. It cannot be compared with an aware one — Python raises TypeError: can't compare offset-naive and offset-aware datetimes rather than guessing — and it cannot be converted, because nothing records what it meant.

The rule that avoids nearly all timezone bugs:

Store and compute in UTC. Convert to local only when displaying.

python
from datetime import datetime, timezone
from zoneinfo import ZoneInfo

now = datetime.now(timezone.utc)                       # store this
local = now.astimezone(ZoneInfo("Asia/Kolkata"))       # show this

zoneinfo is in the standard library from Python 3.9 and reads the operating system's timezone database. On Windows there is often no such database, and you install the free tzdata package to supply one — a common and confusing first failure.

Note the timezone is named Asia/Kolkata, not IST. Abbreviations are ambiguous: IST is India, Israel and Ireland depending on who is speaking. Always use the region name.

Why an offset is not a timezone

India is UTC+5:30, permanently. Most places are not that simple: their offset changes twice a year. Storing "+05:30" records what the offset was at one moment; storing Asia/Kolkata records the rules, including future changes.

Two failures follow from getting this wrong:

  • The hour that does not exist. When clocks go forward, 02:30 local time simply did not occur. A naive datetime holding it cannot be converted to a real instant.
  • The hour that happens twice. When clocks go back, 01:30 occurs twice, and a timestamp without an offset is ambiguous. Sorting events by local time then puts them in the wrong order.

India has no daylight saving, which makes this invisible for a while and then very visible the first time you handle data from a country that does. The half-hour offset creates its own surprise in the other direction: code elsewhere that assumes whole-hour offsets produces times half an hour out.

Parsing and formatting

python
datetime.fromisoformat("2026-09-04T14:30:00+05:30")     # the good case
datetime.strptime("04/09/2026", "%d/%m/%Y")             # a known format
d.strftime("%d %b %Y")                                  # '04 Sep 2026'
d.isoformat()                                           # '2026-09-04'

The codes worth memorising: %Y four-digit year, %m month number, %d day, %H hour on 24, %M minute, %S second, %b short month name, %B full month name.

ISO 8601 — 2026-09-04 — is the format to store and exchange. It sorts correctly as text, it is unambiguous, and every language parses it. 04/09/2026 is 4 September in most of the world and 9 April in the United States, and no amount of care at your end fixes a file that mixes both. When you receive such a file, ask; do not infer from the rows where the day exceeds 12, because that only tells you about those rows.

strptime raises ValueError on anything that does not match exactly, which is the behaviour you want at a boundary. The free dateutil package guesses formats and is useful for messy input, at the cost of guessing.

Epoch seconds

python
ts = now.timestamp()                             # seconds since 1970-01-01 UTC
datetime.fromtimestamp(ts, tz=timezone.utc)

Epoch time is unambiguous and compact, which is why logs and APIs use it. Two cautions: fromtimestamp without tz gives you a naive local datetime, quietly reintroducing the problem; and some systems send milliseconds rather than seconds, which produces dates tens of thousands of years in the future if you do not divide by 1000.

What is deprecated

datetime.utcnow() returns a naive datetime that claims to be UTC while carrying no timezone, which combines the worst of both. It is deprecated from Python 3.12. Use datetime.now(timezone.utc).

Measuring elapsed time

For timing an operation, do not subtract wall-clock datetimes — the system clock can be adjusted mid-measurement, and has been known to produce negative durations.

python
import time
start = time.perf_counter()
do_work()
print(f"{time.perf_counter() - start:.3f}s")

perf_counter is monotonic: it only moves forward, and is unaffected by clock changes. Use datetime for when, perf_counter for how long.

The one thing to keep

Store instants in UTC as aware datetimes and convert with a named zone only for display, and use `perf_counter` rather than clock arithmetic to measure how long something took.

Before you move on

A logging system records `datetime.now()` on servers in Mumbai and Frankfurt and sorts the merged events by that field. Ordering is wrong by hours. What is the underlying defect?

Pick the one you would defend. Nobody sees your answer.

No ads. No data sale. No public scores on people. Ever.

© 2026 Addaly