views:

18415

answers:

5

How do you convert between a DateTime and a Time object in Ruby?

+13  A: 

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 snippits are taken from O'Reilly's Ruby Cookbook. Their code reuse policy permits this.

Gordon Wilson
+4  A: 

This isn't really that hard.

require 'date'

date_time = DateTime.now
# #<DateTime: blah>
date_time.to_time
# #<Time: blah>

time = Time.now
# #<Time: blah>
time.to_datetime
# #<DateTime: blah>
Patrick McKenzie
this doesn't actually work, as written. If you're doing it within rails, for example it partially works (time.to_datetime returns a datetime, but date_time.to_time returns a datetime too), but in ruby alone it fails entirely.
Cameron Price
Epic fail. Classic example of how Rails has polluted Rubyspace. Ramaze FTW!
Pistos
This DOES work. I just loaded up irb, and ran that code, and it worked perfectly. Using 1.8.6.
Myrddin Emrys
Just checked, and it runs in ruby 1.9.0 as well, using revision 20050ish.
Myrddin Emrys
"Works for me", ruby 1.9.0, revision 14709.
Patrick McKenzie
Doesn't work on Ruby-186-26.
Alexander Prokofyev
Works like a charm in the context of Rails. No need for the require 'date'
Sean McCleary
`DateTime.new.to_time` doesn't work on `ruby 1.8.7 (2008-08-11 patchlevel 72) [universal-darwin10.0]`... which shows... I don't know what.
Yar
It's just part of active_support, it works perfectly out of rails...
knoopx
Beside not working in Ruby 1.8, the timezone info is also lost when you're dealing with dates or times with foreign timezone offsets.
Alkaline
+16  A: 
require 'time'
require 'date'

t = Time.now
d = DateTime.now

dd = DateTime.parse(t.to_s)
tt = Time.parse(d.to_s)
anshul
+1 This may not be the most efficient in execution, but it works, it's concise, and it's very readable.
Walt Gordon Jones
Unfortunately this only really works when dealing with local times. If you start with a DateTime or Time with a different timezone, the parse function will convert into local timezone. You basically lose the original timezone.
Alkaline
As of ruby 1.9.1, DateTime.parse does preserve timezone. (I do not have access to earlier versions.)Time.parse doesn't preserve timezone, because it represents the POSIX-standard time_t, which I believe is an integer difference from epoch. Any conversion to Time should have the same behaviour.
anshul
You're right. DateTime.parse works in 1.9.1 but not Time.parse. In any case, it's less error prone (consistent) and likely faster to use DateTime.new(...) and Time.new(..). See my answer for sample code.
Alkaline
@Alkaline, I am not sure I get what you mean. This method is consistent and error free. Do you have any good reasons for implying otherwise?
anshul
Hi @anshul. I'm not implying I'm stating :-). Timezone info is not kept when using Time.parse(). It's easy to test. In your code above, simply replace d = DateTime.nowwith d = DateTime.new(2010,01,01, 10,00,00, Rational(-2, 24)). tt will now show the date d converted into your local timezone. You can still do date arithmetics and all but the original tz info is lost. This info is a context for the date and it is often important.See here: http://stackoverflow.com/questions/279769/convert-to-from-datetime-and-time-in-ruby/3513247#3513247
Alkaline
@Alkaline: `Time` does not store time zone information and this is the intended behaviour. Thus, no method of converting a `DateTime` to `Time` will "preserve" timezone.
anshul
@asnhul. The Ruby 1.9 Time class does support timezone. I keep repeating the same thing but just check the answer I gave below. It has code that converts from DateTime to Time and back to DateTime while preserving the timezone info. The Ruby Time class is a wrapper for the C Standard Library time functions and those don't support timezone (it's stripped). That's true except that the timezone support is implemented in the ruby wrapper. Just check the time.rb file in the Ruby src distribution. Or, just run my sample code to convince yourself.
Alkaline
A: 

Can't add comment at proper location, this will have to do.

@Gordon Wilson:

Works for me with the following caveat: to_local_time() returns the utc time instead. I believe the 'DateTime.now.offset-offset' is overkill, since you are specifying ':local' already. The two terms cancel each other out.

A: 

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.

Alkaline
wow, this sucks.. Why is dealing with time so dang hard!!
allyourcode