How do you convert between a DateTime and a Time object in Ruby?
While making such conversions one should take into consideration the behavior of timezones while converting from one object to the other. I found some good notes and examples in this stackoverflow post.
As an update to the state of the Ruby ecosystem, Date
, DateTime
and Time
now have methods to convert between the various classes. Using Ruby 1.9.2+:
pry
[1] pry(main)> ts = 'Jan 1, 2000 12:01:01'
=> "Jan 1, 2000 12:01:01"
[2] pry(main)> require 'time'
=> true
[3] pry(main)> require 'date'
=> true
[4] pry(main)> ds = Date.parse(ts)
=> #<Date: 2000-01-01 (4903089/2,0,2299161)>
[5] pry(main)> ds.to_date
=> #<Date: 2000-01-01 (4903089/2,0,2299161)>
[6] pry(main)> ds.to_datetime
=> #<DateTime: 2000-01-01T00:00:00+00:00 (4903089/2,0,2299161)>
[7] pry(main)> ds.to_time
=> 2000-01-01 00:00:00 -0700
[8] pry(main)> ds.to_time.class
=> Time
[9] pry(main)> ds.to_datetime.class
=> DateTime
[10] pry(main)> ts = Time.parse(ts)
=> 2000-01-01 12:01:01 -0700
[11] pry(main)> ts.class
=> Time
[12] pry(main)> ts.to_date
=> #<Date: 2000-01-01 (4903089/2,0,2299161)>
[13] pry(main)> ts.to_date.class
=> Date
[14] pry(main)> ts.to_datetime
=> #<DateTime: 2000-01-01T12:01:01-07:00 (211813513261/86400,-7/24,2299161)>
[15] pry(main)> ts.to_datetime.class
=> DateTime
require 'time'
require 'date'
t = Time.now
d = DateTime.now
dd = DateTime.parse(t.to_s)
tt = Time.parse(d.to_s)
You'll need two slightly different conversions.
To convert from Time
to DateTime
you can amend the Time class as follows:
require 'date'
class Time
def to_datetime
# Convert seconds + microseconds into a fractional number of seconds
seconds = sec + Rational(usec, 10**6)
# Convert a UTC offset measured in minutes to one measured in a
# fraction of a day.
offset = Rational(utc_offset, 60 * 60 * 24)
DateTime.new(year, month, day, hour, min, seconds, offset)
end
end
Similar adjustments to Date will let you convert DateTime
to Time
.
class Date
def to_gm_time
to_time(new_offset, :gm)
end
def to_local_time
to_time(new_offset(DateTime.now.offset-offset), :local)
end
private
def to_time(dest, method)
#Convert a fraction of a day to a number of microseconds
usec = (dest.sec_fraction * 60 * 60 * 24 * (10**6)).to_i
Time.send(method, dest.year, dest.month, dest.day, dest.hour, dest.min,
dest.sec, usec)
end
end
Note that you have to choose between local time and GM/UTC time.
Both the above code snippets are taken from O'Reilly's Ruby Cookbook. Their code reuse policy permits this.
Improving on Gordon Wilson solution, here is my try:
def to_time
#Convert a fraction of a day to a number of microseconds
usec = (sec_fraction * 60 * 60 * 24 * (10**6)).to_i
t = Time.gm(year, month, day, hour, min, sec, usec)
t - offset.abs.div(SECONDS_IN_DAY)
end
You'll get the same time in UTC, loosing the timezone (unfortunately)
Also, if you have ruby 1.9, just try the to_time
method
Unfortunately, the DateTime.to_time, Time.to_datetime
and Time.parse
functions don't retain the timezone info. Everything is converted to local timezone during conversion. Date arithmetics still work but you won't be able to display the dates with their original timezones. That context information is often important. For example, if I want to see transactions performed during business hours in New York I probably prefer to see them displayed in their original timezones, not my local timezone in Australia (which 12 hrs ahead of New York).
The conversion methods below do keep that tz info.
For Ruby 1.8, look at Gordon Wilson's answer. It's from the good old reliable Ruby Cookbook.
For Ruby 1.9, it's slightly easier.
require 'date'
# Create a date in some foreign time zone (middle of the Atlantic)
d = DateTime.new(2010,01,01, 10,00,00, Rational(-2, 24))
puts d
# Convert DateTime to Time, keeping the original timezone
t = Time.new(d.year, d.month, d.day, d.hour, d.min, d.sec, d.zone)
puts t
# Convert Time to DateTime, keeping the original timezone
d = DateTime.new(t.year, t.month, t.day, t.hour, t.min, t.sec, Rational(t.gmt_offset / 3600, 24))
puts d
This prints the following
2010-01-01T10:00:00-02:00
2010-01-01 10:00:00 -0200
2010-01-01T10:00:00-02:00
The full original DateTime info including timezone is kept.