crumbls / timeline
A scheduling and occurrence engine for Laravel
Requires
- php: ^8.2
- illuminate/database: ^10.0|^11.0|^12.0
- illuminate/queue: ^10.0|^11.0|^12.0
- illuminate/support: ^10.0|^11.0|^12.0
- rlanvin/php-rrule: ^2.4
Requires (Dev)
- orchestra/testbench: ^8.0|^9.0|^10.0
- pestphp/pest: ^2.0|^3.0
- pestphp/pest-plugin-laravel: ^2.0|^3.0
README
A scheduling and occurrence engine for Laravel.
This package is not a calendar UI. It handles the domain model behind recurring events: defining schedules as RRULE strings, pre-generating concrete occurrence records, and giving you a clean query interface to drive any calendar or scheduling feature you build.
Requirements
- PHP 8.2+
- Laravel 10, 11, or 12
Installation
composer require crumbls/timeline php artisan vendor:publish --tag=timeline-migrations php artisan migrate
Core Concepts
Event — the conceptual thing. "Laravel Meetup" or "Board Meeting". Not a specific date.
Rule — defines when an Event occurs. Stores an RFC 5545 RRULE string. One Event can have multiple Rules.
Occurrence — a concrete scheduled instance. "Laravel Meetup on June 5th." This is the primary query model.
OccurrenceException — a one-off modification to a single Occurrence (cancel, reschedule, modify). Never modifies the parent Rule.
Location — a reusable venue attached to Occurrences.
RRuleBuilder
Writing RRULE strings by hand is error-prone. Use the fluent builder:
use Crumbls\Timeline\Support\RRuleBuilder; // Every Tuesday RRuleBuilder::make()->weekly()->onDays('TU')->toString(); // First Friday of the month RRuleBuilder::make()->monthly()->onNthWeekday(1, 'FR')->toString(); // Every other Monday, 20 times RRuleBuilder::make()->weekly()->every(2)->onDays('MO')->count(20)->toString(); // Human-readable description of any RRULE RRuleBuilder::describe('FREQ=WEEKLY;BYDAY=MO,WE,FR'); // "weekly on Monday, Wednesday and Friday"
See IMPLEMENTATION.md for the full builder reference.
Quick Start
use Crumbls\Timeline\Models\Event; use Crumbls\Timeline\Models\Rule; use Crumbls\Timeline\Enums\EventStatus; // Create an event $event = Event::create([ 'name' => 'Laravel Meetup', 'timezone' => 'America/Denver', 'status' => EventStatus::Published, ]); // Add a weekly rule — occurrences generate automatically Rule::create([ 'event_id' => $event->id, 'starts_at' => '2025-06-03 18:00:00', 'ends_at' => '2025-06-03 20:00:00', 'rrule' => 'FREQ=WEEKLY;BYDAY=TU', ]); // Query occurrences use Crumbls\Timeline\Models\Occurrence; use Carbon\Carbon; $feed = Occurrence::between(Carbon::parse('2025-06-01'), Carbon::parse('2025-06-30')) ->scheduled() ->with(['event', 'location']) ->orderBy('starts_at') ->get();
Occurrence Scopes
Occurrence::upcoming()->get(); Occurrence::today()->get(); Occurrence::between($start, $end)->get(); Occurrence::scheduled()->get(); Occurrence::forEvent($event->id)->get(); Occurrence::atLocation($location->id)->get();
Scopes chain freely:
Occurrence::scheduled()->upcoming()->forEvent($event->id)->paginate(20);
Configuration
// config/timeline.php return [ 'table_prefix' => 'timeline_', 'occurrence_generation_months' => 12, 'default_timezone' => 'UTC', ];
All table names are resolved through table_prefix. Change it before running migrations.
How Occurrence Generation Works
When a Rule is saved, GenerateOccurrencesJob is dispatched. The OccurrenceGenerator service expands the RRULE into concrete dates for the configured window (default: 12 months), then:
- Creates new Occurrence records for dates not already present
- Updates
ends_aton existing records if the duration changed - Deletes future
Scheduledoccurrences no longer in the expansion - Leaves
CancelledandCompletedoccurrences untouched
You can also trigger generation manually:
use Crumbls\Timeline\Services\OccurrenceGenerator; app(OccurrenceGenerator::class)->generate($rule);
Testing
composer test
56 Pest tests covering events, rules, occurrences, generator logic, and exceptions.
Further Reading
See IMPLEMENTATION.md for a full implementation guide including calendar feed construction, location usage, exception handling, queue setup, and an RRULE reference table.
License
MIT