PHPackages                             sanmai/duoclock - 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. [Testing &amp; Quality](/categories/testing)
4. /
5. sanmai/duoclock

ActiveLibrary[Testing &amp; Quality](/categories/testing)

sanmai/duoclock
===============

PHP time mocking for tests - PSR-20 clock with mockable sleep(), time(), and TimeSpy for PHPUnit testing

0.1.3(4mo ago)32.7M—0.2%1[1 PRs](https://github.com/sanmai/DuoClock/pulls)3Apache-2.0PHPPHP &gt;=8.2CI passing

Since Jul 12Pushed 2mo agoCompare

[ Source](https://github.com/sanmai/DuoClock)[ Packagist](https://packagist.org/packages/sanmai/duoclock)[ GitHub Sponsors](https://github.com/sanmai)[ RSS](/packages/sanmai-duoclock/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (4)Dependencies (9)Versions (8)Used By (3)

[![License](https://camo.githubusercontent.com/1d952c48cb7a1e494b822521514b72c2f96164876b212e18cb9c43808eb83913/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f6c6963656e73652f73616e6d61692f64756f636c6f636b2e737667)](LICENSE)[![PHP Version](https://camo.githubusercontent.com/268160a617f31f6c489019f2f8765b7f7d2817c87512f0d27592d60573a5d31b/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f7068702d762f73616e6d61692f64756f636c6f636b2e737667)](https://packagist.org/packages/sanmai/duoclock)

DuoClock
========

[](#duoclock)

DuoClock is a PSR-20-compatible clock abstraction. It provides dual time access (`DateTimeImmutable`, `int`, `float`) and mockable sleep functions (`sleep`, `usleep`, and more) for testing time-sensitive code.

Features
--------

[](#features)

- Implements `Psr\Clock\ClockInterface`.
- Provides:
    - `now(): DateTimeImmutable`
    - `time(): int`
    - `microtime(): float`
- Offers mockable `sleep()`, `usleep()`, `nanosleep()`, and `time_nanosleep()` for test environments.
- Provides `getStartTick()` and `getEndTick()` for measuring elapsed time.
- Mockable time methods: `now()`, `time()`, and `microtime()`.
- Includes a deterministic `TimeSpy` for testing.
- Is minimal, with a lightweight design (depends only on `psr/clock`).
- Has all classes non-final to allow easy mocking and testing.

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

[](#installation)

```
composer require sanmai/duoclock
```

Interfaces
----------

[](#interfaces)

```
namespace DuoClock;

interface DuoClockInterface
{
    public function time(): int;
    public function microtime(): float;
}

interface SleeperInterface
{
    public function sleep(int $seconds): int;
    public function usleep(int $microseconds): void;
}

interface NanoSleeperInterface
{
    public function time_nanosleep(int $seconds, int $nanoseconds): array|bool;
    public function nanosleep(int $nanoseconds): array|bool;
}

interface TickerInterface
{
    public function getStartTick(): float;
    public function getEndTick(): float;
}
```

Usage
-----

[](#usage)

Real Clock:

```
$clock = new DuoClock\DuoClock();

$clock->now();        // DateTimeImmutable
$clock->time();       // int
$clock->microtime();  // float

$clock->sleep(1);     // real sleep
$clock->usleep(1000); // real micro-sleep

$clock->nanosleep(1_500_000_000); // sleep 1.5 seconds
$clock->time_nanosleep(1, 500_000_000); // same as above
```

### Measuring Elapsed Time

[](#measuring-elapsed-time)

```
$clock = new DuoClock\DuoClock();

$timer = $clock->getStartTick();
// ...work...
$timer += $clock->getEndTick();
// $timer now contains elapsed seconds as float
```

TimeSpy, as a testing-time dependency:

```
$clock = new DuoClock\TimeSpy(1752321600); // Corresponds to '2025-07-12T12:00:00Z'

$clock->time();       // 1752321600

$clock->sleep(10);    // advances virtual clock by 10 seconds
$clock->usleep(5000); // advances virtual clock by 0.005 seconds

$clock->time();       // 1752321610
$clock->microtime();  // 1752321610.005
```

### Mocking and Spies

[](#mocking-and-spies)

The recommended approach is to always use TimeSpy for testing (`$clock = new TimeSpy();`) because calls to `$clock->sleep()` and `$clock->usleep()` do not delay execution even if you do not specifically mock them.

```
$mock = $this->createMock(DuoClock\TimeSpy::class);

$mock->expects($this->exactly(1))
    ->method('time')
    ->willReturn(self::TIME_BEFORE_LAUNCH);

$example = new ExampleUsingTime($mock);
$this->assertFalse($example->launch());
```

```
$mock = $this->createMock(DuoClock\TimeSpy::class);

$mock->expects($this->exactly(1))
    ->method('usleep')
    ->with(self::POLL_TIME);

$example = new ExampleUsingSleep($mock);
$example->waitDuringPolling();
```

Why DuoClock Exists
-------------------

[](#why-duoclock-exists)

PHP now has [PSR-20](https://www.php-fig.org/psr/psr-20/), a standard interface for representing the current time using immutable objects. This interface works well for many applications, but assumes that all time-based code should consume `DateTimeImmutable`. In practice, testing time-based code often requires mocking and emulating `sleep()` and `usleep()`, especially for retry logic, timeout simulations, or rate limiters. You do not want to wait for literal seconds for your `sleep()` tests to pass! PSR-20 offers no solution for this, which is where DuoClock steps in.

Development
-----------

[](#development)

```
# Run all checks (tests, static analysis, mutation testing)
make -j -k
```

License
-------

[](#license)

Licensed under the Apache License, Version 2.0. See [LICENSE](LICENSE) for details.

###  Health Score

50

—

FairBetter than 96% of packages

Maintenance80

Actively maintained with recent releases

Popularity47

Moderate usage in the ecosystem

Community16

Small or concentrated contributor base

Maturity44

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 60% 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 ~55 days

Total

4

Last Release

144d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/edcb8dde95c71b1c97c3c91e57d3548795fa2014c657744fb878e2be3b5949fc?d=identicon)[sanmai](/maintainers/sanmai)

---

Top Contributors

[![sanmai](https://avatars.githubusercontent.com/u/139488?v=4)](https://github.com/sanmai "sanmai (6 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (3 commits)")[![Chris53897](https://avatars.githubusercontent.com/u/7104259?v=4)](https://github.com/Chris53897 "Chris53897 (1 commits)")

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StylePHP CS Fixer

Type Coverage Yes

### Embed Badge

![Health badge](/badges/sanmai-duoclock/health.svg)

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

###  Alternatives

[phpspec/prophecy

Highly opinionated mocking framework for PHP 5.3+

8.5k551.7M682](/packages/phpspec-prophecy)[brianium/paratest

Parallel testing for PHP

2.5k118.8M754](/packages/brianium-paratest)[beberlei/assert

Thin assertion library for input validation in business models.

2.4k96.9M570](/packages/beberlei-assert)[orchestra/testbench

Laravel Testing Helper for Packages Development

2.2k39.1M32.1k](/packages/orchestra-testbench)[phpstan/phpstan-symfony

Symfony Framework extensions and rules for PHPStan

78768.9M1.5k](/packages/phpstan-phpstan-symfony)[phpstan/phpstan-doctrine

Doctrine extensions for PHPStan

66766.6M1.1k](/packages/phpstan-phpstan-doctrine)

PHPackages © 2026

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