Skip to content

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

The 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:00

Creating 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:00

You 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:45

Formatting 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, 2026

Here is a quick way to remember: strftime = string from time (datetime to string).

strftime Format Codes Reference

CodeMeaningExample
%Y4-digit year2026
%y2-digit year26
%mMonth as zero-padded number03
%BFull month nameMarch
%bAbbreviated month nameMar
%dDay of the month (zero-padded)15
%AFull weekday nameSunday
%aAbbreviated weekday nameSun
%HHour (24-hour, zero-padded)09
%IHour (12-hour, zero-padded)09
%MMinute (zero-padded)05
%SSecond (zero-padded)07
%pAM/PMAM
%fMicrosecond (zero-padded to 6 digits)000000
%zUTC offset (+HHMM or -HHMM)+0000
%ZTimezone nameUTC
%jDay of the year (001-366)074
%%Literal % character%

Most Used Format Patterns

PatternFormat StringOutput
ISO 8601%Y-%m-%dT%H:%M:%S2026-03-15T09:05:07
US date%m/%d/%Y03/15/2026
European date%d/%m/%Y15/03/2026
Readable date%B %d, %YMarch 15, 2026
Log timestamp%Y-%m-%d %H:%M:%S2026-03-15 09:05:07
12-hour time%I:%M:%S %p09:05:07 AM
Compact date%Y%m%d20260315
File-safe timestamp%Y%m%d_%H%M%S20260315_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:00

Remember: 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:00

Date 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:00

Calculating 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.0

timedelta Constructor Parameters

ParameterDescriptionExample
weeksNumber of weekstimedelta(weeks=2) = 14 days
daysNumber of daystimedelta(days=30)
hoursNumber of hourstimedelta(hours=12)
minutesNumber of minutestimedelta(minutes=45)
secondsNumber of secondstimedelta(seconds=120)
millisecondsNumber of millisecondstimedelta(milliseconds=500)
microsecondsNumber of microsecondstimedelta(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.0

Comparing 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)   # True

Sorting 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, 2026

Working 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:00

Important: 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:00

Using 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:00

Naive vs Aware Datetimes

FeatureNaiveAware
Has timezone infoNoYes
Safe for comparison across zonesNoYes
Created by datetime.now()YesNo (unless tz is passed)
Can mix in arithmeticOnly with other naiveOnly with other aware
Recommended for productionNoYes

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

Experimenting 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

strftimestrptime
Full nameString Format TimeString Parse Time
Directiondatetime -> stringstring -> datetime
Called onA datetime objectThe datetime class
Syntaxdt.strftime("%Y-%m-%d")datetime.strptime(s, "%Y-%m-%d")
ReturnsA formatted stringA datetime object
RaisesNeverValueError if format mismatches
Use caseDisplay, logging, file namesParsing 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:00

FAQ

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.

📚