Rails compare Time with TimeWithZone - ruby-on-rails

In my Rails app, I'm comparing a date from my database to Time.now:
Event.date_start >= Time.now
But Event.date_start is ActiveSupport::TimeWithZone
Everything works perfectly but I want to know if comparing a Time with a TimeWithZone is the proper way, and if this is not the case, how to do it?

The best practice is not to use Time.now in Rails apps. Use Time.current instead. It returns ActiveSupport::TimeWithZone too.

Time.now also works with TimeZone. Look at this:
Time.zone.now
#=> Mon, 12 Sep 2016 16:51:54 UTC +00:00
ExampleModel.date_start
#=> Tue, 28 Jun 2016 12:39:26 UTC +00:00
Time.zone
#=> #<ActiveSupport::TimeZone:0x0055de104e3ce0 #name="UTC", #utc_offset=nil, #tzinfo=#<TZInfo::TimezoneProxy: Etc/UTC>, #current_period=#<TZInfo::TimezonePeriod: nil,nil,#<TZInfo::TimezoneOffset: 0,0,UTC>>>>
You can compare this without restrictions :)

Related

Rails Time Zone: How to pass time zone configured in application.rb file as parameter to the to_datetime method?

I configured my time zone to indian time zone in my Rails app by adding this line config.time_zone = 'Mumbai' to my application.rb file.
I am having a date time field t.datetime :check_in in my table. To this check_in column I am saving the server time like this Person.check_in = DateTime.now. When I save like this, the time is saving properly, with the time zone configured in the app. after that for some reason when I update like this Person.check_in = "24/08/2015 11:50 AM".to_datetime it is not saving the time with the time zone I configured. Below is my rails console output:
prashant#prashant-pc:~/client_proj/template$ rails c
Loading development environment (Rails 4.1.5)
2.2.2 :001 > check_in = DateTime.now
=> Mon, 24 Aug 2015 11:41:16 +0530
2.2.2 :003 > "24/08/2015 11:42 PM".to_datetime
=> Mon, 24 Aug 2015 23:42:00 +0000
2.2.2 :004 >
This is unfortunately the designed behavior of to_datetime function.
This other question is what you are after. They provide the following alternatives:
Time.zone.parse('24/08/2015 11:50 AM').to_datetime
or even:
"24/08/2015 11:50 AM".to_datetime.in_time_zone("Mumbai")
Use in_time_zone from ActiveSupport::TimeWithZone
"2015-08-14 14:38".to_datetime.in_time_zone('Mumbai')
=> Fri, 14 Aug 2015 20:08:00 IST +05:30
"2015-08-14 14:38".to_datetime.in_time_zone('Eastern Time (US & Canada)')
=> Fri, 14 Aug 2015 10:38:00 EDT -04:00
Time.now.in_time_zone("Mumbai")
=> Sat, 22 Aug 2015 12:38:32 IST +05:30
Time.now.in_time_zone("Pacific Time (US & Canada)")
=> Sat, 22 Aug 2015 00:08:21 PDT -07:00
Actually, there are several ways to do the same thing e.g. using Time.zone.local, Time.zone.parse etc. See the above link for more examples.
To, answer your exact question, to pass the time_zone configured in your application.rb file, you have to use this:
check_in = DateTime.now
check_in.in_time_zone(Rails.application.config.time_zone).to_datetime
Use local time gem. It will display the time in local time zone no matter where you are.
It is very good solution as you will don't have to call in time zone method every time you show a date in your views. The Gem has very good documentation as well. Visit https://github.com/basecamp/local_time

Correcting time for daylight savings in rails DateTime.parse

Just adding to the million questions about time zone and DST issues out there.
I have a form with separate date and time fields that I combine to create a DateTime like so
start_time = DateTime.parse("#{parse_date(form_date)} #{form_start_time} #{Time.zone}")
If I fill out my form with 21 Aug 2012 and 15:00, then these are the values that I see when I reload my form. If I then look at my start_time attribute in my model it is correctly set to Tue, 21 Aug 2012 15:00:00 EST +10:00.
The problem I am having occurs if I use a date later this year once daylight savings kicks in (I am in Australia). If I use 21 Dec 2012 and 15:00 then check start_time I see Fri, 21 Dec 2012 16:00:00 EST +11:00.
My interpretation of the problem is that the date is being saved in my current time zone (+10:00) as this is what I have told DateTime.parse to do. However when the value is returned, Rails is looking at the date and saying 'hey, it's daylight savings time in December' and returning the time in the +11:00 time zone.
What I want to do is tell DateTime.parse to save the time in the +11:00 time zone if DST is in effect. Clearly passing Time.zone into my string doesn't achieve this. Is there a simple way of doing this? I can see ways of doing it using Time#dst? but I suspect that this is going to create some really ugly convoluted code. I thought there might be a built in way that I'm missing.
(Answer for Rails 4.2.4, didn't check for older or newer versions)
Instead of using fixed shift +01:00, +02:00, etc, I recommend to use the in_time_zone String method with time zone name as argument :
Summer time :
ruby :001 > "2016-07-02 00:00:00".in_time_zone('Paris')
=> Sat, 02 Jul 2016 00:00:00 CEST +02:00
Winter time :
ruby :002 > "2016-11-02 00:00:00".in_time_zone('Paris')
=> Wed, 02 Nov 2016 00:00:00 CET +01:00
String#in_time_zone is the equivalent of :
ruby :003 > Time.find_zone!("Paris").parse("2016-07-02 00:00:00")
=> Sat, 02 Jul 2016 00:00:00 CEST +02:00
ruby :004 > Time.find_zone!("Paris").parse("2016-11-02 00:00:00")
=> Wed, 02 Nov 2016 00:00:00 CET +01:00
You can get the time zone names by :
$ rake time:zones:all
Or in rails console :
ruby :001 > ActiveSupport::TimeZone.all.map(&:name)
Or build collection for select tag :
ActiveSupport::TimeZone.all.map do |timezone|
formatted_offset = Time.now.in_time_zone(timezone.name).formatted_offset
[ "(GMT#{formatted_offset}) #{timezone.name}", timezone.name ]
end
And store the time zone name instead of the shift.
Note : don't confuse String#in_time_zone method and the Time#in_time_zone method.
consider the time zone for my system is 'Paris'.
ruby :001 > Time.parse("2016-07-02 00:00:00")
=> 2016-07-02 00:00:00 +0200
ruby :002 > Time.parse("2016-07-02 00:00:00").in_time_zone("Nuku'alofa")
=> Sat, 02 Jul 2016 11:00:00 TOT +13:00
Here's my solution so far. I'm hoping someone has a better one.
start_time = DateTime.parse "#{date} #{(form_start_time || start_time)} #{Time.zone}"
start_time = start_time - 1.hour if start_time.dst? && !Time.now.dst?
start_time = start_time + 1.hour if Time.now.dst? && start_time.dst?
It seems to work but I haven't rigorously tested it. I suspect it could be prettied up and shortened but I think this is readable and understandable. Any improvements?
I ran into this exact issue. My app allows users to see upcoming events. In the US we fall of DST on November 2nd and all events on and after that date were showing times an hour early.
We require the opportunity to have the timezone selected and stored to its own field. Before I was using the following to store my datetime:
timezone_offset = Time.now.in_time_zone(params[:opportunity][:time_zone]).strftime("%z") #-0700
DateTime.parse("#{params[:opportunity][:start_datetime]} #{timezone_offset}")
To fix the issue I have changed to:
start_datetime = Time.zone.parse(params[:opportunity][:start_datetime])
To display the correct times we use:
#opportunity.start_datetime.in_time_zone(#opportunity.time_zone)
I wouuld try and use
Australian Eastern Standard Time (AEST) (UTC +10).
Australian Central Standard Time (ACST) (UTC +9 ½).
Australian Western Standard Time (AWST) (UTC +8).
which adjust for Daylight Savings.
With Rails, we can use ActiveSupport::TimeZone for this:
tz = ActiveSupport::TimeZone.new 'Pacific Time (US & Canada)'
tz.parse(date_str_without_zone).to_datetime
I use TZip to get TimeZone strings (e.g. "Pacific Time (US & Canada)") from zip codes.
In case you have a custom date/time format, different than the supported by String#in_time_zone, you could also use (since rails 5) strptime like:
Time.find_zone!('Auckland').strptime('2021-02-02 08.00.00', '%Y-%m-%d %H.%M.%S')

ROR + TodayDate in UTC Format

If I have #today = Date.today.to_s, how do I convert #today into UTC (with the appropriate date only)?
Here I need is only date for example 2011-03-08 ie 08 March 2011. Please suggest something ?
Acutally I am looking for Yesterday date ??
You'll need to convert it to a Time object (or just use Time anyway) and then call Time#utc:
irb > Time.now
=> Tue Mar 08 15:32:36 +1100 2011
irb > Time.now.utc
=> Tue Mar 08 04:32:40 UTC 2011
You can then format it however you need it:
irb > #today = Time.now.utc
=> Tue Mar 08 04:34:25 UTC 2011
irb > #today.strftime("%Y-%m-%d")
=> "2011-03-08"
If you want to convert #today into UTC.
Then try this
>> #today = Date.today.to_s
>> DateTime.parse(#today)
Try
1.day.ago.utc
or
1.day.ago.utc.strftime('%b %B, %Y')
The format below should give you the date format of Yesterday which you are looking for formatted as 07 March, 2011. Look into the ruby Time class manual for more information on strftime time formating function. Good luck!
I strongly recommend you to move towards time zones in rails. Its easier and lot more convenient to work with than Time.now. You should be able to set the time zone in environment.rb with config.time_zone = "Chennai" or your time zone. After doing this, you should be able to get the time with UTC information by Doing Time.zone.now. To find the UTC offset, you could type Time.zone.

Rails custom TimeZone

How can I set my rails application with custom timezone? I want to set my application timezone with UTC-04:00 without daylight saving.
update: To clarify my question, I understand that I can set the time zone name in the environment.rb file with one of the names from TimeZone constants. I want my application timezone as UTC -04:00 without daylight saving.
Thanks,
Soe Moe
Suppose you want a timezone that does NOT use DST, and basically tracks Eastern Standard Time always (also known as Eastern Prevailing Time). You want it to be a -05:00 offset always.
Use the Timezone Etc/GMT+5. Rails will lazy-load timezones not in its normal list from TZInfo, so you can cherry pick from: http://tzinfo.rubyforge.org/svn/trunk/tzinfo/lib/tzinfo/definitions/Etc/
Here are some examples to prove this, in my Rails 3.2 rails console:
Daylight Savings example. October 4 is daylight savings time. You would expect your hours to be off by one for Eastern time and your special timezone. 2011-10-05 04:57:23 in UTC should be 2011-10-05 00:57:23 Eastern Daylight Time.
1.9.3p0 :014 > DateTime.civil(2011,10,5,4,57,23)
=> Wed, 05 Oct 2011 04:57:23 +0000
1.9.3p0 :015 > DateTime.civil(2011,10,5,4,57,23).in_time_zone('Eastern Time (US & Canada)')
=> Wed, 05 Oct 2011 00:57:23 EDT -04:00
1.9.3p0 :016 > DateTime.civil(2011,10,5,4,57,23).in_time_zone('Etc/GMT+5')
=> Tue, 04 Oct 2011 23:57:23 GMT+5 -05:00
Non-Daylight Savings example. December 1 is standard time. You would expect Eastern time and your custom "always EST" to be equivalent. 2011-12-01 02:30:45 should be 2011-11-30 21:30:45 in both EST and your special timezone.
1.9.3p0 :010 > DateTime.civil(2011,12,1,2,30,45)
=> Thu, 01 Dec 2011 02:30:45 +0000
1.9.3p0 :012 > DateTime.civil(2011,12,1,2,30,45).in_time_zone('Eastern Time (US & Canada)')
=> Wed, 30 Nov 2011 21:30:45 EST -05:00
1.9.3p0 :013 > DateTime.civil(2011,12,1,2,30,45).in_time_zone('Etc/GMT+5')
=> Wed, 30 Nov 2011 21:30:45 GMT+5 -05:00
Why is it GMT+5 and not GMT-5? I'm not entirely sure. I would have expected it to be GMT-5, but this is how the etc timezones seem to work in TZInfo.
Normally you'd only want to do this when the location you live in doesn't practice Daylight Savings Time. In your particular case, you're in luck, and can use the "La Paz" timezone. According to this website, it doesn't practice DST.
You can always run rake time:zones:all and see what UTC offsets correspond to the offset you're interested in using. Then google those offsets to see if they change during the year.
I don't know of a general method to tell Rails you want UTC +/- a fixed offset.

Why does this rails query behave differently depending on timezone?

I have a rails time-based query which has some odd timezone sensitive behaviour, even though as far as I know I'm using UTC. In a nutshell, these queries give different answers:
>> Model.find(:all,:conditions=>['created_at<=?',(Time.now-1.hours).gmtime]).length
=> 279
>> Model.find(:all,:conditions=>['created_at<=?',(Time.now-1.hours)]).length
=> 280
Where the DB actually does contain one model created in the last hour, and the total number of models is 280. So only the first query is correct.
However, in environment.rb I have:
config.time_zone = 'UTC'
The system time zone (as reported by 'date') is BST (which is GMT+1) - so somehow this winds up getting treated as UTC and breaking queries.
This is causing me all sorts of problems as I need to parameterise the query passing in different times to an action (which are then converted using Time.parse()), and even though I send in UTC times, this 'off by one hour' DST issue crops a lot. Even using '.gmtime()' doesn't always seem to fix it.
Obviously the difference is caused somehow by an implicit conversion somewhere resulting in BST being incorrectly treated as UTC, but why? Doesn't rails store the timestamps in UTC? Isn't the Time class timezone aware? I am using Rails 2.2.2
So what is going on here - and what is the safe way to program around it?
edit, some additional info to show what the DB and Time class are doing:
>> Model.find(:last).created_at
=> Tue, 11 Aug 2009 20:31:07 UTC +00:00
>> Time.now
=> Tue Aug 11 22:00:18 +0100 2009
>> Time.now.gmtime
=> Tue Aug 11 21:00:22 UTC 2009
The Time class isn't directly aware of your configured timezone. Rails 2.1 added a bunch of timezone support, but Time will still act upon your local timezone. This is why Time.now returns a BST time.
What you likely want is to interact with Time.zone. You can call methods on this like you would the Time class itself but it will return it in the specified time zone.
Time.zone.now # => Tue, 11 Aug 2009 21:31:45 UTC +00:00
Time.zone.parse("2:30 PM Aug 23, 2009") # => Sun, 23 Aug 2009 14:30:00 UTC +00:00
Another thing you have to be careful with is if you ever do queries on the database where you are comparing times, but sure to use the UTC time (even if you have a different time zone specified) because Rails always stores UTC in the database.
Item.all(:conditions => ["published_at <= ?", Time.now.utc])
Also, instead of Time.now-1.hour do 1.hour.ago. It is easier to read and Rails will automatically use the configured timezone.
The TimeZone you need to set is UK, this will automatically handle BST
Time.zone = 'UK'
Time.zone.now
=> Sun, 17 Oct 2010 02:09:54 BST +01:00
start_date_format = DateTime.strptime(#start_date, date_format)
start_date_format_with_hour =
DateTime.strptime((start_date_format.to_i + timezone_offset*60*60).to_s,'%s').strftime(date_format)
end_date_format = DateTime.strptime(#end_date, date_format)
end_date_format_with_hour = DateTime.strptime((end_date_format.to_i + timezone_offset*60*60).to_s,'%s').strftime(date_format)
#filters_date = "invoices.created_at >= ? AND invoices.created_at < ?", start_date_format_with_hour, end_date_format_with_hour

Resources