php 用 TimeZones 填充 <SELECT> 框的最佳方法
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/6921827/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Best way to populate a <SELECT> box with TimeZones
提问by John Green
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.
我需要将时区选择器显示为用户控件,这似乎总是比实际更容易。在内部,我使用 DateTimeZone 标识符存储所有内容,因为这似乎是达到我需要的准确度水平的最明智的方法,因为该项目连接了现实世界,因为它与地面媒体相关。
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).
我不想做的是显示一个包含 300 多个时区的选择框,也不想创建带有“UTC-8”之类的假时区偏移量(它不仅会丢失 DST 信息,还会丢失实际日期)夏令时落在)。
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):
最后,我需要一个带有包含正确 TZD 标识符的选项的选择,就像这样(括号中的 #s 不是必需的,只是为了潜在的最终用户说明):
<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. : )
我添加了一个赏金,以防万一可能会吸引某人与我们分享更好的答案。:)
回答by Zubair1
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>
回答by CONvid19
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
文件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
文件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 :)
我希望它可以帮助你:)
回答by Anomie
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).
如果你想用 zoneinfo 做一些事情,你真的别无选择,只能包含数百个条目,因为这就是 zoneinfo 的工作方式。它通常每个国家至少有一个条目,大约有 200 个国家(根据维基百科)。
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:
我之前所做的是使用timezone_identifiers_list()
并过滤掉不在标准区域之一中的任何条目:
# 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.
这将创建一个包含<optgroup>
元素的列表,因此该列表至少会按区域进行逻辑划分。
回答by Alix Axel
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('', ' '), $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:
有很多的时区的共享完全相同的偏移和DST的时序(Europe/Dublin
,Europe/Lisbon
并Europe/London
仅举几例),我的算法组这些区域(在数组键使用特殊的符号dst?|offset|timestamp
在该组的第一个时区ID)并连接人源化的转变时区 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).
诚然,城市串联仍然很长,但唯一(实际)时区列表已从 414(或 415,如果我们考虑非地理 UTC)下降到 75 - 这是非常好的 IMO,似乎反映了列表Windows 使用的“标准化”时区(也是 75)。
There are two big problems with this automated approach:
这种自动化方法有两个大问题:
- the chosen timezone ID for a group of cities is the first in alphabetic order, this means that for (UTC) Canary, Dublin, Faroe, Guernsey, Isle of Man, Jersey, Lisbon, London, Madeirathe timezone value will be
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 (likeEurope/London
) - the concatenation of cities is clearly the biggest problem, there are just too many of them - one way to solve this issue would be by using
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, Madeirawould become Canary, Dublin, Faroe, Guernseyinstead of the more logical Windows equivalent Dublin, Edinburgh, Lisbon, London.
- 为一组城市选择的时区 ID 是按字母顺序排列的第一个,这意味着对于(UTC) Canary、Dublin、Faroe、Guernsey、Isle of Man、Jersey、Lisbon、London、Madeira,时区值将是
Atlantic/Canary
- 而在那里应该没有任何问题,选择与大城市相关联的时区 ID 会更有意义(例如Europe/London
) - 城市的串联显然是最大的问题,它们太多了 - 解决这个问题的一种方法是
array_slice($cities, 0, $maxCities)
在内爆之前使用,但这不会考虑城市维度,并且限制为 4 Canary, Dublin, Faroe, Guernsey, Isle of Man, Jersey, Lisbon, London, Madeira将变成Canary, Dublin, Faroe, Guernsey而不是更合乎逻辑的 Windows 等价物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.
这应该不是很有用,但我想我会分享 - 也许其他人可以改进它。
回答by OverZealous
I found this to be an excellent resource: http://randomdrake.com/2008/08/06/time-zone-abbreviation-difficulties-with-php/
我发现这是一个很好的资源:http: //randomdrake.com/2008/08/06/time-zone-abbreviation-difficulties-with-php/
回答by John Green
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.
允许您从 PHP 内的整个列表创建时区列表
允许您根据自己的预烘焙定义创建一个不错的列表,计算当前的 UTC 偏移量。
允许您按国家/地区创建列表。
使用相同的缩写对时区进行重复数据删除。请注意,我没有做太多研究来查看列表中是否有我不应该删除的重复项。知道即使两个时区可能报告相同的缩写(例如,亚利桑那州的 MST),它也将进一步确定时区是否支持一年中的任何时候的夏令时,这一点是足够聪明的。
为 Ajax 或内联 JavaScript 输出相当可配置的 HTML(无需进入模板路由)或 JSON。
计算任何给定时区的当前时区偏移量。请注意,这不是一成不变的……它会全年发生变化。
为实际使用提供大量糖分。
What it doesn't do:
它不做什么:
- Separate countries or continents out through an OptGroup. Since I'm already doing a sort by UTC offset, this seemed like it was going to cause more confusion than it was going to alleviate.
- 通过 OptGroup 将国家或大陆分开。由于我已经在按 UTC 偏移量进行排序,因此这似乎会引起更多的混乱而不是减轻。
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.
从模板中构建 HTML,而不是像这样内联。
额外的选择器,比如采用一系列国家而不是单个国家。
分组,但正如我上面提到的,我不打算这样做。
代码很湿。有一些小的编码风格差异。
能够指定友好名称的“首选”列表。例如,虽然“道森”是被列为 PDT 的“最佳”城市的完全可行的候选者,但由于它是 PST 主要组的一部分并遵守夏令时……一个只有 1000 多名居民的城市不应该击败洛杉矶、旧金山、西雅图或温哥华,仅仅因为它首先出现在词汇上。
原始输出,因此没有人被简单的“toSelect”和“toJson”方法所束缚。
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:
<?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):
哇。这是使用示例(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:
这是 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>
回答by KyleWpppd
I can think of a few options.
我能想到几个选项。
First being to populate the whole select list, and use a jQuery plugin like Chosento populate the entire list. This would probably suck since your users might not even know all of the timezones.
首先是填充整个选择列表,然后使用像Chosen这样的 jQuery 插件来填充整个列表。这可能会很糟糕,因为您的用户甚至可能不知道所有的时区。
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.
第二种选择是使用 JS 在隐藏的输入字段中获取浏览器的本地时间,并将其与用户 IP 的知识相结合,尝试透明地推断用户位置。您甚至可以在地图上显示他们假设的位置,并询问他们您是否有正确的时间。
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.
就个人而言,我可能会结合第一个和第二个解决方案来制作一个选择列表,显示您认为用户最接近的区域。
回答by F21
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
也许我们可以使用像这里介绍的那样的预生成列表:http: //www.ultramegatech.com/blog/2009/04/working-with-time-zones-in-php
回答by Alix Axel
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.
在对这个问题进行了大量思考(和测试)之后,我得出的结论是,由于时区规则是动态的(超出您的预期 - 4 个时区将有它们的规则)这一简单事实,您不能使用时区候选列表本月更改)并且组/串联/缩写是静态的。
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);
}
- supports filtering by country and continent simultaneously (as in US/America or US/Pacific)
- the standard (raw) offset is dynamically calculated for each timezone
- timezones are ordered by their offset first and then by their location
- the timezone identifier is converted into a human readable representation
- 支持同时按国家和大洲过滤(如美国/美洲或美国/太平洋)
- 为每个时区动态计算标准(原始)偏移量
- 时区首先按偏移量排序,然后按位置排序
- 时区标识符被转换为人类可读的表示
Demo at http://www.ideone.com/VYWtwand http://jsfiddle.net/hSxa8/embedded/result/.
在http://www.ideone.com/VYWtw和http://jsfiddle.net/hSxa8/embedded/result/ 上进行演示。
回答by Ryan C
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.
这个问题早就有人回答了,但是我对强迫用户浏览 php 知道的所有 TZ 条目的任何答案都不满意。相反,我创建了一个更简洁的列表,将东部时间等时间映射到 America/New_York,并包括亚利桑那等奇数地点的特殊条目。
Github entry: https://github.com/ryanzor/timezone-dropdown
Github 入口:https: //github.com/ryanzor/timezone-dropdown
Demo of just selector: http://lifesnow.com/time-zone-dropdown/
仅选择器的演示: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
}
?>