Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Laravel Date Scopes Laravel Package

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.

View on GitHub
Deep Wiki
Context7

Getting Started

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:

  1. The Scopes section of the README — it is effectively the API reference, grouped by seconds, minutes, hours, days, weeks, months, quarters, years, decades, centuries, millenniums, and *ToDate/*ToNow.
  2. The published config file if you need to change global defaults:
    php artisan vendor:publish --tag="date-scopes-config"
    
  3. Your own model — add the trait only where date-range querying is genuinely useful, rather than to every model.

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.

Implementation Patterns

Use scopes as query builders, then chain normally

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.

Dashboard and KPI cards

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.

Reporting on a non-default timestamp

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:

  • One scope call only → pass column: fluently.
  • One model only → override the model’s created-at column:
    public $timestamps = false;
    const CREATED_AT = 'custom_created_at';
    
  • Whole application → set DATE_SCOPES_CREATED_COLUMN in .env and clear config cache.
  • Only the reported date → keep created_at as the record timestamp and pass the reporting column explicitly.

Inclusive versus exclusive ranges

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

Seeding and testing

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.

Custom start dates

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.

Gotchas and Tips

  • Exclusive is the default. 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.
  • Singular scopes are not range-configurable. ofLastSecond(), ofLastMinute(), ofLastHour(), and similar singular scopes do not accept customRange. Use the plural form when you need inclusive/exclusive control.
  • Century and millennium boundaries are historically defined, not round-number ranges. 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.
  • Timezone consistency matters. Scope boundaries are derived from the application’s current time. Ensure your app timezone, database timestamp storage, and reporting expectations align. If you store UTC and report in a local timezone, convert explicitly before or after the scope rather than assuming the scope knows your display timezone.
  • Index the date columns you filter on. These scopes ultimately produce range predicates on the column you target. Add indexes for created_at and for any frequently used custom columns such as paid_at or approved_at.
  • Custom columns must exist and be queryable. Passing 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.
  • Watch for scope name collisions. If your model or another trait already defines 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.
  • Clear config cache after changing environment values.
    php artisan config:clear
    
    Otherwise DATE_SCOPES_DEFAULT_RANGE and DATE_SCOPES_CREATED_COLUMN changes may not take effect in cached environments.
  • Debug with the query builder, not the results. When a scope returns unexpected rows, inspect the generated SQL and bindings:
    Transaction::ofLast7Days()->toSql();
    Transaction::ofLast7Days()->getBindings();
    
    This quickly reveals whether the issue is the time window, the column, or an additional constraint.
  • Compose, don’t reimplement. If you need a domain-specific window (for example “current billing period”), build it in your own scope or query object by composing the package’s scopes where possible. Avoid scattering raw whereBetween(now()->sub..., now()) calls alongside package scopes, which reintroduces inconsistency.
  • Check version compatibility before upgrading. Recent releases added newer Laravel support; review CHANGELOG.md and UPGRADING.md when moving major framework versions.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
ably/pubsub-server
mstfkhazaal/defender
mfonte/base62x
karnoweb/metapilot
tobya/bcsapiwrapper
swalker2/cpanel
vish4395/laravel-translation-manager
morningtrain/economic
teksite/systeminfo-laravel
atomcms/voting
run6/weather
siokas/laravelembeddirectives
adamlien/pkuuids
gilsonsouza/lara-doc-generator
manelgavalda/enrollment_mobile
unabomber/timezone
delatbabel/contacts
astritzeqiri/g-recaptcha
kevinongko/laravel-genode
developeruz/laravel-analytics