bakame / tokei
Local time and duration primitives without timezone complexity
Fund package maintenance!
Requires
- php: ^8.3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.95
- phpstan/phpstan: ^2.1
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- phpstan/phpstan-strict-rules: ^2.0
- phpunit/phpunit: ^12.5 || ^13.1.8
- symfony/var-dumper: ^7.4 || ^8.0
This package is auto-updated.
Last update: 2026-05-14 12:03:40 UTC
README
Tokei (pronounced: [to̞ke̞ː] or [tokeː]) is a lightweight package to work with time and duration without timezone information attached to them in PHP.
The framework-agnostic package provides strict, immutable value objects designed for precise and reliable time handling. This offers a consistent and expressive way to work with temporal values in a safe and predictable manner.
Installation
composer require bakame/tokei
You need:
- PHP >= 8.3 but the latest stable version of PHP is recommended
Documentation
Time
The Bakame\Tokei\Time object is designed to be, cyclic (24h wrap-around) and precision-aware (microseconds supported)
Instantiation
You can create a Time instance:
- using its time components via the
Time::atmethod; - by parsing a time string using the
Time::parsemethod; - using
Time::atMicroOfDay,Time::atMilliOfDay,Time::atSecondOfDayorTime::atMinuteOfDay; The value will represent respectively the microseconds, milliseconds, seconds or minutes from midnight.
Time::at(int $hour = 0, int $minute = 0, int $second = 0, int $microsecond = 0): Time; Time::parse(string $value, string $separator = ':'): ?Time Time::atMicroOfDay(int $value): Time Time::atMilliOfDay(int $value): Time Time::atSecondOfDay(int $value): Time Time::atMinuteOfDay(int $value): Time
Here's some usage example.
use Bakame\Tokei\Time; $timeB = Time::parse("10:30:15.123456"); $timeA = Time::at(hour: 10, minute: 30, second: 15); $timeC = Time::atMicroOfDay(123_456_789); $timeC = Time::atMilliOfDay(123_456); $timeC = Time::atSecondOfDay(123); $timeC = Time::atMinuteOfDay(456);
Warning
On failure, with Time::parse, null is returned instead of an exception being thrown.
To ease instantiation, predefined instances can be obtained with the following methods:
Time::min(); // 00:00:00 Time::max(); // 23:59:59.999999 Time::noon(); // 12:00:00 Time::midnight(); // alias of min()
Accessors
Once instantiated you can access each time component using the following methods
$time = Time::parse("10:30:15.123456"); $time->hour; // returns 10 $time->minute; // returns 30 $time->second; // returns 15 $time->microsecond; // returns 123456
Formatting
Time::toMicroOfDay(); // returns 37_815_123_456 (the microseconds offset since midnight) Time::format( string $separator = ':', PaddingMode $padding = PaddingMode::Padded, SubSecondDisplay $subSecond = SubSecondDisplay::Auto, ): string
Example:
$time = Time::parse("10:30:15.123456"); $time->format(subSecond: SubSecondDisplay::Auto); // 10:30:15.123456 (default) $time->format(subSecond: SubSecondDisplay::Never); // 10:30:15 $time->format(subSecond: SubSecondDisplay::Always); // 10:30:15.123456 $time->toMicroOfDay(); // 37815123456
SubSecondDisplay::Autoonly show the microseconds if their value is less than0.SubSecondDisplay::Nevernever show the fraction.SubSecondDisplay::Alwaysalways show the fraction..
Modifying time
Because Time is an immutable VO, any change to its value will return a new instance
with the updated value and leave the original object unchanged. You can modify the time
with the following methods:
Time::addwill add a duration to change the time;Time::withwill adjust a specif time component;
Time::add(Duration $duration): Time Time::with(?int $hour = null, ?int $minute = null, ?int $second = null, ?int $microsecond = null): Time
Both methods act differently in regard to wrapping around 24hours automatically.
The Time::add supports wrapping whereas Time::with does not and instead
throws an InvalidTime exception instead
// adding 2 hours $time = Time::noon()->add(Duration::of(hours: 2, minutes: 15)); $time->format(); // returns "14:15:00" // adding 12 hours $time = Time::noon()->add(Duration::of(hours: 12, minutes: 15)); $time->format(); // returns "00:15:00" // setting the hour to $time = Time::noon()->with(hour: 2); $time->format(); // returns "02:15:00" Time::noon()->with(hours: 25); //throws a Bakame\Tokei\InvalidTime exception
Comparing times
It is possible to compare two Time instances using the Time::compareTo method.
Time::compareTo(Time $other): int;
the method returns:
-1if earlier0if equal1if later
Convenient methods derived from Time::compareTo are also available to ease usage:
$time = Time::at(hours: 10); $other = Time::noon(); $time->isBefore($other); // returns true $time->isAfter($other); // returns false $time->isBeforeOrEqual($other); // returns true $time->isAfterOrEqual($other); // returns false $time->equals($other); // returns false
Differences
The class provides two methods to account for differences between two Time instances:
Time::diff(Time $other): Duration; Time::distance(Time $other): Duration;
- the
Time::diffreturns the signed difference between both instances; - the
Time::distancereturns the forward cyclic difference (24 wrap) between both instances;
Here's an example usage to highlight the distinction in returned values between both differences methods:
$a = Time::at(hours: 23); // 23:00 $b = Time::at(hours: 1); // 01:00 $a->diff($b)->toIso8601(); // returns "-PT22H" $a->distance($b)->toIso8601(); // returns "PT2H"
Interacting with PHP's native Date API
Time::extractFrom(DateTimeInterface $datetime): Time Time::applyTo(DateTimeInterface $datetime): DateTimeImmutable;
In one hand, it is possible to extract the time part of any DateTimeInterface
implementing class using the extractFrom method. On the other hand, you
can apply the time to an DateTimeInterface object using the applyTo method
Note
If the DateTimeInterface instance submitted extends the
DateTimeImmutable class then the return type will be of that same type
otherwise PHP's DateTimeImmutable is returned.
use Bakame\Tokei\Time; use Carbon\Carbon; use Carbon\CarbonImmutable; $time = Time::extractFrom(new DateTime('2025-12-27 23:00', new DateTimeZone('Africa/Nairobi'))); // 23:00 $newDate = $time->applyTo(CarbonImmutable::parse('2025-02-23')); $newDate->toDateTimeString(); // returns '2025-02-23 23:00' $newDate::class; // returns CarbonImmutable $altDate = $time->applyTo(Carbon::parse('2025-02-23')); $altDate->toDateTimeString(); // returns '2025-02-23 23:00' $altDate::class; // returns DateTimeImmutable
Duration
The Bakame\Tokei\Duration Value Object provides lightweight utilities for working with durations
Instantiation
The Duration class can be instantiated either by providing:
- each duration parts using the complementary
Duration::ofmethod. - a ISO8601 duration expression.
use Bakame\Tokei\Duration; $durationA = Duration::of(hours: 2, seconds:59); $durationB = Duration::fromIso8601('P2WT3H'); //2 weeks and 3 hours
Important
Duration::fromIso8601 only parse ISO8601 notations with deterministic part (ie: years and months are excluded)
$duration = Duration::fromIso8601('-P2YT3H'); // throws a Bakame\Tokei\InvalidDuration exception // because of the presence of the Y component
Accessors
Once instantiated you can access the duration properties directly.
The object exposes a inverted property which indicates if the original value was negative or not.
And provides a toMicro method to get the microseconds based representation of the duration.
$durationB->hours; // returns 1 $durationB->minutes; // returns 1 $durationB->seconds; // returns 1 $durationB->microseconds; // returns 234_000 $durationB->inverted; // returns false $durationB->isEmpty() // returns true when the duration is zero, false otherhwise
Formatting
Duration::toClockFormat(): string
Formats the instance value into a human-readable string. The following format is used:
[-]H:mm:ss[.microseconds]
- microseconds are optional (only shown if non-zero)
- negative values are prefixed with
-
Duration::toIso8601(): string
Formats the instance value into a ISO8601 compatible string. The returned string
may not be compatible with PHP's DateInterval constructor but is valid
withing the ISO8601 specification.
$duration = Duration::of(hours: 25, seconds: 5); $duration->toIso8601(); // returns 'P1D1H5S'
Important
- Only deterministic duration interval are used
Y,Mfor month are not used - to have a predictive representation
Wis not used;7Dmultiple are used instead.
$duration = Duration::fromIso8610('-P2W'); $duration->toIso8601(); // returns '-P14D'
The Duration class also allows conversion in microseconds and in DateInterval instances.
Duration::toDateInterval(): DateInterval
Duration::toMicro(): int
The method Duration::toDateInterval converts the instance into a PHP DateInterval
instance while preserving its sign (inverted intervals are supported).
$duration = Duration::of(microseconds: 3_661_234_000); $duration->toDateInterval(); // returns DateInterval $durationB->toMicro(); // returns the full duration in microseconds format
Modifying duration
Duration::abs(): Duration Duration::negate(): Duration Duration::truncateTo(Precision $precision): Duration Duration::add(Duration ...$duration): Duration Duration::increment(int $hours = 0, int $minutes = 0, int $seconds = 0, int $microseconds = 0): Duration
You can:
- make it unsigned using the
Duration::absmethod - invert its signing using the
Duration::negatemethod - update the duration using fixed duration parts
Duration::incrementmethod - truncate its value to one of the unit declare on the
Bakame\Tokei\Precisionenum - sum multiple
Durationinstance using theDuration::summethod
$microseconds = 3_661_500_000; $a = Duration::of(microseconds: $microseconds); $b = $a->truncateTo(Precision::Minutes); $c = $b->negate(); $d = $c->increment(minutes: -10); echo $a->toClockFormat(); // returns "1:01:01.500000" echo $b->toClockFormat(); // returns "1:01:00" echo $c->toClockFormat(); // returns "-1:01:00" echo $c->abs()->toClockFormat(); // returns "1:01:00" echo $a->sum($b, $c, $d)->toClockFormat(); // returns "-0:09:58.500000"
Comparing duration
It is possible to compare duration using common methods terminology
Duration::compareTo(Duration $other): int;
Returns:
-1if lesser0if equal1if greater
Convenient methods based on Duration::compareTo are also available:
$duration = Duration::of(microseconds: 3_661_500_000); $other = Duration::fromIso8601('PT1H1S'); $duration->isLessThan($other); // returns false $duration->isLessThanOrEqual($other); // returns false $duration->equals($other); // returns false $duration->isGreaterThan($other); // returns true $duration->isGreaterThanOrEqual($other); // returns true
Testing
The library has:
- a PHPUnit test suite.
- a coding style compliance test suite using PHP CS Fixer.
- a code analysis compliance test suite using PHPStan.
To run the tests, run the following command from the project folder.
composer test
Contributing
Contributions are welcome and will be fully credited. Please see CONTRIBUTING and CONDUCT for details.
Security
If you discover any security related issues, please email nyamsprod@gmail.com instead of using the issue tracker.
Changelog
Please see CHANGELOG for more information on what has changed recently.