Date and Time
By the Syncfusion DevTutorials team · Last updated
The datetime module: date, time and datetime objects, strftime and strptime, timedelta arithmetic, timestamps, and naive against aware moments.
A school runs on dates.
Term starts on a date. Homework is due on a date. A report card says it was printed on a date. Somebody asks how many days are left until the exam, and somebody else asks whether a submission came in before the deadline.
What You Will Learn
In this lesson, you will learn:
- Why dates need their own type, and what
datetimegives you - The three classes you will use:
date,timeanddatetime - How to make one, and how to ask for today's
- How to turn a date into text, and text back into a date
- How to add days to a date and find the gap between two of them
- What a timestamp is, and when you need one
By the end of this lesson, you will be able to create dates, times and moments, show them in any form, read them back from text, work out gaps and lengths, and handle the time zone traps that catch beginners out.
Introduction to Date and Time
Why Date and Time Are Important
Text cannot answer those questions. "14/03/2026" is just characters — Python cannot add thirty days to it or tell you it comes after "05/01/2026". Something built for dates is needed, and here is what it looks like:
A date knows things text does not
from datetime import date, timedelta
exam = date(2026, 3, 14)
print(exam)
print("Year:", exam.year)
print("Day of the week:", exam.strftime("%A"))
print("Thirty days later:", exam + timedelta(days=30))
A date is a value Python understands, not text that looks like a date.
Three questions come up in almost every program that handles dates, and none can be answered with text.
Which came first? Text is compared character by character, so the comparison depends on how the date happens to be written rather than on what it means.
How far apart are they? Subtracting text is not possible at all.
What is thirty days from now? That needs month lengths and leap years.
Text comparison gets it wrong
january_text = "05/01/2026"
march_text = "04/03/2026"
print("As text, is January later than March?", january_text > march_text)
from datetime import date
january = date(2026, 1, 5)
march = date(2026, 3, 4)
print("As dates, is January later than March?", january > march)
print("Days between them:", (march - january).days)
As text the answer is True, which is wrong: 5 January is not after 4 March. Python only compared "05" with "04" and stopped there, because 5 is the bigger character. As dates the answer is right, and the gap comes out as well.
The dangerous part is that the same comparison is sometimes right by accident. Written as "05/01/2026" against "14/03/2026" it gives the correct answer, for the wrong reason — which is how this bug survives testing.
Date and Time in Python
Dates live in the datetime module, which ships with Python. There is nothing to install.
| Class | Holds | Use it for |
|---|---|---|
date | year, month, day | a birthday, a due date, a term start |
time | hour, minute, second | a class period, an alarm |
datetime | both together | a submission, a log entry |
timedelta | a length of time | "thirty days", "two hours" |
All four, side by side
from datetime import date, time, datetime, timedelta
print(date(2026, 3, 14))
print(time(14, 30))
print(datetime(2026, 3, 14, 14, 30))
print(timedelta(days=30))
Now that you know the four main date and time types, let's look at how they are imported and used in real programs.
Datetime Module
Working with Dates
Import what you need by name. The module and the class are both called datetime, which is confusing enough that importing by name is worth the habit.
Importing by name avoids the confusion
from datetime import date, datetime
print(date(2026, 3, 14))
print(datetime(2026, 3, 14, 9, 0))
import datetime as dt
print(dt.date(2026, 3, 14))
from datetime import datetime means the name datetime is now the class. import datetime means it is the module, and the class is datetime.datetime.
Anything to do with a day, a month and a year is date territory:
Working with Times
A time on its own
from datetime import time
period = time(9, 30)
print(period)
print(period.hour, period.minute)
A time holds no date. It is the right type for "when does the first period start", which is true every day.
Date and Time Formatting
Printing a date gives the ISO form, YYYY-MM-DD. strftime() gives you any other form.
One date, many ways to show it
from datetime import date
exam = date(2026, 3, 14)
print(exam)
print(exam.strftime("%d/%m/%Y"))
print(exam.strftime("%d %B %Y"))
print(exam.strftime("%A, %d %b %Y"))
Formatting is covered fully further down this page.
Once a date has been built, the next step is reading and changing its parts:
The datetime module provides several types, but dates are the starting point because they are the simplest.
Date Objects
date Class
A date holds a year, a month and a day. Nothing else.
Creating Dates
date(year, month, day)
All three are required, and they are checked:
A day that does not exist
from datetime import date
print(date(2026, 2, 30))
ValueError: day is out of range for month. February 2026 has 28 days, and Python knows it.
Leap years are handled for you
from datetime import date
print(date(2024, 2, 29))
print(date(2024, 12, 31).timetuple().tm_yday, "days in 2024")
print(date(2026, 12, 31).timetuple().tm_yday, "days in 2026")
2024 is a leap year and 2026 is not, and nothing had to be worked out by hand.
Current Date
date.today() gives today's date on the machine running the program:
Today, wherever this runs
from datetime import date
today = date.today()
print(today)
print("Year:", today.year)
print("Weekday name:", today.strftime("%A"))
The output changes every day, which is the point. Anything that needs a fixed date for testing should be given one rather than reading the clock.
Accessing Year, Month, Day
The three parts are always there, waiting to be read:
The three parts
from datetime import date
exam = date(2026, 3, 14)
print("Year:", exam.year)
print("Month:", exam.month)
print("Day:", exam.day)
These are plain numbers, so they can be used in arithmetic or in an f-string.
Using the parts
from datetime import date
exam = date(2026, 3, 14)
print(f"The exam is on day {exam.day} of month {exam.month}")
print("Is it in the first half of the year?", exam.month <= 6)
They cannot be assigned, because a date is immutable like a string:
A date cannot be changed in place
from datetime import date
exam = date(2026, 3, 14)
exam.year = 2027
replace() gives you a new date instead, which is covered below.
date Methods
Here is the set that comes with every date:
| Method | What it gives | Example result |
|---|---|---|
weekday() | day of week, Monday is 0 | 5 for a Saturday |
isoweekday() | day of week, Monday is 1 | 6 for a Saturday |
isoformat() | the YYYY-MM-DD text | 2026-03-14 |
strftime(fmt) | text in any form you choose | 14 March 2026 |
replace(...) | a new date with parts changed | 2027-03-14 |
toordinal() | days since year 1 | 739690 |
The date methods at work
from datetime import date
exam = date(2026, 3, 14)
print("weekday():", exam.weekday())
print("isoweekday():", exam.isoweekday())
print("Name:", exam.strftime("%A"))
print("isoformat():", exam.isoformat())
print("replace(year=2027):", exam.replace(year=2027))
print("Original is untouched:", exam)
weekday() counting from 0 and isoweekday() counting from 1 is a classic source of off-by-one bugs. Pick one and be deliberate.
Is it a weekend?
from datetime import date
for day in [date(2026, 3, 13), date(2026, 3, 14), date(2026, 3, 15)]:
print(day.strftime("%A"), "- weekend?", day.weekday() >= 5)
date.fromisoformat() goes the other way, turning standard text back into a date:
Reading a standard date from text
from datetime import date
print(date.fromisoformat("2026-03-14"))
print(date.fromisoformat("2026-03-14").strftime("%d %B %Y"))
A date holds no time of day. For that, there is a second class:
Time Objects
time Class
A time holds an hour, a minute, a second and microseconds. It has no date attached.
Creating Times
time(hour, minute, second, microsecond)
Every part is optional and starts at 0.
Times, with parts left out
from datetime import time
print(time(9))
print(time(9, 30))
print(time(14, 30, 5))
print(time())
The hour runs from 0 to 23:
There is no 25 o'clock
from datetime import time
print(time(25, 0))
Current Time
A time has no today(). You take the current moment and ask for its time part.
The time right now
from datetime import datetime
now = datetime.now()
print("Full moment:", now)
print("Just the time:", now.time())
print("Hour:", now.hour)
Accessing Time Components
The parts of a time
from datetime import time
period = time(14, 30, 5)
print("Hour:", period.hour)
print("Minute:", period.minute)
print("Second:", period.second)
print("Microsecond:", period.microsecond)
time Methods
What a time can do
from datetime import time
period = time(14, 30, 5)
print("isoformat():", period.isoformat())
print("24-hour:", period.strftime("%H:%M"))
print("12-hour:", period.strftime("%I:%M %p"))
print("replace(hour=9):", period.replace(hour=9))
Times compare, which is how you check a deadline:
Comparing times
from datetime import time
submitted = time(9, 45)
deadline = time(10, 0)
print("On time?", submitted <= deadline)
print("Late?", submitted > deadline)
Times cannot be added together. "Half past nine plus half past ten" means nothing — adding a length of time is the timedelta job further down.
For anything that happened at a moment — a submission, a log entry — a date and a time are needed together, which is the third class:
Datetime Objects
datetime Class
A datetime holds a date and a time in one value. It is what you use for anything that happened at a moment.
Current Date and Time
Now
from datetime import datetime
now = datetime.now()
print(now)
print("Date part:", now.date())
print("Time part:", now.time())
datetime.today() does almost the same thing. now() is the one to use, because it can also take a time zone.
Creating Datetime Objects
datetime(year, month, day, hour, minute, second)
The date parts are required; the time parts start at 0.
Building a moment
from datetime import datetime
submitted = datetime(2026, 3, 14, 9, 45)
print(submitted)
print(datetime(2026, 3, 14))
Datetime Components
Everything from both classes is available on one value.
All the parts of a moment
from datetime import datetime
submitted = datetime(2026, 3, 14, 9, 45, 30)
print("Year:", submitted.year)
print("Month:", submitted.month)
print("Day:", submitted.day)
print("Hour:", submitted.hour)
print("Minute:", submitted.minute)
print("Second:", submitted.second)
datetime Methods
Pulling a moment apart and putting it back
from datetime import datetime, date, time
submitted = datetime(2026, 3, 14, 9, 45)
print("date():", submitted.date())
print("time():", submitted.time())
print("replace(hour=10):", submitted.replace(hour=10))
print("combine():", datetime.combine(date(2026, 3, 14), time(9, 0)))
print("isoformat():", submitted.isoformat())
print("strftime():", submitted.strftime("%d %B %Y at %I:%M %p"))
combine() is the reverse of date() and time(): it builds one moment from a date and a time you already have.
Datetimes compare and subtract, exactly as dates do:
Comparing two moments
from datetime import datetime
deadline = datetime(2026, 3, 14, 10, 0)
submitted = datetime(2026, 3, 14, 9, 45)
print("On time?", submitted <= deadline)
print("Minutes to spare:", (deadline - submitted).total_seconds() / 60)
Building, reading and showing a date all involve one pair of methods, worth a section of their own:
Formatting and Parsing
Two methods, easy to mix up:
strftime()turns a date into text. Think f for format.strptime()turns text into a date. Think p for parse.
strftime()
A date shown many ways
from datetime import datetime
moment = datetime(2026, 3, 14, 14, 30)
print(moment.strftime("%d/%m/%Y"))
print(moment.strftime("%Y-%m-%d"))
print(moment.strftime("%d %B %Y"))
print(moment.strftime("%A, %d %b"))
print(moment.strftime("%I:%M %p"))
print(moment.strftime("Printed on %d %B %Y at %H:%M"))
Anything that is not a format code is kept as it is, so you can build a whole sentence.
strptime()
strptime() needs the text and a pattern describing its shape.
Reading text as a date
from datetime import datetime
print(datetime.strptime("14/03/2026", "%d/%m/%Y"))
print(datetime.strptime("2026-03-14 09:45", "%Y-%m-%d %H:%M"))
print(datetime.strptime("14 March 2026", "%d %B %Y").date())
The pattern must match the text exactly:
A pattern that does not fit
from datetime import datetime
print(datetime.strptime("14-03-2026", "%d/%m/%Y"))
ValueError: time data '14-03-2026' does not match format '%d/%m/%Y' — the text uses dashes and the pattern expects slashes.
This is also where day-first and month-first formats bite:
The same text, two readings
from datetime import datetime
text = "03/04/2026"
print("Day first: ", datetime.strptime(text, "%d/%m/%Y").strftime("%d %B"))
print("Month first:", datetime.strptime(text, "%m/%d/%Y").strftime("%d %B"))
Same six digits, two different dates, and no error either way. When you read dates from a file, know which form they are in.
Both strftime() and strptime() use the same set of formatting codes. The table below lists the ones you will use most often.
Common Format Codes
Here is the set worth keeping to hand:
| Code | Means | Example |
|---|---|---|
%Y | four-digit year | 2026 |
%y | two-digit year | 26 |
%m | month as a number | 03 |
%B | month name | March |
%b | short month name | Mar |
%d | day of the month | 14 |
%A | weekday name | Saturday |
%a | short weekday name | Sat |
%H | hour, 24-hour | 14 |
%I | hour, 12-hour | 02 |
%M | minute | 30 |
%S | second | 05 |
%p | AM or PM | PM |
%% | a literal percent sign | % |
Every code above, on one date
from datetime import datetime
moment = datetime(2026, 3, 14, 14, 30, 5)
for code in ["%Y", "%y", "%m", "%B", "%b", "%d", "%A", "%a", "%H", "%I", "%M", "%S", "%p"]:
print(code, "->", moment.strftime(code))
Capital %M is minutes; small %m is the month. Capital %H is the 24-hour clock; capital %I is the 12-hour one and needs %p beside it to be unambiguous.
Dates can also be moved forward and back, and measured against each other:
Date and Time Arithmetic
timedelta
A timedelta is a length of time, not a point in time. "Thirty days" rather than "the fourteenth of March".
Lengths of time
from datetime import timedelta
print(timedelta(days=30))
print(timedelta(weeks=2))
print(timedelta(hours=3, minutes=30))
print(timedelta(days=1, hours=12).total_seconds())
It accepts weeks, days, hours, minutes, seconds, milliseconds and microseconds — but not months or years, because those are not fixed lengths. A month can be 28 to 31 days.
Why there is no months argument
from datetime import date
print("January has", (date(2026, 2, 1) - date(2026, 1, 1)).days, "days")
print("February has", (date(2026, 3, 1) - date(2026, 2, 1)).days, "days")
To move by a month, change the month number with replace(), or add the right number of days deliberately.
Adding Dates and Times
Moving forwards and backwards
from datetime import date, datetime, timedelta
exam = date(2026, 3, 14)
print("A week later:", exam + timedelta(weeks=1))
print("Thirty days later:", exam + timedelta(days=30))
print("A week earlier:", exam - timedelta(weeks=1))
submitted = datetime(2026, 3, 14, 9, 45)
print("Two and a half hours later:", submitted + timedelta(hours=2, minutes=30))
Month ends and leap years are handled for you:
Crossing a month end
from datetime import date, timedelta
print(date(2026, 1, 31) + timedelta(days=1))
print(date(2024, 2, 28) + timedelta(days=1))
print(date(2026, 2, 28) + timedelta(days=1))
print(date(2026, 12, 31) + timedelta(days=1))
2024 gets a 29 February and 2026 does not, with nothing to remember.
Difference Between Dates
Subtracting two dates gives a timedelta.
How far apart?
from datetime import date
term_start = date(2026, 1, 5)
exam = date(2026, 3, 14)
gap = exam - term_start
print(gap)
print("Days:", gap.days)
print("Weeks:", gap.days // 7)
Subtracting two datetimes gives a length including the time of day:
A gap measured in hours
from datetime import datetime
start = datetime(2026, 3, 14, 9, 0)
end = datetime(2026, 3, 14, 12, 30)
gap = end - start
print(gap)
print("Hours:", gap.total_seconds() / 3600)
.days is whole days only; .total_seconds() is the whole length as a number, and is what you want for anything shorter than a day.
Where .days misleads
from datetime import datetime
gap = datetime(2026, 3, 14, 23, 0) - datetime(2026, 3, 14, 9, 0)
print("Length:", gap)
print(".days says:", gap.days)
print("total_seconds() says:", gap.total_seconds() / 3600, "hours")
Fourteen hours gives .days of 0, because it is not a whole day.
There is one more way to hold a moment — as a single plain number — which is the last idea of the lesson:
Timestamps
timestamp()
A timestamp is a single number: the seconds since 1 January 1970, a moment known as the epoch. It is how computers store and exchange moments.
A moment as one number
from datetime import datetime, timezone
moment = datetime(2026, 3, 14, 12, 0, tzinfo=timezone.utc)
print(moment)
print(moment.timestamp())
One number, no formatting and no ambiguity, which is why files and databases use them.
fromtimestamp()
Turning the number back into a moment
from datetime import datetime, timezone
stamp = 1773489600.0
print(datetime.fromtimestamp(stamp, tz=timezone.utc))
print(datetime.fromtimestamp(stamp, tz=timezone.utc).strftime("%d %B %Y, %H:%M"))
A timestamp with no time zone attached is read using the machine's own zone, which means the same number gives different answers on different computers:
The same number, read by this machine
from datetime import datetime, timezone
stamp = 1773489600.0
print("As UTC: ", datetime.fromtimestamp(stamp, tz=timezone.utc))
print("As this machine:", datetime.fromtimestamp(stamp))
If those two lines differ, this machine is not on UTC. That is exactly the kind of difference that turns into a bug nobody can reproduce.
A datetime with no time zone is called naive, and .timestamp() on one uses whatever zone the machine happens to be in. When a moment matters — a deadline, a log entry, anything stored — attach timezone.utc.
Naive against aware
from datetime import datetime, timezone
naive = datetime(2026, 3, 14, 12, 0)
aware = datetime(2026, 3, 14, 12, 0, tzinfo=timezone.utc)
print("Naive:", naive, "- tzinfo:", naive.tzinfo)
print("Aware:", aware, "- tzinfo:", aware.tzinfo)
Naive and aware do not mix
from datetime import datetime, timezone
naive = datetime(2026, 3, 14, 12, 0)
aware = datetime(2026, 3, 14, 12, 0, tzinfo=timezone.utc)
print(aware - naive)
TypeError: can't subtract offset-naive and offset-aware datetimes. Python refuses to guess, which is the right behaviour. Keep a program consistently one or the other.
Before moving on, here are some of the mistakes beginners make most often with dates and times.
Common mistakes
Each row is one mistake. The middle column is what you will actually see on screen.
| Mistake | What you see | Why it happens | Fix |
|---|---|---|---|
| Comparing dates as text | January looks later than March | Text compares character by character | Use date objects |
import datetime then datetime(...) | TypeError: 'module' object is not callable | The module and the class share a name | from datetime import datetime |
%M where %m was meant | Minutes printed as the month | Capital is minutes, small is month | Check the case of every code |
A strptime pattern that does not match | ValueError: time data ... does not match format | The pattern must match exactly | Make the separators and order match |
| Day-first text read as month-first | A real but wrong date, with no error | 03/04 is valid either way | Know the source's format |
Expecting timedelta(months=1) | TypeError: unexpected keyword argument 'months' | A month has no fixed length | Use replace(), or a number of days |
Using .days for a gap under a day | 0 for a fourteen-hour gap | .days counts whole days only | total_seconds() |
Assigning to .year | AttributeError: attribute is read-only | Dates are immutable | exam.replace(year=2027) |
.timestamp() on a naive datetime | A number that differs between machines | The machine's own zone is used | Attach timezone.utc |
| Subtracting naive from aware | TypeError: can't subtract offset-naive and offset-aware | Python will not guess a zone | Keep both the same kind |
| A date that does not exist | ValueError: day is out of range for month | 30 February is checked and refused | Check the real month length |
Mixing weekday() and isoweekday() | Everything off by one | One counts from 0, the other from 1 | Pick one and stay with it |
What to Remember
- A date is a value Python understands, not text that looks like a date. Text compares and subtracts wrongly.
dateholds a day,timeholds a clock time,datetimeholds both,timedeltaholds a length.from datetime import datetimeimports the class;import datetimeimports the module.date.today()anddatetime.now()read the machine's clock, so their output changes every run.- Dates are immutable —
replace()gives you a new one. strftime()formats a date as text;strptime()parses text into a date and needs a pattern that matches exactly.%mis the month and%Mis minutes.%Bis the month name,%Athe weekday name.timedeltahas nomonthsoryears, because neither has a fixed length.- Subtracting two dates gives a
timedelta:.daysfor whole days,.total_seconds()for anything shorter. - A timestamp is seconds since 1970, and a naive datetime's timestamp depends on the machine's time zone. Attach
timezone.utcwhen the moment matters.
Next: Classes