Daylight Saving Time start and end dates in Ruby/Rails - ruby-on-rails

I'm working on a Rails app where I need to find the Daylight Saving Time start and end dates given a specific offset or timezone.
I basically save in my database the timezone offset received from a user's browser( "+3", "-5") and I want to modify it when it changes because of daylight saving time.
I know Time instance variables have the dst? and isdst methods which return true or false if the date stored in them is in the daylight saving time or not.
> Time.new.isdst
=> true
But using this to find the Daylight Saving Time beginning and end dates would take too many resources and I also have to do it for each timezone offset I have.
I would like to know a better way of doing this.

Ok, building on what you've said and #dhouty's answer:
You want to be able to feed in an offset and get a set of dates for knowing if there is a DST offset or not. I would recommend ending up with a range made of two DateTime objects, as that is easily used for many purposes in Rails...
require 'tzinfo'
def make_dst_range(offset)
if dst_end = ActiveSupport::TimeZone[offset].tzinfo.current_period.local_end
dst_start = ActiveSupport::TimeZone[offset].tzinfo.current_period.local_start
dst_range = dst_start..dst_end
else
dst_range = nil
end
end
Now you have a method that can do more than just take an offset thanks to the sugar that comes with ActiveSupport. You can do things like:
make_dst_range(-8)
#=> Sun, 08 Mar 2015 03:00:00 +0000..Sun, 01 Nov 2015 02:00:00 +0000
make_dst_range('America/Detroit')
#=> Sun, 08 Mar 2015 03:00:00 +0000..Sun, 01 Nov 2015 02:00:00 +0000
make_dst_range('America/Phoenix')
#=> nil #returns nil because Phoenix does not observe DST
my_range = make_dst_range(-8)
#=> Sun, 08 Mar 2015 03:00:00 +0000..Sun, 01 Nov 2015 02:00:00 +0000
Today happens to be August 29th so:
my_range.cover?(Date.today)
#=> true
my_range.cover?(Date.today + 70)
#=> false
my_range.first
#=> Sun, 08 Mar 2015 03:00:00 +0000
#note that this is a DateTime object. If you want to print it use:
my_range.first.to_s
#=> "2015-03-08T03:00:00+00:00"
my_range.last.to_s
#=> "2015-11-01T02:00:00+00:00"
ActiveSupport gives you all sorts of goodies for display:
my_range.first.to_formatted_s(:short)
#=> "08 Mar 03:00"
my_range.first.to_formatted_s(:long)
#=> "March 08, 2015 03:00"
my_range.first.strftime('%B %d %Y')
#=> "March 08 2015"
As you can see it's completely doable with just the offset, but as I said, offset doesn't tell you everything, so you might want to grab their actual time zone and store that as a string since the method will happily accept that string and still give you the date range. Even if you are just getting the time offset between your zone and theirs, you can easily figure correct that to the UTC offset:
my_offset = -8
their_offset = -3
utc_offset = my_offset + their_offset

What you are probably looking for is TZInfo::TimezonePeriod. Specifically, the methods local_start/utc_start and local_end/utc_end.
Given a timezone offset, you can get a TZInfo::TimezonePeriod object with
ActiveSupport::TimeZone[-8].tzinfo.current_period
Or if you have a timezone name, you can also get a TZInfo::TimezonePeriod object with
ActiveSupport::TimeZone['America/Los_Angeles'].tzinfo.current_period

Related

In Ruby or Rails, what is the best way to convert Eastern Time to UTC?

I'm not referring to
myDateTime = DateTime.now
myDateTime.new_offset(Rational(0, 24))
or
Time.now.utc
What I have is a text date is given in Eastern Time.
I can convert that text date into a DateTime. Let's call it eastern_date_time.
Now, we have a variable containing an DateTime, but nothing knows it's eastern besides us. Converting it ourselves would be quite onerous. If the date in Daylight Savings Time (DST) (March 8 to November 1st this year), we'd have to add 4 hours to our eastern_date_time var to get UTC, and if the date is in Standard Time (ST) we'd have to add 5 hours to our eastern_date_time variable.
How can we specify that what we have is an Eastern DateTime, and then convert it to UTC... something that will determine if the date is in the DST/ST, and apply the 4 or 5 hours properly?
I want to convert any sort of date I get into UTC, for storage in my database.
EDIT:
Using `in_time_zone', I'm unable to convert my Eastern Text Time to UTC. How can I achieve that objective? For example...
text_time = "Nov 27, 2015 4:30 PM" #given as Eastern
myEasternDateTime = DateTime.parse text_time # => Fri, 27 Nov 2015 16:30:00 +0000
#now we need to specify that this myEasternDateTime is in fact eastern. However, it's our default UTC. If we use in_time_zone, it just converts the date at UTC to Eastern
myEasternDateTime.in_time_zone('Eastern Time (US & Canada)') # => Fri, 27 Nov 2015 11:30:00 EST -05:00
myEasternDateTime.utc # => Fri, 27 Nov 2015 16:30:00 +0000
That's not what we want. We have to specify that myEasterDateTime is in fact eastern... so that when we do a myEasterDateTime.utc on 16:30:00 we end up getting 20:30:00.
How can I accomplish this?
There was a time_in_zone method in the DateTime class:
now.time_in_zone('UTC')
It has since been renamed to in_time_zone:
DateTime.now.in_time_zone('US/Pacific')
=> Wed, 22 Apr 2015 12:36:33 PDT -07:00
The objects of Time class have a method called dst? which basically tells you whether or not DST is applicable or not. So you can basically identify whether DST/ST is applicable and decide which to add - 4 or 5.
e.g. Time.now.dst?
If it returns true, add 4, otherwise add 5.
I got it like this with the help of the time zone suggestions.
time_text_1 = "Apr 20, 2015 4:30PM" #Scraped as an Eastern Time, with no offset of -5:00 from UTC included
time_text_2 = "Nov 20, 2015 4:30PM" #Scraped as an Eastern Time, with no offset of -5:00 from UTC included
Time.zone = 'Eastern Time (US & Canada)'
my_time_1 = Time.zone.parse time_text_1 # Output: Mon, 20 Apr 2015 16:30:00 EDT -04:00
my_time_2 = Time.zone.parse time_text_2 # Output: Fri, 20 Nov 2015 16:30:00 EST -05:00
my_time_1.utc # Output: 2015-04-20 20:30:00 UTC
my_time_2.utc # Output: 2015-11-20 21:30:00 UTC
In the edited post, your string of time needs an offset from UTC.
EDIT III: Based on the comments (just having a string set to represent Eastern Time and needing to account for DST, etc.)
text_time = "Nov 27, 2015 4:30 PM"
the_offset = Time.zone_offset('EST') / (60*60)
eastern_time = DateTime.parse(text_time).change(offset: the_offset.to_s) # Fri, 27 Nov 2015 16:30:00 -0500
eastern_time.utc # Fri, 27 Nov 2015 21:30:00 +0000

How can I convert a Time from some timezone to UTC, taking into account Daylight Saving, in Ruby?

I have an app where different users are in different parts of the globe (I know their timezone), and they can enter dates and times, and I need to store everything in UTC on the DB.
Normally to instance my Time variable, I'm doing:
DateTime.new(date.year, date.month, date.day, hours, minutes, 0)
This is already in UTC, but without converting.
If I add a 7th parameter to that string with the time_zone (ie. "Melbourne"), then it will interpret it as a a date in Australia, and the resulting DateTime, when converted to UTC, is 10-ish hours behind, and that works.
However, that's not taking into account Daylight Saving.
What I need is to do exactly that (instance a DateTime from the components of a date/time), such that (in the case of Australia, for example), using the same hours/minutes for April 9th will give me a different offset when converted to UTC than if I used April 5th.
Any ideas?
Thanks!
Daniel
Have a look at tzinfo
irb(main):002:0> require 'tzinfo'
=> true
irb(main):003:0> tz = TZInfo::Timezone.get("Australia/Melbourne")
=> #<TZInfo::DataTimezone: Australia/Melbourne>
irb(main):004:0> tz.utc_to_local(Time.parse("2013-04-05 00:00:00"))
=> Fri Apr 05 11:00:00 UTC 2013
irb(main):005:0> tz.utc_to_local(Time.parse("2013-04-09 00:00:00"))
=> Tue Apr 09 10:00:00 UTC 2013
irb(main):015:0> tz.local_to_utc(Time.parse("2013-04-09 00:00:00"))
=> Mon Apr 08 14:00:00 UTC 2013
irb(main):016:0> tz.local_to_utc(Time.parse("2013-04-05 00:00:00"))
=> Thu Apr 04 13:00:00 UTC 2013

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' utc_to_local and daylight savings time

> e = Event.first
> e.registration_start_utc #registration_start_utc is a datetime column
=> Sat, 23 Oct 2010 06:38:00 UTC +00:00
> e.registration_start_utc.utc?
=> true
> ActiveSupport::TimeZone.find_tzinfo("America/New_York").utc_to_local(e.registration_start_utc)
=> Sat, 23 Oct 2010 02:38:00 UTC +00:00
2 questions about this:
1) Why is that last output showing "UTC" -- the hour got converted (6 => 2) but it still says UTC. Why not EST/EDT?
2) What happens after daylight savings time switches over and the offset for New York moves from -4 to -5? The value in the DB doesn't change so my only conclusion is that my app will start showing "1:38" everywhere instead of the correct 2:38?
I'm mostly concerned with #2 here. #1 is more of a curiosity.
Thanks!
2) utc_to_local uses the date to determine which offset is correct, so the output will always be the same for a given date.
You can test for that like this:
t = Time.utc(2011,3, 14, 12)
# => 2011-03-14 12:00:00 UTC
t2 = Time.utc(2011,3, 11, 12)
# => 2011-03-11 12:00:00 UTC
ActiveSupport::TimeZone.find_tzinfo("America/New_York").utc_to_local(t)
# => 2011-03-14 08:00:00 UTC
ActiveSupport::TimeZone.find_tzinfo("America/New_York").utc_to_local(t2)
# => 2011-03-14 07:00:00 UTC
1) It doesn't seem right to me either. My guess is that they are interested only in the actual value of the hour, minutes, etc... and disregard the timezone.
In any case, you might be better off using:
e.registration_start_utc.in_time_zone("Eastern Time (US & Canada)")
See also this question I just asked...

Resources