Tokei, a Time handling library for PHP

A couple of years ago I released league/period, a package designed to fill a long-standing gap in PHP’s date API: a proper interval object. Its Period class represents a half-open interval between two DateTimeImmutable instances, with one important constraint: the starting date must always be before or equal to the ending date. The package is still actively maintained and remains highly relevant today.

Recently, however, I ran into a completely different problem domain: local time.

  • No dates.
  • No timezones.
  • Just time-of-day values and circular time ranges.

That was the moment I realized PHP still lacks the right abstractions for handling local time properly.

So today, I’d like to introduce bakame/tokei: a small but robust PHP package dedicated to local time handling.

composer require bakame/tokei

Back to basics

Tokei revolves around two core classes:

  • Time
  • Duration

Alongside them, a small set of enums helps make time manipulation expressive and safe.

<?php

use Bakame\Tokei\Time;
use Bakame\Tokei\Duration;
use Bakame\Tokei\SubSecondDisplay;

$dateTime = new DateTimeImmutable('2026-04-23 22:08:56.000456');
$time = Time::fromDate($dateTime);
echo $time->toString(); 
// "22:08:56.000456"

$duration = Duration::of(minutes: 25, hours: 10)->negated();
echo $duration->toClockFormat(); 
// "-10:25:00"

$newTime = $time->add($duration);
echo $newTime->toString(); 
// "11:43:56.000456"

echo $newTime->toString(SubSecondDisplay::Never);
// "11:43:56"

Time and Duration both operate with microsecond precision. Durations can be added to or subtracted from times, and all operations correctly wrap around the 24-hour clock.

The library also provides developer-friendly formatting utilities to simplify displaying times and durations in multiple formats. But that is only the beginning.

You can also:

  • compare times and durations,
  • truncate or round values,
  • clamp values within bounds,
  • compute distances and differences,
  • and convert durations to ISO-8601 representations.
<?php

$noon = Time::noon();
$endOfDay = Time::endOfDay();
$midnight = Time::midnight();

$midnight->isBefore($endOfDay); // true
$noon->isAfter($midnight);      // true

$morningDuration = $midnight->distance($noon);
$eveningDuration = $noon->distance($endOfDay);
$roundedEveningDuration = $eveningDuration->roundTo(Unit::Second);

$morningDuration->isLongerThan($eveningDuration);         // true
$morningDuration->isLongerThan($roundedEveningDuration);  // false

echo $morningDuration->toIso8601();
// 'PT12H'

echo $eveningDuration->toIso8601();
// 'PT11H59M59.999999S'

echo $roundedEveningDuration->toIso8601();
// 'PT12H'

At first glance, these results may seem counterintuitive. But in a circular 24-hour system, a day does not end at midnight — the next day begins there. That subtle distinction matters when performing precise calculations.

Modeling Circular Time

To properly model circular time ranges, Tokei provides two additional classes:

  • Interval
  • IntervalSet

An Interval combines a starting time with a duration.

An IntervalSet is a specialized collection designed to work efficiently with groups of intervals and their interactions.

<?php

$interval = Interval::since(
  start: Time::noon(),
  duration: Duration::of(hours: 6),
);
$interval->format(IntervalFormat::Iso80000);
// "[12:00:00,18:00:00)"

$interval->format(IntervalFormat::Iso8601StartDuration);
// "12:00:00/PT6H"

$interval->start;    // returns a Time instance
$interval->end;      // returns a Time instance
$interval->duration; // returns a Duration instance
$interval->type;     // returns an IntervalType Enum case

$openHours = new IntervalSet(
  Interval::between(
    start: Time::at(hour: 10),
    end: Time::at(hour: 12, minute: 30)
  ),
  Interval::between(
      start: Time::at(hour: 14),
      end: Time::at(hour: 18)
  ),
  Interval::between(
      start: Time::at(hour: 22),
      end: Time::at(hour: 6)
  ),
);

$now = Time::now();
$isOpen = $openHour->any(
    fn (Interval $interval): bool => $interval->includes($now)
);

echo $isOpen
    ? 'The shop is currently open'
    : 'The shop is currently closed';

Unlike traditional date intervals, Interval works on a circular timeline.

That means the starting time may be:

  • before the ending time,
  • equal to the ending time,
  • or after the ending time.

To make interval semantics explicit, each interval exposes a type property backed by the IntervalType enum.

Possible values are:

  • IntervalType::Linear : The start time is before the end time.
  • IntervalType::Overflow : The interval crosses midnight.
  • IntervalType::Circular : The interval spans the full 24 hours.
  • IntervalType::Collapsed : The interval has zero duration.

These distinctions are important because interval behavior changes depending on its topology, especially for comparison, containment, intersection, and normalization operations.

Why This Matters

Local time handling sounds deceptively simple until you encounter real-world problems such as:

  • business opening hours,
  • recurring schedules,
  • night shifts,
  • cron-like systems,
  • media programming,
  • circular availability windows,
  • or any logic crossing midnight.

Most date APIs model time linearly. But clocks are circular. Tokei embraces that reality directly.

Use it today

Tokei is currently in the 0.x.x phase, though I already consider the API close to stable.

I could likely tag a 1.0.0 release today, but I would first like to gather more real-world feedback and usage experience before freezing the API surface permanently.

The package already offers far more than what is covered in this introduction, but I hope this overview gives you a good sense of its goals and capabilities.

Inspirations

For readers familiar with the topic, Tokei takes inspiration from the JavaScript Temporal proposal, while adapting only the concepts that make sense within PHP’s ecosystem.

During development, I initially wanted to name the package Clock. Unfortunately, that name has already been heavily overloaded within PHP and now refers to something entirely different. Reusing it would likely have created confusion and harmed adoption. So instead, I chose the Japanese word for “clock”:

Tokei (時計)

Last but not least

If you work with recurring schedules, opening hours, circular intervals, or simply want better local time abstractions in PHP, give Tokei a try.

And if you find a bug, have suggestions, want to support the work or simply find the package useful, feel free to open an issue or drop a note on its repository. The package is open-source under the MIT License, and contributions of all kinds are welcome and fully credited. Your support makes a real difference.

Happy coding!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.