Dates and Timedeltas in Python
Parse a calendar date, shift it by a number of days, and produce a predictable ISO date string.
Video lesson: Dates and Timedeltas in Python
The preview is stored on this site. YouTube loads only when you press Play. Watch on YouTube
Jump to a chapter
Read the video transcript
Select a timestamp to watch that moment on YouTube.
- 00:00
A date written as year-month-day looks like text, but changing its characters is not calendar arithmetic. Two days after February 28, 2024, is March 1, because 2024 has a leap day. One day after February 28, 2023, is also March 1, because 2023 does not. The difference is exactly why a homemade string shortcut is dangerous. In this video we will run that shortcut, inspect the wrong result, parse the text as a real date, and add a timedelta. We will also count the days between two dates and explain when a date alone is insufficient.
- 00:40
The companion lesson lets you run and edit every example in a browser console. Before we touch the code, keep a separate expected result in mind. If we first see the program's output and only then decide whether it looks reasonable, an impossible February thirtieth can slip through as a seemingly simple number change. Dates appear in deadlines, birthdays, and reports. A whole calendar day is a different thing from a timestamp measured down to seconds. Keeping that distinction visible helps you pick the right Python type. Today's examples concern whole days, so we will use date and not assume any clock time or location.
- 01:21
The string operation produced February thirtieth. Python did not complain because no calendar operation happened. We sliced off the last two characters, converted them to an integer, added two, and glued the pieces back together. For a day in the middle of a month, that may appear to work. At the end of a month it fails, and at the end of a year it fails even more visibly. Adding a few special cases for month lengths soon becomes complicated, especially when leap years are involved. The real problem is that a string does not know it represents a calendar date. We should parse it into a date object, so Python can validate its year, month, and day.
- 02:03
Then we can add a duration with a unit. That is a smaller and more reliable program than rebuilding calendar rules in string manipulation. The shortcut fails at other boundaries too. Changing the day characters of 2024-12-31 by one would invent December 32 rather than January 1 of the next year. A patch for every month and leap year would be longer than using the standard library. The wrong output is useful because it reveals the model error. `date.fromisoformat` converts an ISO year-month-day string to a date object. The first input is valid and comes back in the same format from `isoformat`.
- 02:45
The second input is impossible: 2023 is not a leap year, so February has only twenty-eight days. Python raises ValueError, which we catch only to keep this demonstration running. In an application, you would decide whether to show the user a validation message or let a caller handle the exception. The lesson's practice function lets invalid input raise ValueError, so the checker can confirm it truly parses dates. A date object has a year, month, and day, but no clock time or time zone. It represents a calendar day. That is exactly what we need for a due date measured in whole days.
- 03:26
Next we give Python a number of days to add, using timedelta. Parsing also gives you a clear failure point for bad input. If a web form sends an impossible date, you can ask for a correction instead of computing a deadline from nonsense. The exception is not a nuisance to suppress blindly; it tells you the text did not identify a valid calendar day. `timedelta(days=days)` tells Python exactly what the offset means. Adding it to a date crosses the leap day, an ordinary month boundary, or a year boundary without special string cases. A negative offset moves backward. The function returns an ISO string because that is the interface the exercise asks for.
- 04:11
Inside a larger program you might keep the date object until it is time to display or save it. Notice also that `fromisoformat` validates the starting date before we shift it. We did not invent a recovery rule for impossible input; the caller receives ValueError. Try replacing 2024 with 2023 in the first call and predict whether two days after February 28 is March 1 or March 2. The best way to learn date handling is to test the boundaries rather than only comfortable middle-of-month inputs. Try a zero offset as a control case: a date plus zero days should come back unchanged.
- 04:52
Then test one negative day from March 1 in a leap year; it lands on February 29. These checks help distinguish true calendar arithmetic from code that only special-cases the positive example shown first. Subtracting two date objects gives a timedelta. From February 28 to March 1 in leap year 2024 is two days because February 29 lies between them. Reverse the operands and the sign becomes negative. That is useful for calendar-day counts, overdue checks, or planning tasks. It does not answer a question about an exact instant across locations. A date has no hour or time zone; a meeting at six o'clock in Bratislava and six o'clock in New York are different instants.
- 05:39
For those requirements, use timezone-aware datetime values and define which location's clock you mean. The website task intentionally handles whole calendar days only. Test a leap day, a non-leap February, a year boundary, zero days, and a negative offset, then complete the quiz. The browser checker also tries an impossible date to verify you parsed it rather than edited its characters. As an extension, write a function that returns the number of days until a chosen date using date subtraction, and test both future and past targets. The difference of two dates counts elapsed calendar-day boundaries, so the endpoints are not both counted as full days.
- 06:22
If a product asks for inclusive days in a booking or billing period, that is a separate rule you must define. date subtraction gives the underlying difference; your application decides how to interpret it.
Understand the concept
Calendar dates are values, not just pieces of text. date.fromisoformat('2024-02-28') parses an ISO year-month-day string into a date object. An invalid date raises ValueError. date.isoformat() turns a date back into a consistently formatted string.
timedelta(days=n) represents a duration of n days. Add it to a date to move forward and use a negative number to move backward. Python handles month length and leap-year boundaries: one day after 2024-02-28 is 2024-02-29, while one day after 2023-02-28 is 2023-03-01. Avoid changing the final two characters of a date string as though every month had the same number of days.
Subtracting one date from another returns a timedelta whose .days value can be positive, zero, or negative. Use this when counting calendar days between dates. A date has no time of day or time zone. If your project needs an exact instant across locations, use timezone-aware datetime values and define the relevant time zone rather than treating a date as a timestamp.
The checked task accepts an ISO date and an integer day offset. Return an ISO date string so the result is easy to display and compare. Test a normal day, a month boundary, leap day, a negative offset, and zero. These cases catch the usual string-manipulation shortcut.
- date.fromisoformat parses YYYY-MM-DD
- timedelta shifts a date across month and year boundaries
- date subtraction yields a timedelta
- date has no time zone or time of day
See it step by step
Read the code, predict the output, then compare it with the result.
01. Parse and format a date
from datetime import date
day = date.fromisoformat("2024-02-28")
print(day.isoformat())
print(day.year, day.month, day.day)2024-02-28
2024 2 28fromisoformat checks and parses the string; the attributes expose numeric calendar parts.
02. Cross a leap-day boundary
from datetime import date, timedelta
start = date.fromisoformat("2024-02-28")
print((start + timedelta(days=1)).isoformat())
print((start + timedelta(days=2)).isoformat())2024-02-29
2024-03-01timedelta performs calendar arithmetic, including the extra day in February 2024.
03. Count days in both directions
from datetime import date
first = date.fromisoformat("2024-03-01")
second = date.fromisoformat("2024-02-28")
print((first - second).days)
print((second - first).days)2
-2Subtracting dates yields a timedelta. Reversing the operands reverses the sign.
A closer look
Follow the reasoning, inspect each result, then try the suggested changes in the console below.
Parse a date before doing arithmetic
An ISO string is convenient for displaying and storing a calendar date, but it is still text. Changing the final two characters to add a day works only within some months and creates impossible dates at boundaries. date.fromisoformat parses year-month-day into a date value and checks whether that date exists. The result exposes numeric year, month, and day attributes and can be formatted back with isoformat().
An invalid date such as February 29 in a non-leap year raises ValueError. That error is useful: it tells the caller the input cannot represent a real calendar day. Do not silently replace invalid dates unless your product has a clear rule for doing so. The following example catches the error only to keep the guided code runnable. In the practice task, let the error propagate, so the checker can confirm proper parsing.
from datetime import date
day = date.fromisoformat("2024-02-28")
print(day.isoformat())
print(day.year, day.month, day.day)
try:
date.fromisoformat("2023-02-29")
except ValueError:
print("invalid date")2024-02-28
2024 2 28
invalid date- The first string becomes a checked date object.
- The individual calendar fields are numeric.
- February 29, 2023, is rejected because that year is not a leap year.
Use timedelta across month and year changes
timedelta(days=1) names a one-day duration. Adding it to a date uses calendar arithmetic rather than string editing. February 2024 has an extra day, so February 28 plus one day becomes February 29. February 2023 does not, so the same operation becomes March 1. A negative timedelta moves backward across boundaries as naturally as a positive one moves forward.
The exact number of days is explicit in the constructor. You can return the date object when the rest of your program needs more date operations, or call isoformat() when a text result is the interface. The exercise asks for the text form so a result can be shown and compared consistently. Predict all three outputs before running. They probe leap-year behavior, a regular month boundary, and a year boundary.
from datetime import date, timedelta
print((date.fromisoformat("2024-02-28") + timedelta(days=1)).isoformat())
print((date.fromisoformat("2023-02-28") + timedelta(days=1)).isoformat())
print((date.fromisoformat("2025-01-01") + timedelta(days=-1)).isoformat())2024-02-29
2023-03-01
2024-12-31- The leap-year case lands on February 29.
- The ordinary year moves directly from February 28 to March 1.
- A negative offset crosses into the previous year.
Count calendar days, and know what date omits
Subtracting two date values produces a timedelta. Its days attribute tells you the signed difference in calendar days. In this example, March 1, 2024, is two days after February 28 because February 29 lies between them. Swapping the operands makes the result negative. This is different from subtracting formatted strings, which Python does not interpret as calendar values.
A date contains only a year, month, and day. It has no hour, minute, or time zone. That makes it appropriate for birthdays or due dates defined by calendar day, but not for scheduling an exact moment across cities. For an instant, choose a timezone-aware datetime and decide which time zone the user means. The browser task intentionally handles calendar days only; it should not guess a location or silently interpret a date as midnight UTC.
from datetime import date
start = date.fromisoformat("2024-02-28")
end = date.fromisoformat("2024-03-01")
print((end - start).days)
print((start - end).days)
print(type(end - start).__name__)2
-2
timedelta- Subtracting date objects returns a timedelta.
- The sign depends on operand order.
- Use timezone-aware datetime values when the requirement concerns an exact time, not a calendar day.
Try it in Python
Edit the example and run it. Python starts in your browser the first time you click Run.
Python console
Ready to runNeed input()? Add one value per line
Your output appears here.
Need a hint?
Use start = date.fromisoformat(start_iso), then shifted = start + timedelta(days=days), and return shifted.isoformat().
Complete the task and select Check task to verify your code.
Quick quiz
Three questions. You can change your answers and try again.
Typical mistakes
Everyone meets these errors. See what causes them and how to fix them.
Editing the day characters directly
start = '2024-02-28'
print(start[:-2] + str(int(start[-2:]) + 2))start = date.fromisoformat('2024-02-28')
print((start + timedelta(days=2)).isoformat())What happens: It prints an invalid February 30 instead of crossing into March.
Calendar arithmetic handles the length of each month and leap years.
Trying to add a number directly to a date
date.fromisoformat('2024-02-28') + 1date.fromisoformat('2024-02-28') + timedelta(days=1)What happens: TypeError because a date cannot be added to a bare integer.
Wrap the offset in a timedelta to state its unit and meaning.