Python Tutorial / Work with Dates, Times and Time Zones

Use date for a calendar day and datetime with zoneinfo when an instant and time zone matter. Distinguish time-zone-aware values from naive ones, and define storage and display conventions explicitly.

Separate dates, durations and instants

date represents a calendar date, datetime combines a date and time, and timedelta represents a duration. A naive datetime has no time-zone information; an aware datetime includes it. A displayed 09:00 in Seoul can represent a different instant from 09:00 UTC.

Parse ISO-style date input with fromisoformat and handle invalid dates as ValueError. Adding 30 days does not mean advancing one calendar month, so month-end behavior requires a separate policy.

For the relevant APIs and platform requirements, see Python: zoneinfo.

Business-date input and JSON representation build on the rules demonstrated in Final Project: Build an SQLite Expense Manager.

Convert a UTC instant to Seoul time

Save the complete code below as dates.py in a separate practice folder, using UTF-8. Run python dates.py in a terminal with your project virtual environment selected. Outside a virtual environment on macOS or Ubuntu, use python3 when that is your interpreter command.

from datetime import date, datetime, timedelta, timezone
from zoneinfo import ZoneInfo

day = date.fromisoformat("2026-10-17")
print(day + timedelta(days=7))
utc = datetime(2026, 10, 17, 0, 0, tzinfo=timezone.utc)
seoul = utc.astimezone(ZoneInfo("Asia/Seoul"))
print(utc.isoformat())
print(seoul.isoformat())

Expected output:

2026-10-24
2026-10-17T00:00:00+00:00
2026-10-17T09:00:00+09:00

Prepare zone data and convert correctly

astimezone changes the representation while preserving the instant. replace(tzinfo=...) keeps the clock fields and assigns a time-zone interpretation; it is not a general substitute for conversion. Without knowing the source time zone, an accurate conversion may be impossible.

zoneinfo uses IANA time-zone data. If a system without that data raises ZoneInfoNotFoundError, install the tzdata package in the project environment with python -m pip install tzdata. Time-zone rules can change, so deployed data also requires maintenance.

If the environment lacks time-zone data, run python -m pip install tzdata before this comparison. Converting the UTC instant with astimezone gives 09:00 in Seoul; attaching the Seoul zone with replace preserves 00:00 and identifies a different instant. Subtracting aware and naive values raises TypeError. Do not invent a UTC interpretation for data whose source zone is unknown.

Save the complete example as zones.py and run python zones.py in the same practice directory.

from datetime import datetime, timezone
from zoneinfo import ZoneInfo
utc = datetime(2026, 10, 17, 0, 0, tzinfo=timezone.utc)
print(utc.astimezone(ZoneInfo("Asia/Seoul")).isoformat())
print(utc.replace(tzinfo=ZoneInfo("Asia/Seoul")).isoformat())
try:
    print(datetime(2026, 10, 17) - utc)
except TypeError:
    print("naive and aware cannot be subtracted")

Expected output:

2026-10-17T09:00:00+09:00
2026-10-17T00:00:00+09:00
naive and aware cannot be subtracted

Official behavior and conditions: Python: zoneinfo data sources

Choose a storage rule and test date boundaries

For transaction instants, consider a consistent UTC or offset-aware representation and convert for display. When only the expense’s calendar day matters, storing a date can be clearer than inventing a midnight timestamp.

  1. Change the UTC hour to 23 and verify that Seoul displays the following date.
  2. Try parsing 2026-02-30 and verify that it is rejected.
  3. Compare a naive and aware datetime and explain why subtraction can fail.

Keep the distinction between a business date and an absolute instant visible throughout input, storage and reporting.