# Converting Between Time and DateTime Objects

Problem

You're working with both DateTime and Time objects, created from Ruby's two standard date/time libraries. You can't mix these objects in comparisons, iterations, or date arithmetic because they're incompatible. You want to convert all the objects into one form or another so that you can treat them all the same way.

Solution

To convert a Time object to a DateTime, you'll need some code like this:

```	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

time = Time.gm(2000, 6, 4, 10, 30, 22, 4010)
# => Sun Jun 04 10:30:22 UTC 2000
time.to_datetime.to_s
# => "2000-06-04T10:30:22Z"
```

Converting a DateTime to a Time is similar; you just need to decide whether you want the Time object to use local time or GMT. This code adds the conversion method to Date, the superclass of DateTime, so it will work on both Date and DateTime objects.

```	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

(datetime = DateTime.new(1990, 10, 1, 22, 16, Rational(41,2))).to_s
# => "1990-10-01T22:16:20Z"
datetime.to_gm_time
# => Mon Oct 01 22:16:20 UTC 1990
datetime.to_local_time
# => Mon Oct 01 17:16:20 EDT 1990
```

Discussion

Ruby's two ways of representing dates and times don't coexist very well. But since neither can be a total substitute for the other, you'll probably use them both during your Ruby career. The conversion methods let you get around incompatibilities by simply converting one type to the other:

```	time < datetime
# ArgumentError: comparison of Time with DateTime failed
time.to_datetime < datetime
# => false
time < datetime.to_gm_time
# => false

time - datetime
# TypeError: can't convert DateTime into Float
(time.to_datetime - datetime).to_f
# => 3533.50973962975 # Measured in days
time - datetime.to_gm_time
# => 305295241.50401 # Measured in seconds
```

The methods defined above are reversible: you can convert back and forth between Date and DateTime objects without losing accuracy.

```	time # => Sun Jun 04 10:30:22 UTC 2000
time.usec # => 4010'

time.to_datetime.to_gm_time # => Sun Jun 04 10:30:22 UTC 2000
time.to_datetime.to_gm_time.usec # => 4010

datetime.to_s # => "1990-10-01T22:16:20Z"
datetime.to_gm_time.to_datetime.to_s # => "1990-10-01T22:16:20Z"
```

Once you can convert between Time and DateTime objects, it's simple to write code that normalizes a mixed array, so that all its elements end up being of the same type. This method tries to turn a mixed array into an array containing only Time objects. If it encounters a date that won't fit within the constraints of the Time class, it starts over and converts the array into an array of DateTime objects instead (thus losing anyinformation about Daylight Saving Time):

```	def normalize_time_types(array)
# Don't do anything if all the objects are already of the same type.
first_class = array[0].class
first_class = first_class.super if first_class == DateTime
return unless array.detect { |x| !x.is_a?(first_class) }

normalized = array.collect do |t|
if t.is_a?(Date)
begin
t.to_local_time
rescue ArgumentError # Time out of range; convert to DateTimes instead.
convert_to = DateTime
break
end
else
t
end
end

unless normalized
normalized = array.collect { |t| t.is_a?(
Time) ? t.to_datetime : t }
end
return normalized
end
```

When all objects in a mixed array can be represented as either Time or DateTime objects, this method makes them all Time objects:

```	mixed_array = [Time.now, DateTime.now]
# => [Sat Mar 18 22:17:10 EST 2006,
# #]
normalize_time_types(mixed_array)
# => [Sat Mar 18 22:17:10 EST 2006, Sun Mar 19 03:17:10 EST 2006]
```

If one of the DateTime objects can't be represented as a Time, normalize_time_types turns all the objects into DateTime instances. This code is run on a system with a 32-bit time counter:

```	mixed_array << DateTime.civil(1776, 7, 4)
normalize_time_types(mixed_array).collect { |x| x.to_s }
# => ["2006-03-18T22:17:10-0500", "2006-03-18T22:17:10-0500",
# => "1776-07-04T00:00:00Z"]
```

• Recipe 3.1, "Finding Today's Date"

Ruby Cookbook (Cookbooks (OReilly))
ISBN: 0596523696
EAN: 2147483647
Year: N/A
Pages: 399