sonic
sonic

Reputation: 1430

How to convert string(cointaining offset) to date/time in rails

2.5.0 :071 > "2018-06-16 22:39:09 +0200".to_datetime
 => Sat, 16 Jun 2018 22:39:09 +0200 
2.5.0 :072 > "2018-06-16 22:39:09 +0200".to_datetime.utc_offset
 => 7200 
2.5.0 :073 > "2018-06-16 22:39:09 +0200".to_datetime.utc
 => 2018-06-16 20:39:09 UTC 

How to convert now 2018-06-16 20:39:09 UTC to 2018-06-16 22:39:09 +0200 again

rails console when trying second solution

Running via Spring preloader in process 9869
Loading development environment (Rails 5.2.0)
2.5.0 :001 > orig = "2018-06-16 22:39:09 +0200".to_datetime
 => Sat, 16 Jun 2018 22:39:09 +0200 
2.5.0 :002 > orig_offset = orig.zone # => "+2:00"
 => "+02:00" 
2.5.0 :003 > 
2.5.0 :004 > new = orig.utc
 => 2018-06-16 20:39:09 UTC 
2.5.0 :005 > 
2.5.0 :006 > same_as_orig = new.change(offset: orig_offset)
 => 2018-06-16 20:39:09 +0200 
2.5.0 :007 > same_as_orig == orig # => true
 => false 
2.5.0 :008 > 

Upvotes: 0

Views: 1362

Answers (1)

Cary Swoveland
Cary Swoveland

Reputation: 110725

Not knowing Rails, I will give a pure-Ruby answer (which may be of interest in its own right). For this reason, I cannot help with the extraction of the time zone name. I understand that can be done with Rails or by installing the tzinfo gem.

We begin with a given string:

str = "2018-06-16 22:39:09 +0200"

In Ruby we will need use of various methods from the classes Time and DateTime, so we must require 'time' or require 'date' (not required by Rails, I am told). Note DateTime.superclass #=> Date.

require 'time'

The first step is to create a DateTime object from this string. Two ways of doing that are to use DateTime::parse or DateTime::strptime, the latter being the more demanding and therefore more reliable method.

dtp = DateTime.parse(str)
  #=> #<DateTime: 2018-06-16T22:39:09+02:00 ((2458286j,74349s,0n),+7200s,2299161j)>
dt = DateTime.strptime(str, '%Y-%m-%d %H:%M:%S %z')
  #=> #<DateTime: 2018-06-16T22:39:09+02:00 ((2458286j,74349s,0n),+7200s,2299161j)>

Another approach is to convert the string to an array and then use DateTime::new or Time::new:

*all_but_last, offset = str.split(/[- :]/)
  #=> ["2018", "06", "16", "22", "39", "09", "+0200"]
all_but_last
  #=> ["2018", "06", "16", "22", "39", "09"]
offset
  #=> "+02:00"
arr = [*all_but_last.map(&:to_i), offset.insert(-3, ':')]
  #=> [2018, 6, 16, 22, 39, 9, "+02:00"]
DateTime.new(*arr)
  #=> #<DateTime: 2018-06-16T22:39:09+02:00 ((2458286j,74349s,0n),+7200s,2299161j)>
Time.new(*arr)
  #=> 2018-06-16 22:39:09 +0200

For the time being, trust me that UTC times are in fact instances of Time.

Browsing the methods of the class Time we find that it provides all the methods we need to convert between UTC time and local time, namely Time.gmtime, Time#utc_offset (aka, gmt_offset) and Time#getlocal.

The next step, therefore, is to convert the DateTime object dt to a time object, using DateTime#to_time:

t = dt.to_time
  #=> 2018-06-16 22:39:09 +0200

We may now convert this Time instance to a UTC time:

ut = t.gmtime
 #=> 2018-06-16 20:39:09 UTC

My earlier assertion that UTC times are Time instances can now be confirmed:

ut.class
  #=> Time

To convert this back to a local time we must save the local time's UTC offset:

offset = t.utc_offset
  #=> 7200

This offset is measured in seconds for GMT (7200/3600 = 2 hours).

We may now compute the local time from ut and offset:

ut.getlocal(offset)
  #=> 2018-06-16 22:39:09 +0200

Upvotes: 3

Related Questions