{"id":13579108,"url":"https://github.com/spatie/period","last_synced_at":"2025-05-14T01:03:05.994Z","repository":{"id":37412759,"uuid":"159155863","full_name":"spatie/period","owner":"spatie","description":"Complex period comparisons","archived":false,"fork":false,"pushed_at":"2024-09-23T08:05:16.000Z","size":671,"stargazers_count":1626,"open_issues_count":0,"forks_count":72,"subscribers_count":21,"default_branch":"main","last_synced_at":"2024-10-29T11:23:36.063Z","etag":null,"topics":["calculations","calendar","date","period","schedule","time"],"latest_commit_sha":null,"homepage":"https://spatie.be/open-source","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/spatie.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-11-26T11:04:38.000Z","updated_at":"2024-10-29T02:33:29.000Z","dependencies_parsed_at":"2023-02-16T09:16:00.767Z","dependency_job_id":"1e691563-0880-47b2-bcb4-191fa4e5d571","html_url":"https://github.com/spatie/period","commit_stats":{"total_commits":251,"total_committers":41,"mean_commits":6.121951219512195,"dds":0.50199203187251,"last_synced_commit":"fc19d1fec0a31368935fb365b02d5e0455dc0d6e"},"previous_names":[],"tags_count":43,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spatie%2Fperiod","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spatie%2Fperiod/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spatie%2Fperiod/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/spatie%2Fperiod/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/spatie","download_url":"https://codeload.github.com/spatie/period/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242587446,"owners_count":20154197,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["calculations","calendar","date","period","schedule","time"],"created_at":"2024-08-01T15:01:36.602Z","updated_at":"2025-05-14T01:03:05.985Z","avatar_url":"https://github.com/spatie.png","language":"PHP","funding_links":[],"categories":["PHP"],"sub_categories":[],"readme":"\u003cdiv align=\"left\"\u003e\n    \u003ca href=\"https://spatie.be/open-source?utm_source=github\u0026utm_medium=banner\u0026utm_campaign=period\"\u003e\n      \u003cpicture\u003e\n        \u003csource media=\"(prefers-color-scheme: dark)\" srcset=\"https://spatie.be/packages/header/period/html/dark.webp\"\u003e\n        \u003cimg alt=\"Logo for period\" src=\"https://spatie.be/packages/header/period/html/light.webp\" height=\"190\"\u003e\n      \u003c/picture\u003e\n    \u003c/a\u003e\n\n\u003ch1\u003eComplex period comparisons\u003c/h1\u003e\n\n[![Latest Version on Packagist](https://img.shields.io/packagist/v/spatie/period.svg?style=flat-square)](https://packagist.org/packages/spatie/period)\n[![Quality Score](https://img.shields.io/scrutinizer/g/spatie/period.svg?style=flat-square)](https://scrutinizer-ci.com/g/spatie/period)\n[![Total Downloads](https://img.shields.io/packagist/dt/spatie/period.svg?style=flat-square)](https://packagist.org/packages/spatie/period)\n    \n\u003c/div\u003e\n\nThis package adds support for comparing multiple dates with each other.\nYou can calculate the overlaps and differences between n-amount of periods,\nas well as some more basic comparisons between two periods.\n\nPeriods can be constructed from any type of `DateTime` implementation, \nmaking this package compatible with custom `DateTime` implementations like\n[Carbon](https://carbon.nesbot.com)\n(see [cmixin/enhanced-period](https://github.com/kylekatarnls/enhanced-period) to\nconvert directly from and to CarbonPeriod).\n\nPeriods are always immutable, there's never the worry about your input dates being changed. \n\n## Support us\n\n[\u003cimg src=\"https://github-ads.s3.eu-central-1.amazonaws.com/period.jpg?t=1\" width=\"419px\" /\u003e](https://spatie.be/github-ad-click/period)\n\nWe 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).\n\nWe 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).\n\n## Installation\n\nYou can install the package via composer:\n\n```bash\ncomposer require spatie/period\n```\n\n## Usage\n\n### Creating periods\n\nYou're encouraged to create periods using their static constructor:\n\n```php\n$period = Period::make('2021-01-01', '2021-01-31');\n```\n\nYou 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. \n\nBefore discussing the API provided by this package, it's important to understand both how precision and boundaries are used.\n\n#### Precision\n\nDate precision is of utmost importance if you want to reliably compare two periods.\nThe following example:\n\n\u003e Given two periods: `[2021-01-01, 2021-01-15]` and `[2021-01-15, 2021-01-31]`; do they overlap?\n\nAt first glance the answer is \"yes\": they overlap on `2021-01-15`. \nBut what if the first period ends at `2021-01-15 10:00:00`, \nwhile the second starts at `2021-01-15 15:00:00`? \nNow they don't anymore!\n\nThis is why this package requires you to specify a precision with each period. \nOnly periods with the same precision can be compared.\n\nA more in-depth explanation on why precision is so important can be found [here](https://stitcher.io/blog/comparing-dates).\nA period's precision can be specified when constructing that period:\n\n```php\nPeriod::make('2021-01-01', '2021-02-01', Precision::DAY());\n```\n\nThe default precision is set on days. These are the available precision options:\n\n```php\nPrecision::YEAR()\nPrecision::MONTH()\nPrecision::DAY()\nPrecision::HOUR()\nPrecision::MINUTE()\nPrecision::SECOND()\n```\n\n#### Boundaries\n\nBy default, period comparisons are done with included boundaries. \nThis means that these two periods overlap:\n\n```php\n$a = Period::make('2021-01-01', '2021-02-01');\n$b = Period::make('2021-02-01', '2021-02-28');\n\n$a-\u003eoverlapsWith($b); // true\n```\n\nThe length of a period will also include both boundaries:\n\n```php\n$a = Period::make('2021-01-01', '2021-01-31');\n\n$a-\u003elength(); // 31\n```\n\nIt's possible to override the boundary behaviour:\n\n```php\n$a = Period::make('2021-01-01', '2021-02-01', boundaries: Boundaries::EXCLUDE_END());\n$b = Period::make('2021-02-01', '2021-02-28', boundaries: Boundaries::EXCLUDE_END());\n\n$a-\u003eoverlapsWith($b); // false\n```\n\nThere are four types of boundary exclusion:\n\n```php\nBoundaries::EXCLUDE_NONE();\nBoundaries::EXCLUDE_START();\nBoundaries::EXCLUDE_END();\nBoundaries::EXCLUDE_ALL();\n```\n\n### Reference\n\nThe `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:\n\n- `startsBefore(DateTimeInterface $date): bool`: whether a period starts before a given date.\n- `startsBeforeOrAt(DateTimeInterface $date): bool`: whether a period starts before or at a given date.\n- `startsAfter(DateTimeInterface $date): bool`: whether a period starts after a given date.\n- `startsAfterOrAt(DateTimeInterface $date): bool`: whether a period starts after or at a given date.\n- `startsAt(DateTimeInterface $date): bool`: whether a period starts at a given date.\n- `endsBefore(DateTimeInterface $date): bool`: whether a period ends before a given date.\n- `endsBeforeOrAt(DateTimeInterface $date): bool`: whether a period end before or at a given date.\n- `endsAfter(DateTimeInterface $date): bool`: whether a period ends after a given date.\n- `endsAfterOrAt(DateTimeInterface $date): bool`: whether a period end after or at a given date.\n- `endsAt(DateTimeInterface $date): bool`: whether a period starts ends at a given date.\n- `overlapsWith(Period $period): bool`: whether a period overlaps with another period.\n- `touchesWith(Period $other): bool`: whether a period touches with another period.\n- `contains(DateTimeInterface|Period $other): bool`: whether a period contains another period _or_ a single date.\n- `equals(Period $period): bool`: whether a period equals another period.\n\n---\n\nOn top of comparisons, the `Period` class also offers a bunch of operations:\n\n### `overlap(Period ...$others): ?static`\n\nOverlaps two or more periods on each other. The resulting period will be the union of all other periods combined.\n\n![](./docs/img/period-overlap.png)\n\n### `overlapAny(Period ...$others): PeriodCollection`\n\nOverlaps 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.\n\n![](./docs/img/period-overlap-any.png)\n\n### `subtract(Period ...$others): PeriodCollection`\n\nSubtracts one or more periods from the main period. This is the inverse operation of overlap.\n\n![](./docs/img/period-subtract.png)\n\n### `gap(Period $period): ?static`\n\nGets the gap between two periods, or 0 if the periods overlap.\n\n![](./docs/img/period-gap.png)\n\n### `diffSymmetric(Period $other): PeriodCollection`\n\nPerforms a [symmetric diff](https://www.math-only-math.com/symmetric-difference-using-Venn-diagram.html) between two periods.\n\n![](./docs/img/period-diff-symmetric.png)\n\n### `renew(): static`\n\nRenew the current period, creating a new period with the same length that happens _after_ the current period.\n\n![](./docs/img/period-renew.png)\n\n---\n\nNext, the `Period` class also has some getters:\n\n- `isStartIncluded(): bool`\n- `isStartExcluded(): bool`\n- `isEndIncluded(): bool`\n- `isEndExcluded(): bool`\n- `start(): DateTimeImmutable`\n- `includedStart(): DateTimeImmutable`\n- `end(): DateTimeImmutable`\n- `includedEnd(): DateTimeImmutable`\n- `ceilingEnd(Precision::SECOND): DateTimeImmutable`\n- `length(): int`\n- `duration(): PeriodDuration`\n- `precision(): Precision`\n- `boundaries(): Boundaries`\n\n---\n\nThe `PeriodCollection` class represents a collection of periods and has some useful methods on its own:\n\n### `overlapAll(PeriodCollection ...$others): PeriodCollection`\n\nOverlaps all collection periods on each other.\n\n![](./docs/img/collection-overlap-all.png)\n\n### `subtract(PeriodCollection|Period ...$others): PeriodCollection`\n\nSubtracts a period or a collection of periods from a period collection.\n\n![](./docs/img/collection-subtract.png)\n\n### `boundaries(): ?Period` \n\nCreates a new period representing the outer boundaries of the collection.\n\n![](./docs/img/collection-boundaries.png)\n\n### `gaps(): static`\n\nGives the gaps for all periods within this collection.\n\n![](./docs/img/collection-gaps.png)\n\n### `intersect(Period $intersection): static`\n\nIntersects 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.\n\n![](./docs/img/collection-intersect.png)\n\n### `union(): static`\n\nMerges all periods in collection with overlapping ranges.\n\n![](./docs/img/collection-union.png)\n\n---\n\nFinally, there are a few utility methods available on `PeriodCollection` as well:\n\n- `add(Period ...$periods): static`\n- `map(Closure $closure): static`:\n- `reduce(Closure $closure, $initial = null): mixed`: \n- `filter(Closure $closure): static`: \n- `isEmpty(): bool`:\n\n### Compatibility\n\nYou can construct a `Period` from any type of `DateTime` object such as Carbon:\n\n```php\nPeriod::make(Carbon::make('2021-01-01'), Carbon::make('2021-01-02'));\n```\n\nNote that as soon as a period is constructed, all further operations on it are immutable.\nThere's never the danger of changing the input dates.\n\nYou can iterate a `Period` like a regular `DatePeriod` with the precision specified on creation:\n\n```php\n$datePeriod = Period::make(Carbon::make('2021-01-01'), Carbon::make('2021-01-31'));\n\nforeach ($datePeriod as $date) {\n    /** @var DateTimeImmutable $date */\n    // 2021-01-01\n    // 2021-01-02\n    // ...\n    // (31 iterations)\n}\n\n$timePeriod = Period::make(Carbon::make('2021-01-01 00:00:00'), Carbon::make('2021-01-01 23:59:59'), Precision::HOUR());\n\nforeach ($timePeriod as $time) {\n    /** @var DateTimeImmutable $time */\n    // 2021-01-01 00:00:00\n    // 2021-01-01 01:00:00\n    // ...\n    // (24 iterations)\n}\n```\n\n### Visualizing periods\n\nYou can visualize one or more `Period` objects as well as `PeriodCollection`\nobjects to see how they related to one another:\n\n```php\n$visualizer = new Visualizer([\"width\" =\u003e 27]);\n\n$visualizer-\u003evisualize([\n    \"A\" =\u003e Period::make('2021-01-01', '2021-01-31'),\n    \"B\" =\u003e Period::make('2021-02-10', '2021-02-20'),\n    \"C\" =\u003e Period::make('2021-03-01', '2021-03-31'),\n    \"D\" =\u003e Period::make('2021-01-20', '2021-03-10'),\n    \"OVERLAP\" =\u003e new PeriodCollection(\n        Period::make('2021-01-20', '2021-01-31'),\n        Period::make('2021-02-10', '2021-02-20'),\n        Period::make('2021-03-01', '2021-03-10')\n    ),\n]);\n```\n\nAnd visualize will return the following string:\n\n```\nA          [========]\nB                      [==]\nC                           [========]\nD               [==============]\nOVERLAP         [===]  [==] [==]\n```\n\nThe visualizer has a configurable width provided upon creation\nwhich will control the bounds of the displayed periods:\n\n```php\n$visualizer = new Visualizer([\"width\" =\u003e 10]);\n```\n\n### Testing\n\n``` bash\ncomposer test\n```\n\n### Changelog\n\nPlease see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.\n\n## Contributing\n\nPlease see [CONTRIBUTING](https://github.com/spatie/.github/blob/main/CONTRIBUTING.md) for details.\n\n### Security\n\nIf you've found a bug regarding security please mail [security@spatie.be](mailto:security@spatie.be) instead of using the issue tracker.\n\n## Postcardware\n\nYou'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.\n\nOur address is: Spatie, Kruikstraat 22, 2021 Antwerp, Belgium.\n\nWe publish all received postcards [on our company website](https://spatie.be/en/opensource/postcards).\n\n## Credits\n\n- [Brent Roose](https://github.com/brendt)\n- [All Contributors](../../contributors)\n\n## License\n\nThe MIT License (MIT). Please see [License File](LICENSE.md) for more information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspatie%2Fperiod","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fspatie%2Fperiod","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fspatie%2Fperiod/lists"}