Reputation: 13435
I need to display a timezone selector as a user control, which always seems easier than it really is. Internally, I store everything with a DateTimeZone Identifier, as that seems to be the smartest way to have the level of accuracy I need, as this project bridges real-world times as it is tied to terrestrial media.
What I don't want to do is present a select box with 300+ time zones, nor do I want to create faked timezone offsets with something like 'UTC-8' (which loses not only DST info, but the actual dates that the DST falls on).
In the end, I'll need a select with options containing the proper TZD Identifiers, something like this (the bracketed #s aren't necessary, just for potential end-user illustration):
<select>
<option value="America/Los_Angeles">Los Angeles [UTC-7 | DST]</option>
...
</select>
Does anyone have any pointers for building this list? All of the solutions I've googled have been problematic in one way or another.
I've added a bounty in case that might entice somebody to share a nicer answer with us. : )
Upvotes: 44
Views: 27480
Reputation: 754
Similar to the accepted answer, but alot shorter, and also shows the current time in each timezone:
foreach(timezone_identifiers_list() as $tz)
{
$current_dt = new DateTime('now', new DateTimeZone($tz));
echo '<option value=' . $tz . '>'
. str_replace('_', ' ', $tz) . ' - ' // Some timezones contain _
. $current_dt->format('\G\M\TP (H:i)') // Add the time in $tz
. '</option>';
}
Outputs something like: Europe/Brussels - GMT+02:00 (23:57)
Any valid format (GMT == UTC) can be used for the timezone/current time.
Upvotes: 1
Reputation: 98961
My solution:
To avoid a huge timezone list, have the user select the country first, then use that information to populate a list of timezones.
File populate.php
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html>
<head>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<title>Select test</title>
<script type="text/javascript" src="http://code.jquery.com/jquery-1.6.2.min.js"></script>
<script type="text/javascript" charset="utf-8">
$(function(){
$("select#country").change(function(){
$.getJSON("json.php",{country: $(this).val()}, function(j){
var options = '';
for (var i = 0; i < j.length; i++) {
options += '<option value="' + j[i].optionValue + '">' + j[i].optionDisplay + '</option>';
}
$("#city").html(options);
$('#city option:first').attr('selected', 'selected');
})
})
})
</script>
</head>
<body>
<form action="#">
<label for="country">Country:</label>
<select name="country" id="country">
<option value="Portugal">Portugal</option>
<option value="United States">United States</option>
<option value="Japan">Japan</option>
</select>
<label for="city">Timezone:</label>
<select name="city" id="city">
<option value="Atlantic/Azores">Atlantic/Azores</option>
<option value="Atlantic/Madeira">Atlantic/Madeira</option>
<option value="Europe/Lisbon">Europe/Lisbon</option>
</select>
<input type="submit" name="action" value="Set TZ" />
</form>
file json.php
$country = $_GET['country'];
$citylist = "";
$country_list = file_get_contents("country_iso.txt"); //grab this file @ http://pastebin.com/e8gxcVHm
preg_match_all('/(.*?):'.$country.'/im', $country_list, $country_iso, PREG_PATTERN_ORDER);
$country_iso = $country_iso[1][0];
if(isset($country_iso))
{
$tz = DateTimeZone::listIdentifiers(DateTimeZone::PER_COUNTRY, $country_iso); //php 5.3 needed to use DateTimeZone::PER_COUNTRY !
foreach($tz as $city)
$citylist .= "{\"optionValue\": \"$city\", \"optionDisplay\": \"$city\"}, ";
}
$citylist = preg_replace('/, $/im', '', $citylist);
$citylist = "[".$citylist."]";
echo $citylist;
I hope it helps you :)
Upvotes: 9
Reputation: 1013
This has long been answered but, I wasn't satisfied with any of the answers that forced users to go through all the TZ entries php knows. Instead I created a more concise list that maps times like eastern time to America/New_York and includes special entries for odd locations like Arizona.
Github entry: https://github.com/ryanzor/timezone-dropdown
Demo of just selector: http://lifesnow.com/time-zone-dropdown/
<?php
/**
*
* This get's the timezone offset based on the olson code.
* In this code it is used to find the offset between the given olson code and UTC, but can be used to convert other differences
*
* @param string $remote_tz TZ string
* @param string $origin_tz TZ string, defaults to UTC
* @return int offset in seconds
*/
function ln_get_timezone_offset($remote_tz, $origin_tz = 'UTC') {
$origin_dtz = new DateTimeZone($origin_tz);
$remote_dtz = new DateTimeZone($remote_tz);
$origin_dt = new DateTime("now", $origin_dtz);
$remote_dt = new DateTime("now", $remote_dtz);
$offset = $remote_dtz->getOffset($remote_dt) - $origin_dtz->getOffset($origin_dt);
return $offset;
}
/**
* Converts a timezone difference to be displayed as GMT +/-
*
* @param string $timezone TZ time
* @return string text with GMT
*/
function ln_get_timezone_offset_text($timezone){
$time = ln_get_timezone_offset($timezone);
$minutesOffset = $time/60;
$hours = floor(($minutesOffset)/60);
$minutes = abs($minutesOffset%60);
$minutesFormatted = sprintf('%02d', $minutes);
$plus = '';
if($time >= 0){
$plus = '+';
}
$GMToff = 'GMT '.$plus.$hours.':'.$minutesFormatted;
return $GMToff;
}
/**
* This is for formatting how the timezone option displays.
* It can be converted to include current time, not include gmt or anything like that.
*
* @param string $timezone TZ time
* @param string $text format select box option
*/
function ln_display_timezone_option($timezone, $text){
?>
<option value="<?php echo $timezone; ?>"><?php echo '('.ln_get_timezone_offset_text($timezone).') '.$text; ?></option>
<?php
}
/**
* The concise list of timezones. This generates the html wherever it is called
*/
function ln_display_timezone_selector(){
?>
<select name="timezoneSelectDropdown">
<?php
ln_display_timezone_option('Pacific/Auckland', 'International Date Line West');
ln_display_timezone_option('Pacific/Midway', 'Midway Island, Samoa');
ln_display_timezone_option('US/Hawaii', 'Hawaii');
ln_display_timezone_option('US/Alaska', 'Alaska');
ln_display_timezone_option('US/Pacific', 'Pacific Time (US & Canada)');
ln_display_timezone_option('America/Tijuana', 'Tijuana, Baja California');
ln_display_timezone_option('America/Phoenix', 'Arizona');
ln_display_timezone_option('America/Chihuahua', 'Chihuahua, La Paz, Mazatlan');
ln_display_timezone_option('US/Mountain', 'Mountain Time (US & Canada)');
ln_display_timezone_option('America/Cancun', 'Central America');
ln_display_timezone_option('US/Central', 'Central Time (US & Canada)');
ln_display_timezone_option('America/Mexico_City', 'Guadalajara, Mexico City, Monterrey');
ln_display_timezone_option('Canada/Saskatchewan', 'Saskatchewan');
ln_display_timezone_option('America/Lima', 'Bogota, Lima, Quito, Rio Branco');
ln_display_timezone_option('US/Eastern', 'Eastern Time (US & Canada)');
ln_display_timezone_option('US/East-Indiana', 'Indiana (East)');
ln_display_timezone_option('Canada/Atlantic', 'Atlantic Time (Canada)');
ln_display_timezone_option('America/Caracas', 'Caracas, La Paz');
ln_display_timezone_option('America/Manaus', 'Manaus');
ln_display_timezone_option('America/Santiago', 'Santiago');
ln_display_timezone_option('Canada/Newfoundland', 'Newfoundland');
ln_display_timezone_option('America/Sao_Paulo', 'Brasilia');
ln_display_timezone_option('America/Argentina/Buenos_Aires', 'Buenos Aires, Georgetown');
ln_display_timezone_option('America/Godthab', 'Greenland');
ln_display_timezone_option('America/Montevideo', 'Montevideo');
ln_display_timezone_option('Atlantic/South_Georgia', 'Mid-Atlantic');
ln_display_timezone_option('Atlantic/Cape_Verde', 'Cape Verde Is.');
ln_display_timezone_option('Atlantic/Azores', 'Azores');
ln_display_timezone_option('Africa/Casablanca', 'Casablanca, Monrovia, Reykjavik');
ln_display_timezone_option('UTC', 'Greenwich Mean Time : Dublin, Edinburgh, Lisbon, London');
ln_display_timezone_option('Europe/Amsterdam', 'Amsterdam, Berlin, Bern, Rome, Stockholm, Vienna');
ln_display_timezone_option('Europe/Belgrade', 'Belgrade, Bratislava, Budapest, Ljubljana, Prague');
ln_display_timezone_option('Europe/Brussels', 'Brussels, Copenhagen, Madrid, Paris');
ln_display_timezone_option('Europe/Sarajevo', 'Sarajevo, Skopje, Warsaw, Zagreb');
ln_display_timezone_option('Africa/Windhoek', 'West Central Africa');
ln_display_timezone_option('Asia/Amman', 'Amman');
ln_display_timezone_option('Europe/Athens', 'Athens, Bucharest, Istanbul');
ln_display_timezone_option('Asia/Beirut', 'Beirut');
ln_display_timezone_option('Africa/Cairo', 'Cairo');
ln_display_timezone_option('Africa/Harare', 'Harare, Pretoria');
ln_display_timezone_option('Europe/Helsinki', 'Helsinki, Kyiv, Riga, Sofia, Tallinn, Vilnius');
ln_display_timezone_option('Asia/Jerusalem', 'Jerusalem');
ln_display_timezone_option('Europe/Minsk', 'Minsk');
ln_display_timezone_option('Africa/Windhoek', 'Windhoek');
ln_display_timezone_option('Asia/Kuwait', 'Kuwait, Riyadh, Baghdad');
ln_display_timezone_option('Europe/Moscow', 'Moscow, St. Petersburg, Volgograd');
ln_display_timezone_option('Africa/Nairobi', 'Nairobi');
ln_display_timezone_option('Asia/Tbilisi', 'Tbilisi');
ln_display_timezone_option('Asia/Tehran', 'Tehran');
ln_display_timezone_option('Asia/Muscat', 'Abu Dhabi, Muscat');
ln_display_timezone_option('Asia/Baku', 'Baku');
ln_display_timezone_option('Asia/Yerevan', 'Yerevan');
ln_display_timezone_option('Asia/Kabul', 'Kabul');
ln_display_timezone_option('Asia/Yekaterinburg', 'Yekaterinburg');
ln_display_timezone_option('Asia/Karachi', 'Islamabad, Karachi, Tashkent');
ln_display_timezone_option('Asia/Kolkata', 'Sri Jayawardenepura');
ln_display_timezone_option('Asia/Kolkata', 'Chennai, Kolkata, Mumbai, New Delhi');
ln_display_timezone_option('Asia/Kathmandu', 'Kathmandu');
ln_display_timezone_option('Asia/Almaty', 'Almaty, Novosibirsk');
ln_display_timezone_option('Asia/Dhaka', 'Astana, Dhaka');
ln_display_timezone_option('Asia/Rangoon', 'Yangon (Rangoon)');
ln_display_timezone_option('Asia/Bangkok', 'Bangkok, Hanoi, Jakarta');
ln_display_timezone_option('Asia/Krasnoyarsk', 'Krasnoyarsk');
ln_display_timezone_option('Asia/Shanghai', 'Beijing, Chongqing, Hong Kong, Urumqi');
ln_display_timezone_option('Asia/Singapore', 'Kuala Lumpur, Singapore');
ln_display_timezone_option('Asia/Irkutsk', 'Irkutsk, Ulaan Bataar');
ln_display_timezone_option('Australia/Perth', 'Perth');
ln_display_timezone_option('Asia/Taipei', 'Taipei');
ln_display_timezone_option('Asia/Tokyo', 'Osaka, Sapporo, Tokyo');
ln_display_timezone_option('Asia/Seoul', 'Seoul');
ln_display_timezone_option('Asia/Yakutsk', 'Yakutsk');
ln_display_timezone_option('Australia/Adelaide', 'Adelaide');
ln_display_timezone_option('Australia/Darwin', 'Darwin');
ln_display_timezone_option('Australia/Brisbane', 'Brisbane');
ln_display_timezone_option('Australia/Sydney', 'Canberra, Melbourne, Sydney');
ln_display_timezone_option('Australia/Hobart', 'Hobart');
ln_display_timezone_option('Pacific/Guam', 'Guam, Port Moresby');
ln_display_timezone_option('Asia/Vladivostok', 'Vladivostok');
ln_display_timezone_option('Asia/Magadan', 'Magadan, Solomon Is., New Caledonia');
ln_display_timezone_option('Pacific/Auckland', 'Auckland, Wellington');
ln_display_timezone_option('Pacific/Fiji', 'Fiji, Kamchatka, Marshall Is.');
ln_display_timezone_option('Pacific/Tongatapu', 'Nuku\'alofa');
?>
</select>
<?php
}
?>
Upvotes: 1
Reputation: 2780
function formatOffset($offset) {
$hours = $offset / 3600;
$remainder = $offset % 3600;
$sign = $hours > 0 ? '+' : '-';
$hour = (int) abs($hours);
$minutes = (int) abs($remainder / 60);
if ($hour == 0 AND $minutes == 0) {
$sign = ' ';
}
return $sign . str_pad($hour, 2, '0', STR_PAD_LEFT) .':'. str_pad($minutes,2, '0');
}
$utc = new DateTimeZone('UTC');
$dt = new DateTime('now', $utc);
echo '<select name="userTimeZone">';
foreach(DateTimeZone::listIdentifiers() as $tz) {
$current_tz = new DateTimeZone($tz);
$offset = $current_tz->getOffset($dt);
$transition = $current_tz->getTransitions($dt->getTimestamp(), $dt->getTimestamp());
$abbr = $transition[0]['abbr'];
echo '<option value="' .$tz. '">' .$tz. ' [' .$abbr. ' '. formatOffset($offset). ']</option>';
}
echo '</select>';
The above will output all of the timezones in select menu with the following format:
<select name="userTimeZone">
<option value="America/Los_Angeles">America/Los_Angeles [PDT -7]</option>
</select>
Upvotes: 70
Reputation: 154603
After thinking (and testing) a lot about this problem I've come to the conclusion that you can't use timezone short-lists for the simple fact that timezone rules are dynamic (more than you might expect - 4 timezones will have their rules changed this month) and groups/concatenations/abbreviations are static.
Here is the code for the approach I decided to stick with:
function Timezones($country = null, $continent = null)
{
$result = array();
if (is_array($timezones = DateTimeZone::listIdentifiers()) === true)
{
$timestamp = strtotime('-6 months', time());
if ((strlen($country) == 2) && (defined('DateTimeZone::PER_COUNTRY') === true))
{
$timezones = DateTimeZone::listIdentifiers(DateTimeZone::PER_COUNTRY, $country);
}
foreach (preg_grep('~' . preg_quote($continent, '~') . '/~i', $timezones) as $id)
{
$timezone = new DateTimeZone($id);
if (is_array($transitions = $timezone->getTransitions()) === true)
{
while ((isset($result[$id]) !== true) && (is_null($transition = array_shift($transitions)) !== true))
{
$result[$id] = (($transition['isdst'] !== true) && ($transition['ts'] >= $timestamp)) ? $transition['offset'] : null;
}
}
}
if (array_multisort($result, SORT_NUMERIC, preg_replace('~^[^/]+/~', '', array_keys($result)), SORT_REGULAR, $result) === true)
{
foreach ($result as $key => $value)
{
$result[$key] = sprintf('(GMT %+03d:%02u) %s', $value / 3600, abs($value) % 3600 / 60, ltrim(strstr($key, '/'), '/'));
}
}
}
return str_replace(array(' +00:00', '_', '/'), array('', ' ', ' - '), $result);
}
Demo at http://www.ideone.com/VYWtw and http://jsfiddle.net/hSxa8/embedded/result/.
Upvotes: 2
Reputation: 154603
I came up with a dynamic self-updated solution that doesn't require any lookup tables (select demo):
function Timezones()
{
$result = array();
$timezones = array();
// only process geographical timezones
foreach (preg_grep('~^(?:A(?:frica|merica|ntarctica|rctic|tlantic|sia|ustralia)|Europe|Indian|Pacific)/~', timezone_identifiers_list()) as $timezone)
{
if (is_object($timezone = new DateTimeZone($timezone)) === true)
{
$id = array();
// get only the two most distant transitions
foreach (array_slice($timezone->getTransitions($_SERVER['REQUEST_TIME']), -2) as $transition)
{
// dark magic
$id[] = sprintf('%b|%+d|%u', $transition['isdst'], $transition['offset'], $transition['ts']);
}
if (count($id) > 1)
{
sort($id, SORT_NUMERIC); // sort by %b (isdst = 0) first, so that we always get the raw offset
}
$timezones[implode('|', $id)][] = $timezone->getName();
}
}
if ((is_array($timezones) === true) && (count($timezones) > 0))
{
uksort($timezones, function($a, $b) // sort offsets by -, 0, +
{
foreach (array('a', 'b') as $key)
{
$$key = explode('|', $$key);
}
return intval($a[1]) - intval($b[1]);
});
foreach ($timezones as $key => $value)
{
$zone = reset($value); // first timezone ID is our internal timezone
$result[$zone] = preg_replace(array('~^.*/([^/]+)$~', '~_~'), array('$1', ' '), $value); // "humanize" city names
if (array_key_exists(1, $offset = explode('|', $key)) === true) // "humanize" the offset
{
$offset = str_replace(' +00:00', '', sprintf('(UTC %+03d:%02u)', $offset[1] / 3600, abs($offset[1]) % 3600 / 60));
}
if (asort($result[$zone]) === true) // sort city names
{
$result[$zone] = trim(sprintf('%s %s', $offset, implode(', ', $result[$zone])));
}
}
}
return $result;
}
There are lots of timezones that share the exact same offsets and DST timings (Europe/Dublin
, Europe/Lisbon
and Europe/London
to name a few), my algorithm groups these zones (using a special notation in the array keys dst?|offset|timestamp
) in the first timezone ID of that group and concatenates humanized transformations of the last (usually city level) segment of the timezone ID:
Array
(
[Pacific/Midway] => (UTC -11:00) Midway, Niue, Pago Pago
[America/Adak] => (UTC -10:00) Adak
[Pacific/Fakaofo] => (UTC -10:00) Fakaofo, Honolulu, Johnston, Rarotonga, Tahiti
[Pacific/Marquesas] => (UTC -10:30) Marquesas
[America/Anchorage] => (UTC -09:00) Anchorage, Juneau, Nome, Sitka, Yakutat
[Pacific/Gambier] => (UTC -09:00) Gambier
[America/Dawson] => (UTC -08:00) Dawson, Los Angeles, Tijuana, Vancouver, Whitehorse
[America/Santa_Isabel] => (UTC -08:00) Santa Isabel
[America/Metlakatla] => (UTC -08:00) Metlakatla, Pitcairn
[America/Dawson_Creek] => (UTC -07:00) Dawson Creek, Hermosillo, Phoenix
[America/Chihuahua] => (UTC -07:00) Chihuahua, Mazatlan
[America/Boise] => (UTC -07:00) Boise, Cambridge Bay, Denver, Edmonton, Inuvik, Ojinaga, Shiprock, Yellowknife
[America/Chicago] => (UTC -06:00) Beulah, Center, Chicago, Knox, Matamoros, Menominee, New Salem, Rainy River, Rankin Inlet, Resolute, Tell City, Winnipeg
[America/Belize] => (UTC -06:00) Belize, Costa Rica, El Salvador, Galapagos, Guatemala, Managua, Regina, Swift Current, Tegucigalpa
[Pacific/Easter] => (UTC -06:00) Easter
[America/Bahia_Banderas] => (UTC -06:00) Bahia Banderas, Cancun, Merida, Mexico City, Monterrey
[America/Detroit] => (UTC -05:00) Detroit, Grand Turk, Indianapolis, Iqaluit, Louisville, Marengo, Monticello, Montreal, Nassau, New York, Nipigon, Pangnirtung, Petersburg, Thunder Bay, Toronto, Vevay, Vincennes, Winamac
[America/Atikokan] => (UTC -05:00) Atikokan, Bogota, Cayman, Guayaquil, Jamaica, Lima, Panama, Port-au-Prince
[America/Havana] => (UTC -05:00) Havana
[America/Caracas] => (UTC -05:30) Caracas
[America/Glace_Bay] => (UTC -04:00) Bermuda, Glace Bay, Goose Bay, Halifax, Moncton, Thule
[Atlantic/Stanley] => (UTC -04:00) Stanley
[America/Santiago] => (UTC -04:00) Palmer, Santiago
[America/Anguilla] => (UTC -04:00) Anguilla, Antigua, Aruba, Barbados, Blanc-Sablon, Boa Vista, Curacao, Dominica, Eirunepe, Grenada, Guadeloupe, Guyana, Kralendijk, La Paz, Lower Princes, Manaus, Marigot, Martinique, Montserrat, Port of Spain, Porto Velho, Puerto Rico, Rio Branco, Santo Domingo, St Barthelemy, St Kitts, St Lucia, St Thomas, St Vincent, Tortola
[America/Campo_Grande] => (UTC -04:00) Campo Grande, Cuiaba
[America/Asuncion] => (UTC -04:00) Asuncion
[America/St_Johns] => (UTC -04:30) St Johns
[America/Sao_Paulo] => (UTC -03:00) Sao Paulo
[America/Araguaina] => (UTC -03:00) Araguaina, Bahia, Belem, Buenos Aires, Catamarca, Cayenne, Cordoba, Fortaleza, Jujuy, La Rioja, Maceio, Mendoza, Paramaribo, Recife, Rio Gallegos, Rothera, Salta, San Juan, Santarem, Tucuman, Ushuaia
[America/Montevideo] => (UTC -03:00) Montevideo
[America/Godthab] => (UTC -03:00) Godthab
[America/Argentina/San_Luis] => (UTC -03:00) San Luis
[America/Miquelon] => (UTC -03:00) Miquelon
[America/Noronha] => (UTC -02:00) Noronha, South Georgia
[Atlantic/Cape_Verde] => (UTC -01:00) Cape Verde
[America/Scoresbysund] => (UTC -01:00) Azores, Scoresbysund
[Atlantic/Canary] => (UTC) Canary, Dublin, Faroe, Guernsey, Isle of Man, Jersey, Lisbon, London, Madeira
[Africa/Abidjan] => (UTC) Abidjan, Accra, Bamako, Banjul, Bissau, Casablanca, Conakry, Dakar, Danmarkshavn, El Aaiun, Freetown, Lome, Monrovia, Nouakchott, Ouagadougou, Reykjavik, Sao Tome, St Helena
[Africa/Algiers] => (UTC +01:00) Algiers, Bangui, Brazzaville, Douala, Kinshasa, Lagos, Libreville, Luanda, Malabo, Ndjamena, Niamey, Porto-Novo, Tunis
[Africa/Ceuta] => (UTC +01:00) Amsterdam, Andorra, Belgrade, Berlin, Bratislava, Brussels, Budapest, Ceuta, Copenhagen, Gibraltar, Ljubljana, Longyearbyen, Luxembourg, Madrid, Malta, Monaco, Oslo, Paris, Podgorica, Prague, Rome, San Marino, Sarajevo, Skopje, Stockholm, Tirane, Vaduz, Vatican, Vienna, Warsaw, Zagreb, Zurich
[Africa/Windhoek] => (UTC +01:00) Windhoek
[Asia/Damascus] => (UTC +02:00) Damascus
[Asia/Beirut] => (UTC +02:00) Beirut
[Asia/Jerusalem] => (UTC +02:00) Jerusalem
[Asia/Nicosia] => (UTC +02:00) Athens, Bucharest, Chisinau, Helsinki, Istanbul, Mariehamn, Nicosia, Riga, Sofia, Tallinn, Vilnius
[Africa/Blantyre] => (UTC +02:00) Blantyre, Bujumbura, Cairo, Gaborone, Gaza, Harare, Hebron, Johannesburg, Kigali, Lubumbashi, Lusaka, Maputo, Maseru, Mbabane, Tripoli
[Asia/Amman] => (UTC +02:00) Amman
[Africa/Addis_Ababa] => (UTC +03:00) Addis Ababa, Aden, Antananarivo, Asmara, Baghdad, Bahrain, Comoro, Dar es Salaam, Djibouti, Juba, Kaliningrad, Kampala, Khartoum, Kiev, Kuwait, Mayotte, Minsk, Mogadishu, Nairobi, Qatar, Riyadh, Simferopol, Syowa, Uzhgorod, Zaporozhye
[Asia/Tehran] => (UTC +03:30) Tehran
[Asia/Yerevan] => (UTC +04:00) Yerevan
[Asia/Dubai] => (UTC +04:00) Dubai, Mahe, Mauritius, Moscow, Muscat, Reunion, Samara, Tbilisi, Volgograd
[Asia/Baku] => (UTC +04:00) Baku
[Asia/Kabul] => (UTC +04:30) Kabul
[Antarctica/Mawson] => (UTC +05:00) Aqtau, Aqtobe, Ashgabat, Dushanbe, Karachi, Kerguelen, Maldives, Mawson, Oral, Samarkand, Tashkent
[Asia/Colombo] => (UTC +05:30) Colombo, Kolkata
[Asia/Kathmandu] => (UTC +05:45) Kathmandu
[Antarctica/Vostok] => (UTC +06:00) Almaty, Bishkek, Chagos, Dhaka, Qyzylorda, Thimphu, Vostok, Yekaterinburg
[Asia/Rangoon] => (UTC +06:30) Cocos, Rangoon
[Antarctica/Davis] => (UTC +07:00) Bangkok, Christmas, Davis, Ho Chi Minh, Hovd, Jakarta, Novokuznetsk, Novosibirsk, Omsk, Phnom Penh, Pontianak, Vientiane
[Antarctica/Casey] => (UTC +08:00) Brunei, Casey, Choibalsan, Chongqing, Harbin, Hong Kong, Kashgar, Krasnoyarsk, Kuala Lumpur, Kuching, Macau, Makassar, Manila, Perth, Shanghai, Singapore, Taipei, Ulaanbaatar, Urumqi
[Australia/Eucla] => (UTC +08:45) Eucla
[Asia/Dili] => (UTC +09:00) Dili, Irkutsk, Jayapura, Palau, Pyongyang, Seoul, Tokyo
[Australia/Adelaide] => (UTC +09:30) Adelaide, Broken Hill
[Australia/Darwin] => (UTC +09:30) Darwin
[Antarctica/DumontDUrville] => (UTC +10:00) Brisbane, Chuuk, DumontDUrville, Guam, Lindeman, Port Moresby, Saipan, Yakutsk
[Australia/Currie] => (UTC +10:00) Currie, Hobart, Melbourne, Sydney
[Australia/Lord_Howe] => (UTC +10:30) Lord Howe
[Antarctica/Macquarie] => (UTC +11:00) Efate, Guadalcanal, Kosrae, Macquarie, Noumea, Pohnpei, Sakhalin, Vladivostok
[Pacific/Norfolk] => (UTC +11:30) Norfolk
[Antarctica/McMurdo] => (UTC +12:00) Auckland, McMurdo, South Pole
[Asia/Anadyr] => (UTC +12:00) Anadyr, Fiji, Funafuti, Kamchatka, Kwajalein, Magadan, Majuro, Nauru, Tarawa, Wake, Wallis
[Pacific/Chatham] => (UTC +12:45) Chatham
[Pacific/Enderbury] => (UTC +13:00) Enderbury, Tongatapu
[Pacific/Apia] => (UTC +13:00) Apia
[Pacific/Kiritimati] => (UTC +14:00) Kiritimati
)
Granted, the city concatenation is still pretty damn long but the list of unique (actual) timezones has dropped from 414 (or 415, if we consider the non-geographical UTC) to 75 - which is pretty good IMO and seems to reflect the list of "normalized" timezones Windows uses (also 75).
There are two big problems with this automated approach:
Atlantic/Canary
- while there shouldn't be anything wrong with that, it would make more sense to pick a timezone ID associated with a bigger city (like Europe/London
)array_slice($cities, 0, $maxCities)
before imploding but this wouldn't have the city dimension into account, and for a limit of 4 Canary, Dublin, Faroe, Guernsey, Isle of Man, Jersey, Lisbon, London, Madeira would become Canary, Dublin, Faroe, Guernsey instead of the more logical Windows equivalent Dublin, Edinburgh, Lisbon, London.This shouldn't be very useful as it it, but I thought I'd share - perhaps someone else can improve it.
Upvotes: 6
Reputation: 2495
There is a list/reference provided in the answer to this question. It comes up with just 88 unique timezones that would make up your select list.
Upvotes: 1
Reputation: 13435
At the end of the day, I don't think there is a good solution for pre-culling the too-large list of Timezones... And the issues involved have made me decide to just start out with a decently-sized class. I'm posting it here as there were some people who were interested in it. Hopefully my solution will help somebody else out too.
What it does:
Lets you create a list of timezones from the entire list inside of PHP
Lets you create a nice list from your own pre-baked definitions, calcuating current UTC offsets.
Lets you create a list by country.
De-duplicates timezones with the same abbreviation. Note that I haven't done much research to see if there are duplicates in the list that I shouldn't be getting rid of. It is smart enough to know that even though two timezones may report the same abbreviation (say, MST for Arizona), that it will further key in on whether or not the timezone supports DST any time of the year.
Outputs either fairly configurable HTML (without going down into the template route) or JSON for Ajax or inline JavaScript.
Calculates the current timezone offset for any given timezone. Note that this isn't static... it will change throughout the year.
Provides large amounts of sugar for real-world usage.
What it doesn't do:
Things I might add one day:
Build the HTML out of template instead of inline like this.
Additional selectors, like taking an array of countries instead of a single one.
Grouping, but as I mention above, I'm down on doing that.
Code is pretty wet. There are some minor coding style discrepancies.
Ability to specify a 'preferred' list of friendly names. For instance, although 'Dawson' is a perfectly viable candidate to be listed as 'the' city for PDT, since it is part of the PST main group and observes Daylight Savings time... a city of just over 1000 residents shouldn't beat Los Angeles, San Francisco, Seattle, or Vancouver just because it shows up first lexically.
Raw output, so that nobody is tied to the simple 'toSelect' and 'toJson' methods.
In general, it should suit my needs as is. It properly handles all of the timezones I'm familiar with, and all of the ones that I need for my current project. Unfortunately, my scope of knowledge centers mostly around the US & Western Europe.
Is it perfect? Probably not. Happy to hear about any issues/bugs/improvements anybody might have with this code, as it is likely to wind up in my permanent library. If anybody thinks I'm high... let me know that too. That was the whole point of this question was to find the best way to represent a timezone selector accurately that is actually somewhat useful to end users.
TimezoneList.php:
<?php
class TimezoneList
{
public $timezones = array();
private $_initialized = false;
private $_dt_now;
private $_utc;
function __construct($grouped = false)
{
$this->_utc = new DateTimeZone('UTC');
$this->_dt_now = new DateTime('now', $this->_utc);
}
// Public Static Alternate Constructors
public static function byCountry($countryKey)
{
$retVal = new TimezoneList();
$retVal->_setList($countryKey);
return $retVal;
}
public static function fromTimezones(Array $tzArr)
{
$retVal = new TimezoneList();
foreach ($tzArr as $tzItem)
{
$retVal->timezones[] = $tzItem;
}
$retVal->_initialized = true;
return $retVal;
}
public static function fromIdentifierList($timezoneList, $friendlyNames = NULL)
{
$retVal = new TimezoneList();
if ($friendlyNames)
{
if (count($timezoneList) != count($friendlyNames)) throw new Exception('Array count mismatch in TimezoneBuilder::fromList');
}
// I'd normally use a foreach pattern, but since friendlyNames is optional, this seemed the way to go.
for ($ii = 0; $ii < count($timezoneList); $ii++)
{
$pTimezoneEx = new TimezoneExtended($timezoneList[$ii]);
if ($friendlyNames)
{
$pTimezoneEx->friendlyName = $friendlyNames[$ii];
}
$retVal->timezones[] = $pTimezoneEx;
}
$retVal->_initialized = true;
return $retVal;
}
// Private Statics
// Private utility function [ Thanks to Zubair1 ]
private static function _formatOffset($offset)
{
$hours = $offset / 3600;
$remainder = $offset % 3600;
$sign = $hours > 0 ? '+' : '-';
$hour = (int)abs($hours);
$minutes = (int)abs($remainder / 60);
$sign = (($hour == 0) && ($minutes == 0)) ? ' ' : $sign;
return $sign.str_pad($hour, 2, '0', STR_PAD_LEFT).':'.str_pad($minutes,2, '0');
}
// Publics
public function getUniqueTimezoneList($countryKey = null)
{
$this->_initialize();
$outArr = array();
$usedTzs = array();
foreach ($this->timezones as $timezoneEx)
{
if (!(in_array($timezoneEx->currentKey, $usedTzs)))
{
$usedTzs[] = $timezoneEx->currentKey;
$outArr[] = $timezoneEx;
}
}
usort($outArr,array('self','_orderByOffset'));
return self::fromTimezones($outArr);
}
// In final code, I'll use a PHP include with output buffering as a template.
public function toSelect($displayOffset = true, $displayCurrent = true, $selected = array(), Array $options = array())
{
$pOpts = array();
$pItems = array();
foreach ($options as $key=>$option)
{
$pOpts[] = ' '.$key.'="'.$option.'"';
}
if (!is_array($selected)) $selected = array($selected);
$outVal = '<select'.implode('', $pOpts).'>'."\n";
foreach ($this->timezones as $timezoneEx)
{
$offset = '';
$selectionAttr = '';
if (in_array($timezoneEx->tzkey, $selected))
{
$selectionAttr = ' selected="selected"';
}
if ($displayOffset)
{
$offset = ' ['.$timezoneEx->currentAbbr.' '.self::_formatOffset($timezoneEx->currentOffset);
if ($displayCurrent && (!($timezoneEx->observesDst))) $offset .= ' ( Does not observe DST ) ';
$offset .= ']';
}
$pItems[] = "\t".'<option value="'.$timezoneEx->tzkey.'"'.$selectionAttr.'>'.$timezoneEx->friendlyName.$offset.'</option>';
}
$outVal .= implode("\n", $pItems)."\n".'</select>';
return $outVal;
}
public function toJson()
{
$outArr = array();
foreach ($this->timezones as $timezoneEx)
{
$outArr[] = $timezoneEx->toShallowArray();
}
return json_encode($outArr);
}
// Privates
private function _initialize()
{
if ($this->_initialized) return;
$this->_setList();
}
private function _orderByOffset($a, $b)
{
if( $a->currentOffset == $b->currentOffset ){ return 0 ; }
return ($a->currentOffset < $b->currentOffset) ? -1 : 1;
}
private function _setList($countryKey = NULL)
{
$this->timezones = array();
$listType = ($countryKey) ? DateTimeZone::PER_COUNTRY : DateTimeZone::ALL;
$tzIds = DateTimeZone::listIdentifiers($listType, $countryKey);
foreach ($tzIds as $tzIdentifier)
{
$this->timezones[] = new TimezoneExtended($tzIdentifier);
}
$this->_initialized = true;
}
}
class TimezoneExtended
{
const START_YEAR = 'January 1st';
const MID_YEAR = 'July 1st';
private static $_dt_startYear = NULL; // Static so that we don't have to rebuild it each time we go through.
private static $_dt_midYear = NULL;
private static $_dtz_utc = NULL;
private static $_dt_now = NULL;
private $_baseObj;
public $tzkey;
public $friendlyName;
public $currentKey; // Current Key contains the friendly Timezone Key + whether this timezone observes DST.
// This is unique across the US & Canada. Unsure if it will be unique across other Timezones.
public $currentAbbr;
public $currentOffset;
public $currentlyDst;
public $observesDst = false; // Defaults to off
function __construct($tzKey)
{
if (empty(self::$_dtz_utc)) self::$_dtz_utc = new DateTimeZone('UTC');
if (empty(self::$_dtz_now)) self::$_dt_now = new DateTime('now', self::$_dtz_utc);
if (empty(self::$_dt_startYear)) self::$_dt_startYear = new DateTime(self::START_YEAR, self::$_dtz_utc);
if (empty(self::$_dt_midYear)) self::$_dt_midYear = new DateTime(self::MID_YEAR, self::$_dtz_utc);
$this->tzkey = $tzKey;
$this->_baseObj = new DateTimeZone($tzKey);
if ($this->_baseObj == NULL) throw new Exception('Invalid Timezone Key');
foreach ($this->_baseObj->getTransitions(self::$_dt_startYear->getTimestamp()) as $transition)
{
if ($transition['isdst']) $this->observesDst = true;
}
foreach ($this->_baseObj->getTransitions(self::$_dt_midYear->getTimestamp()) as $transition)
{
if ($transition['isdst']) $this->observesDst = true;
}
$this->friendlyName =str_replace('_',' ',array_pop(explode('/',$tzKey)));
$pTransition = $this->_baseObj->getTransitions(self::$_dt_now->getTimestamp());
$this->currentAbbr = $pTransition[0]['abbr']; // With a Timestamp, we should only get one transition.
$this->currentlyDst = $pTransition[0]['isdst'];
$this->currentKey = $this->currentAbbr.'_'.$this->observesDst;
$this->currentOffset = $this->_baseObj->getOffset(self::$_dt_now);
}
public function toShallowArray()
{
$outArr = array(
'tzkey'=>$this->tzkey,
'friendlyName'=>$this->friendlyName,
'currentOffset'=>$this->currentOffset/3600,
'observesDst'=>$this->observesDst,
'currentlyDst'=>$this->currentlyDst,
'currentAbbr'=>$this->currentAbbr
);
return $outArr;
}
}
Whew. Here's usage examples (timezones.php):
<?php
include_once 'TimezoneList.php';
/* Example 1: Get Select Box by Country Code */
$tzl = TimezoneList::byCountry('US');
$tzl = $tzl->getUniqueTimezoneList();
echo $tzl->toSelect(true,true,'America/Los_Angeles');
echo "\n".'<br />'."\n";
/* Example 2: Get a list by country code, output as JSON for AJAX (or similar uses) */
$_REQUEST['country_code'] = 'US'; // Hack for quick usage.
$tzl_ajax = TimezoneList::byCountry($_REQUEST['country_code']);
$tzl_ajax = $tzl_ajax->getUniqueTimezoneList();
echo '<script type="text/javascript">'."\n";
echo 'var foo = '.$tzl_ajax->toJson().';';
echo "\n".'</script>';
echo "\n".'<br />'."\n";
/* Example 3: Get Select Box from a list of TZDs + friendly names */
$tzl2 = TimezoneList::fromIdentifierList(
array('America/Los_Angeles','America/Boise','America/Phoenix','America/Chicago','America/New_York'),
array('Pacific','Mountain','Mountain (Arizona)','Central','Eastern')
);
// Example shows setting extra properties on the <SELECT>.
echo $tzl2->toSelect(true,false,'America/Los_Angeles',
array('style'=>'font-size:15px; border:1px solid #ccc; padding:4px', 'id'=>'timezone_list', 'class'=>'standard-list', 'name'=>'timezone')
);
echo "\n".'<br />'."\n";
/* Example 4: Get a raw list of timezones */
$tzl3 = new TimezoneList(true);
$tzl3 = $tzl3->getUniqueTimezoneList();
echo $tzl3->toSelect(true,false,'America/Los_Angeles');
And here's the output code from the examples in timezones.php:
<select>
<option value="Pacific/Honolulu">Honolulu [HST -10:00 ( Does not observe DST ) ]</option>
<option value="America/Adak">Adak [HADT -09:00]</option>
<option value="America/Anchorage">Anchorage [AKDT -08:00]</option>
<option value="America/Phoenix">Phoenix [MST -07:00 ( Does not observe DST ) ]</option>
<option value="America/Los_Angeles" selected="selected">Los Angeles [PDT -07:00]</option>
<option value="America/Boise">Boise [MDT -06:00]</option>
<option value="America/Chicago">Chicago [CDT -05:00]</option>
<option value="America/Detroit">Detroit [EDT -04:00]</option>
</select>
<br />
<script type="text/javascript">
var foo = [{"tzkey":"Pacific\/Honolulu","friendlyName":"Honolulu","currentOffset":-10,"observesDst":false,"currentlyDst":false,"currentAbbr":"HST"},{"tzkey":"America\/Adak","friendlyName":"Adak","currentOffset":-9,"observesDst":true,"currentlyDst":true,"currentAbbr":"HADT"},{"tzkey":"America\/Anchorage","friendlyName":"Anchorage","currentOffset":-8,"observesDst":true,"currentlyDst":true,"currentAbbr":"AKDT"},{"tzkey":"America\/Phoenix","friendlyName":"Phoenix","currentOffset":-7,"observesDst":false,"currentlyDst":false,"currentAbbr":"MST"},{"tzkey":"America\/Los_Angeles","friendlyName":"Los Angeles","currentOffset":-7,"observesDst":true,"currentlyDst":true,"currentAbbr":"PDT"},{"tzkey":"America\/Boise","friendlyName":"Boise","currentOffset":-6,"observesDst":true,"currentlyDst":true,"currentAbbr":"MDT"},{"tzkey":"America\/Chicago","friendlyName":"Chicago","currentOffset":-5,"observesDst":true,"currentlyDst":true,"currentAbbr":"CDT"},{"tzkey":"America\/Detroit","friendlyName":"Detroit","currentOffset":-4,"observesDst":true,"currentlyDst":true,"currentAbbr":"EDT"}];
</script>
<br />
<select style="font-size:15px; border:1px solid #ccc; padding:4px" id="timezone_list" class="standard-list" name="timezone">
<option value="America/Los_Angeles" selected="selected">Pacific [PDT -07:00]</option>
<option value="America/Boise">Mountain [MDT -06:00]</option>
<option value="America/Phoenix">Mountain (Arizona) [MST -07:00]</option>
<option value="America/Chicago">Central [CDT -05:00]</option>
<option value="America/New_York">Eastern [EDT -04:00]</option>
</select>
<br />
<select>
<option value="Pacific/Midway">Midway [SST -11:00]</option>
<option value="Pacific/Niue">Niue [NUT -11:00]</option>
<option value="Pacific/Apia">Apia [WST -11:00]</option>
<option value="Pacific/Tahiti">Tahiti [TAHT -10:00]</option>
<option value="Pacific/Honolulu">Honolulu [HST -10:00]</option>
<option value="Pacific/Rarotonga">Rarotonga [CKT -10:00]</option>
<option value="Pacific/Fakaofo">Fakaofo [TKT -10:00]</option>
<option value="Pacific/Marquesas">Marquesas [MART -09:30]</option>
<option value="America/Adak">Adak [HADT -09:00]</option>
<option value="Pacific/Gambier">Gambier [GAMT -09:00]</option>
<option value="America/Anchorage">Anchorage [AKDT -08:00]</option>
<option value="Pacific/Pitcairn">Pitcairn [PST -08:00]</option>
<option value="America/Dawson_Creek">Dawson Creek [MST -07:00]</option>
<option value="America/Dawson">Dawson [PDT -07:00]</option>
<option value="America/Belize">Belize [CST -06:00]</option>
<option value="America/Boise">Boise [MDT -06:00]</option>
<option value="Pacific/Easter">Easter [EAST -06:00]</option>
<option value="Pacific/Galapagos">Galapagos [GALT -06:00]</option>
<option value="America/Resolute">Resolute [CDT -05:00]</option>
<option value="America/Cancun">Cancun [CDT -05:00]</option>
<option value="America/Guayaquil">Guayaquil [ECT -05:00]</option>
<option value="America/Lima">Lima [PET -05:00]</option>
<option value="America/Bogota">Bogota [COT -05:00]</option>
<option value="America/Atikokan">Atikokan [EST -05:00]</option>
<option value="America/Caracas">Caracas [VET -04:30]</option>
<option value="America/Guyana">Guyana [GYT -04:00]</option>
<option value="America/Campo_Grande">Campo Grande [AMT -04:00]</option>
<option value="America/La_Paz">La Paz [BOT -04:00]</option>
<option value="America/Anguilla">Anguilla [AST -04:00]</option>
<option value="Atlantic/Stanley">Stanley [FKT -04:00]</option>
<option value="America/Detroit">Detroit [EDT -04:00]</option>
<option value="America/Boa_Vista">Boa Vista [AMT -04:00]</option>
<option value="America/Santiago">Santiago [CLT -04:00]</option>
<option value="America/Asuncion">Asuncion [PYT -04:00]</option>
<option value="Antarctica/Rothera">Rothera [ROTT -03:00]</option>
<option value="America/Paramaribo">Paramaribo [SRT -03:00]</option>
<option value="America/Sao_Paulo">Sao Paulo [BRT -03:00]</option>
<option value="America/Argentina/Buenos_Aires">Buenos Aires [ART -03:00]</option>
<option value="America/Cayenne">Cayenne [GFT -03:00]</option>
<option value="America/Glace_Bay">Glace Bay [ADT -03:00]</option>
<option value="America/Argentina/San_Luis">San Luis [WARST -03:00]</option>
<option value="America/Araguaina">Araguaina [BRT -03:00]</option>
<option value="America/Montevideo">Montevideo [UYT -03:00]</option>
<option value="America/St_Johns">St Johns [NDT -02:30]</option>
<option value="America/Miquelon">Miquelon [PMDT -02:00]</option>
<option value="America/Noronha">Noronha [FNT -02:00]</option>
<option value="America/Godthab">Godthab [WGST -02:00]</option>
<option value="Atlantic/Cape_Verde">Cape Verde [CVT -01:00]</option>
<option value="Atlantic/Azores">Azores [AZOST 00:00]</option>
<option value="America/Scoresbysund">Scoresbysund [EGST 00:00]</option>
<option value="UTC">UTC [UTC 00:00]</option>
<option value="Africa/Abidjan">Abidjan [GMT 00:00]</option>
<option value="Africa/Casablanca">Casablanca [WET 00:00]</option>
<option value="Africa/Bangui">Bangui [WAT +01:00]</option>
<option value="Europe/Guernsey">Guernsey [BST +01:00]</option>
<option value="Europe/Dublin">Dublin [IST +01:00]</option>
<option value="Africa/Algiers">Algiers [CET +01:00]</option>
<option value="Atlantic/Canary">Canary [WEST +01:00]</option>
<option value="Africa/Windhoek">Windhoek [WAT +01:00]</option>
<option value="Africa/Johannesburg">Johannesburg [SAST +02:00]</option>
<option value="Africa/Blantyre">Blantyre [CAT +02:00]</option>
<option value="Africa/Tripoli">Tripoli [EET +02:00]</option>
<option value="Africa/Ceuta">Ceuta [CEST +02:00]</option>
<option value="Asia/Jerusalem">Jerusalem [IDT +03:00]</option>
<option value="Africa/Addis_Ababa">Addis Ababa [EAT +03:00]</option>
<option value="Africa/Cairo">Cairo [EEST +03:00]</option>
<option value="Antarctica/Syowa">Syowa [SYOT +03:00]</option>
<option value="Europe/Volgograd">Volgograd [VOLST +04:00]</option>
<option value="Europe/Samara">Samara [SAMST +04:00]</option>
<option value="Asia/Tbilisi">Tbilisi [GET +04:00]</option>
<option value="Europe/Moscow">Moscow [MSD +04:00]</option>
<option value="Asia/Dubai">Dubai [GST +04:00]</option>
<option value="Indian/Mauritius">Mauritius [MUT +04:00]</option>
<option value="Indian/Reunion">Reunion [RET +04:00]</option>
<option value="Indian/Mahe">Mahe [SCT +04:00]</option>
<option value="Asia/Tehran">Tehran [IRDT +04:30]</option>
<option value="Asia/Kabul">Kabul [AFT +04:30]</option>
<option value="Asia/Aqtau">Aqtau [AQTT +05:00]</option>
<option value="Asia/Ashgabat">Ashgabat [TMT +05:00]</option>
<option value="Asia/Oral">Oral [ORAT +05:00]</option>
<option value="Asia/Yerevan">Yerevan [AMST +05:00]</option>
<option value="Asia/Baku">Baku [AZST +05:00]</option>
<option value="Indian/Kerguelen">Kerguelen [TFT +05:00]</option>
<option value="Indian/Maldives">Maldives [MVT +05:00]</option>
<option value="Asia/Karachi">Karachi [PKT +05:00]</option>
<option value="Asia/Dushanbe">Dushanbe [TJT +05:00]</option>
<option value="Asia/Samarkand">Samarkand [UZT +05:00]</option>
<option value="Antarctica/Mawson">Mawson [MAWT +05:00]</option>
<option value="Asia/Colombo">Colombo [IST +05:30]</option>
<option value="Asia/Kathmandu">Kathmandu [NPT +05:45]</option>
<option value="Indian/Chagos">Chagos [IOT +06:00]</option>
<option value="Asia/Bishkek">Bishkek [KGT +06:00]</option>
<option value="Asia/Almaty">Almaty [ALMT +06:00]</option>
<option value="Antarctica/Vostok">Vostok [VOST +06:00]</option>
<option value="Asia/Yekaterinburg">Yekaterinburg [YEKST +06:00]</option>
<option value="Asia/Dhaka">Dhaka [BDT +06:00]</option>
<option value="Asia/Thimphu">Thimphu [BTT +06:00]</option>
<option value="Asia/Qyzylorda">Qyzylorda [QYZT +06:00]</option>
<option value="Indian/Cocos">Cocos [CCT +06:30]</option>
<option value="Asia/Rangoon">Rangoon [MMT +06:30]</option>
<option value="Asia/Jakarta">Jakarta [WIT +07:00]</option>
<option value="Asia/Hovd">Hovd [HOVT +07:00]</option>
<option value="Antarctica/Davis">Davis [DAVT +07:00]</option>
<option value="Asia/Bangkok">Bangkok [ICT +07:00]</option>
<option value="Indian/Christmas">Christmas [CXT +07:00]</option>
<option value="Asia/Omsk">Omsk [OMSST +07:00]</option>
<option value="Asia/Novokuznetsk">Novokuznetsk [NOVST +07:00]</option>
<option value="Asia/Choibalsan">Choibalsan [CHOT +08:00]</option>
<option value="Asia/Ulaanbaatar">Ulaanbaatar [ULAT +08:00]</option>
<option value="Asia/Brunei">Brunei [BNT +08:00]</option>
<option value="Antarctica/Casey">Casey [WST +08:00]</option>
<option value="Asia/Singapore">Singapore [SGT +08:00]</option>
<option value="Asia/Manila">Manila [PHT +08:00]</option>
<option value="Asia/Hong_Kong">Hong Kong [HKT +08:00]</option>
<option value="Asia/Krasnoyarsk">Krasnoyarsk [KRAST +08:00]</option>
<option value="Asia/Makassar">Makassar [CIT +08:00]</option>
<option value="Asia/Kuala_Lumpur">Kuala Lumpur [MYT +08:00]</option>
<option value="Australia/Eucla">Eucla [CWST +08:45]</option>
<option value="Pacific/Palau">Palau [PWT +09:00]</option>
<option value="Asia/Tokyo">Tokyo [JST +09:00]</option>
<option value="Asia/Dili">Dili [TLT +09:00]</option>
<option value="Asia/Jayapura">Jayapura [EIT +09:00]</option>
<option value="Asia/Pyongyang">Pyongyang [KST +09:00]</option>
<option value="Asia/Irkutsk">Irkutsk [IRKST +09:00]</option>
<option value="Australia/Adelaide">Adelaide [CST +09:30]</option>
<option value="Asia/Yakutsk">Yakutsk [YAKST +10:00]</option>
<option value="Australia/Currie">Currie [EST +10:00]</option>
<option value="Pacific/Port_Moresby">Port Moresby [PGT +10:00]</option>
<option value="Pacific/Guam">Guam [ChST +10:00]</option>
<option value="Pacific/Truk">Truk [TRUT +10:00]</option>
<option value="Antarctica/DumontDUrville">DumontDUrville [DDUT +10:00]</option>
<option value="Australia/Lord_Howe">Lord Howe [LHST +10:30]</option>
<option value="Pacific/Ponape">Ponape [PONT +11:00]</option>
<option value="Pacific/Kosrae">Kosrae [KOST +11:00]</option>
<option value="Antarctica/Macquarie">Macquarie [MIST +11:00]</option>
<option value="Pacific/Noumea">Noumea [NCT +11:00]</option>
<option value="Pacific/Efate">Efate [VUT +11:00]</option>
<option value="Pacific/Guadalcanal">Guadalcanal [SBT +11:00]</option>
<option value="Asia/Sakhalin">Sakhalin [SAKST +11:00]</option>
<option value="Asia/Vladivostok">Vladivostok [VLAST +11:00]</option>
<option value="Pacific/Norfolk">Norfolk [NFT +11:30]</option>
<option value="Asia/Kamchatka">Kamchatka [PETST +12:00]</option>
<option value="Pacific/Tarawa">Tarawa [GILT +12:00]</option>
<option value="Asia/Magadan">Magadan [MAGST +12:00]</option>
<option value="Pacific/Wallis">Wallis [WFT +12:00]</option>
<option value="Pacific/Kwajalein">Kwajalein [MHT +12:00]</option>
<option value="Pacific/Funafuti">Funafuti [TVT +12:00]</option>
<option value="Pacific/Nauru">Nauru [NRT +12:00]</option>
<option value="Asia/Anadyr">Anadyr [ANAST +12:00]</option>
<option value="Antarctica/McMurdo">McMurdo [NZST +12:00]</option>
<option value="Pacific/Wake">Wake [WAKT +12:00]</option>
<option value="Pacific/Fiji">Fiji [FJT +12:00]</option>
<option value="Pacific/Chatham">Chatham [CHAST +12:45]</option>
<option value="Pacific/Enderbury">Enderbury [PHOT +13:00]</option>
<option value="Pacific/Tongatapu">Tongatapu [TOT +13:00]</option>
<option value="Pacific/Kiritimati">Kiritimati [LINT +14:00]</option>
</select>
Upvotes: 4
Reputation: 33401
Perhaps we could use a pregenerated list like the one presented here: http://www.ultramegatech.com/blog/2009/04/working-with-time-zones-in-php
Upvotes: 3
Reputation: 94834
If you want to do things with zoneinfo you don't really have any choice but to include hundreds of entries, because that's just the way zoneinfo works. It has generally at least one entry per country, and there are around 200 countries (according to Wikipedia).
What I've done before is to use timezone_identifiers_list()
and filter out any entry that isn't in one of the standard regions:
# Output option list, HTML.
$opt = '';
$regions = array('Africa', 'America', 'Antarctica', 'Arctic', 'Asia', 'Atlantic', 'Australia', 'Europe', 'Indian', 'Pacific');
$tzs = timezone_identifiers_list();
$optgroup = '';
sort($tzs);
foreach ($tzs as $tz) {
$z = explode('/', $tz, 2);
# timezone_identifiers_list() returns a number of
# backwards-compatibility entries. This filters them out of the
# list presented to the user.
if (count($z) != 2 || !in_array($z[0], $regions)) continue;
if ($optgroup != $z[0]) {
if ($optgroup !== '') $opt .= '</optgroup>';
$optgroup = $z[0];
$opt .= '<optgroup label="' . htmlentities($z[0]) . '">';
}
$opt .= '<option value="' . htmlentities($tz) . '" label="' . htmlentities(str_replace('_', ' ', $z[1])) . '">' . htmlentities(str_replace('_', ' ', $tz)) . '</option>';
}
if ($optgroup !== '') $opt .= '</optgroup>';
This creates a list with <optgroup>
elements, so the list will at least be logically divided by region.
Upvotes: 8
Reputation: 2030
I can think of a few options.
First being to populate the whole select list, and use a jQuery plugin like Chosen to populate the entire list. This would probably suck since your users might not even know all of the timezones.
Second option being to use JS to get the browser's local time in a hidden input field, and combine that with knowledge of the users IP to try to deduce the users location transparently. You could even display their assumed location on a map and ask them if you have the correct time.
Third option is to actually ask the user where they live. Country and postal code should probably be enough to get the timezone for most users. In edge cases you could ask the user to clarify.
Personally, I'd probably combine the first and second solution to make a select list display the regions you believe the user is closest to.
Upvotes: 3
Reputation: 39570
I found this to be an excellent resource: http://randomdrake.com/2008/08/06/time-zone-abbreviation-difficulties-with-php/
Upvotes: 4