Open-source project
travisjeffery/timecop avatar
travisjeffery/timecop

timecop mocks four clocks at once, and only three of them are on by default

A gem providing "time travel", "time freezing", and "time acceleration" capabilities, making it simple to test time-dependent code. It provides a unified method to mock Time.now, Date.today, and DateTime.now in a single call.

3,423 stars227 forksRubyMIT

At a glance

What is it?
timecop is a dependency-free Ruby gem for testing time-dependent code, with freeze, travel and scale as three separate behaviours and a safe mode that refuses to let mocked time escape a block. Its test suite runs inside a Docker image the Makefile builds.
Who is it for?
timecop fits a test suite where dates decide the outcome and you would rather not stub the clock yourself, and the three behaviours are worth choosing between deliberately: freeze for a fixed instant, travel when the code under test has to keep ageing, scale when a monthly cycle should finish in seconds. Two things to know before you adopt it.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 33 days ago.
What is it written in?
Mainly Ruby, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on November 14, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • travisjeffery/timecop3,423 ★ · -3
Star history
Repository2026-09-242026-11-14
travisjeffery/timecop3.4k3.4k

-3 Stars in the last 51 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

Four clock sources, one call, and one opt-in

The gem's pitch is a single call that covers the clocks a Ruby program actually reads. The description names Time.now, Date.today and DateTime.now, and the body of the page adds Process.clock_gettime to that list, so a test can move the wall clock and the date libraries together rather than stubbing each one. The exception is the monotonic clock. By default timecop does not mock Process.clock_gettime, and there is a separate one-line switch for it:

ruby
# turn on
Timecop.mock_process_clock = true

That asymmetry matters more than it looks. Code that measures elapsed time with the monotonic clock and code that stamps records with Time.now can disagree inside the same test, and only one of the two is being controlled until you flip the switch. It is the kind of detail that turns a passing test into a flaky one, and it is documented in three lines rather than hidden.

freeze and travel differ only in whether time keeps moving

The page spells the difference out with a demonstration rather than a definition. freeze is used to statically mock the concept of now: as the program executes, Time.now will not change unless you make further calls into the API. travel computes an offset between what the library currently thinks Time.now is and the time you passed in, then uses that offset to simulate the passage of time. The demonstration is two identical sleeps:

ruby
new_time = Time.local(2008, 9, 1, 12, 0, 0)
Timecop.freeze(new_time)
sleep(10)
new_time == Time.now # ==> true

Timecop.return # "turn off" Timecop
Timecop.travel(new_time)
sleep(10)
new_time == Time.now # ==> false

Under travel the offset is recomputed against the current mocked value, which is what makes nested travel and freeze work: each block keeps its own interpretation of now. That nesting is a listed feature, not an accident of the implementation.

Safe mode raises instead of leaking mocked time

There is a mode that exists purely to catch a mistake, and its name is safe_mode. Setting it forces the block form of every call, because a block always puts time back the way it was. If you are in safe mode and call the API without a block, the library raises Timecop::SafeModeException with a message saying that safe mode is enabled and only calls passing a block are allowed. You can check the current state with a predicate as well as setting it:

ruby
# turn on safe mode
Timecop.safe_mode = true

# check if you are in safe mode
Timecop.safe_mode?
# => true

The motivation is worth understanding rather than copying. A non-block call is exactly how mocked time escapes a test and infects the next one, and a suite that runs in a random order or in parallel turns that leak into intermittent failures with no obvious cause. Turning safe mode on converts the leak into an exception at the point where it is created.

Scale makes a month of report cycles finish in seconds

The third behaviour is time acceleration, for the case where waiting is the problem rather than control. The stated use case is testing a live integration where entire days pass in minutes while you simulate real activity: reports and invoices on 30 day cycles that have to be exercised without waiting 30 days. The example scales time by 3600 so that seconds behave like hours, and prints Time.now twice with a comment between them explaining that hours have passed:

ruby
# seconds will now seem like hours
Timecop.scale(3600)
Time.now
# => 2012-09-20 21:23:25 -0500
# seconds later, hours have passed and it's gone from 9pm at night to 6am in the morning
Time.now
# => 2012-09-21 06:22:59 -0500

The feature is credited to a specific pull request, number 42, with three named contributors including a company. That credit line is also a hint about where time-based test tooling comes from in Ruby practice: it tends to arrive as a contribution from a team that needed it in production code first.

Six argument shapes, including a bare integer offset

The API accepts the argument in more than one form, and the list is worth reading because it covers most cases without a helper. You can pass a Time instance, a DateTime instance or a Date instance. You can pass individual arguments, meaning year, month, day, hour, minute and second. And you can pass a single integer, which is interpreted as an offset in seconds from the current Time.now, which is what makes the short form in the first example work. The example that opens the usage section is the canonical shape: freeze to thirty days after Date.today inside a block, assert inside it, and let the block restore time on exit. For a whole suite, the page shows the before and after hooks with freeze and return, and for a Rails application it shows an after_initialize block in the test environment file that travels to a fixed instant so fixtures are built at one point in time.

Tests run inside the image the Makefile builds

There is a Dockerfile and a Makefile, and together they describe how this gem is tested. The Makefile builds an image named after the project, and its test target depends on that image, then runs rake inside a container. It also defines a clean target that deletes the lock file, a console target and a shell target, and both of the interactive ones mount the current directory at a working path inside the container. The Dockerfile is short: it starts from the ruby image, copies the gem manifest and the version file, runs bundle with four jobs, copies the rest of the tree and sets the console script as its default command. Two details are worth noting. The bundle install happens before the library source is copied, which only works because the version is read from the one file that was copied first. And because the lock file is not copied in, and a make target deletes it, every image build resolves dependencies from scratch.

Two authors, no releases, and a Rails date caveat

The project has a history that predates its current maintainer. It was created by one developer and is maintained by another, and the repository has no GitHub releases, so the change log lives in a History file at the root rather than in release notes. The README also carries a warning that is easy to miss and expensive to debug: sometimes the Rails date and time methods do not play nicely with the Ruby ones, with a link to a Rails ticket about it, and the concrete advice is to be careful mixing the Ruby Date.today with the Rails Date.tomorrow and Date.yesterday, because things might break. That is a real hazard for anyone using this gem inside a Rails test suite, since ActiveSupport redefines date arithmetic on top of the standard library. The rest of the repository is conventional for a gem: a gemspec, a Gemfile, a Rakefile, lib, test and a bin directory.

Editorial conclusion

timecop fits a test suite where dates decide the outcome and you would rather not stub the clock yourself, and the three behaviours are worth choosing between deliberately: freeze for a fixed instant, travel when the code under test has to keep ageing, scale when a monthly cycle should finish in seconds. Two things to know before you adopt it. Process.clock_gettime is not mocked unless you turn that on, so monotonic-clock code can disagree with Time.now inside the same test. And safe mode exists because a non-block call leaves mocked time in place, so if your suite runs in parallel or leaks state, turning it on will surface the leak as an exception rather than as a mystery failure three tests later.

Frequently asked questions

How do I install timecop?

With bundle add timecop. The gem declares no dependencies and is described as usable with any Ruby project, including Rails applications.

What is the difference between Timecop.freeze and Timecop.travel?

freeze statically mocks now, so Time.now does not change while your program runs. travel computes an offset from the current mocked time to the time passed in and lets time keep moving from there. The README demonstrates it by freezing, sleeping ten seconds and comparing, then returning and travelling instead.

What does Timecop.safe_mode do?

It forces the block form of every call, because a block always restores the previous time. Calling freeze or travel without a block while safe mode is on raises Timecop::SafeModeException. You set it with Timecop.safe_mode and check it with Timecop.safe_mode?.

Does timecop mock Process.clock_gettime?

Not by default. It has to be enabled explicitly with Timecop.mock_process_clock = true, so code reading the monotonic clock is unaffected until you turn that on.

Who maintains timecop and where is its changelog?

travisjeffery maintains it and jtrupiano created it. The repository publishes no GitHub releases, and the change history is kept in a History.md file at the root.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. travisjeffery/timecop on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/travisjeffery-timecop.svg)](https://hysenlabs.com/projects/travisjeffery-timecop)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/travisjeffery-timecop.svg)](https://hysenlabs.com/projects/travisjeffery-timecop)

Was this useful?