Comparison between two ActiveSupport::TimeWithZone objects fails - ruby-on-rails

In my test suite, I have a failing test.
expected[0]['date'] comes from SomeModel.first.created_at
In a debugging console, I have the following:
> expected[0]['date']
=> Tue, 25 Mar 2014 16:01:45 UTC +00:00
> res[0]['date']
=> Tue, 25 Mar 2014 16:01:45 UTC +00:00
> res[0]['date'] == expected[0]['date']
=> false # wtf
> res[0]['date'].class
=> ActiveSupport::TimeWithZone
> expected[0]['date'].class
=> ActiveSupport::TimeWithZone
>
How is this possible ?
I've tried to reproduce this problem (I tought maybe the == operator on TimeWithZone checks the reference, or something like this, but no...) :
> t1 = Time.zone.at(0)
=> Thu, 01 Jan 1970 00:00:00 UTC +00:00
> t2 = Time.zone.parse(t1.to_s)
=> Thu, 01 Jan 1970 00:00:00 UTC +00:00
> t1 == t2
=> true
> t1.class
=> ActiveSupport::TimeWithZone
> t2.class
=> ActiveSupport::TimeWithZone
Edit: More tests...
> res[0]['date'].eql?(expected[0]['date'])
=> false
> res[0]['date'].zone
=> "UTC"
> expected[0]['date'].zone
=> "UTC"
> expected[0]['date'].getlocal
=> 2014-03-25 16:01:45 +0000
> res[0]['date'].getlocal
=> 2014-03-25 16:01:45 +0000
> res[0]['date'].hash
=> -3455877575500291788
> expected[0]['date'].hash
=> -3819233736262144515
>
> t1.hash
=> 2279159074362527997
> t2.hash
=> 2279159074362527997
# inspect...
> expected[0]['date'].inspect
=> "Tue, 25 Mar 2014 16:39:01 UTC +00:00"
> res[0]['date'].inspect
=> "Tue, 25 Mar 2014 16:39:01 UTC +00:00"
Looks like the comparison is based on the hash object. Why res and expected have different hashes ?

Answer #1 rake db:test:prepare
First, attempt dropping the test database and recreating it, and then running rake db:test:prepare. This has resolved this issue for me in the past I know this is a bit of a lame answer, but it is worth a shot.
Answer #2 Spring + Rspec + Shoulda matchers
If having this issue after installing Spring, please checkout this Github Thread, which can cause tests to fail:
https://github.com/rails/spring/issues/209
This issue only started occurring for me after adding Spring to my project.
Adding gem 'shoulda', require: false and manually adding require 'shoulda/matchers' to my spec_helper.rb resolved the issues
Answer #3 Timecop
If still having issues, checkout the Timecop gem and freeze time around date comparisons.
https://github.com/travisjeffery/timecop

Related

Rails Time.zone.name is not the same Time.now.getlocal

In rails console if I run the following commands I get:
>> Time.zone.name
=> "UTC"
>> Time.now.getlocal
=> 2015-08-06 16:34:57 -0400
I'm in NYC and the utc offset is -4 hours so the getlocal seems to get the system time zone rather than the environment time zone. This becomes an issue when using the String to_time method:
>> s = "2015-08-06 16:34:57"
=> "2015-08-06 16:34:57"
>> s.to_time(:utc)
=> 2015-08-06 16:34:57 UTC
>> s.to_time(:local)
=> 2015-08-06 16:34:57 -0400
>> s.to_time
=> 2015-08-06 16:34:57 -0400
Any ideas why? Can I force it to look at the environment time zone rather than the system time zone?
My main motivation is that my tests are run locally (so system time is in NYC), but my app is hosted on heroku which has UTC server time, so this caused an issue where a bug slipped through my tests and into production =/
You can do the following:
"2015-08-06 16:23:25".to_time.in_time_zone
The default behavior of to_time is to use :local according to the API doc (either :local or :utc are valid). For whatever reason, using in_time_zone appears to respect your configured timezone.
To expand on your above posted examples (from my machine):
» Time.zone.name
=> "UTC"
» Time.now
=> "2015-08-06T13:49:05.195-07:00"
» Time.zone.now
=> Thu, 06 Aug 2015 20:49:17 UTC +00:00
» time_string = "2015-08-06 16:34:57"
=> "2015-08-06 16:34:57"
» time_string.to_time
=> "2015-08-06T16:34:57.000-07:00"
» time_string.to_time.in_time_zone
=> Thu, 06 Aug 2015 23:34:57 UTC +00:00
EDIT: Based on the comment below, you can also do the following to not have any time shift added:
» DateTime.parse(time_string)
=> Thu, 06 Aug 2015 16:34:57 +0000
This will use the configured timezone (UTC in my case)

One year and one week time scope

I'm trying to notify my customers that their subscription is about to expire. This is how I look for those users to notify them. They will be billed on the date they subscribed + 1.year:
User.where(subscribed_at: 53.weeks.ago.beginning_of_day..53.weeks.ago.beginning_of_day)
My question is will this create issue with leap years? or is there a better way to do this?
Rails provides Time#advance for "precise Time calculations":
Time.now.advance(years: -1, weeks: -1)
#=> 2013-10-08 17:54:36 +0200
Time#all_day returns a whole day's range:
Time.now.advance(years: -1, weeks: -1).all_day
#=> 2013-10-08 00:00:00 +0200..2013-10-08 23:59:59 +0200
I think you should use 1.year.ago since 52.weeks.ago is not equal to a full year (52*7 = 364 days).
The usage of 1.year.ago would be better because it actually changes the year field of the DateTime, nothing else:
1.9.3p489 :005 > 2.year.ago
# => Mon, 15 Oct 2012 11:51:44 EDT -04:00
1.9.3p489 :006 > 5.year.ago
# => Thu, 15 Oct 2009 11:51:47 EDT -04:00
1.9.3p489 :007 > 999.year.ago
# => Sun, 15 Oct 1015 11:51:50 LMT -04:56 # For some reason the TimeZone changed!
In your case, I would use the following logic: NOPE NOPE, I would use #Stefan's answer!
range = (1.year.ago-1.week).beginning_of_day..(1.year.ago-1.week).end_of_day
User.where(subscribed_at: range)

Rails Time class has a TimeZone Bug. Can this be confirmed?

Here goes :
Time.zone.now => "Eastern Time (US & Canada)"
Time.zone.now => Wed, 15 Aug 2012 06:05:37 EDT -04:00
Time.zone.now + 39.years => Tue, 15 Aug 2051 06:06:03 EST -05:00
And so you have it, the end of our fabled Eastern Daylight Time has been prophesied by Ruby on Rails to end in the year 2051.
Also works for any other TimeZone changing area.
Time.zone
=> "Pacific Time (US & Canada)"
1.9.2p180 :003 > Time.zone.now
=> Wed, 15 Aug 2012 03:08:57 PDT -07:00
1.9.2p180 :004 > Time.zone.now + 39.years
=> Tue, 15 Aug 2051 03:08:57 PST -08:00
This exists in Rails 3.0 and in Rails 3.2.6
Yes, it looks like a bug. It's not Rails, however, it's the Ruby Time class. It has problems with times after 2038.
For example, with Ruby 1.8.7:
> Time.local(2037,8,16,9,30,15)
=> Sun Aug 16 09:30:15 -0400 2037
>
> Time.local(2038,8,16,9,30,15)
=> Mon Aug 16 09:30:15 -0500 2038
JRuby 1.6.7.2 - for instance - does not have this problem:
> Time.local(2038,8,16,9,30,15)
=> Mon Aug 16 09:30:15 -0400 2038
Note that, on MRI Ruby on 64-bit systems, the ActiveSupport time extension which supports the addition of durations ultimately calls Time.local or Time.utc via this method in active_support/core_ext/time/calculations.rb:
# Returns a new Time if requested year can be accommodated by Ruby's Time class
# (i.e., if year is within either 1970..2038 or 1902..2038, depending on system architecture);
# otherwise returns a DateTime
def time_with_datetime_fallback(utc_or_local, year, month=1, day=1, hour=0, min=0, sec=0, usec=0)
::Time.send(utc_or_local, year, month, day, hour, min, sec, usec)
rescue
offset = utc_or_local.to_sym == :local ? ::DateTime.local_offset : 0
::DateTime.civil(year, month, day, hour, min, sec, offset)
end
I guess the issue is that for years >= 2038, they were expecting an overflow exception and for DateTime to be used instead. On 64-bit systems, this doesn't happen.
UPDATE: This analysis is incorrect for Ruby 1.9.2+. Time.local works as expected, but the original problem still occurs.

how to change server time to client time on rails?

So my end goal is to have Heroku server's Time.now to be same as the time that you do new Date.now() from JavaScript. What I have done so far is catching the timezone offset from JavaScript, and set Time.zone on Rails with the appropriate client timezone, so now, Time.zone is the right time zone. But however, Time.now is still reflecting the real timezone instead of the one feeding into Time.zone. I guess that's not what I suppose to do at the first place.
So once again, all I want is, the server time to reflect the client time, so any operations such as Time.now or DateTime.now or Date.today will be shown with the client time.
You should apply the UTC offset when displaying the times.
All server-side time calculation or storage should be done in UTC.
Check this
1.8.7 :001 > Time.zone
=> #<ActiveSupport::TimeZone:0xb740d1b8 #tzinfo=#<TZInfo::TimezoneProxy: Etc/UTC>, #utc_offset=nil, #name="UTC", #current_period=nil>
1.8.7 :002 > Time.now
=> Fri Apr 20 13:13:53 +0530 2012
1.8.7 :003 > Time.zone.now
=> Fri, 20 Apr 2012 07:43:59 UTC +00:00
1.8.7 :004 > Time.zone = "Helsinki"
=> "Helsinki"
1.8.7 :005 > Time.zone
=> #<ActiveSupport::TimeZone:0xb70ab830 #tzinfo=#<TZInfo::TimezoneProxy: Europe/Helsinki>, #utc_offset=nil, #name="Helsinki", #current_period=nil>
1.8.7 :006 > Time.now
=> Fri Apr 20 13:14:48 +0530 2012
1.8.7 :007 > Time.zone.now
=> Fri, 20 Apr 2012 10:45:10 EEST +03:00
1.8.7 :008 > Time.zone.now.to_time.strftime("%c").to_datetime
=> Fri, 20 Apr 2012 10:47:01 +0000
1.8.7 :009 >
So Time.zone.now.to_time.strftime("%c").to_datetime will give you current time in user's timezone as UTC
Time.now always gives time in your server's timezone. Time.zone.now gives time in specified timezone.
Thanks,
Amit Patel

Ruby String to_time broken?

I would expect Time and to_time to be reflective.
UPDATE
This is the answer, if you add the parameter :local, it doesn't ignore the offset. I find it wierd that it ignores data (the offset) by default, though...
Time.zone.now.to_s.to_time(:local) returns 2012-03-20 14:39:03 +0100
/UPDATE
> Time.zone.now.to_s
=> "2012-03-20 12:50:05 +0100"
> Time.zone.now.to_s.to_time.to_s
=> "2012-03-20 12:50:05 UTC"
# PROBLEM:
# UTC is +0000, meaning that there is 1 hour diff in the above
> Time.zone.now
=> Tue, 20 Mar 2012 12:51:32 CET +01:00
> Time.zone.now.to_time
=> Tue, 20 Mar 2012 12:51:32 CET +01:00
> Time.zone.now.to_json
=> "\"2012-03-20T12:50:36+01:00\""
> Time.zone.now.to_json.to_time
=> 2012-03-20 12:50:36 UTC
I have this problem with JSON messages. Anyway, I wouldn't expect to_time to actually change the time. OK to translate to UTC, probably, but it should adjust time accordingly. Anybody know if this is a known issue or "by design"?
This method, String#to_time, defined in ActiveSupport takes one parameter, form, which can be either :utc (default) or :local. So by default, it always returns a Time in UTC.
To get a Time with timezone:
Time.zone.parse("2012-03-20T12:50:36+01:00")

Resources