PHPackages                             spatie/period - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. spatie/period

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

spatie/period
=============

Complex period comparisons

2.4.0(3y ago)1.7k4.0M—2.8%76[1 PRs](https://github.com/spatie/period/pulls)9MITPHPPHP ^8.0CI passing

Since Nov 27Pushed 1mo ago19 watchersCompare

[ Source](https://github.com/spatie/period)[ Packagist](https://packagist.org/packages/spatie/period)[ Docs](https://github.com/spatie/period)[ RSS](/packages/spatie-period/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (10)Dependencies (5)Versions (49)Used By (9)

 [   ![Logo for period](https://camo.githubusercontent.com/a9c02e34532c368899475fc66621c96b05fa7f2c5b17052d1f41aff1d50cc036/68747470733a2f2f7370617469652e62652f7061636b616765732f6865616465722f706572696f642f68746d6c2f6c696768742e77656270)  ](https://spatie.be/open-source?utm_source=github&utm_medium=banner&utm_campaign=period)Complex period comparisons
==========================

[](#complex-period-comparisons)

[![Latest Version on Packagist](https://camo.githubusercontent.com/978fea0fb023ef1f977984e4a99fd3c6a3dd0a5d6e14ecf336bfc2c7e375f45a/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f7370617469652f706572696f642e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/spatie/period)[![Quality Score](https://camo.githubusercontent.com/c1e8824f79147554f98fdc6b3ee228f22c8cdf6370b9bedf6c0cf88eb2c0be0f/68747470733a2f2f696d672e736869656c64732e696f2f7363727574696e697a65722f672f7370617469652f706572696f642e7376673f7374796c653d666c61742d737175617265)](https://scrutinizer-ci.com/g/spatie/period)[![Total Downloads](https://camo.githubusercontent.com/95e86285f784024af273a2813b8516ffb0710b7e706b24dc9929c341fd52377d/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f7370617469652f706572696f642e7376673f7374796c653d666c61742d737175617265)](https://packagist.org/packages/spatie/period)

This package adds support for comparing multiple dates with each other. You can calculate the overlaps and differences between n-amount of periods, as well as some more basic comparisons between two periods.

Periods can be constructed from any type of `DateTime` implementation, making this package compatible with custom `DateTime` implementations like [Carbon](https://carbon.nesbot.com)(see [cmixin/enhanced-period](https://github.com/kylekatarnls/enhanced-period) to convert directly from and to CarbonPeriod).

Periods are always immutable, there's never the worry about your input dates being changed.

Support us
----------

[](#support-us)

[![](https://camo.githubusercontent.com/e0aca87d11e11c68dba614ffbc9a56e95cf95179e58038f286fe8d3ef1111aa0/68747470733a2f2f6769746875622d6164732e73332e65752d63656e7472616c2d312e616d617a6f6e6177732e636f6d2f706572696f642e6a70673f743d31)](https://spatie.be/github-ad-click/period)

We invest a lot of resources into creating [best in class open source packages](https://spatie.be/open-source). You can support us by [buying one of our paid products](https://spatie.be/open-source/support-us).

We highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using. You'll find our address on [our contact page](https://spatie.be/about-us). We publish all received postcards on [our virtual postcard wall](https://spatie.be/open-source/postcards).

Installation
------------

[](#installation)

You can install the package via composer:

```
composer require spatie/period
```

Usage
-----

[](#usage)

### Creating periods

[](#creating-periods)

You're encouraged to create periods using their static constructor:

```
$period = Period::make('2021-01-01', '2021-01-31');
```

You can manually construct a period, but you'll need to manually provide its **precision** and **boundaries**. Using `Period::make`, the default precision (`Precision::DAY()`) and default boundaries (`Boundaries::EXCLUDE_NONE()`) are used.

Before discussing the API provided by this package, it's important to understand both how precision and boundaries are used.

#### Precision

[](#precision)

Date precision is of utmost importance if you want to reliably compare two periods. The following example:

> Given two periods: `[2021-01-01, 2021-01-15]` and `[2021-01-15, 2021-01-31]`; do they overlap?

At first glance the answer is "yes": they overlap on `2021-01-15`. But what if the first period ends at `2021-01-15 10:00:00`, while the second starts at `2021-01-15 15:00:00`? Now they don't anymore!

This is why this package requires you to specify a precision with each period. Only periods with the same precision can be compared.

A more in-depth explanation on why precision is so important can be found [here](https://stitcher.io/blog/comparing-dates). A period's precision can be specified when constructing that period:

```
Period::make('2021-01-01', '2021-02-01', Precision::DAY());
```

The default precision is set on days. These are the available precision options:

```
Precision::YEAR()
Precision::MONTH()
Precision::DAY()
Precision::HOUR()
Precision::MINUTE()
Precision::SECOND()
```

#### Boundaries

[](#boundaries)

By default, period comparisons are done with included boundaries. This means that these two periods overlap:

```
$a = Period::make('2021-01-01', '2021-02-01');
$b = Period::make('2021-02-01', '2021-02-28');

$a->overlapsWith($b); // true
```

The length of a period will also include both boundaries:

```
$a = Period::make('2021-01-01', '2021-01-31');

$a->length(); // 31
```

It's possible to override the boundary behaviour:

```
$a = Period::make('2021-01-01', '2021-02-01', boundaries: Boundaries::EXCLUDE_END());
$b = Period::make('2021-02-01', '2021-02-28', boundaries: Boundaries::EXCLUDE_END());

$a->overlapsWith($b); // false
```

There are four types of boundary exclusion:

```
Boundaries::EXCLUDE_NONE();
Boundaries::EXCLUDE_START();
Boundaries::EXCLUDE_END();
Boundaries::EXCLUDE_ALL();
```

### Reference

[](#reference)

The `Period` class offers a rich API to interact and compare with other periods and collections of periods. Take into account that only periods with the same precision can be compared:

- `startsBefore(DateTimeInterface $date): bool`: whether a period starts before a given date.
- `startsBeforeOrAt(DateTimeInterface $date): bool`: whether a period starts before or at a given date.
- `startsAfter(DateTimeInterface $date): bool`: whether a period starts after a given date.
- `startsAfterOrAt(DateTimeInterface $date): bool`: whether a period starts after or at a given date.
- `startsAt(DateTimeInterface $date): bool`: whether a period starts at a given date.
- `endsBefore(DateTimeInterface $date): bool`: whether a period ends before a given date.
- `endsBeforeOrAt(DateTimeInterface $date): bool`: whether a period end before or at a given date.
- `endsAfter(DateTimeInterface $date): bool`: whether a period ends after a given date.
- `endsAfterOrAt(DateTimeInterface $date): bool`: whether a period end after or at a given date.
- `endsAt(DateTimeInterface $date): bool`: whether a period starts ends at a given date.
- `overlapsWith(Period $period): bool`: whether a period overlaps with another period.
- `touchesWith(Period $other): bool`: whether a period touches with another period.
- `contains(DateTimeInterface|Period $other): bool`: whether a period contains another period *or* a single date.
- `equals(Period $period): bool`: whether a period equals another period.

---

On top of comparisons, the `Period` class also offers a bunch of operations:

### `overlap(Period ...$others): ?static`

[](#overlapperiod-others-static)

Overlaps two or more periods on each other. The resulting period will be the union of all other periods combined.

[![](./docs/img/period-overlap.png)](./docs/img/period-overlap.png)

### `overlapAny(Period ...$others): PeriodCollection`

[](#overlapanyperiod-others-periodcollection)

Overlaps two or more periods on each other. Whenever two or more periods overlap, that overlapping period is added to a collection which will be returned as the final result.

[![](./docs/img/period-overlap-any.png)](./docs/img/period-overlap-any.png)

### `subtract(Period ...$others): PeriodCollection`

[](#subtractperiod-others-periodcollection)

Subtracts one or more periods from the main period. This is the inverse operation of overlap.

[![](./docs/img/period-subtract.png)](./docs/img/period-subtract.png)

### `gap(Period $period): ?static`

[](#gapperiod-period-static)

Gets the gap between two periods, or 0 if the periods overlap.

[![](./docs/img/period-gap.png)](./docs/img/period-gap.png)

### `diffSymmetric(Period $other): PeriodCollection`

[](#diffsymmetricperiod-other-periodcollection)

Performs a [symmetric diff](https://www.math-only-math.com/symmetric-difference-using-Venn-diagram.html) between two periods.

[![](./docs/img/period-diff-symmetric.png)](./docs/img/period-diff-symmetric.png)

### `renew(): static`

[](#renew-static)

Renew the current period, creating a new period with the same length that happens *after* the current period.

[![](./docs/img/period-renew.png)](./docs/img/period-renew.png)

---

Next, the `Period` class also has some getters:

- `isStartIncluded(): bool`
- `isStartExcluded(): bool`
- `isEndIncluded(): bool`
- `isEndExcluded(): bool`
- `start(): DateTimeImmutable`
- `includedStart(): DateTimeImmutable`
- `end(): DateTimeImmutable`
- `includedEnd(): DateTimeImmutable`
- `ceilingEnd(Precision::SECOND): DateTimeImmutable`
- `length(): int`
- `duration(): PeriodDuration`
- `precision(): Precision`
- `boundaries(): Boundaries`

---

The `PeriodCollection` class represents a collection of periods and has some useful methods on its own:

### `overlapAll(PeriodCollection ...$others): PeriodCollection`

[](#overlapallperiodcollection-others-periodcollection)

Overlaps all collection periods on each other.

[![](./docs/img/collection-overlap-all.png)](./docs/img/collection-overlap-all.png)

### `subtract(PeriodCollection|Period ...$others): PeriodCollection`

[](#subtractperiodcollectionperiod-others-periodcollection)

Subtracts a period or a collection of periods from a period collection.

[![](./docs/img/collection-subtract.png)](./docs/img/collection-subtract.png)

### `boundaries(): ?Period`

[](#boundaries-period)

Creates a new period representing the outer boundaries of the collection.

[![](./docs/img/collection-boundaries.png)](./docs/img/collection-boundaries.png)

### `gaps(): static`

[](#gaps-static)

Gives the gaps for all periods within this collection.

[![](./docs/img/collection-gaps.png)](./docs/img/collection-gaps.png)

### `intersect(Period $intersection): static`

[](#intersectperiod-intersection-static)

Intersects given period with every period within a collection. The result is a new collection of overlapping periods between given period and every period in the collection. When there's no overlap, the original period is discarded.

[![](./docs/img/collection-intersect.png)](./docs/img/collection-intersect.png)

### `union(): static`

[](#union-static)

Merges all periods in collection with overlapping ranges.

[![](./docs/img/collection-union.png)](./docs/img/collection-union.png)

---

Finally, there are a few utility methods available on `PeriodCollection` as well:

- `add(Period ...$periods): static`
- `map(Closure $closure): static`:
- `reduce(Closure $closure, $initial = null): mixed`:
- `filter(Closure $closure): static`:
- `isEmpty(): bool`:

### Compatibility

[](#compatibility)

You can construct a `Period` from any type of `DateTime` object such as Carbon:

```
Period::make(Carbon::make('2021-01-01'), Carbon::make('2021-01-02'));
```

Note that as soon as a period is constructed, all further operations on it are immutable. There's never the danger of changing the input dates.

You can iterate a `Period` like a regular `DatePeriod` with the precision specified on creation:

```
$datePeriod = Period::make(Carbon::make('2021-01-01'), Carbon::make('2021-01-31'));

foreach ($datePeriod as $date) {
    /** @var DateTimeImmutable $date */
    // 2021-01-01
    // 2021-01-02
    // ...
    // (31 iterations)
}

$timePeriod = Period::make(Carbon::make('2021-01-01 00:00:00'), Carbon::make('2021-01-01 23:59:59'), Precision::HOUR());

foreach ($timePeriod as $time) {
    /** @var DateTimeImmutable $time */
    // 2021-01-01 00:00:00
    // 2021-01-01 01:00:00
    // ...
    // (24 iterations)
}
```

### Visualizing periods

[](#visualizing-periods)

You can visualize one or more `Period` objects as well as `PeriodCollection`objects to see how they related to one another:

```
$visualizer = new Visualizer(["width" => 27]);

$visualizer->visualize([
    "A" => Period::make('2021-01-01', '2021-01-31'),
    "B" => Period::make('2021-02-10', '2021-02-20'),
    "C" => Period::make('2021-03-01', '2021-03-31'),
    "D" => Period::make('2021-01-20', '2021-03-10'),
    "OVERLAP" => new PeriodCollection(
        Period::make('2021-01-20', '2021-01-31'),
        Period::make('2021-02-10', '2021-02-20'),
        Period::make('2021-03-01', '2021-03-10')
    ),
]);
```

And visualize will return the following string:

```
A          [========]
B                      [==]
C                           [========]
D               [==============]
OVERLAP         [===]  [==] [==]

```

The visualizer has a configurable width provided upon creation which will control the bounds of the displayed periods:

```
$visualizer = new Visualizer(["width" => 10]);
```

### Testing

[](#testing)

```
composer test
```

### Changelog

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

Contributing
------------

[](#contributing)

Please see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details.

### Security

[](#security)

If you've found a bug regarding security please mail  instead of using the issue tracker.

Postcardware
------------

[](#postcardware)

You're free to use this package, but if it makes it to your production environment we highly appreciate you sending us a postcard from your hometown, mentioning which of our package(s) you are using.

Our address is: Spatie, Kruikstraat 22, 2021 Antwerp, Belgium.

We publish all received postcards [on our company website](https://spatie.be/en/opensource/postcards).

Credits
-------

[](#credits)

- [Brent Roose](https://github.com/brendt)
- [All Contributors](../../contributors)

License
-------

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

65

—

FairBetter than 99% of packages

Maintenance59

Moderate activity, may be stable

Popularity68

Solid adoption and visibility

Community40

Growing community involvement

Maturity79

Established project with proven stability

 Bus Factor1

Top contributor holds 52.9% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~35 days

Recently: every ~106 days

Total

45

Last Release

1183d ago

Major Versions

0.5.1 → 1.0.02019-01-17

1.6.0 → 2.0.x-dev2021-03-17

v1.x-dev → 2.3.22021-12-23

PHP version history (3 changes)0.1.0PHP ^7.1

1.5.3PHP &gt;=7.2

2.0.x-devPHP ^8.0

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/7535935?v=4)[Spatie](/maintainers/spatie)[@spatie](https://github.com/spatie)

---

Top Contributors

[![brendt](https://avatars.githubusercontent.com/u/6905297?v=4)](https://github.com/brendt "brendt (165 commits)")[![freekmurze](https://avatars.githubusercontent.com/u/483853?v=4)](https://github.com/freekmurze "freekmurze (35 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (11 commits)")[![Nielsvanpach](https://avatars.githubusercontent.com/u/10651054?v=4)](https://github.com/Nielsvanpach "Nielsvanpach (9 commits)")[![AdrianMrn](https://avatars.githubusercontent.com/u/12762044?v=4)](https://github.com/AdrianMrn "AdrianMrn (8 commits)")[![jeromegamez](https://avatars.githubusercontent.com/u/67554?v=4)](https://github.com/jeromegamez "jeromegamez (8 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (8 commits)")[![patinthehat](https://avatars.githubusercontent.com/u/5508707?v=4)](https://github.com/patinthehat "patinthehat (7 commits)")[![DrMerk](https://avatars.githubusercontent.com/u/121562885?v=4)](https://github.com/DrMerk "DrMerk (7 commits)")[![morrislaptop](https://avatars.githubusercontent.com/u/67807?v=4)](https://github.com/morrislaptop "morrislaptop (6 commits)")[![kylekatarnls](https://avatars.githubusercontent.com/u/5966783?v=4)](https://github.com/kylekatarnls "kylekatarnls (5 commits)")[![masterix21](https://avatars.githubusercontent.com/u/6555012?v=4)](https://github.com/masterix21 "masterix21 (5 commits)")[![rubenvanassche](https://avatars.githubusercontent.com/u/619804?v=4)](https://github.com/rubenvanassche "rubenvanassche (5 commits)")[![endelwar](https://avatars.githubusercontent.com/u/28512?v=4)](https://github.com/endelwar "endelwar (5 commits)")[![AyoobMH](https://avatars.githubusercontent.com/u/37803924?v=4)](https://github.com/AyoobMH "AyoobMH (4 commits)")[![riasvdv](https://avatars.githubusercontent.com/u/3626559?v=4)](https://github.com/riasvdv "riasvdv (3 commits)")[![winkbrace](https://avatars.githubusercontent.com/u/1883930?v=4)](https://github.com/winkbrace "winkbrace (3 commits)")[![jimirobaer](https://avatars.githubusercontent.com/u/8984769?v=4)](https://github.com/jimirobaer "jimirobaer (3 commits)")[![aliowacom](https://avatars.githubusercontent.com/u/91871615?v=4)](https://github.com/aliowacom "aliowacom (2 commits)")[![AlexVanderbist](https://avatars.githubusercontent.com/u/6287961?v=4)](https://github.com/AlexVanderbist "AlexVanderbist (2 commits)")

---

Tags

calculationscalendardateperiodscheduletimespatieperiod

###  Code Quality

TestsPest

### Embed Badge

![Health badge](/badges/spatie-period/health.svg)

```
[![Health](https://phpackages.com/badges/spatie-period/health.svg)](https://phpackages.com/packages/spatie-period)
```

###  Alternatives

[spatie/laravel-package-tools

Tools for creating Laravel packages

945125.5M7.0k](/packages/spatie-laravel-package-tools)[spatie/laravel-data

Create unified resources and data transfer objects

1.8k28.9M627](/packages/spatie-laravel-data)[spatie/laravel-analytics

A Laravel package to retrieve Google Analytics data.

3.2k5.7M57](/packages/spatie-laravel-analytics)[spatie/macroable

A trait to dynamically add methods to a class

72759.6M64](/packages/spatie-macroable)[spatie/regex

A sane interface for php's built in preg\_\* functions

1.1k17.1M59](/packages/spatie-regex)[spatie/enum

PHP Enums

84529.1M68](/packages/spatie-enum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
