Timecop And Date.parse Do Not Get Along

My first NYE in Berlin was quite awesome; red and green fireworks all night long. At the same time though, Timecop seemed to have a party of its own causing green and red specs as soon as the clock hit 00:00.

For those that are not familiar with Timecop, it’s a great gem that provides “test time-dependent code”. In other words, it mocks your your Date and DateTime objects.

A quick intro to Timecop.freeze

How about creating a method that takes a date as a parameter and compares it against current today’s date. The method simply returns true or false depending on whether the date is in the future or past.

First let’s right a simple spec:

#We are not testing for date == Date.today but it too should return false

describe "#date_in_future?" do
  context "when the date is in the future" do
    it "returns 'true'" do
      expect(date_in_future?(Date.new(2014, 2, 11))).to be_true
    end
  end
  
  context "when the date is in the past" do
    it "returns 'false'" do
      expect(date_in_future?(Date.new(2014, 2, 1))).to be_false
    end
  end
end

Now the method:

def date_in_future?(date)
  date > Date.today ? true : false
end

If you run the specs, both should pass.. today. Let’s see what will happen tomorrow when we run the specs again. date is assigned to 11-2-2014, Date.today will also be 11-2-2014. So, the first expecation should fail as date is equal to Date.today. With Timecop’s help, the issue can easily be resolved.

Let’s add Timecop in our specs.

describe "#date_in_future?" do
  around do |example|
    Timecop.freeze(2014, 2, 10, &example)
  end
  ...
end

What we have done with Timecop.freeze is to literally freeze Date.today to a date we want, in this case the 10th of February, 2014. Now, our specs are bulletproof.

Timecop and Date.parse are not the best of friends

Date.parse takes a string and will try to convert it into a date. Ruby is kind of smart about it too. Few examples below:

irb(main):019:0> Date.parse("10Feb2014")
=> #<Date: 2014-02-10 ((2456699j,0s,0n),+0s,2299161j)>
irb(main):020:0> Date.parse("10-Feb-2014")
=> #<Date: 2014-02-10 ((2456699j,0s,0n),+0s,2299161j)>
irb(main):021:0> Date.parse("20140210")
=> #<Date: 2014-02-10 ((2456699j,0s,0n),+0s,2299161j)>
irb(main):022:0> Date.parse("Feb")
=> #<Date: 2014-02-01 ((2456690j,0s,0n),+0s,2299161j)>
irb(main):023:0> Date.parse("10Feb")
=> #<Date: 2014-02-10 ((2456699j,0s,0n),+0s,2299161j)>

The example we are interested in is the last one. If you pass a string that has no year reference, Date.parse assumes is the current year and returns the date as such.

Let’s see now how the last example works with Timecop.

Open up an irb session and fire away:

irb(main):001:0> require 'date'
=> true
irb(main):002:0> Date.today
=> #<Date: 2014-02-10 ((2456699j,0s,0n),+0s,2299161j)>
irb(main):003:0> Date.parse("10Feb")
=> #<Date: 2014-02-10 ((2456699j,0s,0n),+0s,2299161j)>
irb(main):004:0> require "timecop"
=> true
irb(main):005:0> Timecop.freeze(2013, 1, 1)
=> 2013-01-01 00:00:00 +0100
irb(main):006:0> Date.today
=> #<Date: 2013-01-01 ((2456294j,0s,0n),+0s,2299161j)>
irb(main):007:0> Date.parse("10Feb")
=> #<Date: 2014-02-10 ((2456699j,0s,0n),+0s,2299161j)>

Timecop has no effect when parsing dates with no reference to a year. Let’s clear something out first. You would never need to freeze something like Date.parse("20131120") as it’s already an immutable Date object; immutable in the sense that the passing of time will not affect it’s state.

This caused the repo’s build to turn red the minute after NY as in many specs the above scenario occured.

A simple fix

There is a very simple fix to it. When using Date.parse pass the current year as well:

irb(main):001:0> require 'date'
=> true
irb(main):002:0> require 'timecop'
=> true
irb(main):003:0> Timecop.freeze(2013, 1, 1)
=> 2013-01-01 00:00:00 +0100
irb(main):004:0> Date.parse("10Feb")
=> #<Date: 2014-02-10 ((2456699j,0s,0n),+0s,2299161j)>
irb(main):005:0> Date.parse("10Feb#{Date.today.year}")
=> #<Date: 2013-02-10 ((2456334j,0s,0n),+0s,2299161j)>

I’m currently looking into patching this behavior and if it makes sense to make a pull request.

Note: The code runs with Timecop version 0.6.1


yourname

Keep this sentence less than 13 words or it won't look good.