|$ curl https://forge-ai.dev/api/markdown?path=docs/python/datetime-time
$cat docs/datetime-&-time.md
updated Today·18-24 min read·published
datetime & Time
Introduction
Prefer timezone-aware datetimes. The modern stdlib stack is datetime + zoneinfo (IANA tz database). Avoid naive datetimes in production systems.
datetime Basics
dt_basics.py
Python
| 1 | from datetime import datetime, date, time, timedelta, timezone |
| 2 | |
| 3 | now = datetime.now(timezone.utc) |
| 4 | print(now.isoformat()) |
| 5 | print(date.today(), time(12, 30)) |
| 6 | print(datetime(2026, 7, 30, 12, 0, tzinfo=timezone.utc)) |
| 7 | |
| 8 | delta = timedelta(days=1, hours=2, minutes=30) |
| 9 | print(now + delta) |
| 10 | print((now - delta).timestamp()) |
zoneinfo
zoneinfo_demo.py
Python
| 1 | from datetime import datetime, timezone |
| 2 | from zoneinfo import ZoneInfo |
| 3 | |
| 4 | utc = datetime.now(timezone.utc) |
| 5 | ny = utc.astimezone(ZoneInfo("America/New_York")) |
| 6 | tokyo = utc.astimezone(ZoneInfo("Asia/Tokyo")) |
| 7 | print(ny.isoformat(), tokyo.isoformat()) |
| 8 | |
| 9 | # construct in a zone |
| 10 | meeting = datetime(2026, 11, 1, 9, 0, tzinfo=ZoneInfo("America/New_York")) |
| 11 | print(meeting.astimezone(timezone.utc)) |
📝
note
On some Windows installs you may need the tzdata package: pip install tzdata.
Parsing & Formatting
parse.py
Python
| 1 | from datetime import datetime, timezone |
| 2 | |
| 3 | s = "2026-07-30T12:00:00+00:00" |
| 4 | dt = datetime.fromisoformat(s) |
| 5 | print(dt) |
| 6 | |
| 7 | # strptime / strftime |
| 8 | raw = "30/07/2026 12:00" |
| 9 | dt2 = datetime.strptime(raw, "%d/%m/%Y %H:%M").replace(tzinfo=timezone.utc) |
| 10 | print(dt2.strftime("%Y-%m-%dT%H:%M:%SZ")) |
Comparisons & Arithmetic
compare.py
Python
| 1 | from datetime import datetime, timezone, timedelta |
| 2 | from zoneinfo import ZoneInfo |
| 3 | |
| 4 | a = datetime(2026, 1, 1, tzinfo=timezone.utc) |
| 5 | b = datetime(2026, 1, 1, tzinfo=ZoneInfo("America/New_York")) |
| 6 | # Aware datetimes compare correctly across zones: |
| 7 | print(a > b) |
| 8 | print(a - b) |
| 9 | |
| 10 | # Mixing naive and aware raises TypeError |
| 11 | # datetime.now() - a # TypeError |
✕
danger
Never compare naive and aware datetimes. Store UTC; convert for display.
DST Edge Cases
dst.py
Python
| 1 | from datetime import datetime |
| 2 | from zoneinfo import ZoneInfo |
| 3 | |
| 4 | tz = ZoneInfo("America/New_York") |
| 5 | # fold attribute distinguishes ambiguous wall times |
| 6 | ambiguous = datetime(2026, 11, 1, 1, 30, tzinfo=tz, fold=0) |
| 7 | ambiguous2 = datetime(2026, 11, 1, 1, 30, tzinfo=tz, fold=1) |
| 8 | print(ambiguous.astimezone(), ambiguous2.astimezone()) |
pendulum (mention)
Third-party pendulum offers nicer ergonomics (duration math, parsing). Prefer stdlib zoneinfo unless you need pendulum's extras.
pendulum_demo.py
Python
| 1 | # pip install pendulum |
| 2 | # import pendulum |
| 3 | # dt = pendulum.now("Europe/Paris") |
| 4 | # print(dt.add(days=1).to_iso8601_string()) |
timedelta Patterns
td.py
Python
| 1 | from datetime import timedelta |
| 2 | |
| 3 | def humanize(td: timedelta) -> str: |
| 4 | secs = int(td.total_seconds()) |
| 5 | sign = "-" if secs < 0 else "" |
| 6 | secs = abs(secs) |
| 7 | h, rem = divmod(secs, 3600) |
| 8 | m, s = divmod(rem, 60) |
| 9 | return f"{sign}{h:02d}:{m:02d}:{s:02d}" |
| 10 | |
| 11 | print(humanize(timedelta(hours=2, minutes=5, seconds=1))) |
| 12 | print(timedelta(weeks=1).total_seconds()) |
Storage Recommendations
| Layer | Recommendation |
|---|---|
| Database | TIMESTAMPTZ / UTC instant |
| JSON APIs | ISO 8601 with offset or Z |
| Logs | UTC ISO |
| UI | Convert with zoneinfo per user tz |
| Naive datetime | Avoid except date-only domains |
date & time Objects
date_time.py
Python
| 1 | from datetime import date, time, datetime, timezone |
| 2 | |
| 3 | d = date(2026, 7, 30) |
| 4 | t = time(14, 30, tzinfo=timezone.utc) |
| 5 | print(d.isoformat(), t.isoformat()) |
| 6 | print(datetime.combine(d, t)) |
| 7 | print(d.toordinal(), date.fromordinal(d.toordinal())) |
| 8 | print(d.weekday()) # Monday=0 |
calendar Module
calendar_mod.py
Python
| 1 | import calendar |
| 2 | print(calendar.month(2026, 7)) |
| 3 | print(calendar.isleap(2024)) |
| 4 | print(calendar.monthrange(2026, 2)) # weekday of first day, days in month |
time Module (clocks)
time_mod.py
Python
| 1 | import time |
| 2 | |
| 3 | t0 = time.perf_counter() |
| 4 | time.sleep(0.01) |
| 5 | print(time.perf_counter() - t0) |
| 6 | |
| 7 | print(time.time()) # wall clock epoch seconds |
| 8 | print(time.monotonic()) # non-decreasing clock for durations |
✓
best practice
Use time.perf_counter/monotonic for measuring durations; datetime for civil time.
With Pydantic
pyd_dt.py
Python
| 1 | from datetime import datetime |
| 2 | from pydantic import BaseModel |
| 3 | |
| 4 | class Event(BaseModel): |
| 5 | starts_at: datetime |
| 6 | |
| 7 | e = Event.model_validate({"starts_at": "2026-07-30T12:00:00Z"}) |
| 8 | print(e.starts_at, e.starts_at.tzinfo) |
Pitfalls
- datetime.now() without tz → naive
- utcnow() is deprecated — use now(timezone.utc)
- Localize with pytz .localize carefully — prefer zoneinfo
- Storing local wall time without zone loses meaning
dateutil mention
For relative deltas like "next month", the third-party python-dateutil relativedelta is common. Stdlib timedelta cannot express calendar months.
dateutil_demo.py
Python
| 1 | # pip install python-dateutil |
| 2 | # from dateutil.relativedelta import relativedelta |
| 3 | # from datetime import datetime, timezone |
| 4 | # print(datetime.now(timezone.utc) + relativedelta(months=+1)) |
FAQ
| Question | Answer |
|---|---|
| Store UTC or local? | UTC instant; convert for display |
| pytz or zoneinfo? | zoneinfo (3.9+) |
| Measure latency? | time.perf_counter |
| Parse Z suffix? | fromisoformat handles +00:00; replace Z |
parse_z.py
Python
| 1 | from datetime import datetime |
| 2 | |
| 3 | def parse_iso(s: str) -> datetime: |
| 4 | return datetime.fromisoformat(s.replace("Z", "+00:00")) |
| 5 | |
| 6 | print(parse_iso("2026-07-30T12:00:00Z")) |
Summary
Always prefer aware datetimes with zoneinfo, store UTC, convert for display, and use perf_counter for measuring durations. Replace deprecated utcnow() with now(timezone.utc).
utcnow_fix.py
Python
| 1 | from datetime import datetime, timezone |
| 2 | # BAD: datetime.utcnow() # naive |
| 3 | # GOOD: |
| 4 | print(datetime.now(timezone.utc)) |
Checklist
- datetime.now(timezone.utc) for current instant
- zoneinfo for named zones
- ISO 8601 in APIs
- No naive/aware mixing
- perf_counter for benchmarks
- tzdata installed on Windows if needed
ISO Calendar
iso.py
Python
| 1 | from datetime import date |
| 2 | d = date(2026, 7, 30) |
| 3 | print(d.isocalendar()) # year, week, weekday |
| 4 | print(d.isoweekday()) # Monday=1 |
$Blueprint — Engineering Documentation·Section ID: PYTHON-DATETIME·Revision: 1.0
Community
Get help on Slack, Discord or VIP
Stuck on a guide? Join the community and ask.