Skip to content

Working with dates and times

Goal: get today's date, do arithmetic with dates, and turn dates into text and back, using the datetime module.

Why this matters

In Module 6 you wrote a diary program that began by asking Today's date:. That was a little absurd. The computer has a clock. It knows the date, and it can count the days until your holiday, tell you what day of the week you were born, and stamp every diary entry for you. All of that lives in a standard library module called datetime, and it is a good second module to learn, because it shows you something modules can give you that math did not: a whole new kind of value.

Your output will differ

The examples in this lesson were run on Saturday 19 September 2026. Your computer will show its own dates.

Today's date

today.py
from datetime import date

today = date.today()
print(today)
print(today.year)
print(today.month)
print(today.day)
2026-09-19
2026
9
19

Take the first line slowly, because there are two new things in it. datetime is the module. Inside it is date, which is not a function but a type, a new kind of value alongside the ones you know: str, int, float, list, dict. from datetime import date brings that type into your program, which is the from import from last lesson doing exactly the job it is best at.

date.today() is a function that lives inside date, and it returns a date value holding the current date. Print one and you get it in the form year-month-day. A date also knows its own parts. today.year, today.month and today.day are attributes, values that live inside the date, reached with a dot but without parentheses, just like math.pi last lesson. They are ordinary integers, so you can do arithmetic with them.

Making a date

Any date can be made from its three numbers:

make_date.py
from datetime import date

christmas = date(2026, 12, 25)
course_start = date(2026, 9, 10)

print(christmas)
print(f"The course started on day {course_start.day} of month {course_start.month}.")
print(christmas > course_start)
2026-12-25
The course started on day 10 of month 9.
True

date(2026, 12, 25) looks like a function call, and in effect it is one: give it a year, a month and a day and it hands back a date. This is how a new value of any type is made, and you have seen it before without the name: int("12") and str(5) work the same way. Python checks the numbers make sense, so date(2026, 2, 30) is a ValueError, with the message day is out of range for month.

The last line shows that dates can be compared with < and >, and they compare the sensible way: a later date is "bigger". That means if deadline < today: reads exactly as you would say it.

Counting the days between dates

Subtract one date from another and you get the gap between them:

days_until.py
from datetime import date

today = date.today()
christmas = date(2026, 12, 25)

gap = christmas - today
print(gap)
print(gap.days)
print(f"{gap.days} days until Christmas.")
97 days, 0:00:00
97
97 days until Christmas.

The gap is yet another type, called a timedelta, which means a difference in time. Printed on its own it shows days, then hours, minutes and seconds, which for whole dates are always zero. Its .days attribute is the number you actually want, as an ordinary integer. Subtract in the other order, today - christmas, and .days is negative.

Adding days to a date

Going the other way, from a date and a number of days to a new date, needs a timedelta of your own:

add_days.py
from datetime import date, timedelta

today = date.today()
next_week = today + timedelta(days=7)
last_month = today - timedelta(days=30)

print(f"Today: {today}")
print(f"A week from now: {next_week}")
print(f"Thirty days ago: {last_month}")
Today: 2026-09-19
A week from now: 2026-09-26
Thirty days ago: 2026-08-20

You cannot write today + 7, because Python would have to guess whether you meant seven days, weeks or seconds. timedelta(days=7) says which. That days=7 is a keyword argument: instead of relying on position, the call names the parameter it is filling. In Module 5 you gave parameters default values in a def line. This is the other side of that: timedelta has parameters called days, weeks, hours, minutes and seconds, all defaulting to zero, and the caller names whichever ones it wants. You can write timedelta(weeks=2) or timedelta(days=1, hours=12) the same way.

Once you have a timedelta, + and - do what you expect. Notice that the date arithmetic takes care of month ends for you. Thirty days before 19 September is 20 August, and you did not have to know how long August is.

Showing a date nicely

2026-09-19 is fine for a file and poor for a person. The .strftime() method, short for string from time, formats a date however you like:

format_date.py
from datetime import date

today = date.today()

print(today.strftime("%d/%m/%Y"))
print(today.strftime("%A %d %B %Y"))
print(today.strftime("%b %d"))
print(today.strftime("It is %A."))
19/09/2026
Saturday 19 September 2026
Sep 19
It is Saturday.

The string you pass is a template. Each code beginning with % is replaced by a piece of the date, and everything else is copied through unchanged. The codes you will use most:

Code Meaning Example
%d day of the month, two digits 19
%m month as a number, two digits 09
%Y year, four digits 2026
%y year, two digits 26
%B month name September
%b month name, short Sep
%A day of the week Saturday
%a day of the week, short Sat

%A is quietly remarkable: Python worked out that 19 September 2026 is a Saturday. It can do that for any date, including the day you were born, which is one of the exercises.

Turning text into a date

Dates arrive as text: typed by a user, or read from a file. date.fromisoformat() turns the year-month-day form back into a date. ISO format is just the international name for that year-month-day order, the same one print uses. Anything else is a ValueError, so this is a job for the try/except loop from Module 7:

parse_date.py
from datetime import date

def ask_for_date(prompt):
    """Keep asking until the user types a date like 2026-12-25, then return it."""
    while True:
        text = input(prompt)
        try:
            return date.fromisoformat(text)
        except ValueError:
            print("Please use the form YYYY-MM-DD, for example 2026-12-25.")

birthday = ask_for_date("When were you born? ")
print(f"You were born on a {birthday.strftime('%A')}.")
When were you born? 25/12/1990
Please use the form YYYY-MM-DD, for example 2026-12-25.
When were you born? 1990-02-30
Please use the form YYYY-MM-DD, for example 2026-12-25.
When were you born? 1990-12-25
You were born on a Tuesday.

ask_for_date() is ask_for_number() with date.fromisoformat in place of int. The pattern is the same every time: let the conversion try, and ask again when it refuses. The second attempt, 30 February, has the right shape but is not a real date, and it is refused too.

Because print(some_date) writes the ISO form and fromisoformat() reads it, the two make a matched pair for saving dates in files. Write f"{some_date}" and read it back with fromisoformat(), and nothing is lost in between.

The time as well

date knows nothing about hours and minutes. For those, the module has a second type, datetime, which holds a date and a time together:

now.py
from datetime import datetime

now = datetime.now()
print(now)
print(now.strftime("%H:%M"))
print(now.strftime("%Y-%m-%d %H:%M"))
print(now.date())
2026-09-19 16:14:49.040490
16:14
2026-09-19 16:14
2026-09-19

Yes, the type has the same name as the module, which confuses everybody once. from datetime import datetime reads as from the module, import the type. datetime.now() gives the current date and time down to the microsecond, which is usually more than you want, so .strftime() is used to trim it. Two more codes cover the time: %H for the hour on the 24-hour clock and %M for the minute. .date() throws the time away and leaves a plain date.

Project: a countdown to everything

Here is a file of events, one per line, with the date in ISO form. One line has gone wrong on purpose:

events.txt
2026-12-25,Christmas
2027-01-01,New Year's Day
2026-09-10,Started the Python course
soon,Holiday
2026-10-31,Halloween

The program reads it and says how far away each event is:

countdown.py
from datetime import date

FILENAME = "events.txt"

def read_lines(filename):
    """Return the lines of a file as a list, without the newline characters."""
    lines = []
    with open(filename) as file:
        for line in file:
            lines.append(line.strip())
    return lines

def describe(event_date, name, today):
    """Return one line saying how far away the event is."""
    days = (event_date - today).days
    if days == 0:
        return f"{name} is today!"
    elif days > 0:
        return f"{name} is in {days} days."
    else:
        return f"{name} was {-days} days ago."

today = date.today()
print(f"Today is {today.strftime('%A %d %B %Y')}.")
print()

for line in read_lines(FILENAME):
    text, name = line.split(",")
    try:
        event_date = date.fromisoformat(text)
    except ValueError:
        print(f"Skipping '{name}': '{text}' is not a date.")
        continue
    print(describe(event_date, name, today))
Today is Saturday 19 September 2026.

Christmas is in 97 days.
New Year's Day is in 104 days.
Started the Python course was 9 days ago.
Skipping 'Holiday': 'soon' is not a date.
Halloween is in 42 days.

Almost all of it is old friends. read_lines() is from Module 6, .split(",") from Module 4, the try/except with continue from Module 7. The new part is small: one subtraction, (event_date - today).days, and an if on whether the result is zero, positive or negative. When it is negative, -days flips the sign so that the message can say 9 days ago rather than -9 days.

Change the dates in events.txt to your own, add as many lines as you like, and you have a program worth keeping.

Try it

Print today's date in three different formats using strftime(), one of them in your own language's usual order. Then work out what day of the week 1 January 2000 was, and how many days ago that is. Finally, add a line for today's date to events.txt and run the countdown, to see the is today! message.

Common mistakes

AttributeError: module 'datetime' has no attribute 'today'

You wrote import datetime and then datetime.today(). With that import, the type is datetime.date, so you need datetime.date.today(). Writing from datetime import date at the top avoids the double name.

TypeError: unsupported operand type(s) for +: 'datetime.date' and 'int'

You wrote today + 7. A date can only be added to a timedelta: today + timedelta(days=7). Make sure timedelta is in your from datetime import line.

ValueError: day is out of range for month

The numbers do not make a real date, such as date(2026, 2, 30) or date(2026, 4, 31). If they came from a user, catch the ValueError and ask again.

ValueError: Invalid isoformat string: '25/12/2026'

fromisoformat() accepts only the year-month-day form, with hyphens and leading zeros: 2026-12-25. Tell the user the exact form to type, as ask_for_date() does.

The date prints, but the month and day are swapped

date(2026, 12, 25) is year, month, day, in that order, always. There is no setting for this. If your country writes day first, use strftime("%d/%m/%Y") for showing the date, and keep year-month-day everywhere else.

AttributeError: attribute 'year' of 'datetime.date' objects is not writable

You tried some_date.year = 2027. Dates cannot be changed once made. Make a new one instead: date(2027, some_date.month, some_date.day).

Exercises

  1. Stamped diary. Rewrite the Module 6 diary so it no longer asks for the date. Each entry should be saved as 2026-09-19 16:14: what happened, with the date and time filled in by the program.
  2. Age in days. Ask for the user's date of birth with ask_for_date(). Print what day of the week they were born on, how many days they have been alive, and roughly how many years that is.
  3. Weekend or weekday. Ask for a date and print its day name and whether it falls on a weekend. Dates have a method .weekday() that returns a number: run help(date.weekday) to find out which number means which day.
  4. Next birthday. Ask for a date of birth and print the date of the user's next birthday, how many days away it is, and how old they will be. Remember that this year's birthday may already have passed. If it is today, say happy birthday instead.
Solution 1
from datetime import datetime

entry = input("What happened today? ")
stamp = datetime.now().strftime("%Y-%m-%d %H:%M")

with open("diary.txt", "a") as file:
    file.write(f"{stamp}: {entry}\n")

print("Saved. Your diary so far:")
with open("diary.txt") as file:
    print(file.read())

Only two lines changed from the Module 6 version: the input() for the date is gone, and the stamp comes from datetime.now().

Solution 2
from datetime import date

def ask_for_date(prompt):
    """Keep asking until the user types a date like 2026-12-25, then return it."""
    while True:
        text = input(prompt)
        try:
            return date.fromisoformat(text)
        except ValueError:
            print("Please use the form YYYY-MM-DD, for example 2026-12-25.")

birthday = ask_for_date("When were you born? ")
today = date.today()
age_in_days = (today - birthday).days

print(f"You were born on a {birthday.strftime('%A')}.")
print(f"You have been alive for {age_in_days} days.")
print(f"That is about {age_in_days // 365} years.")

Someone born on 25 December 1990 was born on a Tuesday and had been alive for 13,052 days on the day this was written. Dividing by 365 with // gives whole years, ignoring leap days, which is what roughly allows.

Solution 3
from datetime import date

def ask_for_date(prompt):
    """Keep asking until the user types a date like 2026-12-25, then return it."""
    while True:
        text = input(prompt)
        try:
            return date.fromisoformat(text)
        except ValueError:
            print("Please use the form YYYY-MM-DD, for example 2026-12-25.")

chosen = ask_for_date("Which date? ")
day_name = chosen.strftime("%A")

if chosen.weekday() >= 5:
    print(f"{chosen} is a {day_name}. That is the weekend!")
else:
    print(f"{chosen} is a {day_name}. That is a working day.")

help(date.weekday) says Monday == 0 ... Sunday == 6, so the weekend is 5 and 6, and >= 5 catches both. The day name still comes from strftime("%A"), which is simpler than a list of names.

Solution 4
from datetime import date

def ask_for_date(prompt):
    """Keep asking until the user types a date like 2026-12-25, then return it."""
    while True:
        text = input(prompt)
        try:
            return date.fromisoformat(text)
        except ValueError:
            print("Please use the form YYYY-MM-DD, for example 2026-12-25.")

birthday = ask_for_date("When were you born? ")
today = date.today()

next_birthday = date(today.year, birthday.month, birthday.day)
if next_birthday < today:
    next_birthday = date(today.year + 1, birthday.month, birthday.day)

days = (next_birthday - today).days
age = next_birthday.year - birthday.year

if days == 0:
    print(f"Happy birthday! You are {age} today.")
else:
    print(f"Your next birthday is on {next_birthday.strftime('%A %d %B %Y')}.")
    print(f"That is in {days} days. You will be {age}.")

The birthday is rebuilt in the current year from the birth date's month and day. If that date has already gone, it is rebuilt in the next year instead. This solution has one known gap: a birthday on 29 February will cause a ValueError in three years out of four. Handling that is a good stretch task.

Summary

  • from datetime import date gives you the date type. date.today() is today, date(2026, 12, 25) is any date.
  • .year, .month and .day are a date's attributes. Dates can be compared with < and >.
  • Subtracting two dates gives a timedelta, and its .days is the gap as an integer.
  • Add or subtract timedelta(days=7) to move a date. days=7 is a keyword argument: it names the parameter it fills.
  • .strftime("%A %d %B %Y") formats a date for people. date.fromisoformat("2026-12-25") reads one from text, and raises ValueError for anything else.
  • datetime.now() gives the date and time together. %H:%M formats the time.

Next: Writing your own module