Python Datetime: Complete Guide to Dates and Times in Python
Updated on
Working with dates and times in Python should be straightforward, but it rarely is. You need to parse a date string from a CSV file, but "01/02/2026" could mean January 2nd or February 1st depending on the locale. You want to calculate the difference between two timestamps, but one is a string and the other is a Unix epoch. You mix up strftime and strptime for the tenth time this month. Date and time operations are a constant source of bugs, off-by-one errors, and timezone headaches in production code.
The consequences go beyond annoyance. A wrong date format in a financial report means incorrect calculations. A naive datetime comparison that ignores timezones causes duplicate cron jobs. A botched timedelta calculation charges a customer for 31 days instead of 30.
Python's built-in datetime module solves these problems with a clean, consistent API. It provides classes for dates, times, timestamps, and durations. Once you learn its patterns -- and especially the difference between strftime and strptime -- you can handle any date operation without third-party libraries.
Getting Current Date and Time
The most common starting point is getting the current date and time. The datetime module provides several ways to do this.
from datetime import datetime, date
# Current date and time
now = datetime.now()
print(now) # 2026-02-10 14:30:45.123456
# Current date only
today = date.today()
print(today) # 2026-02-10
# datetime.today() is similar to datetime.now() but without timezone support
now_alt = datetime.today()
print(now_alt) # 2026-02-10 14:30:45.123456The difference between datetime.now() and datetime.today() is subtle but important. datetime.now() accepts an optional tz parameter for timezone-aware datetimes. datetime.today() does not. For most code, prefer datetime.now().
from datetime import datetime
from zoneinfo import ZoneInfo
# Timezone-aware current time
utc_now = datetime.now(tz=ZoneInfo("UTC"))
print(utc_now) # 2026-02-10 14:30:45.123456+00:00
tokyo_now = datetime.now(tz=ZoneInfo("Asia/Tokyo"))
print(tokyo_now) # 2026-02-10 23:30:45.123456+09:00Creating Datetime Objects
You can create datetime objects from individual components or from existing data.
from datetime import datetime, date, time
# From year, month, day, hour, minute, second
dt = datetime(2026, 3, 15, 9, 30, 0)
print(dt) # 2026-03-15 09:30:00
# Date only
d = date(2026, 12, 25)
print(d) # 2026-12-25
# Time only
t = time(14, 30, 0)
print(t) # 14:30:00
# Combine date and time
combined = datetime.combine(d, t)
print(combined) # 2026-12-25 14:30:00You can also extract components from an existing datetime.
from datetime import datetime
dt = datetime(2026, 3, 15, 9, 30, 45)
print(dt.year) # 2026
print(dt.month) # 3
print(dt.day) # 15
print(dt.hour) # 9
print(dt.minute) # 30
print(dt.second) # 45
print(dt.weekday()) # 6 (Sunday, Monday=0)
print(dt.isoformat()) # 2026-03-15T09:30:45Formatting Dates with strftime
strftime stands for "string format time." It converts a datetime object into a formatted string. You pass a format string with directives that get replaced by date components.
from datetime import datetime
dt = datetime(2026, 3, 15, 9, 5, 7)
# Common formats
print(dt.strftime("%Y-%m-%d")) # 2026-03-15
print(dt.strftime("%d/%m/%Y")) # 15/03/2026
print(dt.strftime("%B %d, %Y")) # March 15, 2026
print(dt.strftime("%Y-%m-%d %H:%M:%S")) # 2026-03-15 09:05:07
print(dt.strftime("%I:%M %p")) # 09:05 AM
print(dt.strftime("%A, %B %d, %Y")) # Sunday, March 15, 2026Here is a quick way to remember: strftime = string from time (datetime to string).
strftime Format Codes Reference
| Code | Meaning | Example |
|---|---|---|
%Y | 4-digit year | 2026 |
%y | 2-digit year | 26 |
%m | Month as zero-padded number | 03 |
%B | Full month name | March |
%b | Abbreviated month name | Mar |
%d | Day of the month (zero-padded) | 15 |
%A | Full weekday name | Sunday |
%a | Abbreviated weekday name | Sun |
%H | Hour (24-hour, zero-padded) | 09 |
%I | Hour (12-hour, zero-padded) | 09 |
%M | Minute (zero-padded) | 05 |
%S | Second (zero-padded) | 07 |
%p | AM/PM | AM |
%f | Microsecond (zero-padded to 6 digits) | 000000 |
%z | UTC offset (+HHMM or -HHMM) | +0000 |
%Z | Timezone name | UTC |
%j | Day of the year (001-366) | 074 |
%% | Literal % character | % |
Most Used Format Patterns
| Pattern | Format String | Output |
|---|---|---|
| ISO 8601 | %Y-%m-%dT%H:%M:%S | 2026-03-15T09:05:07 |
| US date | %m/%d/%Y | 03/15/2026 |
| European date | %d/%m/%Y | 15/03/2026 |
| Readable date | %B %d, %Y | March 15, 2026 |
| Log timestamp | %Y-%m-%d %H:%M:%S | 2026-03-15 09:05:07 |
| 12-hour time | %I:%M:%S %p | 09:05:07 AM |
| Compact date | %Y%m%d | 20260315 |
| File-safe timestamp | %Y%m%d_%H%M%S | 20260315_090507 |
Parsing Strings with strptime
strptime stands for "string parse time." It is the reverse of strftime -- it converts a string into a datetime object. You provide the string and the format it follows.
from datetime import datetime
# Parse various date string formats
dt1 = datetime.strptime("2026-03-15", "%Y-%m-%d")
print(dt1) # 2026-03-15 00:00:00
dt2 = datetime.strptime("15/03/2026", "%d/%m/%Y")
print(dt2) # 2026-03-15 00:00:00
dt3 = datetime.strptime("March 15, 2026 09:30 AM", "%B %d, %Y %I:%M %p")
print(dt3) # 2026-03-15 09:30:00
dt4 = datetime.strptime("2026-03-15T09:30:00", "%Y-%m-%dT%H:%M:%S")
print(dt4) # 2026-03-15 09:30:00Remember: strptime = string parse time (string to datetime).
Handling Parse Errors
If the string does not match the format, Python raises a ValueError. Always wrap strptime calls in error handling when parsing user input or external data.
from datetime import datetime
def safe_parse_date(date_string, fmt="%Y-%m-%d"):
"""Parse a date string safely, returning None on failure."""
try:
return datetime.strptime(date_string, fmt)
except ValueError as e:
print(f"Could not parse '{date_string}': {e}")
return None
# Valid input
print(safe_parse_date("2026-03-15")) # 2026-03-15 00:00:00
# Invalid input
print(safe_parse_date("15-03-2026")) # Could not parse '15-03-2026': ...
print(safe_parse_date("not a date")) # Could not parse 'not a date': ...Parsing Multiple Formats
When you receive dates in unpredictable formats, try multiple patterns.
from datetime import datetime
def parse_flexible_date(date_string):
"""Try multiple date formats and return the first match."""
formats = [
"%Y-%m-%d",
"%d/%m/%Y",
"%m/%d/%Y",
"%B %d, %Y",
"%b %d, %Y",
"%Y-%m-%dT%H:%M:%S",
"%Y-%m-%d %H:%M:%S",
]
for fmt in formats:
try:
return datetime.strptime(date_string, fmt)
except ValueError:
continue
raise ValueError(f"No matching format found for '{date_string}'")
print(parse_flexible_date("2026-03-15")) # 2026-03-15 00:00:00
print(parse_flexible_date("March 15, 2026")) # 2026-03-15 00:00:00
print(parse_flexible_date("15/03/2026")) # 2026-03-15 00:00:00Date Arithmetic with timedelta
The timedelta class represents a duration -- the difference between two dates or times. You can add or subtract timedelta objects to shift dates forward or backward.
from datetime import datetime, timedelta
now = datetime(2026, 2, 10, 12, 0, 0)
# Add days
tomorrow = now + timedelta(days=1)
print(tomorrow) # 2026-02-11 12:00:00
# Subtract days
last_week = now - timedelta(weeks=1)
print(last_week) # 2026-02-03 12:00:00
# Add hours and minutes
later = now + timedelta(hours=5, minutes=30)
print(later) # 2026-02-10 17:30:00
# Combine multiple units
future = now + timedelta(weeks=2, days=3, hours=6)
print(future) # 2026-02-27 18:00:00Calculating Time Differences
Subtracting one datetime from another returns a timedelta.
from datetime import datetime
start = datetime(2026, 1, 1)
end = datetime(2026, 12, 31)
diff = end - start
print(diff) # 364 days, 0:00:00
print(diff.days) # 364
print(diff.total_seconds()) # 31449600.0timedelta Constructor Parameters
| Parameter | Description | Example |
|---|---|---|
weeks | Number of weeks | timedelta(weeks=2) = 14 days |
days | Number of days | timedelta(days=30) |
hours | Number of hours | timedelta(hours=12) |
minutes | Number of minutes | timedelta(minutes=45) |
seconds | Number of seconds | timedelta(seconds=120) |
milliseconds | Number of milliseconds | timedelta(milliseconds=500) |
microseconds | Number of microseconds | timedelta(microseconds=1000) |
All parameters can be combined. Internally, timedelta stores only days, seconds, and microseconds. Everything else is converted.
from datetime import timedelta
delta = timedelta(weeks=1, days=2, hours=3, minutes=30, seconds=45)
print(delta) # 9 days, 3:30:45
print(delta.days) # 9
print(delta.seconds) # 12645 (3*3600 + 30*60 + 45)
print(delta.total_seconds()) # 790245.0Comparing Dates
Datetime objects support all standard comparison operators. This makes sorting and filtering dates straightforward.
from datetime import datetime
dt1 = datetime(2026, 1, 1)
dt2 = datetime(2026, 6, 15)
dt3 = datetime(2026, 12, 31)
print(dt1 < dt2) # True
print(dt3 > dt2) # True
print(dt1 == dt2) # False
print(dt1 != dt2) # TrueSorting Dates
from datetime import datetime
dates = [
datetime(2026, 12, 25),
datetime(2026, 1, 1),
datetime(2026, 7, 4),
datetime(2026, 2, 14),
]
sorted_dates = sorted(dates)
for d in sorted_dates:
print(d.strftime("%B %d, %Y"))
# January 01, 2026
# February 14, 2026
# July 04, 2026
# December 25, 2026Working with Timestamps
Unix timestamps represent seconds since January 1, 1970 (the Unix epoch). The datetime module can convert between timestamps and datetime objects.
from datetime import datetime, timezone
# Datetime to timestamp
dt = datetime(2026, 3, 15, 9, 30, 0)
ts = dt.timestamp()
print(ts) # 1773814200.0 (depends on local timezone)
# Timestamp to datetime (timezone-aware, recommended)
dt_aware = datetime.fromtimestamp(ts, tz=timezone.utc)
print(dt_aware) # 2026-03-15 01:30:00+00:00Important: datetime.fromtimestamp() without a timezone returns local time. For UTC, always pass tz=timezone.utc.
Timezone Handling
Naive datetimes (without timezone info) are a common source of bugs. Python provides two built-in approaches for timezone-aware datetimes.
Using datetime.timezone (Built-in)
The timezone class handles fixed UTC offsets.
from datetime import datetime, timezone, timedelta
# UTC
utc_now = datetime.now(timezone.utc)
print(utc_now) # 2026-02-10 14:30:00+00:00
# Fixed offset (e.g., UTC+5:30 for India)
ist = timezone(timedelta(hours=5, minutes=30))
india_time = datetime.now(ist)
print(india_time) # 2026-02-10 20:00:00+05:30
# Convert between timezones
utc_time = datetime(2026, 3, 15, 12, 0, 0, tzinfo=timezone.utc)
eastern = timezone(timedelta(hours=-5))
eastern_time = utc_time.astimezone(eastern)
print(eastern_time) # 2026-03-15 07:00:00-05:00Using zoneinfo (Python 3.9+)
For named timezones with proper daylight saving time handling, use the zoneinfo module.
from datetime import datetime
from zoneinfo import ZoneInfo
# Named timezones
utc = ZoneInfo("UTC")
eastern = ZoneInfo("America/New_York")
tokyo = ZoneInfo("Asia/Tokyo")
# Create timezone-aware datetime
dt = datetime(2026, 7, 15, 12, 0, 0, tzinfo=utc)
print(dt) # 2026-07-15 12:00:00+00:00
# Convert to other timezones
print(dt.astimezone(eastern)) # 2026-07-15 08:00:00-04:00 (EDT)
print(dt.astimezone(tokyo)) # 2026-07-15 21:00:00+09:00Naive vs Aware Datetimes
| Feature | Naive | Aware |
|---|---|---|
| Has timezone info | No | Yes |
| Safe for comparison across zones | No | Yes |
Created by datetime.now() | Yes | No (unless tz is passed) |
| Can mix in arithmetic | Only with other naive | Only with other aware |
| Recommended for production | No | Yes |
You cannot compare or subtract a naive datetime from an aware one. Python raises a TypeError.
Practical Examples
Calculate a Person's Age
from datetime import date
def calculate_age(birth_date):
"""Calculate age in years from a birth date."""
today = date.today()
age = today.year - birth_date.year
if (today.month, today.day) < (birth_date.month, birth_date.day):
age -= 1
return age
birthday = date(1995, 8, 20)
print(f"Age: {calculate_age(birthday)} years") # Age: 30 years (as of Feb 2026)Generate a Date Range
from datetime import date, timedelta
def date_range(start, end, step_days=1):
"""Generate dates from start to end (inclusive)."""
current = start
while current <= end:
yield current
current += timedelta(days=step_days)
start = date(2026, 2, 1)
end = date(2026, 2, 7)
for d in date_range(start, end):
print(d.strftime("%A, %B %d"))Count Business Days Between Two Dates
from datetime import date, timedelta
def business_days_between(start, end):
"""Count weekdays (Mon-Fri) between two dates, excluding endpoints."""
count = 0
current = start + timedelta(days=1)
while current < end:
if current.weekday() < 5: # 0=Mon, 4=Fri
count += 1
current += timedelta(days=1)
return count
start = date(2026, 2, 1)
end = date(2026, 2, 28)
print(f"Business days: {business_days_between(start, end)}") # Business days: 19Experimenting with Datetime in Jupyter
Date and time operations benefit from interactive experimentation. When you are parsing inconsistent date formats from a CSV or debugging timezone conversions, being able to test each step in a notebook cell saves significant time.
RunCell (opens in a new tab) is an AI agent that works directly inside Jupyter notebooks. It can inspect your datetime objects, suggest the correct strftime/strptime format codes for your data, and help debug timezone conversion issues in real time.
strftime vs strptime: Quick Comparison
| strftime | strptime | |
|---|---|---|
| Full name | String Format Time | String Parse Time |
| Direction | datetime -> string | string -> datetime |
| Called on | A datetime object | The datetime class |
| Syntax | dt.strftime("%Y-%m-%d") | datetime.strptime(s, "%Y-%m-%d") |
| Returns | A formatted string | A datetime object |
| Raises | Never | ValueError if format mismatches |
| Use case | Display, logging, file names | Parsing CSV, API responses, user input |
from datetime import datetime
# strftime: datetime -> string
dt = datetime(2026, 3, 15, 9, 30)
formatted = dt.strftime("%B %d, %Y at %I:%M %p")
print(formatted) # March 15, 2026 at 09:30 AM
# strptime: string -> datetime
parsed = datetime.strptime("March 15, 2026 at 09:30 AM", "%B %d, %Y at %I:%M %p")
print(parsed) # 2026-03-15 09:30:00FAQ
How do I get the current date and time in Python?
Use datetime.now() from the datetime module. For just the date, use date.today(). For timezone-aware current time, pass a timezone: datetime.now(tz=timezone.utc). These are the standard approaches and do not require third-party libraries.
What is the difference between strftime and strptime in Python?
strftime converts a datetime object to a formatted string (string from time). strptime parses a string and converts it to a datetime object (string parse time). Think of it as: strftime outputs strings, strptime inputs strings. Both use the same format codes like %Y, %m, %d.
How do I add days to a date in Python?
Use the timedelta class. Import it from datetime, then add it to your date: new_date = old_date + timedelta(days=7). You can also use weeks, hours, minutes, and seconds as parameters. Subtracting works the same way: past_date = today - timedelta(days=30).
How do I convert a string to a datetime in Python?
Use datetime.strptime(string, format). You need to provide a format string that matches your input. For example, datetime.strptime("2026-03-15", "%Y-%m-%d") parses an ISO date. If the string does not match the format, Python raises a ValueError, so wrap the call in a try/except block for external data.
How do I handle timezones in Python datetime?
For Python 3.9+, use the built-in zoneinfo module: from zoneinfo import ZoneInfo. Create timezone-aware datetimes with datetime.now(tz=ZoneInfo("UTC")) and convert between zones with dt.astimezone(ZoneInfo("America/New_York")). For fixed UTC offsets, use datetime.timezone(timedelta(hours=N)). Avoid naive datetimes in production code.
Conclusion
Python's datetime module provides everything you need for date and time operations: creating dates, formatting them for display, parsing them from strings, performing arithmetic, and handling timezones. The key patterns are consistent and predictable once you learn them.
Remember the core distinction: strftime formats a datetime into a string, strptime parses a string into a datetime. Use timedelta for date arithmetic. Use zoneinfo (Python 3.9+) for proper timezone handling. Keep datetimes timezone-aware in production code to avoid subtle comparison bugs.
For most projects, the built-in datetime module is sufficient. You do not need pytz in modern Python -- zoneinfo handles named timezones natively. Start with datetime.now(tz=timezone.utc) as your reference point, and convert to local timezones only when displaying to users.