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.
If this post was enjoyable or useful for you, please share it! If you have comments, questions, or feedback, you can email my personal email. To get new posts, subscribe use the RSS feed.