Converting Between Time Zones

Problem

You want to change a time object so that it represents the same moment of time in some other time zone.

Solution

The most common time zone conversions are the conversion of system local time to UTC, and the conversion of UTC to local time. These conversions are easy for both Time and DateTime objects.

The Time#gmtime method modifies a Time object in place, converting it to UTC. The Time#localtime method converts in the opposite direction:

	now = Time.now # => Sat Mar 18 20:15:58 EST 2006
	now = now.gmtime # => Sun Mar 19 01:15:58 UTC 2006
	now = now.localtime # => Sat Mar 18 20:15:58 EST 2006

The DateTime.new_offset method converts a DateTime object from one time zone to another. You must pass in the dstination time zone's offset from UTC; to convert local time to UTC, pass in zero. Since DateTime objects are immutable, this method creates a new object identical to the old DateTime object, except for the time zone offset:

	require 'date'
	local = DateTime.now
	local.to_s # => "2006-03-18T20:15:58-0500"
	utc = local.new_offset(0)
	utc.to_s # => "2006-03-19T01:15:58Z"

To convert a UTC DateTime object to local time, you'll need to call DateTime#new_offset and pass in the numeric offset for your local time zone. The easiest way to get this offset is to call offset on a DateTime object known to be in local time. The offset will usually be a rational number with a denominator of 24:

	local = DateTime.now
	utc = local.new_offset

	local.offset # => Rational(-5, 24)
	local_from_utc = utc.new_offset(local.offset)
	local_from_utc.to_s # => "2006-03-18T20:15:58-0500"
	local == local_from_utc # => true

 

Discussion

Time objects created with Time.at, Time.local, Time.mktime, Time.new, and Time.now are created using the current system time zone. Time objects created with Time.gm and Time.utc are created using the UTC time zone. Time objects can represent any time zone, but it's difficult to use a time zone with Time other than local time or UTC.

Suppose you need to convert local time to some time zone other than UTC. If you know the UTC offset for the destination time zone, you can represent it as a fraction of a day and pass it into DateTime#new_offset:

	#Convert local (Eastern) time to Pacific time
	eastern = DateTime.now
	eastern.to_s # => "2006-03-18T20:15:58-0500"

	pacific_offset = Rational(-7, 24)
	pacific = eastern.new_offset(pacific_offset)
	pacific.to_s # => "2006-03-18T18:15:58-0700"

DateTime#new_offset can convert between arbitrary time zone offsets, so for time zone conversions, it's easiest to use DateTime objects and convert back to Time objects if necessary. But DateTime objects only understand time zones in terms of numeric UTC offsets. How can you convert a date and time to UTC when all you know is that the time zone is called "WET", "Zulu", or "Asia/Taskent"?

On Unix systems, you can temporarily change the "system" time zone for the current process. The C library underlying the Time class knows about an enormous number of time zones (this "zoneinfo" database is usually located in /usr/share/zoneinfo/, if you want to look at the available time zones). You can tap this knowledge by setting the environment variable TZ to an appropriate value, forcing the Time class to act as though your computer were in some other time zone. Here's a method that uses this trick to convert a Time object to any time zone supported by the underlying C library:

	class Time
	 def convert_zone(to_zone)
	 original_zone = ENV["TZ"]
	 utc_time = dup.gmtime
	 ENV["TZ"] = to_zone
	 to_zone_time = utc_time.localtime
	 ENV["TZ"] = original_zone
	 return to_zone_time
	 end
	end

Let's do a number of conversions of a local (Eastern) time to other time zones across the world:

	t = Time.at(1000000000) # => Sat Sep 08 21:46:40 EDT 2001

	t.convert_zone("US/Pacific") # => Sat Sep 08 18:46:40 PDT 2001
	t.convert_zone("US/Alaska") # => Sat Sep 08 17:46:40 AKDT 2001

	t.convert_zone("UTC") # => Sun Sep 09 01:46:40 UTC 2001
	t.convert_zone("Turkey") # => Sun Sep 09 04:46:40 EEST 2001

Note that some time zones, like India's, are half an hour offset from most others:

	t.convert_zone("Asia/Calcutta") # => Sun Sep 09 07:16:40 IST 2001

By setting the TZ environment variable before creating a Time object, you can represent the time in any time zone. The following code converts Lagos time to Singapore time, regardless of the "real" underlying time zone.

	ENV["TZ"] = "Africa/Lagos"
	t = Time.at(1000000000) # => Sun Sep 09 02:46:40 WAT 2001
	ENV["TZ"] = nil

	t.convert_zone("Singapore") # => Sun Sep 09 09:46:40 SGT 2001

	# Just to prove it's the same time as before:
	t.convert_zone("US/Eastern") # => Sat Sep 08 21:46:40 EDT 2001

Since the TZ environment variable is global to a process, you'll run into problems if you have multiple threads trying to convert time zones at once.

See Also

  • Recipe 3.9, "Converting Between Time and DateTime Objects"
  • Recipe 3.8, "Checking Whether Daylight Saving Time Is in Effect"
  • Information on the "zoneinfo" database (http://www.twinsun.com/tz/tz-link.htm)


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