Reader Stacks

How to Fetch Records Between Two Dates in Laravel

whereBetween() handles the basic case cleanly — the real detail to get right is making sure the end date's time component actually includes the whole day, not just midnight.

Filtering records between two dates is one of the most common reporting queries, and Eloquent's whereBetween() handles it cleanly — the detail that actually trips people up is making sure the end date genuinely includes the whole day, not just midnight of that date.

The basic whereBetween query

$orders = Order::whereBetween('created_at', [$startDate, $endDate])->get();

The common gotcha: an end date that silently excludes that day's records

// if $endDate is "2024-03-15" (interpreted as 2024-03-15 00:00:00),
// this MISSES every order created after midnight on the 15th
$orders = Order::whereBetween('created_at', ['2024-03-01', '2024-03-15'])->get();

A plain date string is interpreted with a time of midnight — comparing a full datetime column against a bare date silently excludes anything from later that same day, which is rarely the intended behavior for an "up to and including this date" report.

Fixing it: extend the end date to the end of that day

use Carbon\Carbon;

$start = Carbon::parse($request->start_date)->startOfDay();
$end = Carbon::parse($request->end_date)->endOfDay();

$orders = Order::whereBetween('created_at', [$start, $end])->get();

endOfDay() sets the time to 23:59:59 (or 23:59:59.999999, depending on Carbon's precision setting) — this is what actually makes the range inclusive of every record from the end date, not just those before midnight.

Filtering by date only, ignoring the time component of the column

$orders = Order::whereDate('created_at', '>=', $start)
    ->whereDate('created_at', '<=', $end)
    ->get();

whereDate() compares only the date portion of a datetime column, ignoring its time entirely — a cleaner alternative to the startOfDay()/endOfDay() approach above when the intent is purely date-based filtering rather than a precise datetime range.

A date range from a request with default values

public function report(Request $request)
{
    $start = $request->filled('start_date')
        ? Carbon::parse($request->start_date)->startOfDay()
        : now()->subDays(30)->startOfDay();

    $end = $request->filled('end_date')
        ? Carbon::parse($request->end_date)->endOfDay()
        : now()->endOfDay();

    $orders = Order::whereBetween('created_at', [$start, $end])->get();
}

Defaulting to the last 30 days when no explicit range is provided gives a report page a sensible result on first load, rather than either an empty result or every record ever created.

Grouping results by day within the range

$dailyTotals = Order::whereBetween('created_at', [$start, $end])
    ->selectRaw('DATE(created_at) as date, SUM(total) as total')
    ->groupBy('date')
    ->orderBy('date')
    ->get();