Skip to content

Python Date & Time — datetime, time, calendar

Python ships three complementary modules for working with dates and times:

  • datetime — high-level objects for dates, times, and durations. This is what you reach for most.
  • time — lower-level, OS-facing functions (timestamps, sleeping, performance timers).
  • calendar — calendar-related helpers (leap years, weekday names, month grids).
overview.py
import datetime, time, calendar
 
print(datetime.date.today())        # 2025-01-31
print(time.time())                  # 1738291200.123  (seconds since epoch)
print(calendar.isleap(2024))        # True

It defines four key classes:

ClassRepresents
dateA calendar date — year, month, day.
timeA time of day — hour, minute, second, microsecond.
datetimeA date and a time combined.
timedeltaA duration — the difference between two dates/times.
creating.py
from datetime import date, time, datetime
 
d = date(2025, 1, 31)
print(d.year, d.month, d.day)   # 2025 1 31
 
t = time(14, 30, 15)
print(t.hour, t.minute)         # 14 30
 
dt = datetime(2025, 1, 31, 14, 30, 15)
print(dt)                       # 2025-01-31 14:30:15
 
# "Now" helpers
print(date.today())             # today's date
print(datetime.now())           # local date + time right now
attributes.py
from datetime import datetime
 
dt = datetime(2025, 1, 31, 14, 30, 15)
print(dt.date())        # 2025-01-31
print(dt.time())        # 14:30:15
print(dt.weekday())     # 4  (Mon=0 ... Sun=6)
print(dt.isoweekday())  # 5  (Mon=1 ... Sun=7)
print(dt.isoformat())   # 2025-01-31T14:30:15

strftime (“string format time”) turns a date/time into a string using format codes.

CodeMeaningExample
%Y4-digit year2025
%y2-digit year25
%mMonth, zero-padded01
%BFull month nameJanuary
%bAbbreviated monthJan
%dDay of month31
%AFull weekday nameFriday
%aAbbreviated weekdayFri
%HHour (24-hour)14
%IHour (12-hour)02
%MMinute30
%SSecond15
%pAM / PMPM
%jDay of the year031
strftime.py
from datetime import datetime
 
dt = datetime(2025, 1, 31, 14, 30, 15)
print(dt.strftime("%Y-%m-%d"))            # 2025-01-31
print(dt.strftime("%A, %B %d, %Y"))       # Friday, January 31, 2025
print(dt.strftime("%I:%M %p"))            # 02:30 PM

strptime (“string parse time”) does the reverse — it reads a string into a datetime, given a format that describes its layout.

strptime.py
from datetime import datetime
 
s = "2025-01-31 14:30"
dt = datetime.strptime(s, "%Y-%m-%d %H:%M")
print(dt)             # 2025-01-31 14:30:00
print(type(dt))       # <class 'datetime.datetime'>
 
# ISO strings have a dedicated fast parser:
print(datetime.fromisoformat("2025-01-31T14:30:15"))

A timedelta is a span of time. Add or subtract it from dates; subtract two dates to get a timedelta.

timedelta.py
from datetime import date, datetime, timedelta
 
today = date(2025, 1, 31)
week_later = today + timedelta(days=7)
print(week_later)                 # 2025-02-07
 
start = datetime(2025, 1, 1, 9, 0)
end = datetime(2025, 1, 1, 17, 30)
worked = end - start
print(worked)                     # 8:30:00
print(worked.total_seconds())     # 30600.0
 
# timedelta accepts weeks, days, hours, minutes, seconds
print(timedelta(weeks=2, days=3)) # 17 days, 0:00:00

A datetime with no time zone is naive; one carrying tzinfo is aware. Prefer aware datetimes for anything crossing zones. Since Python 3.9 the standard library includes zoneinfo.

timezones.py
from datetime import datetime, timezone, timedelta
from zoneinfo import ZoneInfo
 
# UTC-aware "now"
now_utc = datetime.now(timezone.utc)
print(now_utc.tzinfo)             # UTC
 
# Convert between zones
ny = datetime(2025, 1, 31, 12, 0, tzinfo=ZoneInfo("America/New_York"))
tokyo = ny.astimezone(ZoneInfo("Asia/Tokyo"))
print(tokyo)                      # 2025-02-01 02:00:00+09:00
sketch Naive and aware datetimes do not mix p5.js
A datetime either carries a timezone or it does not, and the two kinds refuse to be compared or subtracted. The trap is that the function which sounds like it gives you UTC -- utcnow -- returns a NAIVE object with no timezone attached, so it looks right and behaves wrongly. It is deprecated from Python 3.12 for exactly this reason. Use now(timezone.utc), which returns an aware object, and keep everything aware from the moment it enters your program.

Lower-level utilities tied to the operating system clock.

FunctionPurpose
time.time()Seconds since the epoch (a float timestamp).
time.sleep(secs)Pause execution.
time.localtime()Convert a timestamp to a local struct_time.
time.gmtime()Convert a timestamp to a UTC struct_time.
time.strftime(fmt, t)Format a struct_time.
time.perf_counter()High-resolution timer for benchmarking.
time_module.py
import time
 
start = time.perf_counter()
time.sleep(0.1)                       # pause 100 ms
elapsed = time.perf_counter() - start
print(f"Slept for {elapsed:.3f}s")    # Slept for 0.100s
 
ts = time.time()
print(time.strftime("%Y-%m-%d", time.localtime(ts)))

Use perf_counter() to measure durations, and time() to get a timestamp. Never use time() for benchmarking — it can jump if the system clock changes.

Helpers for reasoning about months, weeks, and leap years.

FunctionReturns
calendar.isleap(year)True if the year is a leap year.
calendar.monthrange(y, m)(first_weekday, num_days) for that month.
calendar.weekday(y, m, d)Weekday index (Mon=0).
calendar.month(y, m)A text calendar for the month.
calendar.day_name[i]Full weekday name for index i.
calendar_module.py
import calendar
 
print(calendar.isleap(2024))           # True
print(calendar.monthrange(2025, 2))    # (5, 28)  -> starts Sat, 28 days
print(calendar.day_name[0])            # Monday
 
# Print a whole month grid
print(calendar.month(2025, 1))
age_in_days.py
from datetime import date
 
def days_until_birthday(birth_month, birth_day):
    today = date.today()
    this_year = date(today.year, birth_month, birth_day)
    next_bday = this_year if this_year >= today else date(today.year + 1, birth_month, birth_day)
    return (next_bday - today).days
 
print(days_until_birthday(12, 25))   # days until the next Dec 25
  • Mixing naive and aware datetimes raises TypeError when you subtract them. Pick one and stick with it.
  • weekday() vs isoweekday() — one starts Monday at 0, the other at 1.
  • strptime format must match exactly, including separators, or it raises ValueError.
  • time.sleep blocks the whole thread — fine for scripts, but use async patterns in servers.
  • Use datetime for dates, times, durations (timedelta), and arithmetic.
  • Format with strftime, parse with strptime / fromisoformat.
  • Prefer aware datetimes (timezone.utc, zoneinfo.ZoneInfo) when zones matter.
  • time gives timestamps, sleeping, and perf_counter for benchmarks.
  • calendar answers leap-year, weekday, and month-grid questions.

pch.coffeeTagline

pch.coffeeCta

pch.feedbackHeading

pch.feedbackSubheading