# Removal of timecop ⏲ 👮🏼

**URL:** https://meta.discourse.org/t/removal-of-timecop-timer-clock-policeman-t3/66773
**Category:** Development
**Created:** [July 25, 2017, 12:52pm UTC](https://meta.discourse.org/t/removal-of-timecop-timer-clock-policeman-t3/66773 "2017-07-25T12:52:32Z")
**Posts on this page:** 2
**Page:** 1

<div class="post-metadata">

### Author: ![sam](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/sam/32/102149_2.png) [@sam](https://meta.discourse.org/u/sam)
#### Post date: [July 25, 2017, 12:52pm UTC](https://meta.discourse.org/t/removal-of-timecop-timer-clock-policeman-t3/66773/1 "2017-07-25T12:52:32Z")

</div>

Yesterday I committed a change that removed the[timecop gem](https://github.com/travisjeffery/timecop) and replaced it with these lines of code:

```plaintext
  class TrackTimeStub
    def self.stubbed
      false
    end
  end

  def freeze_time(now=Time.now)
    datetime = DateTime.parse(now.to_s)
    time = Time.parse(now.to_s)

    if block_given?
      raise "nested freeze time not supported" if TrackTimeStub.stubbed
    end

    DateTime.stubs(:now).returns(datetime)
    Time.stubs(:now).returns(time)
    Date.stubs(:today).returns(datetime.to_date)
    TrackTimeStub.stubs(:stubbed).returns(true)

    if block_given?
      begin
        yield
      ensure
        unfreeze_time
      end
    end
  end

  def unfreeze_time
    DateTime.unstub(:now)
    Time.unstub(:now)
    Date.unstub(:today)
    TrackTimeStub.unstub(:stubbed)
  end

```

Migrating from timecop to our `freeze_time` helper should be a breeze as it already supports the block form. So most changes are as simple as:

```plaintext
Timecop.freeze(1.day.from_now) do
    no_longer_living_on_borrowed_time!
end

# to

freeze_time(1.day.from_now) do
    no_longer_living_on_borrowed_time!
end

```

I manually changed every usage of Timecop in both core and core plugins to use the new pattern. It took me less than an hour.

### Why was this done?

I guess the first thing people may ask when confronted with this "Sam!? Wheels why reinvent them? Cheese, why move it?

The catalyst for the change was 3 days of an unstable test suite, caused by misuse of the Timecop library. But that was not the **only** issue I had.

#### Timecop API is too wide

The timecop gem offered too many options and too much flexibility, for example, want to do:

```plaintext
Timecop.travel 1.day.ago do
    Timecop.travel 1.day.ago do
       Timecop.travel 1.day.ago do
       end
    end
end

```

Nesting can very quickly lead to incredibly hard to understand specs. Additionally, it had a bunch of fanciness we never used including date rounding, thread safe operation and time stack walking.

#### Timecop API required consumers to remember to “unfreeze” time, which is called return

```plaintext
before do
   Timecop.freeze(1.day.ago)
end

after do
   # forget this and you leak state into your test suite and break it 
   # cause time is frozen after your test
   Timecop.return
end

```

Our API uses mocha which always cleans up, no need to remember anything.

#### Less dependencies make me happy

I don’t see why carry a whole gem dependency for 20 or so lines of code we control and can trivially debug. We already have a mocking library I don’t see why we need to introduce a second one.

#### There is no reason to have 2 time travel APIs

It is confusing to have 2 ways of mocking time. Developers should not be force to choose between 2 APIs that achieve essentially the same goal.

Additionally, Discourse uses mocha for all object mocking. You end up having 2 mocking libraries and if you mock `Time` with with both you can very quickly get very unexpected results.

---

<div class="post-metadata">

### Author: ![codinghorror](https://sea3.discourse-cdn.com/meta/user_avatar/meta.discourse.org/codinghorror/32/110067_2.png) [@codinghorror](https://meta.discourse.org/u/codinghorror)
#### Post date: [January 21, 2019, 3:15am UTC](https://meta.discourse.org/t/removal-of-timecop-timer-clock-policeman-t3/66773/2 "2019-01-21T03:15:24Z")

</div>


