Today's outcomes
- Choose date, datetime, or timedelta.
- Parse and format known date formats.
- Use aware UTC timestamps for comparable instants.
Before you start: Complete Day 22 and its practical challenge.
Understand the concept
Choose a calendar date, an instant, or a duration
A date represents a calendar label such as October 3, 2026. A datetime includes a date and time of day. A timedelta represents a duration. A time object represents time-of-day fields without a date. These types answer different questions and should not be replaced indiscriminately with strings.
A due date for a reading plan may require only a calendar date. An audit event shared across time zones needs a clearly specified instant. The elapsed duration of a local operation is often measured with a monotonic clock rather than subtracting wall-clock timestamps that can be adjusted.
The time module and datetime module therefore serve related but different purposes. time.sleep waits, and time.monotonic supports elapsed-time measurement. datetime types support calendar fields and timestamp representation. Choose the tool from the question rather than from a familiar name.
Parsing and formatting are opposite boundaries
Parsing turns accepted text into a date or datetime object. Formatting turns an object into display text. date.fromisoformat supports documented ISO date representations; strptime uses a specified format. strftime produces text according to a format string.
Prefer unambiguous input contracts. 03/04/2026 can mean different dates under different regional conventions. A format such as %Y-%m-%d states year-month-day order explicitly. Parsing cannot infer your intended convention from an ambiguous value.
For interchange, isoformat is useful because its ordering and separators are explicit. A friendly display such as “03 Oct 2026” may be suitable for readers, but abbreviated month names depend on locale. Do not use that display format as a universal parsing contract without controlling the locale.
Aware and naive datetimes carry different information
A naive datetime does not specify how its clock fields map to a global instant. An aware datetime has timezone information. Creating a naive value and assuming it is UTC without stating that policy can shift events or make comparisons invalid.
For simple comparable audit timestamps, use aware UTC values. Named local zones require zoneinfo and careful treatment of daylight-saving transitions, discussed in Noetrion's deeper time-zone article. Adding a timezone label is not always the same operation as converting an existing instant.
Worked example
from datetime import date, datetime, timedelta, timezone
start = date.fromisoformat("2026-10-03")
review = start + timedelta(days=3)
elapsed_days = (review - start).days
event = datetime(2026, 10, 3, 2, 0, tzinfo=timezone.utc)
later = event + timedelta(minutes=90)
assert review.isoformat() == "2026-10-06"
assert elapsed_days == 3
assert later.isoformat() == "2026-10-03T03:30:00+00:00"
print("Review date:", review.isoformat())
print("UTC event:", event.isoformat())
print("Later:", later.isoformat())
| Value | Type | Question answered |
|---|---|---|
| start | date | Which calendar day begins the plan? |
| timedelta(days=3) | timedelta | How much calendar-day offset is applied? |
| review | date | Which calendar day is the review? |
| event | Aware datetime | Which specified UTC instant? |
| later minus event | timedelta | What duration separates these UTC values? |
All dates here are fixed fixtures. The output does not depend on the computer's current date or local timezone, making the expected answers reproducible.
Guided practice
- Predict: add one day to February 28, 2024 and February 28, 2025. Expect February 29 in the leap year and March 1 otherwise. Do not implement month-length arithmetic manually.
- Modify: calculate the date 30 days after December 15, 2026. Let timedelta and date arithmetic handle the year boundary.
- Reconstruct: parse an explicit year-month-day datetime using strptime, then format it with isoformat. Explain whether it is aware or naive and which additional policy would locate it globally.
Keep duration.units visible. .days on a timedelta is one stored component, not always a fractional total duration. Use total_seconds when your calculation needs the complete duration in seconds, then convert to your chosen unit.
Daily challenge
Implement review_date(start_text, offset_days). Accept text supported by date.fromisoformat and a nonnegative actual integer offset, excluding Booleans. Return the resulting date as an ISO string. Invalid date text should propagate ValueError; an invalid offset should raise ValueError explicitly.
| Start | Offset | Expected result |
|---|---|---|
| 2026-10-03 | 3 | 2026-10-06 |
| 2024-02-28 | 1 | 2024-02-29 |
| 2026-12-31 | 1 | 2027-01-01 |
| 2026-10-03 | 0 | 2026-10-03 |
| 2026-02-30 | 1 | ValueError |
This function returns calendar labels, not timezone-aware timestamps. It is appropriate for the stated date-offset task and should not be described as scheduling a globally unambiguous notification instant.
Reference solution
Compare after attempting the challenge
from datetime import date, timedelta
def review_date(start_text, offset_days):
if type(offset_days) is not int or offset_days < 0:
raise ValueError("offset_days must be a nonnegative integer")
start = date.fromisoformat(start_text)
return (start + timedelta(days=offset_days)).isoformat()
assert review_date("2026-10-03", 3) == "2026-10-06"
assert review_date("2024-02-28", 1) == "2024-02-29"
assert review_date("2026-12-31", 1) == "2027-01-01"
assert review_date("2026-10-03", 0) == "2026-10-03"
for start, offset in [("2026-02-30", 1), ("2026-10-03", -1), ("2026-10-03", True)]:
try:
review_date(start, offset)
except ValueError:
pass
else:
raise AssertionError("Invalid date or offset should fail")
print("Calendar review dates passed")
Debug and explain
If dates are swapped by month and day, inspect the input format. If subtraction between datetimes fails, inspect aware versus naive status. If a duration seems to lose hours, inspect whether you read only its days component. If a notification shifts between regions, inspect the intended timezone rather than merely its formatted clock value.
Explain the difference between a date, an instant, and a duration using the study plan. Then identify which of those this challenge returns and which it deliberately does not determine.
Review and next step
Save the leap-year and year-boundary cases. Rebuild the date-offset function at your next review. Day 24 introduces regular expressions for well-defined text patterns, while preserving the lesson that a pattern match is not complete semantic validation.
Sources and verification
Original teaching examples checked with Python 3.12.14. Library behavior reviewed against these primary references on October 3, 2026. Day 26's examples also use Beautiful Soup 4.14.3.
Check your understanding
Five questions, 20 points each. Pass at 85% or above, which requires all five correct. Feedback explains each answer; revise the lesson and retry when needed.
Review your practical work
Check these statements after running your own challenge. This is your self-review; the website does not execute or assess your Python code.
Pass the knowledge check and finish the practical self-review to save completion.