Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Time Continuum Laravel Package

innmind/time-continuum

InnMind Time Continuum is a PHP library for modeling time as a continuum of instants and intervals. It offers immutable temporal objects, precise duration arithmetic, comparisons, and conversions for scheduling and time-based domain logic.

View on GitHub
Deep Wiki
Context7

Getting Started

Install

composer require innmind/time-continuum

Check composer.json for the PHP constraint before committing — Innmind libraries track recent PHP versions closely, and the constraint is the most common reason the install fails on an older runtime.

Where to look first

The entire library is five interfaces. Read these five files before anything else, and you will understand 90% of the API surface:

  • src/Clock.php — the entry point; produces PointInTime values
  • src/PointInTime.php — an instant in time, tied to a timezone
  • src/Period.php — a relative amount (2 hours, 3 days), not an absolute duration
  • src/Timezone.php — named timezone value objects
  • src/Format.php — a strategy object that renders a PointInTime to string

Everything else under src/ is an implementation of one of those five. If you are looking for a helper, it is almost certainly a new Period or Format implementation rather than a method.

First use case: "when does this job expire?"

use Innmind\TimeContinuum\Clock\RealClock;
use Innmind\TimeContinuum\Timezone\Earth\UTC;
use Innmind\TimeContinuum\Format\ISO8601;
use Innmind\TimeContinuum\Period\Earth\Hours;

$clock = new RealClock(new UTC);
$now   = $clock->now();

$expiresAt = $now->goForward(new Hours(2));

echo $now->format(new ISO8601);
echo $expiresAt->format(new ISO8601);

// later, measuring a duration
$elapsed = $now->elapsedSince($someEarlierPointInTime);

Note the shape of the API: goForward returns a new point, nothing is mutated. That single fact drives most of the patterns below.

Implementation Patterns

Inject the Clock, never the PointInTime

The clock is your seam between "what time is it" and "what does the business rule say". Inject Clock into services; call now() once at the start of a use case and pass the resulting PointInTime down. This makes the whole graph testable without mocking static time() calls, and it is the one thing that makes this library worth its weight over raw \DateTimeImmutable.

final class ExpireSession
{
    public function __construct(
        private Clock $clock,
        private Sessions $sessions,
    ) {}

    public function __invoke(SessionId $id): void
    {
        $this->sessions->expire(
            $id,
            $this->clock->now()->goForward(new Hours(2)),
        );
    }
}

Keep domain code typed, format at the edges

Never let an ISO string travel through your domain. PointInTime in, PointInTime out. Convert in two places only: the persistence adapter and the HTTP/console boundary. This is the same discipline as using value objects for money, and it is where the library genuinely pays off — you cannot accidentally pass a datetime string where a duration belongs, because the types are distinct.

// Persistence edge
$row['expires_at'] = $entity->expiresAt->format(new ISO8601);

// Rehydration via the clock, which parses using a Format
$expiresAt = $clock->at(new ISO8601, $row['expires_at']);

Store UTC, render local

Construct your RealClock with a fixed UTC in infrastructure code, and convert to a display timezone only in the presentation layer. Because each PointInTime carries its own timezone, conversion is a local operation on the value — no hidden global state, no date_default_timezone_set() contamination from a third-party library.

Express business rules as Period values

Anything a product owner says as a duration — "grace period of 15 minutes", "retry after 30 seconds", "we hold the reservation for 2 days" — becomes a named Period implementation or a configured constant. This turns magic numbers in your code into readable, mockable, greppable values, and lets tests substitute a tiny period instead of sleeping.

Compare with equals / before / after, not string comparison

Ordering comparisons operate on the instant, so two points with different timezone labels but the same moment compare equal. That is almost always what you want for authorization and expiry checks, and it silently fixes an entire class of bug that string-comparison code carries.

Testing: swap the clock

The package ships alternative Clock implementations under src/Clock/ intended exactly for this — a frozen/fixed clock you can advance by a Period. Bind Clock in your container, then in tests bind a frozen clock set to a known instant and advance it explicitly:

$clock = new FrozenClock(/* starting point */);
$clock = $clock->goForward(new Hours(1)); // time passes, deterministically

Grep src/Clock/ for the exact class names before writing tests — this is the least-churned area of the library but also the least documented.

Interop with the rest of your app

If you are on Symfony or Laravel, write a two-method adapter implementing Clock and delegating to the framework clock. Do not mix Carbon and PointInTime in the same layer; the conversion boundary should be a single class that formats to string and re-parses, so you never leak a \DateTimeInterface into domain code.

Gotchas and Tips

It is archived and has zero dependents. This is the headline risk. The library is a personal component of the Innmind ecosystem, not a community project. It is MIT licensed, so you can vendor it or fork it, but treat any version you adopt as the version you maintain forever. For a greenfield project with no Innmind dependency, the honest recommendation is Symfony Clock or Carbon; adopt this one if you specifically want its type model or are already inside Innmind.

Every operation returns a new object. The number one bug is writing $point->goForward(new Hours(1)); and discarding the result. There is no __toString, no JsonSerializable, and no fluent side effects to remind you — PHP will not warn you. Assign the result.

Period is not a Duration. Adding "1 month" to January 31st is not adding a fixed number of seconds; the library models periods as calendar-relative on purpose. If you need elapsed time between two points, that is elapsedSince, returning an ElapsedPeriod — a different type. Do not try to add two Periods and expect a Duration; check the interface before assuming arithmetic is available.

DST is not handled for you. goForward(new Day(1)) across a DST boundary is a calendar-day operation, not 24 hours. Any scheduling, billing, or SLA code that assumes "one day == 86400 seconds" will be subtly wrong twice a year. Test explicitly with a Europe/Paris or America/New_York point in late March and late October.

Timezone must be explicit. new RealClock(...) wants a timezone; a server whose date.timezone differs from your expectation is a classic production-only failure. Pin UTC in infrastructure code and convert for display, so the answer does not depend on php.ini.

Debugging is noisy. var_dump of a PointInTime dumps a graph of nested value objects. Always debug through ->format(new ISO8601). Do the same in log context, or you will flood your logs.

Create Format and Period objects once. They are stateless value objects; instantiating new ISO8601 inside a hot loop is pure allocation overhead on top of an already allocation-heavy design. In tight loops (bulk imports, report generation over millions of rows) this library will be measurably slower than \DateTimeImmutable — benchmark before adopting it for high-volume batch work.

Extension points are the intended escape hatch. Clock, Format, Period, and Timezone are all interfaces. If the built-in formats are missing something (a custom log format, a business-specific "half-day" period), implement Format or Period in your own namespace rather than converting to \DateTime and back. That keeps the type model intact and makes the behaviour unit-testable.

Pin the version and read the changelog before bumping. Innmind packages make breaking changes across majors and the API renames classes rather than deprecating them. Since the project is archived, there will be no deprecation path — a composer update is a code change. Commit composer.lock, and if you patch anything, record it in a fork or use cweagans/composer-patches so the fix survives a teammate's composer install.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
frankyso/ipaymu
3mdikram/worldmoneywords
snowsoft/config
itmosoft/laravel-subscriptions
larva/laravel-ai-qwen
diephp/sequences
users-au/socialite-provider
twidpay/twid-logger
mrnewport/laravel-plaid
mrnewport/laravel-repo
mrnewport/laravel-docsign
mrnewport/laravel-stow
mrnewport/laravel-flow
mrnewport/laravel-priceable
mrnewport/laravel-nestedterms
fredmeng/laravel-apidoc-generator
itstructure/laravel-multilingual-tools
pdustdar/elasticsearch
markupai/php-sdk
rlacerda83/lumem-microservice-shipment