laracraft-tech/laravel-date-scopes
Laravel Date Scopes adds a wide range of ready-made date scopes to your Eloquent models via a simple trait. Query records with ofToday(), ofLastWeek(), monthToDate(), ofLastYear(), and many more—fully chainable with any builder method like sum or avg.
Install the package and give the trait to any model you want to query by date:
composer require laracraft-tech/laravel-date-scopes
use LaracraftTech\LaravelDateScopes\DateScopes;
class Transaction extends Model
{
use DateScopes;
}
Your first useful call is usually replacing a hand-written whereBetween in a dashboard or report:
// Instead of manually calculating Carbon boundaries:
Transaction::ofToday()->sum('amount');
Transaction::ofLast7Days()->count();
Transaction::monthToDate()->avg('amount');
Transaction::ofLastYear()->where('status', 'paid')->sum('amount');
Where to look first:
*ToDate/*ToNow.php artisan vendor:publish --tag="date-scopes-config"
Configuration is optional. The two defaults are:
'default_range' => env('DATE_SCOPES_DEFAULT_RANGE', DateRange::EXCLUSIVE->value),
'created_column' => env('DATE_SCOPES_CREATED_COLUMN', 'created_at'),
The default is exclusive, so ofLast7Days() means the previous seven days and does not include today. Confirm that semantic with stakeholders before wiring it into reports.
Every scope returns an Eloquent builder, so the package fits into normal Laravel query composition:
Transaction::ofLast30Days()
->where('tenant_id', $tenantId)
->where('status', 'completed')
->groupBy('currency')
->selectRaw('currency, sum(amount) as total')
->get();
This is the main day-to-day pattern: use the scope for the date window, then apply your existing authorization, tenant, status, and eager-loading constraints.
The plural scopes map cleanly to common dashboard widgets:
$kpis = [
'today' => Transaction::ofToday()->sum('amount'),
'yesterday' => Transaction::ofYesterday()->sum('amount'),
'last_7_days' => Transaction::ofLast7Days()->sum('amount'),
'last_30_days' => Transaction::ofLast30Days()->sum('amount'),
'mtd' => Transaction::monthToDate()->sum('amount'),
'ytd' => Transaction::yearToDate()->sum('amount'),
];
For period-to-date reporting, prefer the *ToDate scopes (weekToDate(), monthToDate(), quarterToDate(), yearToDate()) over manually constructing startOfMonth()/now() boundaries. They express intent and keep reporting code consistent.
Many scopes accept a column argument, which is useful when a model has multiple meaningful dates:
Transaction::ofToday(column: 'approved_at')->count();
Transaction::ofLast30Days(column: 'paid_at')->sum('amount');
Choose the right level for configuration:
column: fluently.public $timestamps = false;
const CREATED_AT = 'custom_created_at';
DATE_SCOPES_CREATED_COLUMN in .env and clear config cache.created_at as the record timestamp and pass the reporting column explicitly.The package defaults to exclusive ranges. When a stakeholder says “last 7 days including today”, make that explicit at the call site:
use LaracraftTech\LaravelDateScopes\DateRange;
Transaction::ofLast7Days(customRange: DateRange::INCLUSIVE);
This fluent override is supported for the plural ofLast* scopes; do not assume it applies to singular scopes such as ofLastHour() or ofLastSecond().
If most of your product interprets ranges inclusively, change the global default instead of repeating customRange: everywhere:
DATE_SCOPES_DEFAULT_RANGE=inclusive
Freeze time in tests so scope boundaries are deterministic:
use Illuminate\Support\Carbon;
Carbon::setTestNow('2026-04-15 12:00:00');
Transaction::factory()->create(['created_at' => now()->subDays(1)]);
Transaction::factory()->create(['created_at' => now()]);
$this->assertSame(1, Transaction::ofLast7Days()->count());
For seeders and demo data, generate timestamps across the periods you want represented (subDays, subMonths, subYears) so dashboard widgets show non-empty data.
For finite historical reporting, use the documented startFrom parameter:
Transaction::ofLastYear(startFrom: '2020-01-01');
This is useful for backfills, migrations, and reports where “last year” should be anchored to a historical point rather than the current date.
ofLast7Days() does not include today. This is the most likely source of “the numbers don’t match” bugs. Decide the semantic once, document it, and make it visible in the report labels.ofLastSecond(), ofLastMinute(), ofLastHour(), and similar singular scopes do not accept customRange. Use the plural form when you need inclusive/exclusive control.ofLastCentury() covers 1901-01-01 through 2000-12-31, and ofLastMillennium() covers 1001-01-01 through 2000-12-31. This matches Wikipedia’s ordinal century/millennium convention but often surprises people expecting 1900–1999.created_at and for any frequently used custom columns such as paid_at or approved_at.column: 'approved_at' will fail at the database level if the column is absent or named differently. The same applies to CREATED_AT overrides — also remove $table->timestamps() from the migration and define the custom timestamp column instead.ofToday, monthToDate, etc., PHP trait conflicts or unexpected overrides can occur. Prefer the package trait on models that do not already own those names, or rename your local scopes.php artisan config:clear
Otherwise DATE_SCOPES_DEFAULT_RANGE and DATE_SCOPES_CREATED_COLUMN changes may not take effect in cached environments.Transaction::ofLast7Days()->toSql();
Transaction::ofLast7Days()->getBindings();
This quickly reveals whether the issue is the time window, the column, or an additional constraint.whereBetween(now()->sub..., now()) calls alongside package scopes, which reintroduces inconsistency.CHANGELOG.md and UPGRADING.md when moving major framework versions.How can I help you explore Laravel packages today?