Contact
Evil Genius SocietyHomeLearnForumLeaderboardSocialsMessages
← Back to module

Lesson 6 of 10

Lesson 7.7 introduced datetime. Here's the full picture — including the mistakes that cause real bugs.

The four types

from datetime import date, time, datetime, timedelta

print(date(2026, 3, 15))
print(time(14, 30))
print(datetime(2026, 3, 15, 14, 30))
print(timedelta(days=7))
  • date — a calendar day
  • time — a clock time
  • datetime — both together
  • timedelta — a duration

Now

from datetime import date, datetime

today = date.today()
now = datetime.now()

print(isinstance(today, date))
print(isinstance(now, datetime))
print(now.year > 2000)

Arithmetic

Add or subtract a timedelta; subtracting two dates gives a timedelta:

from datetime import date, timedelta

start = date(2026, 3, 1)

print(start + timedelta(days=30))
print(start - timedelta(weeks=2))

end = date(2026, 12, 25)
gap = end - start
print(gap.days)
print(type(gap).__name__)

timedelta accepts days, hours, minutes, seconds, weeks — but not months or years, because those have no fixed length. Adding "one month" to 31 January is ambiguous, so Python refuses to guess. Use dateutil.relativedelta (third-party) if you need calendar-aware maths.

Formatting with strftime

Date to string:

from datetime import datetime

d = datetime(2026, 3, 15, 14, 30, 5)

print(d.strftime("%Y-%m-%d"))
print(d.strftime("%d/%m/%Y %H:%M"))
print(d.strftime("%B %d, %Y"))
print(d.strftime("%A"))

| Code | Means | | --- | --- | | %Y / %y | 4-digit / 2-digit year | | %m / %B / %b | month number / full name / short name | | %d | day of month | | %A / %a | weekday full / short | | %H / %I | hour 24 / 12 | | %M / %S | minute / second | | %p | AM or PM |

Parsing with strptime

String to date — same codes, describing the input format:

from datetime import datetime

d = datetime.strptime("2026-03-15", "%Y-%m-%d")
print(d.date())

d2 = datetime.strptime("15/03/2026 14:30", "%d/%m/%Y %H:%M")
print(d2)

A mismatch raises ValueError, so parsing user input needs try/except:

from datetime import datetime

def parse_date(text):
    try:
        return datetime.strptime(text, "%Y-%m-%d").date()
    except ValueError:
        return None

print(parse_date("2026-03-15"))
print(parse_date("not a date"))

Remember: strFtime formats, strPtime parses.

ISO format

For storing dates — in JSON, a database, a filename — use ISO 8601 and skip custom formats entirely:

from datetime import date, datetime

d = date(2026, 3, 15)
print(d.isoformat())
print(date.fromisoformat("2026-03-15"))
print(datetime.fromisoformat("2026-03-15T14:30:00"))

ISO sorts correctly as plain text, is unambiguous internationally, and is what json output should carry. Never store 15/03/2026 — is that March or December? Depends who's reading.

Time zones

A datetime with no timezone is naive — it doesn't know where it is. That's the source of most date bugs.

from datetime import datetime, timezone, timedelta

naive = datetime(2026, 3, 15, 14, 30)
print(naive.tzinfo)

aware = datetime(2026, 3, 15, 14, 30, tzinfo=timezone.utc)
print(aware.tzinfo)

phnom_penh = timezone(timedelta(hours=7))
local = aware.astimezone(phnom_penh)
print(local.strftime("%H:%M %Z"))

The rule that avoids trouble: store UTC, display local. Convert at the edges only. Comparing a naive and an aware datetime raises TypeError, which is Python protecting you.

Try it yourself

Checkpoint

  • date, time, datetime, timedelta — day, clock, both, duration.
  • Subtracting dates gives a timedelta; timedelta has no months or years.
  • strftime formats out, strptime parses in; a bad parse raises ValueError.
  • Store dates in ISO format — sortable, unambiguous, standard.
  • Naive datetimes have no timezone; store UTC and convert only for display.

Questions about this lesson

Log in to ask a question.