Tutorial #48
Time Zones Background   2014-01-31

Introduction

With visitors to your web site in all parts of the world, it is important that any Time values that you provide are represented in their local Time Zone.

Image 1 for this tutorial

This image is from Wikimedia Commons

We deal with Time Zones all the time when we travel or when we call friends in another country. But in that case we know exactly where we and the other party are located and we either know or we can obtain the time difference.

In the general sense of a web site, with visitors from around the world, the situation is a lot more involved. We need to ask for, or determine their location so we can figure out the Time Zone. We then need to check if that zone is currently using Daylight Saving Time. And it gets more complicated still once we have to deal with mobile devices like phones and laptops.

This tutorial offers some background to Time Zones and some of the issues that arise when you use them in your applications.


Time Zone Standards

It takes 24 hours for the Earth to make one rotation so it would seem logical that there are only 24 time zones. If only life were that simple...

While it true that the vast majority of places around the planet use one of the 24 one hour offsets from UTC, the names given to time zones are far more complex. The main standard for these is the IANA Time Zone Database and the names listed here are the usually the ones that you use to refer to zones in your code... but not always. Another way to view the zones is Here.


Storing Times on the Server

Storing time information on a server is straightforward as long as we convert it to Coordinated Universal Time (UTC), which is equivalent to Greenwich Mean Time (GMT). Storing time data as UTC gives us a single reference point that we can convert into the local time zone for a specific user.

Many databases and server frameworks will treat all times as UTC, but don't assume this. Look at the configuration documentation and test it out.

Rails uses this default but you can override it. There is a rake task that will show the current 'local' timezone that the server is set up to use. I run this server on Heroku, which uses Amazon EC2 servers which could be located in any time zone. I don't know where they are - and I don't need to if I use UTC. Here is the configuration for the Web Apprentice server, showing that it is using UTC:

$ heroku run rake time:zones:local
* UTC +00:00 *

Converting To and From UTC

Time zone conversion should be straightforward in all server frameworks, as long as the source and destination time zones are clearly specified.

Most of my server work is written in Rails - this is how you do it in that framework (I'm using the Rails Console, running on Heroku - and I have edited the console prompts)

# The server uses UTC so the default time output uses that
> Time.now
=> 2014-02-04 16:57:08 +0000

# I can specify a target time zone to make the conversion
# Note the difference in format
> Time.now.in_time_zone("Pacific Time (US & Canada)")
=> Tue, 04 Feb 2014 08:57:09 PST -08:00

# Casting the output to a string ensures a standard output format
> Time.now.to_s
=> "2014-02-04 16:57:22 +0000"
> Time.now.in_time_zone("Pacific Time (US & Canada)").to_s
=> "2014-02-04 08:57:27 -0800"

The in_time_zone method comes from Rails, not from Ruby, so this will not work outside of a Rails application.

All the IANA standard time zone names appear to be recognized but when you list out all the zones from Rails you see some differences, which is confusing:

> ActiveSupport::TimeZone.all.map(&:name)
=> ["American Samoa", "International Date Line West", "Midway Island", "Hawaii", 
"Alaska", "Pacific Time (US & Canada)", "Tijuana", "Arizona",
...
]

To convert a Time into UTC you use the same approach with UTC as the target

> t = Time.now.in_time_zone("Pacific Time (US & Canada)")
=> Tue, 04 Feb 2014 09:56:57 PST -08:00

> t.in_time_zone('UTC')
=> Tue, 04 Feb 2014 17:56:57 UTC +00:00

While I am on the topic on conversion, a standard way to represent Time information in computer systems is Unix Time which is defined as the number of seconds since 00:00:00 1 January 1970 (also known as the Epoch)

In Ruby/Rails you generate the Unix Time by simply casting a Time value to an integer, like this:

> Time.now.to_i
=> 1391550625

We will need this representation in the next section of the tutorial


Determining the User's Time Zone

Now we know how to do the conversion, but how do we determine which Time Zone the user is located in?

Bear in mind that there are TWO pieces of information that you need to specify the correct current time zone.

Where the user is located is the most important, but you also need to know When the request is taking place so you can compensate for Daylight Savings Time if necessary.

We have several options, depending on our server application and the device being used by the user to access it.

#1: Ask the User

If your site has user accounts then a simple and effective way is to have the user select their time zone from a pull down menu and store this in the database.

But if the application has no concept of accounts then you would have to store this in a cookie and provide some way for the user to update the zone if their location changes. Even with the user input, you still need to decide is a Daylight Savings Time correction currently applies.

#2: Use the User's Location

If we know the location of the user, with reasonable accuracy, then we can use that, combined with the current Time, to find the correct Time Zone.

The Google Time Zone API provides a convenient way to do this.

You access this API by creating a custom URL with several parameters, which you send to the Google endpoint. In response, the API sends a block of text that specifies the current time difference from UTC.

A URL and the response looks like this:

$ curl "https://maps.googleapis.com/maps/api/timezone/json?location=47.606209,-122.332071×tamp=1331161200&sensor=false"
{
   "dstOffset" : 0,
   "rawOffset" : -28800,
   "status" : "OK",
   "timeZoneId" : "America/Los_Angeles",
   "timeZoneName" : "Pacific Standard Time"
}

The URL can be broken down like this:
The endpoint at Google is https://maps.googleapis.com/maps/api/timezone
The response can be in JSON or XML, which you specify by adding /json or /xml to the URL
 
Three parameters are required:

Note that there is no API key required to use this API, but this may be required for heavy use in commercial sites.

The response shown here is in JSON format, which I prefer over XML.

The most useful key/value pair in the response is the timeZoneId value (America/Los_Angeles), which can be passed to in_time_zone.

So to use the Time Zone API in Rails, the steps might be:

... I said it was complicated

Sending an HTTP request to the Google API for Every time conversion is not feasible. In practice you might check the timezone everytime they log in, or perhaps once a day, storing the value in a cookie or the database.


How do we get the User's Location?

This API doesn't do anything for us unless we have the location of the user. In Tutorial 19 I described how Geolocation can be used in HTML5. This is a good solution only if we can get a reasonably accurate location.

Using GPS

Many modern smart phones include GPS chips which allow them to determine their current location with high accuracy. Tutorial 19 showed how to access this using navigator.geolocation.getCurrentPosition. You could use this to get the Latitude and Longitude values and pass them to the server as hidden fields in a form, via Ajax or by setting a cookie.

This has the great advantage that as the user physically moves into another Time Zone, you have the potential of detecting this right away and reacting to it. This mimics the behaviour that users expect in their phones which update their clock automatically.

Using the IP address

Unfortunately many device do not contain GPS chips. With these, geolocation will attempt to derive location based on the IP address that is being used. Depending on the specific network they connect to this can work well even if the accuracy of geolocation is low, placing the user somewhere in a given city.

There are two problems with this, however. On some networks the ability to localize a machine may be extremely poor, being able to say no more than the user is somewhere in the US, for example.

But a more serious issue exists if the user connects via the internal network for a large company. I have seen this with users on the internal network for international pharmaceutical companies where their requests are routed from the origin (Germany) through the private network to a gateway on the East Coast of the US. The IP address for that gateway is what is used in geolocation, resulting in a time zone that is grossly incorrect.

The accuracy data given by geolocation can indicate if there might be a problem with location. In that case your application might prompt the user to confirm the estimate of their Time Zone or change it if needed.


On the Client use the Unix Time for Timestamps

Unix Time, as mentioned above, describes a point in time as the number of seconds since the Epoch (1 January 1970) and as such this should be a universal reference for all computers.

To send a timestamp to the Server, from a form or via Ajax, you could send it as the Unix Time.

The Date library has a number of functions that can parse and manipulate dates and timestamps.

If the device being used has a Time Zone configured then this will be used when creating a Date object.

Also among these is getTime() which converts a Date object to the number of Milliseconds since the Epoch (Milliseconds - NOT seconds!)

So you can use this to represent a timestamp in the browser in universal Unix Time and send that to the server, where the application can convert into its internal timestamp format. For example: in the browser's console:

>d = new Date();
Tue Feb 04 2014 16:14:49 GMT-0800 (PST)
> Math.round(d.getTime() / 1000)
1391559290

At that same time, on the server, the Rails console gave the same value:

Time.now.to_i
=> 1391559290

The JavaScript Date library does not report the current Time Zone as a text string but it does have a function called getTimezoneOffset() which uses the Time Zone that the device is currently set to. This comes from the computer system, so if we can trust that configuration then we can trust the value from this function.


Summary

Time Zones are hard - you need to be aware of how these are handled in the browser and the server - you need to TEST all the code that works with timestamps - and even then you can run into problems when devices move between zones.

Keeping all timestamps internally in UTC / Unix Time and only applying conversions when you need to present the values to the users seems to be the best approach.


More Information

Wikipedia entry on Time Zones

Google Time Zone API

Railscast - Time Zones and Rails

JavaScript Date library


Share this tutorial



Comment on this Tutorial


comments powered by Disqus