Dates & Times (datetime)

Reviewed & published by Brayan K

Almost every real program deals with time: when an order was placed, how old an account is, what to schedule next. Python's datetime module gives you proper date objects you can compare, format, and do arithmetic with — far safer than juggling strings.

Part of the free Python course at LearnCodingFast — hands-on lessons with examples you run in your browser, plus practice exercises and a quick quiz.

In this lesson you'll create dates, format them for humans, parse them back from text, and calculate durations.

What You'll Learn in This Lesson

1 Why Not Just Use Strings?

You could store "2024-01-15" as a plain string. But the moment you need to ask "is this date before that one?" or "how many days until then?", strings fall apart. Comparing "2024-1-9" and "2024-1-10" as text gives the wrong answer because text sorts character by character.

Real date objects know they're dates and behave correctly:

from datetime import date

d1 = date(2024, 1, 9)
d2 = date(2024, 1, 10)

print(d1 < d2)          # True — correct chronological order
print((d2 - d1).days)   # 1   — real arithmetic, not string slicing

📦 The four classes you'll use:

2 Creating and Reading Dates

from datetime import date, datetime

# The current moment
now = datetime.now()
print(now)               # 2024-01-15 14:32:08.123456
print(date.today())      # 2024-01-15

# Build a specific datetime: year, month, day, hour, minute, second
launch = datetime(2024, 6, 1, 9, 30, 0)
print(launch)            # 2024-06-01 09:30:00

# Pull out individual pieces
print(launch.year)       # 2024
print(launch.month)      # 6
print(launch.day)        # 1
print(launch.hour)       # 9
print(launch.weekday())  # 5  (Monday=0 ... Sunday=6 -> 5 = Saturday)

Every attribute is a plain integer, so you can use them in conditions, f-strings, and calculations directly.

🎨 Formatting for Humans — strftime

A raw datetime prints as 2024-01-15 14:32:08. To control the look, use strftime with format codes:

from datetime import datetime

now = datetime(2024, 1, 15, 14, 32, 8)

print(now.strftime("%Y-%m-%d"))          # 2024-01-15
print(now.strftime("%d/%m/%Y"))          # 15/01/2024
print(now.strftime("%A, %B %d, %Y"))     # Monday, January 15, 2024
print(now.strftime("%I:%M %p"))          # 02:32 PM
print(now.strftime("%H:%M:%S"))          # 14:32:08
CodeMeaningExample
%Y4-digit year2024
%mMonth number (01-12)01
%B / %bMonth name / shortJanuary / Jan
%dDay of month15
%A / %aWeekday name / shortMonday / Mon
%H / %IHour 24h / 12h14 / 02
%M %SMinute, second32 08
%pAM or PMPM

3 Parsing Text Into Dates — strptime

Data arrives as text — from forms, CSVs, APIs. strptime turns a string into a real datetime by telling Python the exact layout:

from datetime import datetime

# "string parse time": text + a format that describes it
d = datetime.strptime("15/01/2024", "%d/%m/%Y")
print(d)            # 2024-01-15 00:00:00
print(d.year)       # 2024

# Parse a full timestamp from a log file
ts = datetime.strptime("2024-01-15 14:32:08", "%Y-%m-%d %H:%M:%S")
print(ts.hour)      # 14

Memory trick: strftime goes datetime → string (formatting), strptime goes string → datetime (parsing).

from datetime import datetime

# Parse a date the user "typed", then reformat it nicely
raw = "2024-12-25"
holiday = datetime.strptime(raw, "%Y-%m-%d")

print("Stored as:", holiday)
print("Pretty:", holiday.strftime("%A, %B %d, %Y"))
print("Short:", holiday.strftime("%d %b '%y"))

# Is it a weekend?
day = holiday.weekday()   # Mon=0 ... Sun=6
print("Weekday number:", day)
print("Is weekend?", day >= 5)

# ✅ Expected output:
# Stored as: 2024-12-25 00:00:00
# Pretty: Wednesday, December 25, 2024
# Short: 25 Dec '24
# Weekday number: 2
# Is weekend? False

4 Date Arithmetic with timedelta

A timedelta represents a span of time. Add it to a date to move forward or back; subtract two dates to get the gap between them:

from datetime import datetime, timedelta

today = datetime(2024, 1, 15)

# Move forward and back
next_week  = today + timedelta(days=7)
yesterday  = today - timedelta(days=1)
in_90_min  = today + timedelta(hours=1, minutes=30)
print(next_week.date())   # 2024-01-22
print(yesterday.date())   # 2024-01-14

# Difference between two dates
launch = datetime(2024, 6, 1)
gap = launch - today
print(gap.days)           # 138  (days until launch)
print(type(gap))          # <class 'datetime.timedelta'>

# A trial that lasts 14 days
signup = datetime(2024, 1, 1)
trial_ends = signup + timedelta(days=14)
print("Trial ends:", trial_ends.date())   # 2024-01-15

📖 Worked Example: A Library Loan Slip

Read this one line by line before you write anything yourself. It uses all four ideas together: parse text into a date, add a duration, subtract two dates to get a gap, and format the result for a human. Every non-obvious line says what it produces.

from datetime import datetime, timedelta

# --- WORKED EXAMPLE: a library loan slip ---
# The library stores dates as text. Turn that text into real date
# objects, do the arithmetic, then turn it back into text for the slip.

LOAN_DAYS = 14                    # how long a book may be kept

# 1. Parse the text you were given into a real datetime object.
#    "%d/%m/%Y" says: 2-digit day, slash, 2-digit month, slash, 4-digit year.
borrowed_text = "03/01/2024"
borrowed = datetime.strptime(borrowed_text, "%d/%m/%Y")
print(borrowed)                   # 2024-01-03 00:00:00  (midnight is assumed)

# 2. Add a duration. timedelta handles month rollover for you.
due = borrowed + timedelta(days=LOAN_DAYS)
print(due.strftime("%d/%m/%Y"))   # 17/01/2024

# 3. Compare against "today". A fixed date is used so the output never changes.
today = datetime(2024, 1, 20)

# Subtracting two datetimes gives a timedelta (a duration), not a plain number.
gap = due - today
print(gap.days)                   # -3  (negative = the due date is in the past)

# 4. Turn that duration into a human sentence.
if gap.days < 0:
    status = f"OVERDUE by {abs(gap.days)} days"
else:
    status = f"due in {gap.days} days"

# 5. Format for the printed slip. %A = full weekday, %B = full month name.
print("Borrowed:", borrowed.strftime("%A %d %B %Y"))
print("Due back:", due.strftime("%A %d %B %Y"))
print("Status:  ", status)

# ✅ Expected output:
# 2024-01-03 00:00:00
# 17/01/2024
# -3
# Borrowed: Wednesday 03 January 2024
# Due back: Wednesday 17 January 2024
# Status:   OVERDUE by 3 days

The important habit here is the shape: text in → real date objects → arithmetic → text out. Never do the maths on the strings themselves.

🎯 Your Turn: Membership Renewal Reminder

Everything is written for you except the three things this lesson taught: the strptime format, the timedelta length, and a strftime pattern. Replace each ___ and run it. If your output matches the expected output at the bottom, you've got it.

from datetime import datetime, timedelta

# 🎯 YOUR TURN — replace the three ___ blanks

joined_text = "2024-03-01"        # the text your database stores

# 1) Parse that text into a real datetime.
#    The layout is 4-digit year, dash, 2-digit month, dash, 2-digit day.
joined = datetime.strptime(joined_text, "___")   # 👉 replace ___ with the matching format

# 2) A membership lasts 30 days. Build that duration.
membership = timedelta(days=___)                 # 👉 replace ___ with a whole number

renews_on = joined + membership

# 3) Print the renewal date the same way as the join date:
#    %A = weekday name, %d = day number, %B = month name, %Y = 4-digit year.
print("Joined:", joined.strftime("%A %d %B %Y"))
print("Renews:", renews_on.strftime("___"))      # 👉 replace ___ with those four codes

# Days from a fixed "today" until renewal (subtracting gives a timedelta).
today = datetime(2024, 3, 20)
print("Days to go:", (renews_on - today).days)

# ✅ Expected output:
# Joined: Friday 01 March 2024
# Renews: Sunday 31 March 2024
# Days to go: 11

1) "%Y-%m-%d" — year, month, day joined by dashes, in that order.

3) "%A %d %B %Y" — the same pattern used on the line above it.

If you got ValueError: time data '2024-03-01' does not match format ..., your format codes are in the wrong order or you used the wrong separator.

Real-World Example: Subscription & Age Calculator

This is the kind of logic that powers billing dashboards and "account created 3 months ago" labels:

from datetime import datetime, date

def account_summary(name, signup_str, birthday_str):
    today = date(2024, 1, 15)   # pretend "today" so output is stable

    # Parse the incoming text into real dates
    signup = datetime.strptime(signup_str, "%Y-%m-%d").date()
    birthday = datetime.strptime(birthday_str, "%Y-%m-%d").date()

    # How long has the account existed?
    days_active = (today - signup).days

    # Age in whole years (subtract 1 if birthday hasn't happened yet this year)
    age = today.year - birthday.year
    if (today.month, today.day) < (birthday.month, birthday.day):
        age -= 1

    print(f"=== {name} ===")
    print(f"Member for: {days_active} days ({days_active // 30} months)")
    print(f"Age:        {age} years")
    print(f"Joined on:  {signup.strftime('%B %d, %Y')}")

account_summary("Ada Lovelace", "2023-07-10", "1990-03-22")
# === Ada Lovelace ===
# Member for: 189 days (6 months)
# Age:        33 years
# Joined on:  July 10, 2023

Notice the age trick: comparing the (month, day) tuples handles the "has their birthday happened yet this year?" question in one clean line.

🌍 Timezones — Store UTC, Display Local

A datetime without timezone info is called naive — it doesn't know which timezone it belongs to. For anything user-facing, work with aware datetimes:

from datetime import datetime, timezone, timedelta

# Aware UTC timestamp — the gold standard for storage
utc_now = datetime.now(timezone.utc)
print(utc_now.isoformat())   # 2024-01-15T14:32:08.123456+00:00

# Convert to a fixed offset for display (e.g. New York, UTC-5)
eastern = timezone(timedelta(hours=-5))
local = utc_now.astimezone(eastern)
print(local.strftime("%Y-%m-%d %H:%M %Z"))
from datetime import datetime, timedelta

# A simple countdown to an event
event = datetime(2024, 12, 31, 23, 59, 59)
now   = datetime(2024, 1, 15, 12, 0, 0)

remaining = event - now
print("Days left:", remaining.days)
print("Total hours left:", remaining.total_seconds() // 3600)

# Schedule reminders: 7, 3 and 1 day before the event
for days_before in (7, 3, 1):
    reminder = event - timedelta(days=days_before)
    print(f"{days_before}-day reminder:", reminder.strftime("%b %d at %H:%M"))

# ✅ Expected output:
# Days left: 351
# Total hours left: 8435.0
# 7-day reminder: Dec 24 at 23:59
# 3-day reminder: Dec 28 at 23:59
# 1-day reminder: Dec 30 at 23:59

🧩 Reorder Challenge

These lines should print how many days until a deadline, but they're shuffled. Find the right order:

A) print("Days left:", gap.days)
B) from datetime import datetime
C) gap = deadline - today
D) deadline = datetime(2024, 12, 1)
E) today = datetime(2024, 11, 20)

Correct order: B → D → E → C → A

from datetime import datetime
deadline = datetime(2024, 12, 1)
today = datetime(2024, 11, 20)
gap = deadline - today
print("Days left:", gap.days)   # Days left: 11

Import first, build both datetimes, subtract to get a timedelta, then read its .days attribute.

🏆 Mini-Challenge: Sprint Schedule

No filled-in logic this time — just an outline. A team runs two-week sprints starting on 5 February 2024. Print the start and end date of the first four sprints. A sprint that starts on day X ends 13 days later (the fortnight includes its first day).

Watch what happens across the end of February — 2024 is a leap year, and you should never have to think about that yourself.

from datetime import datetime, timedelta

# 🎯 MINI-CHALLENGE: Sprint schedule
# 1. Make a datetime called project_start for 5 February 2024
# 2. Loop over sprint numbers 1, 2, 3, 4  (range(1, 5))
# 3. For each one:
#      - start = project_start plus 2 weeks for every sprint ALREADY done
#                (sprint 1 adds 0 weeks, sprint 2 adds 2, sprint 3 adds 4 ...)
#      - end   = start plus 13 days
#      - print: f"Sprint {n}: {start:%d %b} to {end:%d %b}"
#        (or use start.strftime('%d %b') if you prefer)
#
# ✅ Expected output:
# Sprint 1: 05 Feb to 18 Feb
# Sprint 2: 19 Feb to 03 Mar
# Sprint 3: 04 Mar to 17 Mar
# Sprint 4: 18 Mar to 31 Mar

# your code here
from datetime import datetime, timedelta

project_start = datetime(2024, 2, 5)

for sprint in range(1, 5):
    # weeks=2 * (sprint - 1): sprint 1 adds 0 weeks, sprint 2 adds 2, and so on
    start = project_start + timedelta(weeks=2 * (sprint - 1))
    end = start + timedelta(days=13)
    print(f"Sprint {sprint}: {start.strftime('%d %b')} to {end.strftime('%d %b')}")

Sprint 2 ends on 3 March, not 2 March, because timedelta knows February 2024 had 29 days. That is exactly the bug you avoid by never doing date maths by hand.

🧠 Quick Recall — Predict the Output

from datetime import date
print(date(2024, 2, 1) < date(2024, 1, 31))

False — Feb 1 comes after Jan 31, so it is NOT less than it. Date objects compare chronologically, unlike strings.

from datetime import datetime
print(datetime(2024, 7, 4).strftime("%B"))

July — %B is the full month name. (%b would give the short form, "Jul".)

from datetime import datetime, timedelta
d = datetime(2024, 1, 31) + timedelta(days=1)
print(d.strftime("%Y-%m-%d"))

2024-02-01 — timedelta correctly rolls over the month boundary. You never have to think about how many days are in January.

❓ Frequently Asked Questions

You can now bend time to your will!

You can create dates, format them with strftime, parse text with strptime, do arithmetic with timedelta, and handle timezones safely. These are skills you'll use in nearly every project that touches the real world.

Practice quiz

Which datetime class holds ONLY a calendar date (year, month, day)?

  • time
  • datetime
  • date
  • timedelta

Answer: date. date holds just year/month/day; datetime combines date and time; timedelta is a duration.

Which class represents a duration like '3 days' or '2 hours'?

  • timedelta
  • date
  • time
  • datetime

Answer: timedelta. timedelta represents a span of time you can add to or subtract from dates.

How do you get the current date and time?

  • date.today()
  • datetime.current()
  • time.now()
  • datetime.now()

Answer: datetime.now(). datetime.now() returns the current local date and time; date.today() gives just the date.

What does strftime do?

  • Parses a string into a datetime
  • Formats a datetime into a text string
  • Adds days to a date
  • Compares two dates

Answer: Formats a datetime into a text string. strftime ('string FROM time') formats a datetime into text using codes like %Y-%m-%d.

Which function parses text INTO a datetime object?

  • strptime
  • strftime
  • isoformat
  • fromtext

Answer: strptime. strptime ('string parse time') reads a string into a datetime, given a matching format.

What does datetime(2024, 7, 4).strftime('%B') return?

  • '07'
  • 'Jul'
  • 'July'
  • 'Thursday'

Answer: 'July'. %B is the full month name, so it returns 'July' (%b would give 'Jul').

What does datetime(2024, 1, 31) + timedelta(days=1) give?

  • 2024-01-32
  • 2024-02-01
  • an error
  • 2024-01-31

Answer: 2024-02-01. timedelta correctly rolls over the month boundary, giving 2024-02-01.

What does (date(2024, 1, 10) - date(2024, 1, 9)).days return?

  • 0
  • -1
  • an error
  • 1

Answer: 1. Subtracting two dates gives a timedelta; its .days is 1.

Why store dates in UTC?

  • It is faster
  • UTC has no daylight-saving shifts and is unambiguous worldwide
  • It uses less memory
  • It is required by Python

Answer: UTC has no daylight-saving shifts and is unambiguous worldwide. UTC avoids DST ambiguity; store/compute in UTC and convert to local time only for display.

What error comes from mixing naive and aware datetimes in subtraction?

  • ValueError
  • KeyError
  • TypeError: can't subtract offset-naive and offset-aware datetimes
  • It works fine

Answer: TypeError: can't subtract offset-naive and offset-aware datetimes. You can't subtract a naive (no timezone) datetime from an aware one; it raises a TypeError.

Continue this course

Frequently asked questions

What is the difference between date, time and datetime in Python?

datetime.date holds only a calendar date (year, month, day). datetime.time holds only a clock time. datetime.datetime combines both into a single timestamp. Most real work uses datetime.

How do I get the current date and time?

Use datetime.now() for local time including the time portion, or date.today() for just today's calendar date. For UTC, use datetime.now(timezone.utc).

What is strftime and strptime?

strftime ('string from time') formats a datetime object into a text string using format codes like %Y-%m-%d. strptime ('string parse time') does the reverse — it reads a text string into a datetime object.

How do I add or subtract days from a date?

Use timedelta. For example datetime.now() + timedelta(days=7) gives the date one week from now. Subtracting two datetimes also returns a timedelta.

Why should I store dates in UTC?

UTC has no daylight-saving shifts and is unambiguous worldwide. Store and compute in UTC, then convert to the user's local timezone only when displaying — this avoids a huge class of off-by-one-hour bugs.