Skip to content

Python Date & Time — datetime, time, calendar

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

  • datetimedatetime — high-level objects for dates, times, and durations. This is what you reach for most.
  • timetime — lower-level, OS-facing functions (timestamps, sleeping, performance timers).
  • calendarcalendar — 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
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

The datetimedatetime module

It defines four key classes:

ClassRepresents
datedateA calendar date — year, month, day.
timetimeA time of day — hour, minute, second, microsecond.
datetimedatetimeA date and a time combined.
timedeltatimedeltaA duration — the difference between two dates/times.

Creating dates and 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
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

Accessing parts

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
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

Formatting with strftimestrftime

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

CodeMeaningExample
%Y%Y4-digit year2025
%y%y2-digit year25
%m%mMonth, zero-padded01
%B%BFull month nameJanuary
%b%bAbbreviated monthJan
%d%dDay of month31
%A%AFull weekday nameFriday
%a%aAbbreviated weekdayFri
%H%HHour (24-hour)14
%I%IHour (12-hour)02
%M%MMinute30
%S%SSecond15
%p%pAM / PMPM
%j%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
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

Parsing with strptimestrptime

strptimestrptime (“string parse time”) does the reverse — it reads a string into a datetimedatetime, 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"))
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"))

Date arithmetic with timedeltatimedelta

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

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
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

Time zones (aware vs naive)

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

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
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

The timetime module

Lower-level utilities tied to the operating system clock.

FunctionPurpose
time.time()time.time()Seconds since the epoch (a float timestamp).
time.sleep(secs)time.sleep(secs)Pause execution.
time.localtime()time.localtime()Convert a timestamp to a local struct_timestruct_time.
time.gmtime()time.gmtime()Convert a timestamp to a UTC struct_timestruct_time.
time.strftime(fmt, t)time.strftime(fmt, t)Format a struct_timestruct_time.
time.perf_counter()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)))
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()perf_counter() to measure durations, and time()time() to get a timestamp. Never use time()time() for benchmarking — it can jump if the system clock changes.

The calendarcalendar module

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

FunctionReturns
calendar.isleap(year)calendar.isleap(year)TrueTrue if the year is a leap year.
calendar.monthrange(y, m)calendar.monthrange(y, m)(first_weekday, num_days)(first_weekday, num_days) for that month.
calendar.weekday(y, m, d)calendar.weekday(y, m, d)Weekday index (Mon=0).
calendar.month(y, m)calendar.month(y, m)A text calendar for the month.
calendar.day_name[i]calendar.day_name[i]Full weekday name for index ii.
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))
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))

Putting it together

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
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

Common pitfalls

  • Mixing naive and aware datetimes raises TypeErrorTypeError when you subtract them. Pick one and stick with it.
  • weekday()weekday() vs isoweekday()isoweekday() — one starts Monday at 0, the other at 1.
  • strptimestrptime format must match exactly, including separators, or it raises ValueErrorValueError.
  • time.sleeptime.sleep blocks the whole thread — fine for scripts, but use async patterns in servers.

Practice Exercises

Exercise 1 – Format today’s date

Exercise 2 – Days between two dates

Exercise 3 – Is it a leap year?

Summary

  • Use datetimedatetime for dates, times, durations (timedeltatimedelta), and arithmetic.
  • Format with strftimestrftime, parse with strptimestrptime / fromisoformatfromisoformat.
  • Prefer aware datetimes (timezone.utctimezone.utc, zoneinfo.ZoneInfozoneinfo.ZoneInfo) when zones matter.
  • timetime gives timestamps, sleeping, and perf_counterperf_counter for benchmarks.
  • calendarcalendar answers leap-year, weekday, and month-grid questions.

If this helped you, consider buying me a coffee ☕

Buy me a coffee

Was this page helpful?

Let us know how we did