PHPackages                             centamiv/chronoset - 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. centamiv/chronoset

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

centamiv/chronoset
==================

The ultimate Time Period management toolkit for PHP/Laravel.

10PHPCI passing

Since Dec 20Pushed 4mo ago1 watchersCompare

[ Source](https://github.com/centamiv/chronoset)[ Packagist](https://packagist.org/packages/centamiv/chronoset)[ RSS](/packages/centamiv-chronoset/feed)WikiDiscussions master Synced 1mo ago

READMEChangelogDependenciesVersions (2)Used By (0)

ChronoSet
=========

[](#chronoset)

**ChronoSet** is a powerful, immutable Laravel/PHP library designed to treat time as a mathematical set.

Managing complex schedules, booking availabilities, and recurring time blocks can quickly become a headache with standard date libraries. **ChronoSet** solves this by allowing you to **add**, **subtract**, **intersect**, and **normalize** collections of dates effortlessly.

---

Key Features
------------

[](#key-features)

- **Period Normalization**: Automatically merges overlapping or contiguous periods.
- **Set Operations**: Union, Intersection, Difference, and Symmetric Difference.
- **Immutability**: Built on `CarbonImmutable`.
- **Infinite Bounds**: Supports `null` as "infinity".
- **Gap Finding**: Easily find free slots (availability).
- **Precise**: Second-level precision. Periods are **inclusive** (`[start, end]`). ChronoSet distinguishes between *overlapping* and *touching*: a period ending at `10:00:00` does **NOT** conflict with one starting at `10:00:00`. This allows seamless back-to-back scheduling.

---

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

[](#installation)

```
composer require centamiv/chronoset
```

---

Core Concepts &amp; Usage
-------------------------

[](#core-concepts--usage)

### 1. Time as a Mathematical Set

[](#1-time-as-a-mathematical-set)

ChronoSet treats time not as a sequence of dates, but as a continuous mathematical line. "Sets" of time can be added, subtracted, and intersected just like Venn diagrams.

### 2. Period vs PeriodCollection

[](#2-period-vs-periodcollection)

- **Period**: A single continuous span of time (e.g., "Meeting from 9am to 10am").
- **PeriodCollection**: A set containing zero, one, or multiple **disjoint** (non-overlapping) Periods. This allows you to represent complex schedules like "Mondays and Wednesdays from 9-5" as a single object.

### 3. Open/Closed Boundaries &amp; Infinity

[](#3-openclosed-boundaries--infinity)

- Periods are inclusive of their start and end points (`[start, end]`).
- `null` represents **Infinity**.
    - `new Period(null, '2025-01-01')` covers everything from the beginning of time up to Jan 1st 2025.

### 4. Normalization

[](#4-normalization)

This is the library's superpower. When you add periods to a `PeriodCollection`, it automatically **merges overlaps** and **connects touching periods**. You never have to manually check for conflicting times.

```
Input:
Period A: [=======]
Period B:      [=======]
Period C:              [===]

Normalized:
Result:   [================]

```

```
$schedule = new PeriodCollection([
    new Period('09:00', '10:00'),
    new Period('09:30', '11:00'),
    new Period('11:00', '12:00'),
]);
$clean = $schedule->normalize(); // Result: One period from 09:00 -> 12:00
```

### 5. Set Operations

[](#5-set-operations)

Since time is a set, you can perform standard set operations on `PeriodCollection`s:

- **Union**: Combine two schedules (`$a->union($b)`).
- **Intersection**: Find common free time (`$a->intersect($b)`).
- **Difference**: Remove booked time from a schedule (`$a->diff($b)`).

### 6. Finding Availability (Gaps)

[](#6-finding-availability-gaps)

Find free slots by "subtracting" your booked periods from a "boundary" period (like a work day).

```
Boundary: [=========================]
Booked:      [=====]       [=====]

Gaps:     [=]       [=====]       [=]

```

```
$workDay = new Period('09:00', '18:00');
$booked  = new PeriodCollection([
    new Period('10:00', '11:00'),
    new Period('14:00', '15:00')
]);

// "Work Day" minus "Booked" = "Free Time"
$free = $booked->gaps($workDay);
// Result: [09:00-10:00], [11:00-14:00], [15:00-18:00]
```

### 7. Immutability

[](#7-immutability)

All classes are **immutable**. Operations like `merge` or `subtract` return a *new* instance, leaving the original unchanged. This prevents side-effects and makes the code easier to reason about.

---

API Reference
-------------

[](#api-reference)

### Period

[](#period)

- `overlaps(Period $other): bool`
- `overlapsOrTouches(Period $other): bool`
- `containsDate(Carbon $date): bool`
- `containsPeriod(Period $other): bool`
- `subtract(Period $other): Period[]`
- `intersect(Period $other): ?Period`
- `merge(Period $other): Period`
- `duration(): ?CarbonInterval`
- `splitByDays(): PeriodCollection`

### PeriodCollection

[](#periodcollection)

- `normalize(): self` - Merges overlapping/adjacent periods.
- `subtractPeriod(Period $p): self`
- `union(PeriodCollection $others): self`
- `diff(PeriodCollection $others): self`
- `intersect(PeriodCollection $others): self`
- `symmetricDifference(PeriodCollection $others): self`
- `gaps(Period $boundary): self`
- `totalDuration(): ?CarbonInterval`

Detailed API Reference
----------------------

[](#detailed-api-reference)

### `ChronoSet\Period`

[](#chronosetperiod)

#### `new Period($start, $end)`

[](#new-periodstart-end)

Creates a new period instance. `null` represents infinity.

```
$p = new Period('2023-01-01', '2023-01-02');
$forever = new Period(null, null); // Infinite duration
```

#### `static make($start, $end): self`

[](#static-makestart-end-self)

Static factory method to create a new period.

```
$p = Period::make('2023-01-01', '2023-02-01');
```

#### `durationIn(string $unit = 'hours'): float`

[](#durationinstring-unit--hours-float)

Calculates the duration of the period in the specified unit (seconds, minutes, hours, days). Returns `INF` if infinite.

```
$p = new Period('09:00', '10:30');
echo $p->durationIn('minutes'); // 90.0
echo $p->durationIn('hours');   // 1.5
```

#### `duration(): ?CarbonInterval`

[](#duration-carboninterval)

Returns the duration as a `CarbonInterval` object, or `null` if infinite.

```
$p = new Period('09:00', '10:30');
echo $p->duration()->forHumans(); // "1 hour 30 minutes"
```

#### `overlaps(Period $other): bool`

[](#overlapsperiod-other-bool)

Determines if this period strictly overlaps with another. Touching boundaries (e.g., end == start) is NOT considered an overlap.

```
Case 1 (True):
A: [=========]
B:      [=========]

Case 2 (False - Touching):
A: [=========]
B:           [=========]

```

```
$a = new Period('09:00', '10:00');
$b = new Period('09:30', '10:30');
$c = new Period('10:00', '11:00');

$a->overlaps($b); // true
$a->overlaps($c); // false (just touches)
```

#### `overlapsOrTouches(Period $other): bool`

[](#overlapsortouchesperiod-other-bool)

Determines if periods overlap OR if they just touch boundaries. Useful for finding periods that can be merged.

```
$a->overlapsOrTouches($c); // true
```

#### `containsDate($date): bool`

[](#containsdatedate-bool)

Checks if a specific date/time falls within the period (inclusive).

```
$p = new Period('09:00', '10:00');
$p->containsDate('09:30'); // true
$p->containsDate('10:00'); // true
```

#### `containsPeriod(Period $other): bool`

[](#containsperiodperiod-other-bool)

Checks if this period completely encloses another period.

```
$parent = new Period('09:00', '12:00');
$child  = new Period('10:00', '11:00');

$parent->containsPeriod($child); // true
```

#### `intersect(Period $other): ?Period`

[](#intersectperiod-other-period)

Returns a new Period representing the shared time interval, or `null` if no overlap exists.

```
A:     [=========]
B: [=========]

Result:    [=]

```

```
$a = new Period('09:00', '11:00');
$b = new Period('10:00', '12:00');

$intersection = $a->intersect($b);
// Result: Period('10:00', '11:00')
```

#### `merge(Period $other): Period`

[](#mergeperiod-other-period)

Combines two overlapping or touching periods into a single continuous period. Throws detailed exception if they are disjoint.

```
A: [=========]
B:      [=========]

Result: [=============]

```

```
$a = new Period('09:00', '10:00');
$b = new Period('10:00', '11:00');

$merged = $a->merge($b);
// Result: Period('09:00', '11:00')
```

#### `subtract(Period $other): Period[]`

[](#subtractperiod-other-period)

Removes a period from the current one. Returns an array containing 0, 1, or 2 resulting periods.

```
Case 1: Punching a hole
A: [==================]
B:      [======]

Result: [===]      [===]

```

```
$base = new Period('09:00', '12:00');
$remove = new Period('10:00', '11:00');

$result = $base->subtract($remove);
// Result: [Period('09:00', '10:00'), Period('11:00', '12:00')]
```

#### `splitByDays(): PeriodCollection`

[](#splitbydays-periodcollection)

Splits a multi-day period into daily 00:00-23:59 chunks.

```
$trip = new Period('2023-01-01 10:00', '2023-01-03 15:00');
$days = $trip->splitByDays();
// Result:
// 1. 10:00 -> 23:59:59 (Day 1)
// 2. 00:00 -> 23:59:59 (Day 2)
// 3. 00:00 -> 15:00:00 (Day 3)
```

### `ChronoSet\PeriodCollection`

[](#chronosetperiodcollection)

#### `normalize(): self`

[](#normalize-self)

Merges all overlapping or contiguous periods in the collection into the minimal set of disjoint periods.

```
Input:
Period A: [=======]
Period B:      [=======]
Period C:              [===]

Result:   [================]

```

```
$col = new PeriodCollection([
    new Period('09:00', '10:00'),
    new Period('09:30', '11:00')
]);
$norm = $col->normalize();
// Result: [Period('09:00', '11:00')]
```

#### `subtractPeriod(Period $p): self`

[](#subtractperiodperiod-p-self)

Subtracts a single `Period` from every period in the collection.

```
$col = new PeriodCollection([new Period('09:00', '12:00')]);
$lunch = new Period('12:00', '13:00'); // No overlap example
$break = new Period('10:00', '10:15');

$col->subtractPeriod($break);
// Result: [09:00->10:00, 10:15->12:00]
```

#### `union($others): self`

[](#unionothers-self)

Adds another `PeriodCollection` (or array of periods) to this one and normalizes the result.

```
Coll A: [=====]       [=====]
Coll B:       [=====]

Result: [===================]

```

```
$morning = new PeriodCollection([new Period('09:00', '12:00')]);
$afternoon = new PeriodCollection([new Period('13:00', '17:00')]);

$workDay = $morning->union($afternoon);
// Result: [09:00->12:00, 13:00->17:00]
```

#### `diff($others): self`

[](#diffothers-self)

Calculates **A - B**. Preserves time in A that is NOT in B.

```
Coll A: [===================]
Coll B:       [=====]

Result: [=====]       [=====]

```

```
$available = new PeriodCollection([new Period('09:00', '17:00')]);
$meetings = new PeriodCollection([new Period('10:00', '11:00')]);

$freeTime = $available->diff($meetings);
// Result: [09:00->10:00, 11:00->17:00]
```

#### `intersect($others): self`

[](#intersectothers-self)

Calculates **A AND B**. Keeps only time present in BOTH collections.

```
Coll A: [=====]   [=====]
Coll B:    [=========]

Result:    [=]    [=]

```

```
$alice = new PeriodCollection([new Period('09:00', '12:00')]);
$bob   = new PeriodCollection([new Period('11:00', '15:00')]);

$common = $alice->intersect($bob);
// Result: [11:00->12:00]
```

#### `symmetricDifference($others): self`

[](#symmetricdifferenceothers-self)

Calculates **A XOR B**. Keeps time present in A OR B, but NOT both.

```
Coll A: [=======]
Coll B:     [=======]

Result: [===]   [===]

```

```
$a = new PeriodCollection([new Period('09:00', '11:00')]);
$b = new PeriodCollection([new Period('10:00', '12:00')]);

$xor = $a->symmetricDifference($b);
// Result: [09:00->10:00, 11:00->12:00]
// The overlap (10-11) is removed.
```

#### `gaps(Period $boundary): self`

[](#gapsperiod-boundary-self)

Finds availability within a specific boundary period. Validates which parts of `$boundary` are NOT covered by this collection.

```
$schedule = new PeriodCollection([new Period('10:00', '12:00')]);
$day = new Period('08:00', '18:00');

$free = $schedule->gaps($day);
// Result: [08:00->10:00, 12:00->18:00]
```

#### `totalDuration(): ?CarbonInterval`

[](#totalduration-carboninterval)

Sums the length of all periods in the collection. Returns `null` if any period is infinite.

```
$col = new PeriodCollection([
    new Period('09:00', '10:00'), // 1h
    new Period('14:00', '16:00')  // 2h
]);
echo $col->totalDuration()->forHumans(); // "3 hours"
```

---

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

[](#contributing)

Contributions are welcome! Please submit a Pull Request.

License
-------

[](#license)

The MIT License (MIT). Please see License File for more information.

###  Health Score

19

—

LowBetter than 10% of packages

Maintenance51

Moderate activity, may be stable

Popularity2

Limited adoption so far

Community7

Small or concentrated contributor base

Maturity14

Early-stage or recently created project

 Bus Factor1

Top contributor holds 100% 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.

### Community

Maintainers

![](https://www.gravatar.com/avatar/4bb6e94628f1de489fb3f7114991eef0c4d84f8b42d507cadc2bd54fefc75091?d=identicon)[centamiv](/maintainers/centamiv)

---

Top Contributors

[![centamiv](https://avatars.githubusercontent.com/u/4162703?v=4)](https://github.com/centamiv "centamiv (3 commits)")

### Embed Badge

![Health badge](/badges/centamiv-chronoset/health.svg)

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

###  Alternatives

[myclabs/deep-copy

Create deep copies (clones) of your objects

8.9k849.8M169](/packages/myclabs-deep-copy)[torann/geoip

Support for multiple Geographical Location services.

2.2k14.2M76](/packages/torann-geoip)[simshaun/recurr

PHP library for working with recurrence rules

1.6k15.7M40](/packages/simshaun-recurr)[jetbrains/phpstorm-attributes

PhpStorm specific attributes

41416.0M647](/packages/jetbrains-phpstorm-attributes)[babdev/pagerfanta-bundle

Bundle integrating Pagerfanta with Symfony

20817.8M65](/packages/babdev-pagerfanta-bundle)[jakeasmith/http_build_url

Provides functionality for http\_build\_url() to environments without pecl\_http.

19817.7M93](/packages/jakeasmith-http-build-url)

PHPackages © 2026

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