Python with AI Tutorial · Chapter 26 of 48
Python dates live in the built-in datetime module. It gives you date for a calendar day, datetime for a day plus time, timedelta for a duration, and strftime / strptime to convert between dates and text. Master those four and every due date, ageing report and month-end calculation becomes a few lines.
Dates look simple and are a leading source of business bugs: 03/04/2026 means April 3rd in the US and March 4th in India, months have different lengths, and a server in another timezone can shift an invoice into the wrong day. This chapter shows the standard-library tools that keep all of that under control.
Getting today and now
Import the classes you need from the module. date.today() gives the current day; datetime.now() adds the time down to microseconds. Both come from your computer clock, so the output below will differ when you run it.
from datetime import date, datetime
today = date.today()
now = datetime.now()
print("Today:", today)
print("Now:", now)
print("Year:", today.year, "Month:", today.month, "Day:", today.day)
Output (depends on when you run it): Today: 2026-09-05 Now: 2026-09-05 10:42:17.318204 Year: 2026 Month: 9 Day: 5
Creating a specific date
Pass year, month, day (and optionally hour, minute, second) to build a fixed date. Every attribute is then available, including the weekday, where Monday is 0 and Sunday is 6.
from datetime import datetime
invoice_date = datetime(2026, 3, 31, 14, 30)
print(invoice_date)
print("Quarter:", (invoice_date.month - 1) // 3 + 1)
print("Weekday number:", invoice_date.weekday())
print("ISO week:", invoice_date.isocalendar().week)
Output: 2026-03-31 14:30:00 Quarter: 1 Weekday number: 1 ISO week: 14
Use date when the time of day does not matter (invoice dates, birthdays, deadlines) and datetime when it does (log entries, meeting slots). Mixing the two in one comparison raises an error, so decide early and convert with .date() when you need to.
Formatting dates with strftime
strftime (string-format-time) turns a date into text using codes that start with %. This is what you use for report headers, file names and anything a human reads.
from datetime import datetime
invoice_date = datetime(2026, 3, 31, 14, 30)
print(invoice_date.strftime("%d-%m-%Y"))
print(invoice_date.strftime("%d %b %Y"))
print(invoice_date.strftime("%A, %d %B %Y"))
print(invoice_date.strftime("%I:%M %p"))
print(invoice_date.strftime("Sales_Report_%Y%m%d.xlsx"))
Output: 31-03-2026 31 Mar 2026 Tuesday, 31 March 2026 02:30 PM Sales_Report_20260331.xlsx
| Code | Meaning | Example |
|---|---|---|
%Y |
Four-digit year | 2026 |
%y |
Two-digit year | 26 |
%m |
Month number, zero-padded | 03 |
%B / %b |
Month name, full / short | March / Mar |
%d |
Day of month, zero-padded | 31 |
%A / %a |
Weekday name, full / short | Tuesday / Tue |
%H / %I |
Hour, 24-hour / 12-hour | 14 / 02 |
%M / %S |
Minute / second | 30 / 00 |
%p |
AM or PM | PM |
%j |
Day of year | 090 |
%Z / %z |
Timezone name / offset | IST / +0530 |
Parsing text into dates with strptime
strptime (string-parse-time) is the reverse: give it the text and the pattern it follows, and you get a real datetime you can sort, compare and add to. Text dates from CSV exports and web forms almost always need this step.
from datetime import datetime
raw = "15/08/2026"
parsed = datetime.strptime(raw, "%d/%m/%Y")
print(parsed)
print(parsed.strftime("%A"))
print(parsed.date())
iso_only = datetime.fromisoformat("2026-03-31T14:30:00")
print(iso_only.hour, iso_only.minute)
Output: 2026-08-15 00:00:00 Saturday 2026-08-15 14 30
"15/08/2026" with "%m/%d/%Y" raises ValueError: time data ... does not match format because there is no month 15. When a column mixes formats, try several patterns in a loop and log the rows that fail rather than guessing.Date arithmetic with timedelta
Adding or subtracting a timedelta moves a date forward or back. Subtracting two dates returns a timedelta whose .days attribute is the gap. This is the whole basis of due-date and ageing logic.
from datetime import date, timedelta
invoice_date = date(2026, 3, 31)
due_date = invoice_date + timedelta(days=30)
paid_on = date(2026, 5, 12)
print("Due:", due_date)
print("Days late:", (paid_on - due_date).days)
print("Reminder date:", due_date - timedelta(weeks=1))
Output: Due: 2026-04-30 Days late: 12 Reminder date: 2026-04-23
timedelta accepts days, seconds, minutes, hours and weeks, but not months or years, because those are not fixed lengths. For “same day next month” logic use the calendar module below or the third-party python-dateutil package.
Service length and month lengths
Two everyday HR and finance needs: how long has someone been employed, and how many days are in a given month. The calendar module answers the second one and also tells you the weekday the month starts on.
from datetime import date
import calendar
joined = date(2019, 7, 1)
as_of = date(2026, 9, 5)
service = as_of - joined
print("Days of service:", service.days)
print("Completed years:", service.days // 365)
first_weekday, days_in_month = calendar.monthrange(2026, 2)
print("Feb 2026 has", days_in_month, "days")
print("Month end:", date(2026, 2, days_in_month))
print("Leap year?", calendar.isleap(2026))
Output: Days of service: 2623 Completed years: 7 Feb 2026 has 28 days Month end: 2026-02-28 Leap year? False
Sorting and filtering a list of date strings
Text dates sort alphabetically, which is wrong for almost every format except ISO. Parse first, then sort or filter by the real date value.
from datetime import datetime, date
invoices = [
("INV-104", "05/09/2026"),
("INV-098", "28/06/2026"),
("INV-101", "12/08/2026"),
]
parsed = [(no, datetime.strptime(d, "%d/%m/%Y").date()) for no, d in invoices]
parsed.sort(key=lambda item: item[1])
for no, d in parsed:
print(no, d.strftime("%d %b %Y"))
q3 = [no for no, d in parsed if date(2026, 7, 1) <= d <= date(2026, 9, 30)]
print("Q3 invoices:", q3)
Output: INV-098 28 Jun 2026 INV-101 12 Aug 2026 INV-104 05 Sep 2026 Q3 invoices: ['INV-101', 'INV-104']
The same pattern works for any report filter: parse once into real dates, keep the parsed value next to the original record, and compare against date boundaries. It is faster and far less error-prone than slicing strings to pull out a month number.
Timezones with zoneinfo
A plain datetime is naive: it carries no timezone, so Python cannot tell whether 14:30 is Mumbai time or New York time. Attach a zone with ZoneInfo to make it aware, then convert with astimezone. Daylight saving is handled for you.
from datetime import datetime, timezone
from zoneinfo import ZoneInfo
meeting = datetime(2026, 3, 31, 14, 30, tzinfo=ZoneInfo("Asia/Kolkata"))
print(meeting)
print(meeting.astimezone(ZoneInfo("America/New_York")))
print(meeting.astimezone(timezone.utc).strftime("%H:%M %Z"))
Output: 2026-03-31 14:30:00+05:30 2026-03-31 05:00:00-04:00 09:00 UTC
On Windows the zone database is not always present. If you see ZoneInfoNotFoundError, run python -m pip install tzdata once; Mac and Linux ship the database with the operating system.
Ask for a robust parser when your export mixes several date formats.
Write a Python function parse_any_date(text) that tries these formats in order: "%d/%m/%Y", "%d-%m-%Y", "%Y-%m-%d", "%d %b %Y" and "%B %d, %Y". Return a date object, or None if nothing matches, and never raise. Then show it running on this list: ["31/03/2026", "2026-04-15", "15 Aug 2026", "April 3, 2026", "bad value"].
Turn a business rule about ageing buckets into working code.
I have a list of dicts with keys invoice_no, invoice_date (string dd/mm/yyyy) and amount. Using only the datetime module, write Python that calculates days outstanding as of today, assigns each invoice to a bucket (Current, 1-30, 31-60, 61-90, 90+) and prints the total amount per bucket in a neat aligned table. Explain the timedelta arithmetic in comments.
Common mistakes
- Comparing a
datewith adatetime; call.date()on the datetime first or you get aTypeError. - Mixing naive and aware datetimes in one calculation. Pick one convention per project, ideally aware UTC in storage.
- Assuming
%m/%dwhen the data is%d/%m. Check a date above the 12th to know which order the source uses. - Adding
timedelta(days=30)and calling it “one month”. Usecalendar.monthrangefor real month ends. - Sorting date strings as text. Parse to
dateobjects before sorting.
Exercise
Given start = date(2026, 1, 1), print the last working day (Monday to Friday) of each month in 2026 in the format Jan 2026: Fri 30 Jan.
Show answer
from datetime import date, timedelta
import calendar
for month in range(1, 13):
last = date(2026, month, calendar.monthrange(2026, month)[1])
while last.weekday() > 4: # 5 = Saturday, 6 = Sunday
last -= timedelta(days=1)
print(f"{last.strftime('%b %Y')}: {last.strftime('%a %d %b')}")
# Jan 2026: Fri 30 Jan
# Feb 2026: Fri 27 Feb
# ...
Related chapters
FAQ
What is the difference between strftime and strptime?
strftime formats a date object into text using % codes. strptime parses text back into a datetime using the same codes. Format out, parse in.
How do I add one month to a date in Python?
timedelta has no month unit. Either compute the new month and clamp the day with calendar.monthrange, or install python-dateutil and use relativedelta(months=1).
Why does Python show a timezone error on Windows?
ZoneInfo reads the IANA database, which Windows does not include. Run python -m pip install tzdata once and the error disappears.
Working with spreadsheets too? Ready-made Excel, Google Sheets and Power BI templates are at NextGenTemplates.com.
Chapter 26 of 48 · Python with AI: all 48 chapters



