Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 956
brad Avatar asked Aug 21 '12 13:08

brad


3 Answers

(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 
like image 98
Jean-Marc Delafont Avatar answered Nov 15 '22 11:11

Jean-Marc Delafont


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?

like image 21
brad Avatar answered Nov 15 '22 12:11

brad


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)
like image 2
Styledev Avatar answered Nov 15 '22 13:11

Styledev