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

Категории