Is Rails time broken for midnight? - ruby-on-rails

Let's look at the date:
1.9.2p320 :008 > Date.today
=> Wed, 03 Oct 2012
1.9.2p320 :009 > Time.now
=> 2012-10-03 22:32:55 -0400
Now, given that when is midnight?
1.9.2p320 :005 > Date.today.midnight
=> Wed, 03 Oct 2012 00:00:00 UTC +00:00
Makes sense. But what about yesterday?
1.9.2p320 :006 > Date.yesterday.midnight
=> Wed, 03 Oct 2012 00:00:00 UTC +00:00
Uh, that doesn't quite make sense. Midnight today is the same as midnight yesterday? You can't be serious!
1.9.2p320 :026 > Date.today.midnight == Date.yesterday.midnight
=> true
1.9.2p320 :033 > 1.day.ago.midnight == Date.yesterday.midnight
=> true
1.9.2p320 :034 > 1.day.ago.midnight == Date.today.midnight
=> true
Oh, you are serious. What about tomorrow?
1.9.2p320 :007 > Date.tomorrow.midnight
=> Fri, 05 Oct 2012 00:00:00 UTC +00:00
Wait, if midnight today is 00:00 on the 3rd, and midnight yesterday is 00:00 on the 3th, but midnight tomorrow is 00:00 on the 5th, where's 00:00 on the 4th?
Here it is:
1.9.2p320 :010 > 0.days.ago
=> Thu, 04 Oct 2012 02:34:58 UTC +00:00
1.9.2p320 :011 > 0.days.ago.midnight
=> Thu, 04 Oct 2012 00:00:00 UTC +00:00
but isn't zero days ago today? Apparently not.
Is it me, or is this not at all internally consistent? It seem to me that Date.today should be the same as 0.days.ago.
I understand that days.ago is actually using the Time object, and that this is a time zone issue:
1.9.2p320 :030 > Date.today
=> Wed, 03 Oct 2012
1.9.2p320 :021 > Time.now
=> 2012-10-03 22:40:09 -0400
1.9.2p320 :023 > 0.days.ago
=> Thu, 04 Oct 2012 02:40:22 UTC +00:00
1.9.2p320 :022 > Time.zone.now
=> Thu, 04 Oct 2012 02:40:14 UTC +00:00
But it seems as though, given that these are convenience functions, it's kind of mean to throw a timezone assumption into one convenience function and not throw it into another convenience function, both of which, by all accounts, mean the same thing.
Even setting that aside, it doesn't seem to explain the fact that Date.today.midnight == Date.yesterday.midnight, which is– quite simply– barking mad.
Since I know that I can't be the first to have been bitten by this, I ask what am I missing?

Rails will base relative date calculations such as yesterday, tomorrow, and midnight off of Date.current which will attempt to use the configured Time.zone: https://github.com/rails/rails/blob/master/activesupport/lib/active_support/core_ext/date/calculations.rb#L46
Since your Time.zone is set to UTC, you won't get the same results as calculations based off Date.today, which will use your computer's clock time, unless you're actually sitting in UTC time.
So, if the time difference between you and UTC is greater than the time to midnight, Date.yesterday and Date.today actually return the same date!
Try setting your Rails time zone with Time.zone = 'Eastern Time (US & Canada)' or whatever time zone you're in and retry your examples.

rossta identified the culprit. You may have better luck with Time.now.to_date and specifying the time zone if necessary:
> Time.now.in_time_zone("Asia/Tokyo")
=> Thu, 04 Oct 2012 12:54:43 JST +09:00
> Time.now.in_time_zone("Asia/Tokyo").to_date.midnight
=> Thu, 04 Oct 2012 00:00:00 JST +09:00
Time.now.in_time_zone("Asia/Tokyo").to_date.yesterday.midnight
=> Wed, 03 Oct 2012 00:00:00 JST +09:00
> Time.zone = "America/Los_Angeles"
=> "America/Los_Angeles"
> Time.now.in_time_zone
=> Wed, 03 Oct 2012 20:55:35 PDT -07:00
> Time.now.to_date # using the system time
=> Wed, 03 Oct 2012
> Time.now.to_date.midnight
=> Wed, 03 Oct 2012 00:00:00 PDT -07:00
> Time.now.to_date.yesterday.midnight
=> Tue, 02 Oct 2012 00:00:00 PDT -07:00

Related

Different result between Date.yesterday and Date.today.prev_day and 1.day.ago in RoR

I am currently getting different results depend on which command I use to get the previous day. The only correct answer is Date.today.prev_day.
I can understand why 1.day.ago and Date.yesterday might be different based on time zones, but I would expect normalized results from the two Date calls.
irb(main):026:0> Date.today.prev_day
=> Thu, 02 Mar 2017
irb(main):027:0> Date.yesterday
=> Fri, 03 Mar 2017
irb(main):028:0> 1.day.ago
=> Fri, 03 Mar 2017 02:45:05 UTC +00:00
Why am I getting different results with these three commands?
It works as I would expect:
2.2.3 :007 > Rails.version
=> "4.2.6"
2.2.3 :002 > Date.today
=> Sun, 05 Mar 2017
2.2.3 :003 > Date.today.prev_day
=> Sat, 04 Mar 2017
2.2.3 :004 > Date.yesterday
=> Sun, 05 Mar 2017
2.2.3 :005 > 1.day.ago
=> Sun, 05 Mar 2017 00:10:13 UTC +00:00
I would start by checking the result of Date.today, i.e. is the issue in the today method or in the prev_day method. I would also check that the method hasn't been overridden somehow. You can do something like the following:
Date.today.method(:prev_day).source_location
Which returns nil for me.

Why Time.now returns PST if my TIme.zone is UTC?

In my Rails app I do the following:
Time.zone.name #=> 'UTC'
Why when I do:
Time.parse('2014-12-19').end_of_day.to_datetime
I get: Fri, 19 Dec 2014 23:59:59 -0800
And when I do:
Time.use_zone('Pacific Time (US & Canada)') { Time.parse('2014-12-19').end_of_day.to_datetime }
I get the exact same thing: Fri, 19 Dec 2014 23:59:59 -0800
Why is the zone not being applied?
You need to use Time.zone.parse to make it use the proper timezone.
2.1.3 (main):0 > Time.zone.name
=> "UTC"
2.1.3 (main):0 > Time.zone.parse('2014-12-19').end_of_day.to_datetime
=> Fri, 19 Dec 2014 23:59:59 +0000
Are you looking for this
Time.use_zone('Pacific Time (US & Canada)') { Time.parse('2014-12-19').end_of_day.to_datetime.in_time_zone}
This would give you the correct time zone. For more information on this have a look at THIS SO QUESTION
You are probably on Windows, which has a bug in Rails and Rails will always return your local time zone. See How does Rails know my timezone?
You can also use
zone = ActiveSupport::TimeZone['Hawaii']
zone.at(Time.now)
=> Tue, 16 Jun 2015 09:22:53 HST -10:00
But I rather like Pamio's idea
Time.now.in_time_zone('Hawaii')
=> Tue, 16 Jun 2015 09:43:17 HST -10:00

Convert string to datetime in specific timezone

I'm using datetimepicker and need to save the string datetime obtained from params to a datetime in a specific, user dependent time zone. It will allow me to save proper UTC datetime to database.
params[:notify_at] #=> "2014-07-05 14:30:00"
user.time_zone #=> #<ActiveSupport::TimeZone:0x00000007535ac8 #name="Warsaw", #utc_offset=nil, #tzinfo=#<TZInfo::TimezoneProxy: Europe/Warsaw>, #current_period=nil>
And I would like to do something like:
date = params[:notify_at].to_datetime(user.time_zone) #=> Sat, 05 Jul 2014 12:30:00 +0000
(its 14:30 in user's localtime but 12:30 in UTC)
You can use the in_time_zone method. For example:
DateTime.current.in_time_zone("Alaska")
# => Fri, 23 May 2014 07:21:30 AKDT -08:00
So for your use case:
params[:notify_at].to_datetime.in_time_zone(user.time_zone)
Pro Tip: If using Rails v4+ you can actually do this directly on the string:
"2014-07-05 14:30:00".in_time_zone("Alaska")
# => Sat, 05 Jul 2014 14:30:00 AKDT -08:00
UPDATE
You can parse a string directly into a time zone (where the String should already be IN that time zone) like this:
Time.zone.parse("2014-07-05 14:30:00")
# => Sat, 05 Jul 2014 14:30:00 CEST +02:00
So for your use case do:
user.time_zone.parse(params[:notify_at])
Try this:-
date = params[:notify_at].to_datetime.in_time_zone(user.time_zone)
Rails console output:-
1.9.3p385 :004 > d= "2014-07-05 14:30:00"
=> "2014-07-05 14:30:00"
1.9.3p385 :010 > d.to_datetime.in_time_zone("Pacific Time (US & Canada)")
=> Sat, 05 Jul 2014 07:30:00 PDT -07:00
1.9.3p385 :011 > d.to_datetime.in_time_zone("Alaska")
=> Sat, 05 Jul 2014 06:30:00 AKDT -08:00
1.9.3p385 :012 > to_datetime.in_time_zone

How do get a random DateTime rounded to beginning of hour in Rails?

Basically I'd like to get a random datetime within the last year:
rand(1.year).ago #=> Sun, 22 Sep 2013 18:37:44 UTC +00:00 (example)
But how do I go about specifying or limiting this to times on the hour? For example:
Sun, 22 Sep 2013 18:00:00 UTC +00:00
Sat, 02 Nov 2013 10:00:00 UTC +00:00
Fri, 12 Apr 2013 21:00:00 UTC +00:00
I finally found what I was looking for. #Stoic's answer is very good but I found this available method (http://api.rubyonrails.org/classes/DateTime.html):
rand(1.year).ago.beginning_of_hour
Does exactly the same thing but looks neater and prevents you from having to write your own function.
Rounding datetime to the nearest hour in Rails would be
(DateTime.now + 30.minutes).beginning_of_hour
Not the answer to the actual question, but it does answer the title of the question (which is how i got here).
Try this:
def random_time_to_nearest_hour
time = rand(1.year).ago
time - time.sec - 60 * time.min
end
Examples:
[1] pry(main)> random_time_to_nearest_hour
=> Sun, 28 Apr 2013 16:00:00 UTC +00:00
[2] pry(main)> random_time_to_nearest_hour
=> Sat, 08 Jun 2013 15:00:00 UTC +00:00
[3] pry(main)> random_time_to_nearest_hour
=> Thu, 22 Aug 2013 23:00:00 UTC +00:00
[4] pry(main)> random_time_to_nearest_hour
=> Tue, 29 Jan 2013 14:00:00 UTC +00:00
[5] pry(main)> random_time_to_nearest_hour
=> Tue, 13 Aug 2013 06:00:00 UTC +00:00
[6] pry(main)> random_time_to_nearest_hour
=> Mon, 03 Jun 2013 08:00:00 UTC +00:00
[7] pry(main)>
Note that, this method will always floor down to the nearest hour, but since you are anyways generating a random time, it wont matter if this time is getting floor'ed down or getting round'ed. :)

Best way to convert timeZone in Rails

I want to convert one time from one timezone to another.
Example:
Image that I have a first Time, like
t=....
puts t
=> Sun Aug 12 00:00:00 +0200 2012
I want to have the exact same hour, but in a different time zone.
I can do this:
mytimezone.local(t.year,t.month,t.day,t.hour,t.min)
=> Sun, 12 Aug 2012 00:00:00 BRT -03:00
So is there a way to accomplish the same things in a better way?
Have you tried in_time_zone method?
> t = Time.parse("Sun Aug 12 00:00:00 +0200 2012")
=> 2012-08-12 02:00:00 +0400
> t.in_time_zone('Eastern Time (US & Canada)')
=> Sat, 11 Aug 2012 18:00:00 EDT -04:00
> mytimezone = ActiveSupport::TimeZone.new('Eastern Time (US & Canada)')
=> (GMT-05:00) Eastern Time (US & Canada)
> t.in_time_zone(mytimezone)
=> Sat, 11 Aug 2012 18:00:00 EDT -04:00

Resources