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
The inclusive output crosses leap day and the end of February.

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.

Share.
Leave A Reply