Calendar-based tasks often need every day between a start date and an end date. Python’s date and timedelta classes let you move forward one day at a time to build that sequence.
I’ll show how to create a range that includes the end date, then explain when to stream the dates or store them.
TL;DR: Create an inclusive range with date and timedelta
Use Python’s built-in date and timedelta classes to advance from a start date through an end date, adding one day each time and choosing whether the final date belongs in the result.
- Import date and timedelta from datetime. You do not need to install a package for this.
- Use a generator when you want to process each date in turn, or collect its results when you need to reuse them.
- For a pandas workflow, use pandas.date_range and set inclusive explicitly when a boundary matters.
What is a date range in Python?
A date range is an ordered sequence of calendar dates that follows a step, such as one day. Python’s datetime module provides date values and timedelta for fixed durations in days, seconds, and microseconds. A timedelta has no calendar-month unit because month lengths differ.
| Value | What it represents | Use it when |
|---|---|---|
| date | A calendar day with year, month, and day fields | You do not need a time or timezone |
| datetime | A date combined with a time | The time of day matters |
| timedelta | A duration, such as one day | You need to move by a fixed amount |
Use date when time of day does not matter, and choose datetime for clock-time boundaries. Both types ship with Python’s datetime module, so the examples need no separate installation. For related arithmetic, see How to add Days to a Date in Python.
The output spans February 27 through March 2 and contains five dates. For an inclusive daily range, calculate the count as (end minus start).days + 1. A same-day range contains one item.
How to create a range of dates step by step
Start with two valid date values, then advance by a one-day timedelta while the current date is within the chosen boundary. The example below includes both endpoints and raises a clear error when the end comes before the start.
Step 1: Set the start and end dates
Use date(year, month, day) when your program already has separate integer fields. Python checks that each value forms a valid calendar date, so an invalid month or a nonexistent day raises ValueError instead of silently rolling into another month.
Parse date text with the source’s documented format: date.fromisoformat() handles ISO input, while datetime.strptime() needs a specified layout for ambiguous values such as 03/04/2024. If parsing raises ValueError, log the original value and row before rejecting the record.
from datetime import date, datetime
start = date.fromisoformat("2024-02-27")
end = datetime.strptime("03/02/2024", "%m/%d/%Y").date()
Step 2: Advance by one day and include the end
The source validates the bounds before yielding its first date. I ran date_range_demo.py with the command shown below.
Python treats any function containing yield as a generator function. Its body, including the reversed-bound check, runs when iteration begins rather than when the generator object is created. This timing matters if the surrounding program expects validation before it schedules other work.
A generator computes each date when the caller asks for it rather than storing the full sequence. Iterate over it once for sequential work, or call list() when a later operation needs indexing or another pass.
from datetime import date, timedelta
def dates_inclusive(start: date, end: date):
if end < start:
raise ValueError("end must be on or after start")
current = start
while current <= end:
yield current
if current == end:
return
current += timedelta(days=1)
def main():
start = date(2024, 2, 27)
end = date(2024, 3, 2)
print(f"Dates from {start} through {end}:")
for day in dates_inclusive(start, end):
print(day.isoformat())
if __name__ == "__main__":
main()
Run the file from the directory where you saved it:
python3 date_range_demo.py
I ran the command below from the named source file. It exited with status 0 and printed the following output.
Dates from 2024-02-27 through 2024-03-02:
2024-02-27
2024-02-28
2024-02-29
2024-03-01
2024-03-02
February 29 appears because 2024 is a leap year. The day-by-day step also crosses a month boundary without special-case code.
The terminal shows ISO year-month-day text with a four-digit year and zero-padded month and day. Call isoformat() when an output file or API requires a string.
When the end is date.max, the generator returns after yielding that date. It avoids adding another day, which would exceed Python’s supported range.
Step 3: Choose whether to stream or store the dates
Use this helper when the caller needs a fixed number of output values, even though the end date is unknown. range(count) supplies integer offsets from zero, so the start is first, zero gives no dates, and a negative count raises ValueError.
def dates_by_count(start: date, count: int):
if count < 0:
raise ValueError("count must be non-negative")
for offset in range(count):
yield start + timedelta(days=offset)
Date range edge cases and library choices
The table highlights input cases that change the returned sequence or raise an error.
| Situation | What happens | Choice |
|---|---|---|
| Start equals end | An inclusive range has one date | Keep it when both bounds belong |
| End precedes start | The demo raises ValueError | Reject it, or deliberately support reverse iteration |
| End boundary should be omitted | An inclusive loop would add one unwanted date | Use a less-than comparison |
| Leap day falls inside the interval | Date arithmetic includes February 29 in a leap year | Let date handle the calendar |
| Input date text has another format | Parsing may fail or interpret the fields incorrectly | Use a parser format that matches the input |
| Datetime values have different awareness | Python rejects comparisons between naive and aware values | Use compatible timezone-aware values or date-only values |
| End is date.max | Advancing beyond the last supported date raises an overflow error | Stop after yielding the inclusive end |
The demo raises ValueError for reversed bounds instead of returning an empty sequence. For reverse-chronological output, implement a separate function with an explicit negative step.
Use an exclusive end for adjacent date windows
A half-open range includes its start and excludes its end. Adjacent intervals can meet at one boundary without duplicating a date.
def dates_exclusive(start: date, end: date):
if end < start:
raise ValueError("end must be on or after start")
current = start
while current < end:
yield current
current += timedelta(days=1)
first = list(dates_exclusive(date(2024, 3, 1), date(2024, 3, 2)))
second = list(dates_exclusive(date(2024, 3, 2), date(2024, 3, 3)))
assert first == [date(2024, 3, 1)]
assert second == [date(2024, 3, 2)]
Skip weekends in a daily range
date.weekday() numbers Monday as 0 and Sunday as 6, so values below five keep Monday through Friday. Public holidays and regional workweek differences require an applicable calendar.
def weekdays_inclusive(start: date, end: date):
for current in dates_inclusive(start, end):
if current.weekday() < 5:
yield current
| Datetime case | What changes | Action |
|---|---|---|
| Naive and aware values | Python rejects their comparison | Align the inputs’ timezone and awareness |
| Local business date | The same UTC timestamp can fall on different local dates | Convert to the business timezone before extracting the date |
| Daylight-saving transition | Adding 24 hours can differ from keeping the same local clock time next date | Choose elapsed-time or calendar-day semantics |
A local calendar day during a daylight-saving change is not always 24 elapsed hours.
| Approach | Result | Use it when |
|---|---|---|
| Generator | One date at a time | You process results in sequence or want to avoid storing them all |
| List | A reusable Python collection | You need indexing or more than one pass |
| pandas.date_range | A DatetimeIndex | Your next step is pandas time-series work |
pandas.date_range defaults to daily frequency and returns a DatetimeIndex. Set periods for a known count or a calendar frequency, and choose inclusive=”both” for both endpoints, “left” for start only, “right” for end only, or “neither” for neither, as described in the pandas reference.
Check the boundary cases
I ran eight tests for leap day, same-day and reversed bounds, count-based output, exclusive ends, weekdays, and date.max. The assertions below show two boundary cases that catch off-by-one and final-increment errors.
from datetime import date
from date_range_demo import dates_inclusive
assert list(dates_inclusive(date(2024, 2, 29), date(2024, 2, 29))) == [date(2024, 2, 29)]
assert list(dates_inclusive(date.max, date.max)) == [date.max]
assert list(dates_by_count(date(2024, 2, 27), 3)) == [date(2024, 2, 27), date(2024, 2, 28), date(2024, 2, 29)]
assert list(dates_exclusive(date(2024, 2, 27), date(2024, 3, 1))) == [date(2024, 2, 27), date(2024, 2, 28), date(2024, 2, 29)]
If you need ISO week labels after generating dates, the date object’s isocalendar method provides the week-year and week number. The ISO week-year can differ from the calendar year near New Year’s Day. The related AskPython guide, How to Get Week Numbers in Python, explains that separate task.
Conclusion: Make the endpoint rule explicit
Use this approach when a task needs an ordered series of calendar dates, and extend the boundary tests whenever an input format or calendar policy changes so downstream code can rely on the same contract. Keep date values through comparisons and format them only when writing the output. The Python datetime documentation and pandas.date_range reference describe related types and frequency options.
FAQ
These short answers cover the endpoint and output choices that change a date range.
How do I get a range of dates in Python?
Create start and end date objects, then add timedelta(days=1) until the chosen endpoint. Use a generator to process each date in order, or convert it to a list if later code needs indexing or more than one pass.
Does a Python date range include the end date?
The loop condition decides. Use current <= end to include the last date and current < end to stop before it. Check this convention against an adjacent range or a database filter.
How can I create a fixed number of dates?
Add offsets from zero through count minus one to the starting date. The start is the first result, so count=3 gives start, start plus one day, and start plus two days. A zero count returns an empty sequence.
Can I create a range from date strings?
Yes. Parse the input into a date first. Use date.fromisoformat() for ISO text and a specified format for other layouts, because strings such as 03/04/2024 can be day-first or month-first. Catch ValueError for invalid dates.
When should I use pandas.date_range?
Use it when later work needs a pandas DatetimeIndex, a non-daily calendar frequency, or a known number of periods. Its inclusive option controls whether the first and last values appear, so choose it explicitly when the interval boundary matters.
Can Python’s range function generate dates?
The built-in range function yields integers. Add each integer as a day offset to a starting date to create date values.
How do I skip weekends in a date range?
The helper’s weekday() test keeps Monday through Friday. Define regional workdays explicitly, and use a separate holiday calendar for public holidays, substitute days, and company closures.
What is a half-open date range?
A half-open range includes its start and excludes its end. Adjacent intervals can then meet at one boundary without assigning that date to both.
What happens when the start and end dates are equal?
An inclusive range returns that date once, making it useful for a one-day window. An exclusive range returns no dates because it stops before the matching end.
What if the end date is date.max?
The inclusive helper returns the upper bound, then exits before another day is added. This guard avoids overflowing Python’s supported date range, which has a defined maximum.

