Dates, times, and timezones
datetime, timedelta, formatting, parsing, and why every timestamp you store should be UTC.
Why this matters in AI / ML / GenAI
Time features drive forecasting and drift detection, log timestamps drive incident analysis, and token-usage reports are grouped by day. Naive local timestamps are a reliable source of off-by-hours bugs across regions.
1. Read
Understand the idea in plain English first.
2. Run
Load any example into the compiler and press Run.
3. Change
Edit one value, rerun, and learn from the output.
The objects
The datetime module gives you date, time, datetime, timedelta, and timezone.
datetime.now() returns local time; datetime.now(timezone.utc) returns UTC. Prefer UTC everywhere in stored data and logs, and convert to local only when displaying to a person.
A datetime is naive (no timezone) or aware (with one). Subtracting a naive from an aware datetime raises TypeError, which is Python protecting you from a meaningless result.
timedelta represents a duration. Subtracting two datetimes gives one; .total_seconds() converts it to a number you can log or compare.
Formatting and parsing
strftime(fmt) formats a datetime as text. strptime(text, fmt) parses text into a datetime.
Common codes: %Y 4-digit year, %m month, %d day, %H hour (24h), %M minute, %S second, %f microseconds, %z UTC offset, %A weekday name, %B month name.
For machine-readable output use ISO 8601: dt.isoformat() produces 2026-09-04T14:30:00+00:00, and datetime.fromisoformat() reads it back. ISO strings sort correctly as plain text, which is why they belong in filenames, JSON, and database columns.
Unix timestamps: dt.timestamp() and datetime.fromtimestamp(ts, tz=timezone.utc).
Practical rules
- Store UTC, display local. A user in Hyderabad and one in London must see the same event at their own wall-clock time.
- Use aware datetimes at boundaries — anything entering a database or an API.
- Never do date arithmetic with
timedelta(days=30)and call it "a month". Months vary; usedateutil.relativedeltaif you need calendar months. - Measure durations with
time.perf_counter(), notdatetime.now()differences.perf_counteris monotonic and unaffected by clock adjustments. - Include a timezone in log timestamps, or a cross-region incident becomes unreconstructable.
Hands-on practice
Compiler on the left. Examples on the right.
On desktop, keep the compiler beside the examples. On mobile, the same blocks stack cleanly. Pick an example, try it in the compiler, then change one small thing.
Build a run manifest with timestamps
Live Python compiler
Try it — in-browser Python
Change the training duration and confirm the finish time and rate update.
Code editor
Output
Python runs in your browser. First run downloads the runtime.
Press Run (or Ctrl+Enter) to execute.
Runs CPython in your browser. NumPy, pandas, scikit-learn and Matplotlib load on demand. Charts appear below the output. No input(), no GPU, no network installs.
Clear code examples
Every example is copy-ready. Use Try in compiler when you want to experiment without scrolling around.
Example
Creating and inspecting datetimes
Note the difference between naive local time and aware UTC.
from datetime import datetime, date, timedelta, timezone
naive = datetime(2026, 9, 4, 14, 30, 0)
aware = datetime(2026, 9, 4, 14, 30, 0, tzinfo=timezone.utc)
print("naive:", naive, "| tzinfo:", naive.tzinfo)
print("aware:", aware, "| tzinfo:", aware.tzinfo)
print("iso :", aware.isoformat())
print("parts:", aware.year, aware.month, aware.day, aware.hour)
print("weekday:", aware.strftime("%A"), "| month:", aware.strftime("%B"))
print("date only:", aware.date(), "| time only:", aware.time())
try:
naive - aware
except TypeError as err:
print("mixing naive and aware:", err)Example
timedelta arithmetic
Durations add and subtract like numbers.
from datetime import datetime, timedelta, timezone
start = datetime(2026, 9, 1, 9, 0, tzinfo=timezone.utc)
end = datetime(2026, 9, 4, 17, 30, tzinfo=timezone.utc)
duration = end - start
print("duration :", duration)
print("total hours :", round(duration.total_seconds() / 3600, 2))
print("days component :", duration.days)
print("\nretention cutoff:", (end - timedelta(days=30)).date())
print("next run :", (end + timedelta(hours=6)).isoformat())
print("timeout window :", timedelta(seconds=90) > timedelta(minutes=1))Example
Parsing and formatting log timestamps
strptime reads text; isoformat writes the machine-readable version.
from datetime import datetime, timezone
raw_lines = [
"2026-09-04 10:22:31 INFO started",
"2026-09-04 10:25:08 ERROR timeout",
]
events = []
for line in raw_lines:
stamp_text = line[:19]
stamp = datetime.strptime(stamp_text, "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc)
events.append((stamp, line[20:]))
for stamp, message in events:
print(stamp.isoformat(), "|", message)
gap = events[1][0] - events[0][0]
print("\ntime to failure:", gap.total_seconds(), "seconds")
print("round trip:", datetime.fromisoformat(events[0][0].isoformat()) == events[0][0])
print("display format:", events[0][0].strftime("%d %b %Y, %I:%M %p UTC"))Example
Group usage by day
The pattern behind every daily cost or traffic report.
from collections import defaultdict
from datetime import datetime, timezone
calls = [
{"ts": "2026-09-02T10:00:00+00:00", "tokens": 500},
{"ts": "2026-09-02T18:30:00+00:00", "tokens": 700},
{"ts": "2026-09-03T09:15:00+00:00", "tokens": 1200},
{"ts": "2026-09-04T11:45:00+00:00", "tokens": 300},
]
by_day = defaultdict(int)
for call in calls:
day = datetime.fromisoformat(call["ts"]).astimezone(timezone.utc).date()
by_day[day] += call["tokens"]
for day in sorted(by_day):
bar = "#" * (by_day[day] // 100)
print(f"{day} {by_day[day]:>5} tokens {bar}")
print("\ntotal:", sum(by_day.values()))Example
Measure durations with perf_counter
Monotonic clock — immune to system clock changes.
import time
from datetime import datetime, timezone
start = time.perf_counter()
total = sum(i * i for i in range(200_000))
elapsed_ms = (time.perf_counter() - start) * 1000
print(f"computed {total} in {elapsed_ms:.2f} ms")
print("logged at:", datetime.now(timezone.utc).isoformat(timespec="seconds"))
print("filename-safe stamp:", datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S"))Takeaways
- Store UTC with aware datetimes; convert to local only for display.
- isoformat/fromisoformat for machine data, strftime/strptime for human formats.
- Measure elapsed time with time.perf_counter, not datetime subtraction.