The problem:
I was recently tasked with building an application that relied heavily on accurate time zone conversions. I, like many people I soon found out, thought there were just a handful of timezones and the usual select list would suffice:
(UTC -12:00) Eniwetok, Kwajalein
(UTC -11:00) Midway Island, Samoa
(UTC -10:00) Hawaii
(UTC -9:00) Alaska
(UTC -8:00) Pacific Time (US &Canada)
(UTC -7:00) Mountain Time (US &Canada)
(UTC -6:00) Central Time (US &Canada), Mexico City
(UTC -5:00) Eastern Time (US &Canada), Bogota, Lima
(UTC -4:00) Atlantic Time (Canada),Caracas, La Paz
(UTC -3:30) Newfoundland
(UTC -3:00) Brazil, Buenos Aires,Georgetown
(UTC -2:00) Mid-Atlantic
(UTC -1:00 hour) Azores, Cape VerdeIslands
(UTC) Western Europe Time, London, Lisbon,Casablanca
(UTC +1:00 hour) Brussels, Copenhagen,Madrid, Paris
(UTC +2:00) Kaliningrad, South Africa
(UTC +3:00) Baghdad, Riyadh, Moscow, St.Petersburg
(UTC +3:30) Tehran
(UTC +4:00) Abu Dhabi, Muscat, Baku,Tbilisi
(UTC +4:30) Kabul
(UTC +5:00) Ekaterinburg, Islamabad,Karachi, Tashkent
(UTC +5:30) Bombay, Calcutta, Madras, NewDelhi
(UTC +5:45) Kathmandu
(UTC +6:00) Almaty, Dhaka, Colombo
(UTC +7:00) Bangkok, Hanoi, Jakarta
(UTC +8:00) Beijing, Perth, Singapore,HongKong
(UTC +9:00) Tokyo, Seoul, Osaka, Sapporo,Yakutsk
(UTC +9:30) Adelaide, Darwin
(UTC +10:00) Eastern Australia, Guam,Vladivostok
(UTC +11:00) Magadan, Solomon Islands,New Caledonia
(UTC +12:00) Auckland, Wellington, Fiji, Kamchatka
The deeper I looked into the problem, the deeper it got: the list above only shows a few time offsets from UTC, but it doesn’t tell me, beyond a shadow of a doubt, exactly what time it is where the user is situated, nor can I rely on that time for calculations in the future. The fact is, there are a LOT of timezones in the world. Hundreds. If you have a linux server, you can see exactly how many there are by opening up /usr/share/zoneinfo/zone.tab , but here is the full list:
Africa/Abidjan Africa/Accra Africa/Addis_Ababa Africa/Algiers Africa/Asmara Africa/Asmera Africa/Bamako Africa/Bangui Africa/Banjul Africa/Bissau Africa/Blantyre Africa/Brazzaville Africa/Bujumbura Africa/Cairo Africa/Casablanca Africa/Ceuta Africa/Conakry Africa/Dakar Africa/Dar_es_Salaam Africa/Djibouti Africa/Douala Africa/El_Aaiun Africa/Freetown Africa/Gaborone Africa/Harare Africa/Johannesburg Africa/Kampala Africa/Khartoum Africa/Kigali Africa/Kinshasa Africa/Lagos Africa/Libreville Africa/Lome Africa/Luanda Africa/Lubumbashi Africa/Lusaka Africa/Malabo Africa/Maputo Africa/Maseru Africa/Mbabane Africa/Mogadishu Africa/Monrovia Africa/Nairobi Africa/Ndjamena Africa/Niamey Africa/Nouakchott Africa/Ouagadougou Africa/Porto-Novo Africa/Sao_Tome Africa/Timbuktu Africa/Tripoli Africa/Tunis Africa/Windhoek America/Adak America/Anchorage America/Anguilla America/Antigua America/Araguaina America/Argentina/Buenos_Aires America/Argentina/Catamarca America/Argentina/ComodRivadavia America/Argentina/Cordoba America/Argentina/Jujuy America/Argentina/La_Rioja America/Argentina/Mendoza America/Argentina/Rio_Gallegos America/Argentina/Salta America/Argentina/San_Juan America/Argentina/San_Luis America/Argentina/Tucuman America/Argentina/Ushuaia America/Aruba America/Asuncion America/Atikokan America/Atka America/Bahia America/Barbados America/Belem America/Belize America/Blanc-Sablon America/Boa_Vista America/Bogota America/Boise America/Buenos_Aires America/Cambridge_Bay America/Campo_Grande America/Cancun America/Caracas America/Catamarca America/Cayenne America/Cayman America/Chicago America/Chihuahua America/Coral_Harbour America/Cordoba America/Costa_Rica America/Cuiaba America/Curacao America/Danmarkshavn America/Dawson America/Dawson_Creek America/Denver America/Detroit America/Dominica America/Edmonton America/Eirunepe America/El_Salvador America/Ensenada America/Fort_Wayne America/Fortaleza America/Glace_Bay America/Godthab America/Goose_Bay America/Grand_Turk America/Grenada America/Guadeloupe America/Guatemala America/Guayaquil America/Guyana America/Halifax America/Havana America/Hermosillo America/Indiana/Indianapolis America/Indiana/Knox America/Indiana/Marengo America/Indiana/Petersburg America/Indiana/Tell_City America/Indiana/Vevay America/Indiana/Vincennes America/Indiana/Winamac America/Indianapolis America/Inuvik America/Iqaluit America/Jamaica America/Jujuy America/Juneau America/Kentucky/Louisville America/Kentucky/Monticello America/Knox_IN America/La_Paz America/Lima America/Los_Angeles America/Louisville America/Maceio America/Managua America/Manaus America/Marigot America/Martinique America/Mazatlan America/Mendoza America/Menominee America/Merida America/Mexico_City America/Miquelon America/Moncton America/Monterrey America/Montevideo America/Montreal America/Montserrat America/Nassau America/New_York America/Nipigon America/Nome America/Noronha America/North_Dakota/Center America/North_Dakota/New_Salem America/Panama America/Pangnirtung America/Paramaribo America/Phoenix America/Port-au-Prince America/Port_of_Spain America/Porto_Acre America/Porto_Velho America/Puerto_Rico America/Rainy_River America/Rankin_Inlet America/Recife America/Regina America/Resolute America/Rio_Branco America/Rosario America/Santarem America/Santiago America/Santo_Domingo America/Sao_Paulo America/Scoresbysund America/Shiprock America/St_Barthelemy America/St_Johns America/St_Kitts America/St_Lucia America/St_Thomas America/St_Vincent America/Swift_Current America/Tegucigalpa America/Thule America/Thunder_Bay America/Tijuana America/Toronto America/Tortola America/Vancouver America/Virgin America/Whitehorse America/Winnipeg America/Yakutat America/Yellowknife Antarctica/Casey Antarctica/Davis Antarctica/DumontDUrville Antarctica/Mawson Antarctica/McMurdo Antarctica/Palmer Antarctica/Rothera Antarctica/South_Pole Antarctica/Syowa Antarctica/Vostok Arctic/Longyearbyen Asia/Aden Asia/Almaty Asia/Amman Asia/Anadyr Asia/Aqtau Asia/Aqtobe Asia/Ashgabat Asia/Ashkhabad Asia/Baghdad Asia/Bahrain Asia/Baku Asia/Bangkok Asia/Beirut Asia/Bishkek Asia/Brunei Asia/Calcutta Asia/Choibalsan Asia/Chongqing Asia/Chungking Asia/Colombo Asia/Dacca Asia/Damascus Asia/Dhaka Asia/Dili Asia/Dubai Asia/Dushanbe Asia/Gaza Asia/Harbin Asia/Ho_Chi_Minh Asia/Hong_Kong Asia/Hovd Asia/Irkutsk Asia/Istanbul Asia/Jakarta Asia/Jayapura Asia/Jerusalem Asia/Kabul Asia/Kamchatka Asia/Karachi Asia/Kashgar Asia/Kathmandu Asia/Katmandu Asia/Kolkata Asia/Krasnoyarsk Asia/Kuala_Lumpur Asia/Kuching Asia/Kuwait Asia/Macao Asia/Macau Asia/Magadan Asia/Makassar Asia/Manila Asia/Muscat Asia/Nicosia Asia/Novosibirsk Asia/Omsk Asia/Oral Asia/Phnom_Penh Asia/Pontianak Asia/Pyongyang Asia/Qatar Asia/Qyzylorda Asia/Rangoon Asia/Riyadh Asia/Saigon Asia/Sakhalin Asia/Samarkand Asia/Seoul Asia/Shanghai Asia/Singapore Asia/Taipei Asia/Tashkent Asia/Tbilisi Asia/Tehran Asia/Tel_Aviv Asia/Thimbu Asia/Thimphu Asia/Tokyo Asia/Ujung_Pandang Asia/Ulaanbaatar Asia/Ulan_Bator Asia/Urumqi Asia/Vientiane Asia/Vladivostok Asia/Yakutsk Asia/Yekaterinburg Asia/Yerevan Atlantic/Azores Atlantic/Bermuda Atlantic/Canary Atlantic/Cape_Verde Atlantic/Faeroe Atlantic/Faroe Atlantic/Jan_Mayen Atlantic/Madeira Atlantic/Reykjavik Atlantic/South_Georgia Atlantic/St_Helena Atlantic/Stanley Australia/ACT Australia/Adelaide Australia/Brisbane Australia/Broken_Hill Australia/Canberra Australia/Currie Australia/Darwin Australia/Eucla Australia/Hobart Australia/LHI Australia/Lindeman Australia/Lord_Howe Australia/Melbourne Australia/North Australia/NSW Australia/Perth Australia/Queensland Australia/South Australia/Sydney Australia/Tasmania Australia/Victoria Australia/West Australia/Yancowinna Brazil/Acre Brazil/DeNoronha Brazil/East Brazil/West Canada/Atlantic Canada/Central Canada/East-Saskatchewan Canada/Eastern Canada/Mountain Canada/Newfoundland Canada/Pacific Canada/Saskatchewan Canada/Yukon CET Chile/Continental Chile/EasterIsland CST6CDT Cuba EET Egypt Eire EST EST5EDT Etc/GMT Etc/GMT+0 Etc/GMT+1 Etc/GMT+10 Etc/GMT+11 Etc/GMT+12 Etc/GMT+2 Etc/GMT+3 Etc/GMT+4 Etc/GMT+5 Etc/GMT+6 Etc/GMT+7 Etc/GMT+8 Etc/GMT+9 Etc/GMT-0 Etc/GMT-1 Etc/GMT-10 Etc/GMT-11 Etc/GMT-12 Etc/GMT-13 Etc/GMT-14 Etc/GMT-2 Etc/GMT-3 Etc/GMT-4 Etc/GMT-5 Etc/GMT-6 Etc/GMT-7 Etc/GMT-8 Etc/GMT-9 Etc/GMT0 Etc/Greenwich Etc/UCT Etc/Universal Etc/UTC Etc/Zulu Europe/Amsterdam Europe/Andorra Europe/Athens Europe/Belfast Europe/Belgrade Europe/Berlin Europe/Bratislava Europe/Brussels Europe/Bucharest Europe/Budapest Europe/Chisinau Europe/Copenhagen Europe/Dublin Europe/Gibraltar Europe/Guernsey Europe/Helsinki Europe/Isle_of_Man Europe/Istanbul Europe/Jersey Europe/Kaliningrad Europe/Kiev Europe/Lisbon Europe/Ljubljana Europe/London Europe/Luxembourg Europe/Madrid Europe/Malta Europe/Mariehamn Europe/Minsk Europe/Monaco Europe/Moscow Europe/Nicosia Europe/Oslo Europe/Paris Europe/Podgorica Europe/Prague Europe/Riga Europe/Rome Europe/Samara Europe/San_Marino Europe/Sarajevo Europe/Simferopol Europe/Skopje Europe/Sofia Europe/Stockholm Europe/Tallinn Europe/Tirane Europe/Tiraspol Europe/Uzhgorod Europe/Vaduz Europe/Vatican Europe/Vienna Europe/Vilnius Europe/Volgograd Europe/Warsaw Europe/Zagreb Europe/Zaporozhye Europe/Zurich Factory GB GB-Eire GMT GMT+0 GMT-0 GMT0 Greenwich Hongkong HST Iceland Indian/Antananarivo Indian/Chagos Indian/Christmas Indian/Cocos Indian/Comoro Indian/Kerguelen Indian/Mahe Indian/Maldives Indian/Mauritius Indian/Mayotte Indian/Reunion Iran Israel Jamaica Japan Kwajalein Libya MET Mexico/BajaNorte Mexico/BajaSur Mexico/General MST MST7MDT Navajo NZ NZ-CHAT Pacific/Apia Pacific/Auckland Pacific/Chatham Pacific/Easter Pacific/Efate Pacific/Enderbury Pacific/Fakaofo Pacific/Fiji Pacific/Funafuti Pacific/Galapagos Pacific/Gambier Pacific/Guadalcanal Pacific/Guam Pacific/Honolulu Pacific/Johnston Pacific/Kiritimati Pacific/Kosrae Pacific/Kwajalein Pacific/Majuro Pacific/Marquesas Pacific/Midway Pacific/Nauru Pacific/Niue Pacific/Norfolk Pacific/Noumea Pacific/Pago_Pago Pacific/Palau Pacific/Pitcairn Pacific/Ponape Pacific/Port_Moresby Pacific/Rarotonga Pacific/Saipan Pacific/Samoa Pacific/Tahiti Pacific/Tarawa Pacific/Tongatapu Pacific/Truk Pacific/Wake Pacific/Wallis Pacific/Yap Poland Portugal PRC PST8PDT ROC ROK Singapore Turkey UCT Universal US/Alaska US/Aleutian US/Arizona US/Central US/East-Indiana US/Eastern US/Hawaii US/Indiana-Starke US/Michigan US/Mountain US/Pacific US/Pacific-New US/Samoa UTC W-SU WET Zulu
So it looked like I was going to have to output hundreds of timezone options in order to have something accurate to pass to PHP’s DateTimeZone class and from there perform my calculations against the server time zone. DateTimeZone is insanely great because it offers so much information, including the offset from GMT (or UTC.. the distinction is negligible for this type of application) and whether or not that time zone is currently in daylight savings.
So, the problem was: do I list all the available time zones in the giant select list and hope the user knows which one they’re in? Some would argue that this drop down list is too long to be usable. This is valid because it’s a very long list, and might be confusing. I personally think the jury is still out on this though, because there are around 190 countries in the World on a given day, yet we still seem to find our own. The difference is that most of us know which country we’re in, but will we necessarily know our actual timezone? Users just might not know which one to choose. For example: Vancouver, BC is in time zone “America/Vancouver” whereas Vancouver, WA, just across the border is in time zone “America/Los_Angeles”. This could be a little confusing.
The solutions:
First I thought I could use something like IP2Location data to pull the time zone for the user’s IP, but this would not be reliable for my particular application where the user was choosing a meeting time that was in the future. Where they are when setting the meeting and where they will be when in the meeting could be different, and to make matters worse, one of us could go into daylight savings between now and then. Strike it from the list.
I decided on the approach of using a combination of the Google Maps API and GeoNames.org web service to get the user’s time zone based on the geographic location they enter into a text field, and the latitude / longitude returned by Google’s geocoding service.
The basic steps:
Check that there’s input text
Send to google
Validate the result
Make call to php script for GeoNames
Validate the result
Format a string with the results that the user can confirm
Display results
The HTML:
1
2
3
<input style="padding: 5px; border: 1px solid #333; font-weight: bold" type="text" name="address" id="address" size="40" maxsize="100" /> <a href="#" id="lookup">Look-up</a><br />
<span id="result"> </span></p>
<div id="map" style="width: 293px; height: 100px"></div>
The Javascript:
My example uses a little Mootools, so you if you base yours on this you will also have to include Mootools Core.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
var Demo = {
start: function ( ) {
Demo.lookup ( ) ;
} ,
lookup: function ( ) {
$( 'lookup' ) .addEvent ( 'click' , function ( e) {
e.stop ( ) ;
$( 'result' ) .set ( 'html' , ' ' ) ;
var address = $( 'address' ) .value ;
if ( address== '' ) {
alert ( "Please enter your location in the text field" ) ;
$( 'address' ) .focus ( ) ;
} else {
map = new GMap2( $( "map" ) ) ;
var geocoder;
geocoder = new GClientGeocoder( ) ;
geocoder.getLocations ( address, Demo.addToMap ) ;
}
} )
} ,
addToMap: function ( response) {
map.setCenter ( new GLatLng( 47.040182144806664 ,- 30.234375 ) , 1 ) ;
map.clearOverlays ( ) ;
if ( ! response || response.Status .code != 200 ) {
reset_map( ) ;
} else {
var place = response.Placemark [ 0 ] ;
point = new GLatLng( place.Point .coordinates [ 1 ] , place.Point .coordinates [ 0 ] ) ;
map.checkResize ( ) ;
map.setCenter ( point, 6 ) ;
var marker = new GMarker( point) ;
map.addOverlay ( marker) ;
var coords = place.Point .coordinates ;
var tmp = String( place.Point .coordinates ) ;
var coords = tmp.split ( "," ) ;
var lat = coords[ 1 ] ;
var lng = coords[ 0 ] ;
try {
//eg paris
var city = place.AddressDetails .Country .AdministrativeArea .SubAdministrativeArea .Locality .LocalityName ;
} catch ( err) {
try {
//eg vancouver
var city = place.AddressDetails .Country .AdministrativeArea .Locality .LocalityName ;
} catch ( err) {
try {
//eg dubai
var city = place.AddressDetails .Country .Locality .LocalityName ;
} catch ( err) {
//eg Monaco
var city = place.AddressDetails .Country .CountryName ;
}
}
}
Demo.getTimezone ( city, lat, lng) ;
}
} ,
getTimezone: function ( city, lat, lng) {
new Request( {
url: "index.php" ,
data: "lat=" + lat+ "&lng=" + lng,
onSuccess: function ( response) {
var responseArray = JSON.decode ( response) ;
var timezone = responseArray.timezone ;
var result = "You are located in " + city+ ". <br />Your timezone is " + timezone;
$( 'result' ) .set ( 'html' , result) ;
}
} ) .send ( ) ;
}
}
window.addEvent ( 'load' , function ( ) {
Demo.start ( ) ;
} ) ;
The PHP (index.php):
1
2
3
4
5
6
7
8
9
10
if ( isset ( $_POST [ 'lat' ] ) ) {
$doc = new DOMDocument( ) ;
$location = "http://ws.geonames.org/timezone?lat=" . $_POST [ 'lat' ] . '&lng=' . $_POST [ 'lng' ] ;
if ( $doc -> load ( $location ) ) {
$timezones = $doc -> getElementsByTagName ( "timezoneId" ) ;
$coordinates [ 'timezone' ] = $timezones -> item ( 0 ) -> nodeValue ;
}
echo json_encode ( $coordinates ) ;
die ;
}
…and that’s it! Now you have a real time zone for the user so you can use PHP’s DateTimeZone class to make reliable date and time calculations. This particular implementation isn’t fool proof.. you would have to have more checks and communication back to the user to make this more functional, but the concept is clear and it sure beats offering a select list of over 400 time zone options.
See a Demo
I type in my city, state and country – dallas, tx, us and nothing comes back.
Comment by EllisGL — May 7, 2009 @ 4:05 pm
Yes, today I ran into some trouble with the Google Maps API not responding consistently to the geocoding done with PHP, so I switched the code around a little to do the geocoding with Javascript.
Comment by Nelson — May 7, 2009 @ 10:30 pm
[...] the Vancouver Web Consultants blog there’s this new tutorial about grabbing latitude and longitude information for a location and determining its current time [...]
Pingback by Vancouver Web Consultants Blog: Getting Time Zone from Latitude & Longitude | Cole Design Studios — May 8, 2009 @ 10:24 am
[...] Getting time zone from latitude & longitude Plut
Pingback by Revue de presse | Simple Entrepreneur — May 13, 2009 @ 10:21 pm
nice post, thanks! I just used curl to request the timezone due to better performance.
Comment by michael — June 15, 2009 @ 9:31 am
Great Article. I was wrestling with having the user select their timezone as well and this seems much better!
Comment by Charles Himmer — October 8, 2009 @ 10:58 am
@michael
Michael: where do you request the timezone with Curl?
Comment by Soloren2001 — November 20, 2009 @ 4:38 pm
Thanks, this worked awesome.
Comment by Amdizzle — February 23, 2010 @ 6:48 pm