jtrupiano / timecop
- Source
- Commits
- Network (6)
- Issues (1)
- Downloads (8)
- Wiki (1)
- Graphs
-
Branch:
master
Pledgie Donations
Once activated, we'll place the following badge in your repository's detail box:
A gem providing “time travel” and “time freezing” capabilities, making it dead simple to test time-dependent code. It provides a unified method to mock Time.now, Date.today, and DateTime.now in a single call. — Read more
-
I am not totally sure whether I'm just doing something wrong or whether this is a bug, but I'm trying to find Order objects which need to be delivered on Date.today, with a named_scope like this:
named_scope :for_today, :include => [:delivery => :delivery_slot], :conditions => ['delivery_slots.date = ?', Date.today], :order => 'delivery_slots.timeslot'and in a console session:
>> Order.for_today.count => 9 >> require 'timecop' => [] >> Timecop.travel(Date.today + 1) do ?> Order.for_today.count >> end => 9 >> Timecop.travel(Date.today + 5) do ?> Order.for_today.count >> end => 9 >> Timecop.travel(Date.today + 5) do ?> puts Date.today >> end 2009-11-11 # <= this is correct as right now it's 6 Nov. => nil >> Timecop.travel(Date.today + 500) do ?> Order.for_today.count >> end => 9 >> quit </pre> <p>There are definitely no orders which have an associated Delivery which is 500 days in the future, so I'm wondering whether this is perhaps a bug with named_scope?Comments
-
There are a few issues with time zones.
1. DateTime offset is not kept when I use a DateTime object as reference
2. Using a utc Time object as reference changes the timeThere's an easy workaround for 2., but 1. is a problem.
I added tests to illustrate this : http://github.com/piglop/timecop/tree/timezone_tests
Comments
@piglop,
I can confirm that the test cases you have supplied do in fact fail. I am not certain yet whether or not I think they are valid test cases though. I'll be taking a deeper look into these issues later this week.
-John
The problem is when I freeze or travel, DateTime.now always returns an UTC time (offset is 0) with local values in individual fields (year, hour, etc.). Whereas standard DateTime.now returns a true local time (local values in individual fields and local offset).
The problem arises when you convert DateTime.now to another timezone (or even the local timezone). The individual fields are changed according to the new timezone. But since the individual values were already local, the converted time is wrong.
I've added another test in the branch to illustrate this.@piglop thanks for following up with more information. This will be helpful.
I found a solution and made a single commit from master here : http://github.com/piglop/timecop/commit/7478f7ff28f2b5ea4b5476622c5de36bf7edca9f
I also added a fix for the problem with Time.utc here : http://github.com/piglop/timecop/commit/550183c5fb9fca7ac4351e16dc1cae4469c292cb@piglop it's taken me a couple of extra days to get around to this. I'll be able to review these patches this weekend and hopefully get them merged in.
Thanks again.
-John
Should be fixed with http://github.com/piglop/timecop/commit/607af97d9d352b6b7e077658d2b759704d9749ba
I haven't studied the code changes, but timecop 0.3.2 has definitely broken in terms of how Time.now works when freezing. For example, here's a tiny "test" to illustrate:
it "should behave" do puts "Time (local) at start is: #{Time.now}" puts "Time (UTC) at start is : #{Time.now.utc}" Timecop.freeze(3.hours.from_now) do puts "Time (local) after timecop added 3 hours: #{Time.now.utc}" puts "Time (UTC) after timecop added 3 hours : #{Time.now.utc}" end endIf I run that with Timecop 0.3.1, I get:
Time (local) at start is: Mon Oct 26 13:40:39 -0700 2009
Time (UTC) at start is : Mon Oct 26 20:40:39 UTC 2009
Time (local) after timecop added 3 hours: Mon Oct 26 23:40:39 UTC 2009
Time (UTC) after timecop added 3 hours : Mon Oct 26 23:40:39 UTC 2009But, if I run that with Timecop 0.3.2, I get:
Time (local) at start is: Mon Oct 26 13:41:30 -0700 2009
Time (UTC) at start is : Mon Oct 26 20:41:30 UTC 2009
Time (local) after timecop added 3 hours: Mon Oct 26 16:41:30 UTC 2009
Time (UTC) after timecop added 3 hours : Mon Oct 26 16:41:30 UTC 2009So, now, time has gone backwards. In both cases my local timezone has been changed to UTC. What it's doing in 0.3.2 appears to be that it's adding 3 hours to the "Time.now" result, and not taking into account the Time zone.
@chris thanks for the report. If you have a free moment and can add a failing test to the test suite, I'd really appreciate it.
In the meantime, I suspect you'll get away with 0.3.1. It will probably be several days before I can get around to this.
While I was trying to write a test case for this problem, I discovered the current test suite fails since DST shift.
Indeed, in the new tests, we check that DateTime.now returns a local time, i.e. a DateTime with the current local offset. But when we create a DateTime in summer while we're in winter, the local offset should not be the same.
And I couldn't find a way to get the local offset at a specific time with the DateTime class. It may be possible with the Time class.I couldn't write a test case for chris's problem, but I could reproduce it in a Rails environment.
I won't have a lot of time to check these problems this week.
I'm looking into it. I'm starting to think it's a conflict with Rails though. I forked the code, added a test case and it behaves properly when using simple math to add 3 hours to the time. So, I'm going to look into whether it's Rails' extensions for doing things like "3.hours.from_now" and if that messes with it. Like you guys, I'm pretty busy, and Timecop 0.3.1 works for me, so I'm not sure when I'll resolve this.
So I've had some time to really dig into this.
@chris, I think I have your issues worked out. I'll be releasing a prerelease version of timecop in a couple of days that I'll want you to try out for me. If you care to try my latest release candidate before I get the prerelease version sorted out, you can pull it down from a new branch named 'rewind'.
@piglop, there is a separate problem related to the patch you originally submitted. You had noticed behavior where traveling to times/datetimes in a different timezone were losing the timezone information, causing that time to be represented as UTC. Your patch corrected this, but at the same time underscored a new issue that I don't think we can solve.
The problem lies in the fact that a DateTime is incapable of representing DST properly. A DateTime only carries an offset (e.g. +0200), but this is not enough information for us to know if this particular DateTime instance should be subject to DST. Different timezones exist within the same offset, and some observe DST while others do not.
Due to this limitation, you can end up with the following test case failing:
t = DateTime.parse("2009-10-11 00:38:00 +0200") assert_equal "+02:00", t.zone Timecop.freeze(t) do assert_equal t, DateTime.now.new_offset(t.offset) endI have pushed a new branch "rewind" to GitHub where you can see this test currently failing.
As a result of this finding, I am seriously considering removing the ability to pass a DateTime instance to Timecop calls. My concern is that this is simply too little known (that DateTime's cannot represent DST) and will cause many people headaches if we allow them to do so.
I'm curious what your thoughts are.
The gemspec in the 'rewind' branch now specifies a prerelease version (0.3.3.rc1). I'd really appreciate it if you guys (@chris and @piglop) could give it a try with your respective problems and report back to me.
Note that if you had previously installed 0.3.3 that it will take precedence over 0.3.3.rc1. I'm still getting the hang of this prerelease workflow...
I have released 0.3.4.rc1 as a prerelease gem. You can install it via: gem install/update --prerelease timecop
Hey guys,
I found some other problems with 0.3.4.rc1 that I think I've fixed.
Can you take a moment to try upgrading to timecop 0.3.4.rc2 on your respective projects and let me know if I've introduced any regressions? It is a prerelease gem so you'll need to specify that when installing the gem: gem install timecop --pre -v0.3.4.rc2
This is just for verification purposes. Your projects should not depend on this version of the gem. If no regressions are found I will then officially release version 0.3.4 at which point you should upgrade your projects' dependencies.
Thanks for your help.
-John
@jtrupiano not sure this is exactly the same bug, but you've fixed mine in your prerelease gem.
I have a rails app with the timezone set to Eastern. My box is running Central
In 0.3.2:
require 'timecop'
>> time = Time.local(2000, 1, 1) => Sat Jan 01 00:00:00 -0600 2000 >> Timecop.freeze(time) => Sat, 01 Jan 2000 00:00:00 EST -05:00 >> Time.now => Sat, 01 Jan 2000 00:00:00 EST -05:00Note it's an hour off. In 0.3.4.rc2 it is correct.
0.3.4.rc2 is working in all my current tests. It does still change the local time zone to be UTC (from whatever it previously was) though. e.g. (output from my same example above):
Time (local) at start is: Tue Dec 01 21:13:45 -0800 2009 Time (UTC) at start is : Wed Dec 02 05:13:45 UTC 2009 Time (local) after timecop added 3 hours: Wed Dec 02 08:13:45 UTC 2009 Time (UTC) after timecop added 3 hours : Wed Dec 02 08:13:45 UTC 2009@jerryvos excellent. Your issue is one of the issues I have been trying to solve since 0.3.1.
@chris can you show me a sample session from either irb or script/console? Specifically I want to know which set of arguments you're passing to Timecop.freeze or Timecop.travel.
-
Make freeze/travel return result of the block, instead of current time
1 comment Created 3 months ago by metatribeHello,
Would it be possible to change the implementation so the freeze/travel methods return the result of the passed in block, instead of current time? I think that would be more useful, because then you could do something like this:
user = Timecop.freeze(2.years.ago) { User.create!(:name => '...') }Instead of this:
user = nil Timecop.freeze(2.years.ago) do user = User.create!(:name => '...') end
I think that first line is necessary, because otherwise the
uservariable is lost when it comes out of block's scope.Comments
This is an interesting use case, one that I've never really used Timecop for. I'm not 100% sold on the change...I like the consistency of returning Time.now from all 3 functions (freeze, travel, return). Furthermore, you can invoke #freeze and #travel without passing a block.
Can you get away with using an instance variable (@user)? That will eliminate the need to define the variable prior to the method call.
It would be trivial to apply the change you're asking for, but I don't think I'm going to add it to the library. I know its not ideal, but it would be pretty simple to maintain your own fork. This library, though only versioned 0.3.0, is pretty mature. I don't foresee many changes to the codebase moving forward.
-
timecop should reset global settings before defining each test
4 comments Created 7 months ago by sjainThis is not a bug in Timecop per se but a usability issue which may cause a few lost hours when it happens. Ideally you ensure Timecop.return to be called as part of teardown so that each new test gets pristine times/dates.
However, since in ruby a test can get created as part of executing a piece of code (shoulda does this as part of defining contexts, setup, teardown etc.), there is a significant possibility for somebody to freeze a time in once of their test files and all tests that get defined after this file inherit the frozen time. For example, consider following piece of code inside a test class:
class TestMyModel < Test::Unit::TestCase
(20 .. 26).each do |day|Timecop.freeze(Date.parse("April #{day}, 2009")) should "be valid for #{Date.today.to_s(:header)}" do Timecop.freeze(Date.parse("April #{day}, 2009")) @time_sheet.valid_timesheet_for_today? assert @time_sheet.valid_timesheet_for_today? endend end
In above code, Timecop.freeze gets called while the tests are being created. A quick fix would be to call Timecop.return inside the block after the test is defined. But, we can do better. We patch Shoulda so that Timecop.return gets called after each test is defined. Following is one way to do this:
module Thoughtbot
module Shouldaclass Context unless method_defined?(:merge_block_with_timecop_return) def merge_block_with_timecop_return(*args, &blk) merge_block_without_timecop_return(*args, &blk) Timecop.return end alias_method_chain :merge_block, :timecop_return end endend end
I am assuming, necessary guards can be put around this so it is a no-op for folks not using Shoulda. There must be a similar timecop-reset that could be defined for folks using old-fashioned def test_x; end; type of constructs.
Comments
Why do you call Timecop.freeze before the should() call in the above scenario? Isn't the call inside the block passed to should() sufficient? I suspect you want it so that Date.today.to_s(:header) evals properly, but you can do that yourself...you're iterating over the dates you're jumping around to. You don't need Timecop to generate that string.
Please correct me if I'm misinterpreting the issue, but it appears you're only having problems because you're trying to use Timecop to name that test case.
To be clear, I'm suggesting you change:
Timecop.freeze(Date.parse("April #{day}, 2009")) should "be valid for #{Date.today.to_s(:header)}" do; endto:
should "be valid for #{Date.parse("April #{day}, 2009").to_s(:header)}" do; end:-) my bad. I guess, it is more of a "gotcha" to not use Timecop.freeze outside of of test definition. Thanks much.





@futurechimp, this is a problem that many folks run into. The issue is how your named_scope is being defined. What happens is Date.today is being interpreted when the file is loaded, not when the named_scope is invoked. If you change the named_scope declaration to use a lambda as below, this will all work as expected.
This is a common mistake many people make when defining named_scopes that depend on time and is not a problem specific to Timecop.
-John