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"]

 

See Also

  • Recipe 3.1, "Finding Today's Date"


Strings

Numbers

Date and Time

Arrays

Hashes

Files and Directories

Code Blocks and Iteration

Objects and Classes8

Modules and Namespaces

Reflection and Metaprogramming

XML and HTML

Graphics and Other File Formats

Databases and Persistence

Internet Services

Web Development Ruby on Rails

Web Services and Distributed Programming

Testing, Debugging, Optimizing, and Documenting

Packaging and Distributing Software

Automating Tasks with Rake

Multitasking and Multithreading

User Interface

Extending Ruby with Other Languages

System Administration



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

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net