Take separate time and timezone strings, and convert into UTC - ruby

I know a bunch of ways to convert local times into UTC time with Ruby, but I'm not sure how to do this when the time and the zone are separate pieces of data. If I have something like "12:00 PM -0500", I can easily convert this into UTC with .getutc(). However, what if I have "12:00 PM" and "Eastern Time (US & Canada)"? How can I combine these two to find the UTC time?

The DateTime::parse method may be what you are looking for.
x = DateTime.parse("12:00 PM Eastern Time (US & Canada)")
will return a result of
#<DateTime: 2017-11-15T12:00:00-05:00 ((2458073j,61200s,0n),-18000s,2299161j)>
From there, there are many ways to convert the time to UTC. For example,
utc = x.new_offset(0)
will return a result of
#<DateTime: 2017-11-15T17:00:00+00:00 ((2458073j,61200s,0n),+0s,2299161j)>

If you are using Rails, or if you don't mind requiring ActiveSupport, you can use ActiveSupport::TimeZone to do this.
>> time_zone = "Eastern Time (US & Canada)"
>> time_without_zone = "12:00 PM"
>> ActiveSupport::TimeZone[time_zone].parse(time_without_zone)
#=> Wed, 15 Nov 2017 12:00:00 EST -05:00

Related

How to convert UTC to EST/EDT in Ruby?

How do I convert UTC timestamp in the format '2009-02-02 00:00:00' to EST/EDT in Ruby? Note that I am not using Rails, instead it is a simple Ruby script.
1If the date range falls between EST (usually Jan-Mid March) it needs to to UTC-5hrs. For EDT it is UTC-4hrs.
So far I have the following function to convert UTC to EST/EDT.
def utc_to_eastern(utc)
eastern = Time.parse(utc) # 2009-02-02 00:00:00 -0500
offset_num = eastern.to_s.split(" -")[1][1].to_i # 5
eastern_without_offset = (eastern-offset_num*60*60).strftime("%F %T") # 2009-02-01 19:00:00
return eastern_without_offset
end
puts utc_to_eastern("2009-02-02 00:00:00") # 2009-02-01 19:00:00
puts utc_to_eastern("2009-04-02 00:00:00") # 2009-04-01 20:00:00
The above code does what I want, however there's two issues with my solution:
I do not want to reinvent the wheel, meaning I do not wish to write the time conversion functionality instead use existing methods provided by Ruby. Is there a more intuitive way to do this?
The parsing uses my local timezone to convert UTC to EST/EDT, however I would like to explicitly define the timezone conversion ("America/New_York"). Because this means someone running this on a machine on central time would not be using EST/EDT.
The best approach would be to use TZInfo.
require 'tzinfo'
require 'time'
def utc_to_eastern utc
tz = TZInfo::Timezone.get("America/New_York")
tz.to_local(Time.parse(utc)).strftime('%Y-%m-%d %H:%M:%S')
end
utc_to_eastern "2020-02-02 00:00:00 UTC" => "2020-02-01 19:00:00"
utc_to_eastern "2020-04-02 00:00:00 UTC" => "2020-04-01 20:00:00"

Parse time with strptime using Time.zone

I am trying to parse a datetime with Time class in Ruby 2.0. I can't figure out how to parse date and get it in a specified timezone. I have used Time.zone.parse to parse a date where I first call Time.zone and set it to a specified timezone. In the below example, I set the zone but it does not effect strptime, I have tried doing Time.zone.parse(date) but I can't get it parse a date like the one below.
Time.zone = "Central Time (US & Canada)"
#=> "Central Time (US & Canada)"
irb(main):086:0> Time.strptime("08/26/2013 03:30 PM","%m/%d/%Y %I:%M %p")
#=> 2013-08-26 15:30:00 -0400
Time.zone isn’t a part of Ruby, it’s a part of ActiveSupport (which is included with Rails). As such, strptime does not know about Time.zone at all. You can, however, convert a normal Ruby Time into an ActiveSupport::TimeWithZone using in_time_zone, which uses Time.zone’s value by default:
require 'active_support/core_ext/time'
Time.zone = 'Central Time (US & Canada)'
time = Time.strptime('08/26/2013 03:30 PM', '%m/%d/%Y %I:%M %p')
#=> 2013-08-26 15:30:00 -0400
time.in_time_zone
#=> Mon, 26 Aug 2013 14:30:00 CDT -05:00
If you are only looking at Ruby2.0, you may find the time lib useful:
require 'time'
time.zone # return your current time zone
a = Time.strptime("08/26/2013 03:30 PM","%m/%d/%Y %I:%M %p")
# => 2013-08-26 15:30:00 +1000
a.utc # Convert to UTC
a.local # Convert back to local
# Or you can add/subtract the offset for the specific time zone you want:
a - 10*3600 which gives UTC time too
strptime gets its parameters from the time string. As such, the time string must contain time zone information.
If you are parsing time strings in a specific time zone, but the time strings that you receive do not have it embedded - then you can add time zone information before passing the time string to srtptime, and asking strptime to parse the time zone offset using %z or name using %Z.
In a nutshell, if you have a time string 08/26/2013 03:30 PM and you want it parsed in the UTC time zone, you would have:
str = '08/26/2013 03:30 PM'
Time.strptime("#{str} UTC}", "%m/%d/%Y %I:%M %p %Z")

Rails Time Zone and cron scheduling

I have an AWS server that runs daily cron jobs reporting on our user base. I want to ensure my report is run for the full day the previous day in MST. Currently I use this as the code for the data quering
Time.new(Time.now.year, Time.now.month, Time.now.day).yesterday.beginning_of_day.in_time_zone('MST)..Time.new(Time.now.year, Time.now.month, Time.now.day).yesterday.end_of_day.in_time_zone('MST)
I read it is bad practice to use Time.now as that is the system (UTC) time? I am wondering if what I am doing is a big no no or if there is a more efficient way?
thank you!
Mountain Standard Time is 7 hours behind UTC, so when you capture all the data points from the day of July 22rd in MST, you want the UTC times to be from 7/22 at 7:00AM UTC to 7/23 at 7:00AM UTC.
I don't think your code is correct because you are calling in_time_zone("MST") after beginning_of_day.
When you run this code on a server that is on UTC, the evaluated times are different:
>> Time.new.yesterday.beginning_of_day.in_time_zone('MST').utc
=> 2013-07-22 00:00:00 UTC
>> Time.new.in_time_zone("MST").yesterday.beginning_of_day.utc
=> 2013-07-22 07:00:00 UTC
Here is how you can determine the start and end times properly:
>> t = Time.new
=> 2013-07-23 19:45:10 +0000
>> start_time = t.in_time_zone("MST").yesterday.beginning_of_day
=> Mon, 22 Jul 2013 00:00:00 MST -07:00
>> end_time = t.in_time_zone("MST").yesterday.end_of_day
=> Mon, 22 Jul 2013 23:59:59 MST -07:00
When we convert the start and end times to UTC, we get the desired result.
>> start_time = t.in_time_zone("MST").yesterday.beginning_of_day.utc
=> 2013-07-22 07:00:00 UTC
>> end_time = t.in_time_zone("MST").yesterday.end_of_day.utc
=> 2013-07-23 06:59:59 UTC
I don't know what you are trying to do, but
Time.new(Time.now.year, Time.now.month, Time.now.day)
is definitely a terrible code fragment. For example, if the time lag between the execution time of Time.now.year and that of Time.now.month overlaps the moment of the change of the year, then the time object created with the main Time.new will be neither of the two moments. If you want to get the current time, just do
Time.new
or
Time.now
If you are trying to create a time range calculated out of a single time, then whatever your code should be, create time only once:
t = Time.now
and use that in the rest of your code:
t.some_method..t.some_other_method

formatting date in ruby

I have a date in the format 05/22/2011 13:10 Eastern Time (US & Canada)
How can I convert that into a date object? This is what I used to parse the date from sting but I'm getting invalid date error.
str = "05/22/2011 13:10 Eastern Time (US & Canada)"
Date.strptime(str, "%d/%m/%Y %H:%M:%S %Z")
Your sting: 05/22/2011 13:10 Eastern Time (US & Canada).
Here is a number of mistakes in your pattern:
Month is a first argument
Day is a second
Here is no any seconds in your time, only hours and minutes
Also your string includes Date and Time as well, so you'd better to use DateTime class instead of Date class:
Date.strptime(str, "%m/%d/%Y %H:%M %Z")
#=> Sun, 22 May 2011
or
DateTime.strptime(str, "%m/%d/%Y %H:%M %Z")
#=> Sun, 22 May 2011 13:10:00 -0500
To work with datetime you should require it first:
require 'date'
dt = DateTime.strptime("05/22/2011 13:10 Eastern Time (US & Canada)", "%m/%d/%Y %H:%M %Z")
#=> #<DateTime: 353621413/144,-5/24,2299161>
dt.to_s
#=> "2011-05-22T13:10:00-05:00"
dt.hour
#=> 13
...
There are 2 bugs in your call to Date.strptime
1) The date and month are reversed
2) There is no seconds field in your string

How to get Rails to interpret a time as being in a specific time zone?

In Ruby 1.8.7, how to set the time zone of a time?
In the following examples, my system time zone is PST (-8:00 hours from UTC)
Given a time (21 Feb 2011, 20:45), presume that the time is in EST:
#this interprets the time as system time zone, i.e. PST
Time.local(2011,02,21,20,45)
#=> Mon Feb 21 20:45:00 -0800 2011
#this **converts** the time into EST, which is wrong!
Time.local(2011,02,21,20,45).in_time_zone "Eastern Time (US & Canada)"
#=> Mon, 21 Feb 2011 23:45:00 EST -05:00
But, the output I want is:
Mon Feb 21 20:45:00 -0500 2011 (Note the -0500 (EST) as opposed to -0800 (PST) and the hour is same, i.e. 20, not 23)
UPDATE (see the better version of this below)
I managed to get this to work, but I don't like it:
DateTime.new(2011,02,21,20,45).change :offset => -(300.0 / 1440.0)
# => Mon, 21 Feb 2011 20:45:00 +0500
Where
300 = 5 hrs x 60 minutes
1440 = number of minutes in a day
or the "right" way:
DateTime.civil(2011,02,21,20,45,0,Rational(-5, 24))
Question: Now, is there a way to determine the accurate(i.e. catering for daylight saving time etc) UTC offset from Time.zone so that I can pass it to the change method?
Reference: DateTime::change method
UPDATE (better version)
Thanks to #ctcherry for all the help!
Determine the accurate time zone info from Time.zone:
DateTime.civil(2011,02,21,20,45,0,Rational((Time.zone.tzinfo.current_period.utc_offset / 3600), 24))
In ruby 1.8.7 it doesn't appear to be very easy to do what are asking for according to the documentation:
http://www.ruby-doc.org/core-1.8.7/classes/Time.html
However in 1.9 it looks a lot easier by passing the timezone offset to the localtime() method on a Time object:
http://www.ruby-doc.org/core/classes/Time.html#M000346
UPDATE
The offset for Time.zone is easy since its an object on its own: (This is in a Rails console)
ruby-1.8.7-p248 :001 > Time.zone
=> #<ActiveSupport::TimeZone:0x103150190 #current_period=nil, #name="Central Time (US & Canada)", #tzinfo=#<TZInfo::TimezoneProxy: America/Chicago>, #utc_offset=nil>
ruby-1.8.7-p248 :002 > Time.zone.utc_offset
=> -21600
ruby-1.8.7-p248 :003 > Time.zone.formatted_offset
=> "-06:00"
So I think this will (almost) accomplish what you want:
require 'time'
t = "21 Feb 2011, 20:45"
Time.parse(t) # => Mon Feb 21 20:45:00 -0700 2011
t += " -05:00" # this is the trick
Time.parse(t) # => Mon Feb 21 18:45:00 -0700 2011
It still returns the time based on your system time zone, but the actual time is the correct time that you are seeking.
By the way, this is tested on 1.8.7-p334.

Resources