# Mastering Time with Python’s Standard Library: The Complete Guide to datetime > **Series 03 – From date/time arithmetic to time zones and format conversion, all in one go** Time isn’t just a “string.” Dates roll over, months change, daylight‑saving kicks in, and local conventions differ. When working with dates and times, it’s essential to separate the display string from the calculable time value and to include time‑zone handling in your workflow. ![A wizard’s clockwork workshop](/media/editor_temp/6/5843a4cb-f3a6-4d8d-9a13-89c1cbb0ef6c.png) In this post we’ll focus on the `datetime` module and cover: * Creating the current date and time * Calculating durations (`timedelta`) * Formatting and parsing strings (`strftime`, `strptime`) * Handling time zones (`timezone`, `zoneinfo`) * Common pitfalls and reliable patterns --- ## 1. What does `datetime` provide? {#sec-5f3cbdfb3d40} `datetime` contains several types that look similar but serve different purposes. * `date` – when you only need year‑month‑day * `time` – when you only need hour:minute:second * `datetime` – the most common, combining date and time * `timedelta` – the difference or duration between two moments * `timezone` – a fixed‑offset time zone (e.g., UTC+9) Python 3.9+ added the standard library `zoneinfo`, making it easier to work with regional time zones like `Asia/Tokyo`. --- ## 2. Getting “now”: naive vs aware {#sec-80466bd4f739} ### 2.1 What are naive and aware? {#sec-893b0793d467} `datetime` objects fall into two categories: * **Naive datetime** – no time‑zone information * **Aware datetime** – carries a `tzinfo` object Mixing the two in calculations or comparisons can raise errors or produce unexpected results. ### 2.2 Recommended default: start with UTC‑aware {#sec-f876fdec8f2b} Normalizing storage and calculations to UTC keeps things tidy. ```python from datetime import datetime, timezone utc_now = datetime.now(timezone.utc) # aware (UTC) print(utc_now) ``` When you need a local time, convert at the display stage. ```python from datetime import datetime, timezone from zoneinfo import ZoneInfo utc_now = datetime.now(timezone.utc) tokyo_now = utc_now.astimezone(ZoneInfo("Asia/Tokyo")) print(utc_now) print(tokyo_now) ``` --- ## 3. Date/time arithmetic: `timedelta` at the core {#sec-5497d843fd3e} ### 3.1 Adding and subtracting {#sec-0288780aa297} ```python from datetime import datetime, timedelta, timezone now = datetime.now(timezone.utc) print(now + timedelta(days=3)) print(now - timedelta(hours=2)) ``` ### 3.2 Difference between two moments {#sec-c781f333910b} ```python from datetime import datetime, timezone a = datetime(2026, 1, 1, tzinfo=timezone.utc) b = datetime(2026, 1, 10, tzinfo=timezone.utc) delta = b - a print(delta.days) # 9 print(delta.total_seconds()) # 777600.0 ``` ### 3.3 Expressing a “period” with `timedelta` {#sec-c81173d2a4f5} `timedelta` works in days, seconds, and microseconds. Roughly “one month later” can be approximated with `timedelta(days=30)`, but calendar‑based logic (e.g., the same day next month) requires tools like `dateutil` outside the standard library. --- ## 4. Formatting and parsing: `strftime` / `strptime` {#sec-a17a8e259a48} ### 4.1 datetime → string (`strftime`) {#sec-8d6f34025b2e} ```python from datetime import datetime, timezone dt = datetime(2026, 1, 30, 14, 5, 0, tzinfo=timezone.utc) print(dt.strftime("%Y-%m-%d %H:%M:%S %z")) ``` Common format patterns: * `%Y-%m-%d` : 2026-01-30 * `%H:%M:%S` : 14:05:00 * `%z` : +0000 (UTC offset) ### 4.2 string → datetime (`strptime`) {#sec-f2bbc1672485} ```python from datetime import datetime s = "2026-01-30 14:05:00" dt = datetime.strptime(s, "%Y-%m-%d %H:%M:%S") print(dt) ``` The resulting `dt` is **naive**. To attach a time zone, use `replace(tzinfo=...)`. ```python from datetime import datetime, timezone s = "2026-01-30 14:05:00" dt = datetime.strptime(s, "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc) print(dt) ``` > `replace(tzinfo=…)` labels the time without converting it. For conversion, use `astimezone()` (see next section). --- ## 5. Time zones: distinguishing `timezone` and `zoneinfo` {#sec-05b1d0f7ef82} ### 5.1 Fixed offset – use `timezone` {#sec-67fec65074ab} ```python from datetime import datetime, timezone, timedelta kst_fixed = timezone(timedelta(hours=9)) dt = datetime(2026, 1, 30, 12, 0, tzinfo=kst_fixed) print(dt) ``` ### 5.2 Regional zone – use `zoneinfo` {#sec-834b663bce83} Zones that observe daylight‑saving need `zoneinfo`. ```python from datetime import datetime, timezone from zoneinfo import ZoneInfo utc_now = datetime.now(timezone.utc) ny_now = utc_now.astimezone(ZoneInfo("America/New_York")) print(ny_now) ``` ### 5.3 `replace(tzinfo=…)` vs `astimezone(...)` {#sec-35bf0fbaa9df} * `replace(tzinfo=…)`: keeps the clock value, just tags a zone * `astimezone(...)`: converts the same instant to another zone’s clock Knowing this difference cuts many time‑zone bugs. --- ## 6. Common pitfalls {#sec-552ca3933648} ### 6.1 Mixing naive/aware {#sec-21a4c96b9022} * Keep internal calculations in **aware (UTC)** for stability. * Normalize external input to a single zone as soon as it arrives. ### 6.2 “Local time” varies by environment {#sec-e2bd08b413c3} `datetime.now()` follows the host’s local settings. In containers or servers, the local time might be UTC or something else. Prefer `datetime.now(timezone.utc)` to avoid surprises. ### 6.3 String parsing format mismatches {#sec-e52b753858ac} `strptime` fails if the format differs by even one character. If you expect multiple formats, pre‑process or try candidates sequentially. Ideally, standardize the input format. --- ## 7. Three handy patterns {#sec-b244af674a6a} ### 7.1 Store as ISO 8601 {#sec-4b1979b29466} ```python from datetime import datetime, timezone dt = datetime.now(timezone.utc) print(dt.isoformat()) # e.g., 2026-01-30T05:12:34.567890+00:00 ``` ### 7.2 File names with today’s date {#sec-ae13a5dac423} ```python from datetime import datetime stamp = datetime.now().strftime("%Y%m%d") filename = f"report_{stamp}.json" print(filename) ``` ### 7.3 Time remaining until a target {#sec-b509d1188ff7} ```python from datetime import datetime, timezone target = datetime(2026, 2, 1, 0, 0, tzinfo=timezone.utc) now = datetime.now(timezone.utc) remaining = target - now print(remaining) print(remaining.total_seconds()) ``` --- ## 8. Wrap‑up {#sec-9fa53305f95a} `datetime` moves you beyond simple string creation to a full‑blown, calculable time tool. Coupled with `timezone` and `zoneinfo`, you can write code that behaves consistently across environments. Next time, we’ll separate `random` into its own post, covering random generation, sampling, shuffling, seeding, and secure randomness with `secrets`. --- **Related posts:** - [Using datetime and timezone correctly in Django](/ko/whitedec/2025/11/10/django-datetime-timezone/) - [Django’s time‑management magic – the complete guide to `django.utils.timezone`](/ko/whitedec/2025/11/14/django-utils-timezone-guide/) --- **Previous series** - [[Python Standard Library – 0] What is the Python Standard Library? A beginner’s guide](/ko/whitedec/2026/1/29/python-standard-library/) - [[Python Standard Library – 1] Mastering the file system & OS environment: pathlib vs os](/ko/whitedec/2026/1/29/file-system-os-environment-master-pathlib-vs-os/) - [[Python Standard Library – 2] Data storage & serialization: json, pickle, csv](/ko/whitedec/2026/1/30/python-json-pickle-csv/)