Installation
composer require league/period
Add to composer.json if using Laravel’s require-dev or require explicitly.
First Use Case: Creating a Period
use League\Period\Period;
// Create a period from two DateTimeImmutable objects
$period = Period::createFromDateTimeImmutable(
new DateTimeImmutable('2023-01-01'),
new DateTimeImmutable('2023-12-31')
);
Key Classes to Know
Period: Represents a time range (immutable).Period\Bounds: Defines the start/end boundaries (e.g., Bounds::inclusive()).Period\Interval: Represents a fixed duration (e.g., Interval::day(1)).Where to Look First
src/Period.php for core logic (if extending).tests/ for edge-case patterns (e.g., invalid ranges).Period Creation
// From strings
$period = Period::createFromString('2023-01-01', '2023-12-31');
// From DateTime objects (Laravel-friendly)
$start = now()->startOfDay();
$end = now()->endOfDay();
$period = Period::createFromDateTime($start, $end);
// With bounds (e.g., half-open intervals)
$period = Period::create(
new DateTimeImmutable('2023-01-01'),
new DateTimeImmutable('2023-12-31'),
Bounds::inclusive(),
Bounds::exclusive()
);
Common Operations
// Check if a DateTime is within the period
$isInPeriod = $period->contains($dateTime);
// Get the duration (as Interval)
$duration = $period->duration();
// Iterate over days/weeks (useful for Laravel scheduling)
foreach ($period->days() as $day) {
// Process each day
}
// Convert to Carbon (Laravel interop)
$start = $period->getStart()->toCarbon();
$end = $period->getEnd()->toCarbon();
Laravel-Specific Patterns
use League\Period\Period;
use Carbon\Carbon;
$period = Period::createFromDateTimeImmutable(
Carbon::parse('2023-01-01'),
Carbon::parse('2023-12-31')
);
$query->whereBetween('created_at', [
$period->getStart()->format('Y-m-d'),
$period->getEnd()->format('Y-m-d')
]);
$schedule->command('daily-report')->dailyAt('09:00')->between(
$period->getStart()->format('Y-m-d'),
$period->getEnd()->format('Y-m-d')
);
Advanced: Custom Periods
// Create a recurring period (e.g., every Monday)
$mondays = Period::createFromDateTimeImmutable(
new DateTimeImmutable('2023-01-02'), // First Monday
new DateTimeImmutable('2023-12-25')
)->filter(function (DateTimeImmutable $date) {
return $date->format('N') === '1'; // Monday
});
Immutable Objects
start/end directly; create new instances:
$newPeriod = $period->withStart($newStartDate);
Bounds Confusion
inclusive() for both start/end. Explicitly set bounds if using half-open intervals:
$period = Period::create($start, $end, Bounds::inclusive(), Bounds::exclusive());
Timezone Sensitivity
$period->getStart()->setTimezone(new DateTimeZone('America/New_York'));
Edge Cases in contains()
contains() respects bounds. For example, an exclusive end will exclude the end date:
$period = Period::create($start, $end, Bounds::inclusive(), Bounds::exclusive());
$period->contains($end->modify('+1 second')); // false
Performance with Large Periods
foreach ($period->days() as $day) {
// Process in batches
}
Visualize Periods
echo $period->getStart()->format('Y-m-d H:i:s') . ' to ' .
$period->getEnd()->format('Y-m-d H:i:s');
Check Bounds
$period->getStartBounds()->isInclusive(); // bool
$period->getEndBounds()->isExclusive(); // bool
Validate Inputs
start ≤ end; otherwise, throw InvalidArgumentException:
if ($start > $end) {
throw new \InvalidArgumentException('Start must be before end.');
}
Custom Iterators
Override Period::days()/weeks() for business-specific logic:
$period->filter(function (DateTimeImmutable $date) {
return $date->format('w') === '5'; // Only Fridays
});
Laravel Service Provider Bind Period to the container for dependency injection:
$this->app->bind(Period::class, function () {
return Period::createFromDateTimeImmutable(
now()->startOfDay(),
now()->endOfDay()
);
});
Testing
Use PeriodFactory for test doubles:
$mockPeriod = Period::createFromDateTimeImmutable(
new DateTimeImmutable('2023-01-01'),
new DateTimeImmutable('2023-01-02')
);
$this->assertTrue($mockPeriod->contains(new DateTimeImmutable('2023-01-01')));
Serialization Periods are not JSON-serializable by default. Use a custom encoder:
$serialized = [
'start' => $period->getStart()->format('Y-m-d H:i:s'),
'end' => $period->getEnd()->format('Y-m-d H:i:s'),
'startBounds' => $period->getStartBounds()->value,
'endBounds' => $period->getEndBounds()->value,
];
How can I help you explore Laravel packages today?