458 words, 3 min read

When building applications, you often need to iterate over a range of dates—whether it's for generating reports, scheduling tasks, or processing time-series data. If you're coming from languages like PHP or Ruby, you might reach for external libraries. In Elixir, the solution is elegantly built into the standard library.

The basics: Date.range/2

Elixir provides Date.range/2 out of the box, which creates an enumerable range of dates. Here's the simplest example:

start_date = ~D[2026-01-26]
end_date = ~D[2026-02-08]
dates = Date.range(start_date, end_date) |> Enum.to_list()
# => [~D[2026-01-26], ~D[2026-01-27], ..., ~D[2026-02-08]]

The ~D sigil creates a Date struct at compile time, making it both performant and type-safe.

Iterating over dates

Since Date.range/2 returns an enumerable, you can use all your favourite Enum functions:

Date.range(start_date, end_date)
|> Enum.each(fn date ->
IO.puts("Processing #{date}")
# Your business logic here
end)

This lazy evaluation means you're not creating a massive list in memory—Elixir generates each date as needed.

Practical use case: Phoenix mix task

Here's how you might use this in a real Phoenix application, perhaps for a scheduled job that needs to backfill data:

defmodule MyApp.Mix.Tasks.BackfillReports do
use Mix.Task
@shortdoc "Backfill reports for a date range"
def run(_args) do
Mix.Task.run("app.start")
~D[2026-01-01]
|> Date.range(~D[2026-01-31])
|> Enum.each(fn date ->
MyApp.Reports.generate_daily_report(date)
IO.puts("✓ Generated report for #{date}")
end)
end
end

Advanced patterns

Stepping through dates

Need every other day? Or every week? Just add a step parameter:

# Every 7 days (weekly)
Date.range(start_date, end_date, 7)
|> Enum.to_list()
# Backwards iteration
Date.range(end_date, start_date, -1)
|> Enum.to_list()

Filtering weekdays

Want to process only business days? Chain with Enum.filter/2:

Date.range(start_date, end_date)
|> Enum.filter(fn date ->
Date.day_of_week(date) in 1..5 # Monday = 1, Sunday = 7
end)
|> Enum.each(&process_business_day/1)

Parsing from user input

When working with dynamic dates from APIs or user input:

with {:ok, start_date} <- Date.from_iso8601("2026-01-26"),
{:ok, end_date} <- Date.from_iso8601("2026-02-08") do
Date.range(start_date, end_date)
|> Enum.to_list()
else
{:error, _} -> {:error, "Invalid date format"}
end

Performance considerations

One of the beautiful things about Date.range/2 is its lazy evaluation. When you write:

Date.range(~D[2020-01-01], ~D[2026-12-31])
|> Enum.take(10)

Elixir only generates the first 10 dates, not all ~2500 dates in the range. This makes it memory-efficient even for large ranges.

Comparison with Other Languages

Coming from PHP/Laravel? This is your CarbonPeriod:

$period = CarbonPeriod::create($start, $end);

This translates to Elixir as:

period = Date.range(start_date, end_date)

From Ruby/Rails? This replaces manual iteration:

(start_date..end_date).each { |date| ... }

In Elixir, this is essentially the same:

Date.range(start_date, end_date)
|> Enum.each(fn date -> ... end)

Conclusion

Elixir's standard library provides powerful, memory-efficient date range functionality without needing external dependencies. The combination of Date.range/2 and the Enum module gives you all the tools you need for date manipulation in a functional, composable way.

Next time you need to iterate over dates in your Phoenix app, remember: it's just Date.range/2 away.