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

Enhanced Period Laravel Package

cmixin/enhanced-period

Enhanced Period extends CarbonPeriod with a richer API for working with date and time ranges in PHP. It adds methods for checking containment and overlap, intersecting, merging, splitting, filtering, mapping, and formatting periods, making complex interval logic simpler and more expressive.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require cmixin/enhanced-period:^1.2.1

This pulls in nesbot/carbon and spatie/period (v2). Check composer show cmixin/enhanced-period for the exact constraint before you commit — the last release is from June 2023, so verify it resolves against your Carbon 2/3 and PHP versions.

Pin to ^1.2.1 or later: 1.2.1 is a PHP 8.2 compatibility fix, so on PHP 8.2+ anything earlier can emit deprecations (or fail) at load time. If you are still on PHP 8.1 or below, 1.2.1 is a drop-in no-op, so there is no reason to stay on an older tag.

Register the mixin once, at boot

The package is a Carbon mixin: it adds conversion methods to CarbonPeriod as class-level macros. That is global static state, so register it in a service provider (or your framework bootstrap), never inside a controller or a loop.

// app/Providers/AppServiceProvider.php
use Carbon\CarbonPeriod;
use Cmixin\EnhancedPeriod\EnhancedPeriod;

public function boot(): void
{
    CarbonPeriod::mixin(EnhancedPeriod::class);
}

Orient yourself in 5 minutes

The package is tiny and thinly documented. Before writing code against it, read the source — it is a handful of methods:

grep -rn "function " vendor/cmixin/enhanced-period/src
php artisan tinker
>>> CarbonPeriod::hasMacro('toSpatiePeriod')

Confirm the exact method names (to-Spatie and from-Spatie) there; the surface is small enough to read in full, and getting the names right up front avoids "undefined method" confusion that looks like a missing install.

First use case: "do these two ranges overlap, and by how much?"

You already build ranges with CarbonPeriod because it iterates, formats, and accepts human date strings. What it cannot do is set algebra. That is the whole reason to convert:

use Carbon\CarbonPeriod;

$booked    = CarbonPeriod::create('2023-06-01', '2023-06-10'); // inclusive both ends
$requested = CarbonPeriod::create('2023-06-08', '2023-06-15');

$spatieBooked    = $booked->toSpatiePeriod();
$spatieRequested = $requested->toSpatiePeriod();

if ($spatieBooked->overlapsWith($spatieRequested)) {
    $clash = $spatieBooked->intersect($spatieRequested);

    // back to Carbon for iteration/formatting
    $carbonClash = CarbonPeriod::fromSpatiePeriod($clash); // or $clash->toCarbonPeriod()

    foreach ($carbonClash as $day) {
        echo $day->isoFormat('ll'), PHP_EOL;
    }
}

Implementation Patterns

Keep the bridge at the edges of your domain Do not let Spatie\Period\Period leak into models, controllers, or API resources. Have one small factory/service that accepts CarbonPeriod (or Carbon dates) and returns Spatie periods, and one that goes back. This is a 200-line dependency with 19 stars; the wrapper is what protects you if you have to vendor the conversion logic later.

final class PeriodBridge
{
    public static function toSpatie(CarbonPeriod $period): SpatiePeriod
    {
        return $period->toSpatiePeriod();
    }

    public static function toCarbon(SpatiePeriod $period): CarbonPeriod
    {
        return CarbonPeriod::fromSpatiePeriod($period);
    }
}

Pick a boundary convention and enforce it in one place Carbon's CarbonPeriod::create('2023-06-01', '2023-06-10') is inclusive of both endpoints. Spatie's Period::make() also defaults to inclusive boundaries, but Boundary::EXCLUDE_END / EXCLUDE_START are idiomatic there and easy to introduce. Decide that your app speaks half-open [start, end) internally, normalize on conversion, and assert it in tests. This is the single biggest source of off-by-one-day bugs when mixing the two libraries.

Set algebra workflows this unlocks The conversion is only worth the dependency if you use Spatie's operations, which CarbonPeriod does not have:

  • Availability / booking: union() all open windows, subtract() reservations, gaps() for the next free slot.
  • Billing & payroll: intersect a pay period with employment dates; subtract unpaid leave; sum ->length() instead of hand-rolling day counts.
  • On-call / shift rosters: merge adjacent shifts into continuous coverage with union(), find uncovered holes with gaps(), detect double-booking with overlapsWith().
  • Multi-range math: convert a list of CarbonPeriods into a PeriodCollection (PeriodCollection::make(...)), do the arithmetic once, convert the results back to CarbonPeriods for display.

Convert once, compute, then convert back Do not convert inside foreach ($carbonPeriod as $day). Materialize the Carbon side only when you need iteration or formatting; keep the Spatie side for the arithmetic.

$covered = PeriodCollection::make(
    ...array_map(fn ($p) => $p->toSpatiePeriod(), $shifts)
)->union();

$uncovered = $window->toSpatiePeriod()->gaps(...$covered);

Choose precision explicitly Spatie periods carry a Precision (year → second). A datetime range with hours/minutes needs Precision::SECOND or Precision::MINUTE; day-granularity math on a period whose bounds are 2023-06-01 09:30 will throw. When you only care about dates, truncate the Carbon bounds with ->startOfDay() before converting, and use Precision::DAY.

Testing Because the mixin is class-level static state, register it in your TestCase::setUp() (or make sure the provider is booted by the test app), otherwise every test that touches a converted period fails with a confusing "method does not exist". For deterministic ranges use Carbon::setTestNow() — but remember it shifts Carbon's now, not Spatie's arithmetic, so compute relative windows from the Carbon side.

CI matrix note Since 1.2.1 is explicitly a PHP 8.2 fix, put PHP 8.2 (and 8.3, which you are implicitly trusting to behave like 8.2) in your test matrix rather than only your local runtime. The package is unmaintained enough that you will discover the next syntax-level break before your users do.

CLI / scheduled work The sweet spot is nightly batch jobs: recompute coverage, store the gaps as [start, end] pairs in a table, and render them later with Carbon. Conversions are cheap; the expensive part is the set operations over many ranges, so batch them.

Gotchas and Tips

  • Global macro state. CarbonPeriod::mixin() is process-wide. Register it in boot(); in Octane/FrankenPHP workers it persists across requests, which is fine, but do not register it conditionally per-tenant or twice with a "skip if exists" check you can't reason about. If you ever see the methods disappear, the mixin was never registered in that code path (e.g. a queue worker bootstrapped differently).
  • Namespace collision. Both libraries want the word "Period". Always alias: use Spatie\Period\Period as SpatiePeriod;. Also alias the mixin class if you import it next to Carbon\CarbonPeriod.
  • Inclusive vs exclusive endpoints. CarbonPeriod includes its end date; Spatie can exclude it. Naively converting [Jan 1, Jan 31] to EXCLUDE_END and back silently loses or duplicates a day. Round-trip a one-day period and assert iterator_count() — a one-line test that catches most of these.
  • Counting differs. count($carbonPeriod) uses DatePeriod semantics and can disagree with Spatie's ->length() at the boundaries. Use iterator_count() on the Carbon side or length() on the Spatie side, but don't compare them across libraries without a shared convention.
  • Timezones. Carbon bounds carry a timezone; Spatie compares absolute instants. Two periods at Europe/Paris and UTC that "look" adjacent may not be. Normalize to UTC (or your app timezone) before converting, especially for anything crossing midnight.
  • DST. Iterating daily over a DST change gives 23/25-hour days; Spatie day math is calendar-based. If your feature is hour-sensitive billing, test around late March and late October in the relevant timezone.
  • Precision exceptions. InvalidPeriod / precision mismatch exceptions almost always mean a bound carries a time component that the declared precision forbids. Truncate deliberately rather than catching the exception.
  • PHP 8.2 deprecations point here first. On PHP 8.2+, unexplained deprecation notices firing when the mixin is registered (dynamic properties, ${var} interpolation, utf8_encode) usually mean you resolved a pre-1.2.1 tag. Check with composer show cmixin/enhanced-period before debugging your own code; the upgrade is a version bump, not a patch.
  • No IDE / static-analysis support. Mixin methods don't exist in CarbonPeriod's reflection, so PHPStan and PhpStorm will flag them. Either add a @method docblock on your wrapper, ship a stub file, or funnel everything through PeriodBridge — this is the day-to-day tax of the package.
  • Undefined method when the mixin is fine. If toSpatiePeriod() is undefined but the class is installed, check you called CarbonPeriod::mixin(...) and not Carbon::mixin(...) — the mixin targets the period class, not Carbon itself.
  • Version risk. 1.2.1 (PHP 8.2 compat) is a maintenance patch, not a revival: no features, no dependency bumps, no new tests documented. Last functional change is still 2023, 19 stars, zero known dependents. It is MIT and the conversion surface is a couple of methods, so budget for vendoring or inlining if spatie/period v3 or Carbon 3 breaks it. Pin ^1.2.1 in composer.json and add a test that round-trips a period so an upstream or PHP bump fails loudly.
  • Extension points. All customization lives in your wrapper, not the package: default precision, default boundary, timezone normalization, and the inclusive/exclusive policy belong in PeriodBridge. Treat the mixin as a low-level primitive and never call it from more than one or two places.
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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings