PHPackages                             thesis/time - 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. thesis/time

Abandoned → [thesis/time-span](/?search=thesis%2Ftime-span)Library[Utility &amp; Helpers](/categories/utility)

thesis/time
===========

Thesis TimeSpan

0.1.0(1y ago)12502[1 PRs](https://github.com/thesis-php/time/pulls)MITPHPPHP ^8.3CI passing

Since May 6Pushed 1mo ago2 watchersCompare

[ Source](https://github.com/thesis-php/time)[ Packagist](https://packagist.org/packages/thesis/time)[ Fund](https://www.tinkoff.ru/cf/5MqZQas2dk7)[ RSS](/packages/thesis-time/feed)WikiDiscussions 0.2.x Synced 1mo ago

READMEChangelog (5)Dependencies (3)Versions (3)Used By (0)

Thesis TimeSpan
===============

[](#thesis-timespan)

[![PHP Version Requirement](https://camo.githubusercontent.com/df17c9b6e6b68ad23f3c79dbcda4116458db1936612a1a189c9631916a7449a8/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f646570656e64656e63792d762f7468657369732f74696d652d7370616e2f706870)](https://packagist.org/packages/thesis/time-span)[![GitHub Release](https://camo.githubusercontent.com/99520acbde8d97dcce5e8c781e1c291c1b74a98096335826c9e4978f751b129a/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f762f72656c656173652f7468657369732d7068702f74696d652d7370616e)](https://github.com/thesis-php/time-span/releases)[![Code Coverage](https://camo.githubusercontent.com/84acdd0c658f276e3de2666e084e451cf5238c3876dabb9f2158c732a105818c/68747470733a2f2f636f6465636f762e696f2f67682f7468657369732d7068702f74696d652d7370616e2f6272616e63682f302e322e782f67726170682f62616467652e737667)](https://codecov.io/gh/thesis-php/time-span/tree/0.2.x)[![Mutation testing badge](https://camo.githubusercontent.com/5831b3d53bc691f8a6ca4c62995d9a4a3aa92fbb9ab6873d6a5a783eeeb4aa5b/68747470733a2f2f696d672e736869656c64732e696f2f656e64706f696e743f7374796c653d666c61742675726c3d687474707325334125324625324662616467652d6170692e737472796b65722d6d757461746f722e696f2532466769746875622e636f6d2532467468657369732d70687025324674696d652d7370616e253246302e322e78)](https://dashboard.stryker-mutator.io/reports/github.com/thesis-php/time-span/0.2.x)

An immutable, nanosecond-precise time duration type for PHP 8.3+.

```
$span = TimeSpan::from(hours: 1, minutes: 30);

echo $span->toMinutes(); // 90
echo $span->format();    // 01:30:00
```

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

[](#installation)

```
composer require thesis/time-span
```

Why not DateInterval?
---------------------

[](#why-not-dateinterval)

PHP's built-in `DateInterval` covers calendar durations — periods like "3 months" or "1 year" that only make sense relative to a specific date. `TimeSpan` covers the complementary case: a **fixed amount of elapsed time** that exists independently of any calendar.

`DateInterval``TimeSpan`Representscalendar periodfixed durationSupports months/yearsyesnoImmutablenoyesArithmeticno`add` `sub` `mul` `div`Comparisonno`compareTo` `isLessThan` …Negative valuesawkward `invert` flagsigned intPrecisionmicrosecondsnanoseconds`DateInterval` is the right tool when you need "30 days from now" or "next month". `TimeSpan` is the right tool when you need "wait 30 seconds" or "this request took 42 ms".

Use cases
---------

[](#use-cases)

```
// HTTP / DB timeouts
$client = new HttpClient(timeout: TimeSpan::fromSeconds(30));

// Cache TTL
$cache->set($key, $value, ttl: TimeSpan::fromMinutes(5));

// Rate limiting — window duration
$limiter = new RateLimiter(window: TimeSpan::fromHours(1), limit: 1000);

// Retry with exponential backoff
$delay = TimeSpan::fromMilliseconds(100);
foreach (range(1, 5) as $attempt) {
    try {
        return $this->call();
    } catch (TransientException) {
        sleep($delay->toSeconds());
        $delay = $delay->mul(2);
    }
}

// Benchmarking
$start = TimeSpan::hrtime();
$result = $this->heavyComputation();
$elapsed = TimeSpan::hrtime()->sub($start);
$this->logger->info("Computed in {$elapsed->toMilliseconds(precision: 2)} ms");

// SLA / deadline check
$elapsed = TimeSpan::between($requestTime, new \DateTimeImmutable());
if ($elapsed->isGreaterThan(TimeSpan::fromSeconds(5))) {
    $this->metrics->increment('sla.violated');
}
```

Creating a TimeSpan
-------------------

[](#creating-a-timespan)

### From multiple units

[](#from-multiple-units)

```
use Thesis\TimeSpan;

$span = TimeSpan::from(days: 1, hours: 2, minutes: 30, seconds: 15);
$span = TimeSpan::from(milliseconds: 250);
$span = TimeSpan::from(seconds: 90); // same as 1 minute 30 seconds
```

### From a single unit

[](#from-a-single-unit)

```
$span = TimeSpan::fromNanoseconds(1_500_000);
$span = TimeSpan::fromMicroseconds(1_500);
$span = TimeSpan::fromMilliseconds(1.5);
$span = TimeSpan::fromSeconds(90);
$span = TimeSpan::fromMinutes(1.5);
$span = TimeSpan::fromHours(0.25);
$span = TimeSpan::fromDays(7);
```

All constructors accept `int|float`. Floats are rounded to the nearest nanosecond.

### From a DateInterval

[](#from-a-dateinterval)

```
TimeSpan::fromInterval(new \DateInterval('PT90S')); // 90 seconds
TimeSpan::fromInterval(new \DateInterval('P7D'));   // 7 days
```

> **Note:** Intervals with years or months cannot be converted to a fixed duration and will throw an `InvalidArgumentException`. Intervals produced by `DateTimeInterface::diff()` are also rejected due to DST ambiguity — use `TimeSpan::between()` instead.

### Between two datetimes

[](#between-two-datetimes)

```
$start = new \DateTimeImmutable('2024-01-01 10:00:00');
$end   = new \DateTimeImmutable('2024-01-01 11:30:00');

$span = TimeSpan::between($start, $end); // 1 hour 30 minutes
```

The result is signed: `between($a, $b)` returns a negative span if `$b` is in the past relative to `$a`.

### From the high-resolution timer

[](#from-the-high-resolution-timer)

```
$start = TimeSpan::hrtime();
doSomething();
$elapsed = TimeSpan::hrtime()->sub($start);

echo $elapsed->toMilliseconds(precision: 3); // e.g. 42.731
```

### Directly from nanoseconds

[](#directly-from-nanoseconds)

```
new TimeSpan(5_000_000_000); // 5 seconds
new TimeSpan();              // zero span
```

Converting to other units
-------------------------

[](#converting-to-other-units)

Every `to*()` method returns `int` by default. Pass a `$precision` argument to get a `float` with that many decimal places.

```
$span = TimeSpan::from(
    days: 1,
    hours: 1,
    minutes: 30,
    seconds: 45,
    milliseconds: 500,
    microseconds: 89,
    nanoseconds: 23,
);

$span->toNanoseconds();         // 91_845_500_089_023
$span->toMicroseconds();        // 91_845_500_089
$span->toMilliseconds();        // 91_845_500
$span->toSeconds();             // 91846
$span->toSeconds(precision: 1); // 91845.5
$span->toMinutes();             // 1531
$span->toMinutes(precision: 4); // 1530.7583
$span->toHours();               // 26
$span->toDays();                // 1
```

The optional second argument controls rounding mode (defaults to `PHP_ROUND_HALF_UP`):

```
$span->toSeconds(precision: 2, roundingMode: PHP_ROUND_HALF_DOWN);
$span->toSeconds(precision: 3, roundingMode: PHP_ROUND_HALF_EVEN);
```

Arithmetic
----------

[](#arithmetic)

All arithmetic methods return a new `TimeSpan` instance and throw `\OverflowException`if the result exceeds the `int` range (~292 years in nanoseconds on 64-bit platform).

```
$m30 = TimeSpan::fromMinutes(30);
$m15 = TimeSpan::fromMinutes(15);

$m30->add($m15);  // 45 minutes
$m30->sub($m15);  // 15 minutes
$m30->mul(3);   // 90 minutes
$m30->mul(0.5); // 15 minutes
$m30->div(2);   // 15 minutes
$m30->div(3);   // 10 minutes
```

`div()` throws `\DivisionByZeroError` when the factor is `0`.

Comparison
----------

[](#comparison)

```
$s10 = TimeSpan::fromSeconds(10);
$s20 = TimeSpan::fromSeconds(20);

$s10->compareTo($s20);              // -1 (less than)
$s20->compareTo($s10);              // 1  (greater than)
$s10->compareTo($s10);              // 0  (equal)

$s10->isEqualTo($s20);              // false
$s10->isLessThan($s20);             // true
$s10->isLessThanOrEqualTo($s20);    // true
$s10->isGreaterThan($s20);          // false
$s10->isGreaterThanOrEqualTo($s20); // false
```

Sign checks
-----------

[](#sign-checks)

```
$s_5 = TimeSpan::fromSeconds(-5);

$s_5->isNegative();       // true
$s_5->isNegativeOrZero(); // true
$s_5->isPositive();       // false
$s_5->isPositiveOrZero(); // false
$s_5->isZero();           // false

$s_5->abs();              // TimeSpan(5 seconds)
$s_5->negated();          // TimeSpan(5 seconds)

TimeSpan::fromSeconds(5)->negated(); // TimeSpan(-5 seconds)
```

Formatting
----------

[](#formatting)

### format()

[](#format)

`format()` renders a span as a human-readable string. The default pattern is `%-%h:%i:%s`.

PlaceholderUnitWidth`%-`sign0–1`%d`days&gt;=1 (unpadded)`%h`hours&gt;=2 (zero-padded)`%i`minutes&gt;=2 (zero-padded)`%s`seconds&gt;=2 (zero-padded)`%ms`milliseconds&gt;=3 (zero-padded)`%us`microseconds&gt;=3 (zero-padded)`%ns`nanoseconds&gt;=3 (zero-padded)The **largest unit present** in the format receives the total cumulative value; each smaller unit shows only the remainder after the larger ones are subtracted:

```
$span = TimeSpan::from(
    days: 1,
    hours: 2,
    minutes: 3,
    seconds: 4,
    milliseconds: 500,
    microseconds: 600,
    nanoseconds: 700,
);

$span->format('%-%d %h:%i:%s.%ms_%us_%ns'); // "1 02:03:04.500_600_700"
$span->format('%-%d %h:%i:%s');             // "1 02:03:04"
$span->format('%-%d');                      // "1"
$span->format();                            // "26:03:04"
$span->format('%-%h:%i:%s.%ms');            // "26:03:04.500"
$span->format('%-%h:%i:%s.%ms_%us_%ns');    // "26:03:04.500_600_700"
$span->format('%-%i:%s.%ms_%us_%ns');       // "1563:04.500_600_700"
$span->format('%-%ns');                     // "93784500600700"
$span->format('%-%h h %h:%i:%s');           // "26 h 26:03:04" (repeated placeholders are fine)
$span->format('fixed 5 seconds');           // "fixed 5 seconds" (no placeholders — literal string)
```

The sign is only included when `%-` is explicitly present in the format:

```
$span = TimeSpan::fromSeconds(-90);

$span->format('%-%i:%s'); // "-01:30"
$span->format('%i:%s');   // "01:30" — no sign without %-
```

### \_\_toString()

[](#__tostring)

`__toString()` is equivalent to `format()` with the default pattern:

```
$span = TimeSpan::from(hours: 1, minutes: 30);

echo $span; // "01:30:00"
```

License
-------

[](#license)

MIT

###  Health Score

38

—

LowBetter than 85% of packages

Maintenance72

Regular maintenance activity

Popularity15

Limited adoption so far

Community14

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

 Bus Factor1

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

###  Release Activity

Cadence

Every ~2 days

Total

3

Last Release

374d ago

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/2552865?v=4)[Valentin Udaltsov](/maintainers/vudaltsov)[@vudaltsov](https://github.com/vudaltsov)

---

Top Contributors

[![vudaltsov](https://avatars.githubusercontent.com/u/2552865?v=4)](https://github.com/vudaltsov "vudaltsov (39 commits)")[![pageantry](https://avatars.githubusercontent.com/u/23061799?v=4)](https://github.com/pageantry "pageantry (6 commits)")[![Evgymart](https://avatars.githubusercontent.com/u/111608901?v=4)](https://github.com/Evgymart "Evgymart (3 commits)")[![kafkiansky](https://avatars.githubusercontent.com/u/37590388?v=4)](https://github.com/kafkiansky "kafkiansky (1 commits)")

###  Code Quality

TestsPHPUnit

### Embed Badge

![Health badge](/badges/thesis-time/health.svg)

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

###  Alternatives

[lcobucci/clock

Yet another clock abstraction

790190.9M114](/packages/lcobucci-clock)[symfony/clock

Decouples applications from the system clock

430168.9M205](/packages/symfony-clock)[eventsauce/eventsauce

A pragmatic event sourcing library for PHP with a focus on developer experience.

8632.1M47](/packages/eventsauce-eventsauce)[ecotone/ecotone

Supporting you in building DDD, CQRS, Event Sourcing applications with ease.

558549.8k17](/packages/ecotone-ecotone)[mcp/sdk

Model Context Protocol SDK for Client and Server applications in PHP

1.4k423.9k30](/packages/mcp-sdk)[flow-php/etl

PHP ETL - Extract Transform Load - Abstraction

374468.4k51](/packages/flow-php-etl)

PHPackages © 2026

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