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

Timecop Bundle Laravel Package

kolemp/timecop-bundle

Timecop Bundle freezes, travels, and scales time in your tests so date- and time-dependent code behaves predictably. Control the clock globally, write deterministic assertions for Carbon and DateTime, and eliminate flaky time-based failures.

View on GitHub
Deep Wiki
Context7

Getting Started

What this actually is: kolemp/timecop-bundle is a Symfony bundle (by Philipp Koltermann) that wires a Timecop-style time-freezing helper into the Symfony container/kernel. It is not a Laravel package, has 4 stars, 0 dependents, and its last release was September 2018. Treat it as a code reference or a small dev-only utility, not as a dependency you build a test suite on.

Minimal steps if you insist on installing it:

composer require --dev kolemp/timecop-bundle
  • It will pull symfony/* constraints from the 3.x/4.x era. On Laravel 9+ this frequently conflicts with the framework's own Symfony components. If composer refuses, that is the package telling you it's not viable — don't --ignore-platform-reqs your way past it.
  • There is no Flex recipe and no Laravel auto-discovery. You'd need to write a shim service provider yourself (see below).

Where to look first (in vendor/kolemp/timecop-bundle/):

  1. src/ — the bundle class and the Timecop service/facade. This is a small package; read all of it in ~15 minutes.
  2. DependencyInjection/Configuration.php — tells you whether there's a kolemp_timecop: config tree and what its defaults are.
  3. Resources/config/services.yml — tells you the service id you'll fetch in tests (likely something like kolemp.timecop). Don't guess this; it varies and it's the first thing people get wrong.
  4. Check how it freezes time — does it hook time()/date()/microtime() via symfony/phpunit-bridge ClockMock, or does it manipulate DateTime/Carbon? This single question determines whether the bundle will work at all for your codebase.

First use case (Symfony): freeze the clock inside a functional test so created_at/expiry assertions are deterministic.

protected function setUp(): void
{
    parent::setUp();
    $this->clock = self::$container->get('kolemp.timecop'); // verify the id in services.yml
    $this->clock->freeze(new \DateTimeImmutable('2024-01-01 09:00:00'));
}

protected function tearDown(): void
{
    $this->clock->return(); // whatever the un-freeze method is named
    parent::tearDown();
}

First use case (Laravel): you almost certainly want the native equivalent instead.

$this->travelTo(now()->setDate(2024, 1, 1)->setTime(9, 0));
$this->travel(5)->minutes();
$this->freezeTime();
// or, outside tests:
Carbon::setTestNow('2024-01-01 09:00:00');

If you genuinely want this bundle's API inside Laravel, wrap it rather than registering the bundle:

// app/Providers/TimecopServiceProvider.php (dev only)
public function register(): void
{
    if (! $this->app->environment('testing')) {
        return;
    }

    $this->app->singleton(\Kolemp\Timecop\Timecop::class, fn () => new \Kolemp\Timecop\Timecop());
}

Then inject the class. You'll be reimplementing ~30 lines of the bundle's wiring; that's normal for Symfony→Laravel ports.

Implementation Patterns

1. Always pair freeze with a finally. A frozen clock that leaks into the next test produces failures that look like random data corruption.

protected function at(string $moment, callable $assertions): void
{
    $clock = $this->app->make(Timecop::class);
    $clock->freeze(new DateTimeImmutable($moment));
    try {
        $assertions();
    } finally {
        $clock->return();
    }
}

2. Freeze after migrations, not before. If you combine this with RefreshDatabase/DatabaseMigrations, freezing during setUp() means every migrated row gets an identical created_at, which breaks unique indexes on timestamp columns and makes ordering assertions meaningless. Freeze inside the test body or after the trait has booted.

3. Scope to the narrowest window. Instead of freezing in setUp() for a whole class, freeze around the specific act/assert. This keeps unrelated setup (factories, auth, seeding) on real time so you don't accidentally change ID ordering or cache keys.

4. Test expiry, not just "now". The highest-value use of any time-travel helper:

$clock->freeze(new DateTimeImmutable('2024-01-01 00:00:00'));
$token = $this->issueToken();          // expires in 15 minutes

$clock->travel(new DateInterval('PT16M')); // if the bundle supports it; else re-freeze

$this->getJson('/api/me', ['Authorization' => "Bearer {$token}"])
     ->assertStatus(401);

If the bundle has no travel()/offset method, call freeze() again with the later timestamp — the effect is the same.

5. Queues and async work. Freezing affects only the current PHP process. With QUEUE_CONNECTION=sync, jobs run in-process and inherit the frozen clock; with database/redis drivers, a separate worker will use real time. For worker-based tests, use Queue::fake() and assert the payload timestamps, or pass the timestamp explicitly into the job.

6. HTTP/feature tests. Freeze before the request so controller-side now() calls see it. But note: the freeze won't reach database-side defaults (CURRENT_TIMESTAMP), Redis TTLs, or anything evaluated outside PHP. Assertions on "record expired after 24h" need the comparison done in PHP, not SQL.

7. Prefer an injected Clock over a global freeze. This is the pattern that outlives the package, and it's what I'd actually take from reading it:

interface Clock { public function now(): DateTimeImmutable; }

final class SystemClock implements Clock
{
    public function now(): DateTimeImmutable { return new DateTimeImmutable(); }
}

final class FrozenClock implements Clock
{
    public function __construct(private DateTimeImmutable $now) {}
    public function now(): DateTimeImmutable { return $this->now; }
}

Bind Clock to SystemClock in AppServiceProvider, rebind to FrozenClock in tests. Your production code stays testable without any package, namespace trickery, or static state.

8. If you keep the bundle, isolate it behind a trait (FreezesTime) so swapping it out later is a one-file change. Given the release date, you will be swapping it out.

Gotchas and Tips

The package is effectively abandoned. Last release 2018-09-18, 4 stars, 0 dependents. Symfony 5/6/7 bundle registration, PHP 8 type deprecations, and Dynamic property warnings are all likely problems. Do not add it to require (only require-dev), and do not let it sit in a long-lived production dependency tree.

The biggest gotcha: it may not freeze the clock you think it does. There are two incompatible strategies:

  • Function mocking (symfony/phpunit-bridge ClockMock) — overrides time(), date(), microtime(), sleep() only for classes in the annotated namespace. It will not affect Carbon::now(), Illuminate\Support\Carbon, or new DateTime() in other namespaces.
  • Carbon/DateTime hijacking — sets a static "now" inside Carbons/DateTime factories, but won't affect raw time() calls or DateTime::createFromFormat.

Check which one this bundle uses before debugging anything else. If your application calls Carbon::now(), a time()-level freeze will silently do nothing and your test will pass for the wrong reason.

Freeze leakage is the #1 source of flaky suites. If an assertion throws before return(), every subsequent test runs in 2024. Use finally, and add a sanity assertion in your base test case:

$this->assertLessThan(60, abs(time() - Carbon::now()->getTimestamp()));

Container visibility (Symfony). If the Timecop service is private, self::$container->get() throws. Either declare it public in config/services_test.yaml or use the TestContainer and get() on a public test alias.

Config quirks. If Configuration.php defines a root key, its defaults may enable the freeze unconditionally. Guard any bundle config with when@test: in config/packages/.

PHP 8 friction. 2018-era code commonly lacks return types, uses each(), and relies on untyped properties. Expect Deprecated: Creation of dynamic property noise in PHPUnit output — either patch locally or ignore it deliberately.

Debug the freeze directly, don't infer it. Immediately after freezing, log both:

dump(date('c'), \Carbon\Carbon::now()->toIso8601String());

If date() is frozen but Carbon isn't (or vice versa), you've found your mismatch in one line instead of an hour.

MIT license is your escape hatch. The package is small. Vendoring the relevant ~200 lines into tests/Support/ and dropping the composer dependency is legal, removes the version-conflict risk, and is very likely the right call here. You get the behavior without the abandoned-dependency liability.

Prefer the maintained equivalents for 90% of use cases. Laravel's InteractsWithTime (travel(), travelTo(), freezeTime(), travelBack()) plus Carbon::setTestNow() cover everything this bundle does, are actively maintained, and are already installed. Reach for kolemp/timecop-bundle only as a reference implementation for how to structure a container-bound clock service.

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.
coleus/frame
thtg88/journalism
filagora/filagora-ai-forums
rlacerda83/query-parser
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings