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 datetime gives you
  • The three classes you will use: date, time and datetime
  • 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.

ClassHoldsUse it for
dateyear, month, daya birthday, a due date, a term start
timehour, minute, seconda class period, an alarm
datetimeboth togethera submission, a log entry
timedeltaa 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:

MethodWhat it givesExample result
weekday()day of week, Monday is 05 for a Saturday
isoweekday()day of week, Monday is 16 for a Saturday
isoformat()the YYYY-MM-DD text2026-03-14
strftime(fmt)text in any form you choose14 March 2026
replace(...)a new date with parts changed2027-03-14
toordinal()days since year 1739690

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:

CodeMeansExample
%Yfour-digit year2026
%ytwo-digit year26
%mmonth as a number03
%Bmonth nameMarch
%bshort month nameMar
%dday of the month14
%Aweekday nameSaturday
%ashort weekday nameSat
%Hhour, 24-hour14
%Ihour, 12-hour02
%Mminute30
%Ssecond05
%pAM or PMPM
%%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.

MistakeWhat you seeWhy it happensFix
Comparing dates as textJanuary looks later than MarchText compares character by characterUse date objects
import datetime then datetime(...)TypeError: 'module' object is not callableThe module and the class share a namefrom datetime import datetime
%M where %m was meantMinutes printed as the monthCapital is minutes, small is monthCheck the case of every code
A strptime pattern that does not matchValueError: time data ... does not match formatThe pattern must match exactlyMake the separators and order match
Day-first text read as month-firstA real but wrong date, with no error03/04 is valid either wayKnow the source's format
Expecting timedelta(months=1)TypeError: unexpected keyword argument 'months'A month has no fixed lengthUse replace(), or a number of days
Using .days for a gap under a day0 for a fourteen-hour gap.days counts whole days onlytotal_seconds()
Assigning to .yearAttributeError: attribute is read-onlyDates are immutableexam.replace(year=2027)
.timestamp() on a naive datetimeA number that differs between machinesThe machine's own zone is usedAttach timezone.utc
Subtracting naive from awareTypeError: can't subtract offset-naive and offset-awarePython will not guess a zoneKeep both the same kind
A date that does not existValueError: day is out of range for month30 February is checked and refusedCheck the real month length
Mixing weekday() and isoweekday()Everything off by oneOne counts from 0, the other from 1Pick 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.
  • date holds a day, time holds a clock time, datetime holds both, timedelta holds a length.
  • from datetime import datetime imports the class; import datetime imports the module.
  • date.today() and datetime.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.
  • %m is the month and %M is minutes. %B is the month name, %A the weekday name.
  • timedelta has no months or years, because neither has a fixed length.
  • Subtracting two dates gives a timedelta: .days for 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.utc when the moment matters.

Next: Classes