Dates and times are fundamental to nearly every application—from logging timestamps and scheduling tasks to calculating durations and processing time-series data. Python’s standard library provides powerful, built-in tools to work with dates and times, eliminating the need for third-party dependencies in most cases. In this guide, we’ll explore the core modules for date-time handling: datetime, time, and calendar. By the end, you’ll be equipped to parse, format, manipulate, and compare dates and times with confidence.
Table of Contents
- Introduction
- The
datetimeModule - The
timeModule: Low-Level Time Operations - The
calendarModule: Calendar-Specific Functions - Common Operations
- Pitfalls to Avoid
- Conclusion
- References
The datetime Module
The datetime module is the cornerstone of Python’s date-time handling. It defines five core classes: date, time, datetime, timedelta, and tzinfo (for time zones). Python 3.9+ adds zoneinfo, a concrete implementation of tzinfo for IANA time zones (e.g., America/New_York).
The date Class: Working with Dates
The date class represents a date (year, month, day) independent of time. It supports creating dates, accessing components, and performing date arithmetic.
Creating a date Object
Use date(year, month, day) to create a date. Month and day must be valid (e.g., no February 30th).
from datetime import date
# Create a date object for October 5, 2023
my_date = date(2023, 10, 5)
print(my_date) # Output: 2023-10-05
Key Attributes and Methods
- Attributes:
year,month,day(e.g.,my_date.year→2023). today(): Get the current local date.weekday()/isoweekday(): Return the weekday (0=Monday to 6=Sunday forweekday(); 1=Monday to 7=Sunday forisoweekday()).strftime(format): Format the date as a string (see formatting section).
today = date.today()
print(f"Today: {today}") # Output: Today: 2023-10-05 (varies by current date)
print(f"Weekday: {today.weekday()}") # e.g., 3 (Thursday)
print(f"ISO Weekday: {today.isoweekday()}") # e.g., 4 (Thursday)
The time Class: Working with Times
The time class represents a time (hour, minute, second, microsecond) independent of dates. It supports 24-hour and 12-hour formats, but avoids time zones by default (naive time).
Creating a time Object
Use time(hour=0, minute=0, second=0, microsecond=0, tzinfo=None). All arguments are optional.
from datetime import time
# 2:30:45 PM with 123 microseconds
my_time = time(14, 30, 45, 123)
print(my_time) # Output: 14:30:45.000123
# Midnight (default values)
midnight = time()
print(midnight) # Output: 00:00:00
Key Attributes
hour,minute,second,microsecond: Access individual time components.tzinfo: Time zone information (Nonefor naive times).
The datetime Class: Combining Dates and Times
The datetime class combines date and time into a single object, representing a specific moment (e.g., October 5, 2023, 14:30:45). It inherits methods from both date and time.
Creating a datetime Object
Use datetime(year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None), or convenience methods like datetime.now() (current local time) or datetime.utcnow() (current UTC time).
from datetime import datetime
# Explicit creation
dt = datetime(2023, 10, 5, 14, 30, 45)
print(dt) # Output: 2023-10-05 14:30:45
# Current local time
now_local = datetime.now()
print(now_local) # e.g., 2023-10-05 15:45:30.123456
# Current UTC time (no time zone info → naive)
now_utc = datetime.utcnow()
print(now_utc) # e.g., 2023-10-05 22:45:30.123456
Accessing Components
datetime objects expose year, month, day, hour, minute, etc., as attributes:
print(dt.year) # 2023
print(dt.hour) # 14
print(dt.weekday()) # 3 (Thursday, same as date.weekday())
The timedelta Class: Time Intervals
A timedelta represents a duration (e.g., 3 days, 2 hours) or difference between two date/datetime objects. Use it for arithmetic like adding/subtracting time from dates.
Creating a timedelta
timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0). All arguments are optional and can be negative.
from datetime import timedelta
# 1 week and 2 days
delta = timedelta(weeks=1, days=2)
print(delta) # 9 days, 0:00:00 (1 week = 7 days)
# 3 hours and 30 minutes
delta = timedelta(hours=3, minutes=30)
print(delta) # 3:30:00
Arithmetic with timedelta
Add/subtract timedelta to/from date/datetime objects:
from datetime import date, timedelta
today = date.today()
tomorrow = today + timedelta(days=1)
last_week = today - timedelta(weeks=1)
print(today) # 2023-10-05
print(tomorrow) # 2023-10-06
print(last_week) # 2023-09-28
Subtract two datetime objects to get a timedelta:
from datetime import datetime
start = datetime(2023, 1, 1)
end = datetime(2023, 1, 5)
duration = end - start
print(duration) # 4 days, 0:00:00
print(duration.days) # 4 (total days)
Time Zones: tzinfo and zoneinfo
By default, datetime objects are naive (no time zone info). To work with time zones, use aware objects. Python 3.9+ introduced zoneinfo (standard library) for IANA time zones (e.g., Europe/Paris). For older versions, third-party libraries like pytz are common, but we’ll focus on zoneinfo here.
Using zoneinfo
zoneinfo.ZoneInfo loads time zone data from the system or the tzdata package (install with pip install tzdata for full IANA support).
from datetime import datetime
from zoneinfo import ZoneInfo # Python 3.9+
# Create an aware datetime (UTC)
utc_dt = datetime(2023, 10, 5, 12, tzinfo=ZoneInfo("UTC"))
print(utc_dt) # 2023-10-05 12:00:00+00:00
# Convert to New York time (EDT/EST)
ny_dt = utc_dt.astimezone(ZoneInfo("America/New_York"))
print(ny_dt) # 2023-10-05 08:00:00-04:00 (EDT is UTC-4)
# Get current time in Tokyo
tokyo_now = datetime.now(ZoneInfo("Asia/Tokyo"))
print(tokyo_now) # e.g., 2023-10-06 01:00:00+09:00
Key Notes:
- IANA Time Zone Names: Use names like
Europe/LondonorAsia/Singapore(full list here). - Naive vs. Aware: Always prefer aware
datetimeobjects to avoid ambiguity (e.g., “3 PM” could be local time or UTC).
The time Module: Low-Level Time Operations
The time module provides low-level functions for working with epoch time (seconds since January 1, 1970, UTC) and time-related system calls. It’s useful for measuring execution time or sleeping.
Epoch Time
time.time() returns the current epoch time as a float (seconds + microseconds):
import time
epoch_seconds = time.time()
print(epoch_seconds) # e.g., 1696500000.123456
Convert epoch time to a readable struct_time (tuple-like object) with time.localtime() (local time) or time.gmtime() (UTC):
local_time = time.localtime(epoch_seconds)
print(local_time) # time.struct_time(tm_year=2023, tm_mon=10, ..., tm_sec=0)
utc_time = time.gmtime(epoch_seconds)
print(utc_time.tm_hour) # UTC hour
Formatting with strftime
time.strftime(format, struct_time) formats a struct_time into a string using format codes (same as datetime.strftime):
formatted = time.strftime("%Y-%m-%d %H:%M:%S", local_time)
print(formatted) # 2023-10-05 14:00:00
Sleeping
time.sleep(seconds) pauses execution for the given duration (supports fractions of a second):
print("Start")
time.sleep(2) # Pause for 2 seconds
print("End")
The calendar Module: Calendar-Specific Functions
The calendar module simplifies calendar-related tasks, such as generating monthly calendars, checking leap years, or finding the number of days in a month.
Checking Leap Years
calendar.isleap(year) returns True if the year is a leap year:
import calendar
print(calendar.isleap(2024)) # True (divisible by 4, not by 100 unless by 400)
print(calendar.isleap(2023)) # False
Days in a Month
calendar.monthrange(year, month) returns a tuple (first_weekday, num_days), where first_weekday is 0 (Monday) to 6 (Sunday):
first_weekday, num_days = calendar.monthrange(2023, 2)
print(num_days) # 28 (February 2023 has 28 days)
print(first_weekday) # 2 (Wednesday, since 2023-02-01 is a Wednesday)
Generating Calendars
calendar.month(year, month) prints a text-based monthly calendar:
print(calendar.month(2023, 10))
# October 2023
# Mo Tu We Th Fr Sa Su
# 1 2 3 4 5
# 6 7 8 9 10 11 12
# 13 14 15 16 17 18 19
# 20 21 22 23 24 25 26
# 27 28 29 30 31
calendar.calendar(year) generates a full-year calendar.
Common Operations
Parsing Strings to Date/Time
Use datetime.strptime(string, format) to parse a string into a datetime object. Specify the format with strftime directives.
Example: Parse “2023-10-05 14:30:00”:
from datetime import datetime
date_str = "2023-10-05 14:30:00"
dt = datetime.strptime(date_str, "%Y-%m-%d %H:%M:%S")
print(dt) # 2023-10-05 14:30:00
Formatting Date/Time to Strings
Use datetime.strftime(format) to convert a datetime object to a string. Common directives:
%Y: 4-digit year (2023)%m: 2-digit month (10)%d: 2-digit day (05)%H: 24-hour (14),%I: 12-hour (02)%M: minute (30),%S: second (00)%p: AM/PM (PM)
Example: Format to “October 5, 2023 (Thursday) at 2:30 PM”:
formatted = dt.strftime("%B %d, %Y (%A) at %I:%M %p")
print(formatted) # October 05, 2023 (Thursday) at 02:30 PM
Arithmetic with Dates and Times
Add/subtract timedelta to date/datetime objects (see timedelta section).
Comparing Dates and Times
datetime/date objects support comparison operators (<, >, ==):
dt1 = datetime(2023, 10, 5)
dt2 = datetime(2023, 10, 6)
print(dt1 < dt2) # True (dt1 is earlier)
print(dt1 == dt2) # False
Handling Time Zones in Practice
Convert between time zones using astimezone() with zoneinfo:
from datetime import datetime
from zoneinfo import ZoneInfo
# Start with UTC time
utc_dt = datetime(2023, 10, 5, 12, tzinfo=ZoneInfo("UTC"))
# Convert to London time (BST is UTC+1 in October)
london_dt = utc_dt.astimezone(ZoneInfo("Europe/London"))
print(london_dt) # 2023-10-05 13:00:00+01:00
# Convert to Sydney time (AEDT is UTC+11 in October)
sydney_dt = utc_dt.astimezone(ZoneInfo("Australia/Sydney"))
print(sydney_dt) # 2023-10-06 00:00:00+11:00
Pitfalls to Avoid
- Daylight Saving Time (DST): Clocks “spring forward” (lose an hour) or “fall back” (gain an hour), causing non-existent or ambiguous times. Use
zoneinfoto handle DST transitions (raisesNonExistentTimeErrororAmbiguousTimeError). - Naive
datetimeObjects: Avoid naive objects in global applications—“noon” could mean different things in Tokyo vs. New York. - Epoch Time Limits:
time.time()uses 64-bit floats, which can represent times up to ~292 billion years, but some systems may have 32-bit limits (2038 problem). - Strftime Directives: Not all directives work on all platforms (e.g.,
%zfor time zone offset is Python 3.2+).
Conclusion
Python’s standard library provides robust tools for handling dates and times. The datetime module is your go-to for most tasks, with date, time, datetime, and timedelta covering dates, times, and intervals. Use zoneinfo (Python 3.9+) for time zones, time for low-level epoch operations, and calendar for calendar-specific logic. By mastering these modules, you’ll write clear, maintainable code for any date-time scenario.