☰ Learn Python Tutorial Menu

Dates and Times

Written by CSA mentors · Updated 26 Sept 2026 · 5 min read


Is "01/02/2026" the first of February or the second of January? In Pakistan it's February. To a system built in the US it's January, and we've watched an invoice ageing report go wrong for exactly that reason. Dates look simple and hide traps everywhere. Months have different lengths, you can't subtract two strings, and sorting date text alphabetically gives nonsense. Python's datetime module turns dates into proper objects you can compare, add to and format, and this lesson covers the parts you'll use weekly.

The three main types

TypeHoldsExample
dateYear, month, daydate(2026, 9, 24)
datetimeDate plus hour, minute, seconddatetime(2026, 9, 24, 14, 30)
timedeltaA durationtimedelta(days=30)
from datetime import date, datetime, timedelta

today = date.today()
now = datetime.now()
print(today, now)
print(now.year, now.month, now.day, now.hour)

invoice = date(2026, 9, 1)
print(invoice.weekday())          # 1  (Monday is 0, Sunday is 6)
print(invoice.strftime("%A"))     # Tuesday

Text to date and back

strptime converts text to a datetime object, strftime converts it back, with a table of format codes and timedelta arithmetic

strptime (string parse time) reads text using a format pattern. strftime (string format time) writes text from an object. The format codes are the same in both directions, and yes, everyone mixes the two names up for the first month.

from datetime import datetime

raw = "14/08/2026"
d = datetime.strptime(raw, "%d/%m/%Y")
print(d)                              # 2026-08-14 00:00:00
print(d.strftime("%d %b %Y"))         # 14 Aug 2026
print(d.strftime("%Y-%m-%d"))         # 2026-08-14  (ISO format, sorts correctly as text)
print(d.date())                       # just the date part
CodeMeaningExample
%dDay, two digits14
%mMonth, two digits08
%Y / %yYear four / two digits2026 / 26
%b / %BMonth short / full nameAug / August
%a / %AWeekday short / fullFri / Friday
%H %M %SHour (24h), minute, second14 30 05
%I %pHour (12h), AM/PM02 PM

If the text doesn't match the pattern, strptime raises ValueError. When a file mixes formats, try each pattern in turn inside try/except. For ISO strings like "2026-08-14T14:30:00", use datetime.fromisoformat(), which needs no pattern at all.

Arithmetic with timedelta

from datetime import date, timedelta

invoice_date = date(2026, 8, 14)
due = invoice_date + timedelta(days=30)
print("Due:", due)                             # 2026-09-13

today = date(2026, 9, 24)
overdue_by = today - due                       # date minus date gives a timedelta
print(overdue_by.days, "days late")            # 11 days late
print(overdue_by > timedelta(days=7))          # True

start = date(2026, 1, 1)
mondays = [start + timedelta(days=i) for i in range(365) if (start + timedelta(days=i)).weekday() == 0]
print(len(mondays), "Mondays in 2026")

timedelta accepts days, hours, minutes, seconds and weeks, but not months or years, because those vary in length. To move by months, adjust year and month yourself with d.replace(month=...), or install the third-party dateutil package locally. Watch out for the 31st, since replace(month=2) on 31 January raises an error.

Comparing and sorting

Date objects compare naturally with <, > and ==, and lists of them sort correctly. This is the main reason to convert text to dates rather than comparing strings. As text, "14/08/2026" > "02/09/2026" is True because the comparison only looks at the first characters ("1" against "0"), so 14 August wrongly lands after 2 September. Date objects compare by real calendar order.

from datetime import datetime

texts = ["14/08/2026", "02/09/2026", "28/07/2026"]
dates = sorted(datetime.strptime(t, "%d/%m/%Y").date() for t in texts)
print(dates)                                   # earliest first
print(dates[-1] - dates[0])                    # 36 days, 0:00:00

Time zones

By default datetime.now() is "naive", carrying no time zone. That's fine for a local report. When data arrives from an API in UTC, or you combine records from Dubai and Karachi, use aware datetimes.

from datetime import datetime, timezone, timedelta

utc_now = datetime.now(timezone.utc)
pkt = timezone(timedelta(hours=5))             # Pakistan Standard Time, UTC+5
print(utc_now.astimezone(pkt).strftime("%H:%M %Z"))

On Python 3.9 and later you can also use zoneinfo.ZoneInfo("Asia/Karachi"), which knows about historical changes.

Worked example, an ageing report

from datetime import date, datetime

today = date(2026, 9, 24)
invoices = [("INV-201", "2026-06-30", 45000), ("INV-214", "2026-08-20", 12500),
            ("INV-230", "2026-09-15", 78000)]

buckets = {"current": 0, "1-30": 0, "31-60": 0, "60+": 0}
for ref, issued, amount in invoices:
    age = (today - date.fromisoformat(issued)).days
    if age <= 0:
        buckets["current"] += amount
    elif age <= 30:
        buckets["1-30"] += amount
    elif age <= 60:
        buckets["31-60"] += amount
    else:
        buckets["60+"] += amount

for bucket, total in buckets.items():
    print(f"{bucket:<8} Rs {total:>10,}")

Letters of credit in Faisalabad: A textile exporter tracks LCs that expire 90 days after shipment. The old spreadsheet held dates as text in three different formats, so the "days to expiry" column was often blank or negative and nobody trusted it. A Python script parses each format with a short list of strptime patterns, computes expiry = shipped + timedelta(days=90), and emails a list of LCs expiring within 14 days every Monday morning. It caught two close calls in its first month.

The one rule

Do date logic on date, datetime and timedelta objects, never on strings. Parse with strptime(text, pattern), format with strftime(pattern), and remember the codes are shared. Adding a timedelta moves a date and subtracting two dates gives a timedelta with .days. Date objects compare and sort correctly, and so does ISO text (YYYY-MM-DD), which is why we store dates that way. Use aware datetimes when data crosses time zones.

Homework

  1. Parse "24-09-2026 14:30" into a datetime, add 45 minutes, and print the result as 02:15 PM on Thursday style text.
  2. Given a list of dates as "dd/mm/yyyy" strings, count how many fall on a weekend.
  3. Write days_until_eid(today) that returns the number of days from a given date to 2027-03-20, and print a message if it is under 30.

Lesson 16 of 26

Sign in to track your progress and earn learning points for every lesson you finish.

Example

print("Hello from CSA!")

for i in range(5):
    print(i * i)
Try it Yourself »