Лучший способ заполнить поле <SELECT>с помощью часовых поясов - PullRequest
43 голосов
/ 03 августа 2011

Мне нужно отобразить селектор часовых поясов как пользовательский элемент управления, который всегда кажется проще, чем есть на самом деле.Внутренне я храню все с идентификатором DateTimeZone, так как это, кажется, самый разумный способ достичь необходимого уровня точности, поскольку этот проект соединяет реальные времена, поскольку он привязан к наземным носителям.

ЧтоЯ не хочу делать это, чтобы выбрать поле выбора с 300+ часовыми поясами, и при этом я не хочу создавать поддельные смещения часового пояса с чем-то вроде 'UTC-8' (который теряет не только информацию о летнем времени, но и фактические даты, когда летнее время)

В конце мне понадобится выбор с опциями, содержащими правильные идентификаторы TZD, что-то вроде этого (заключенные в скобки # не нужны, просто для иллюстрации потенциального конечного пользователя):

<select>
<option value="America/Los_Angeles">Los Angeles [UTC-7 | DST]</option>
...
</select>

У кого-нибудь есть указатели для создания этого списка?Все решения, которые я погуглил, были так или иначе проблематичны.


Я добавил вознаграждение на случай, если кто-нибудь может побудить кого-нибудь поделиться с нами более приятным ответом.:)

Ответы [ 11 ]

65 голосов
/ 11 августа 2011
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>';

Выше будут выведены все часовые пояса в меню выбора в следующем формате:

<select name="userTimeZone">
<option value="America/Los_Angeles">America/Los_Angeles [PDT -7]</option>
</select>
9 голосов
/ 16 августа 2011

Мое решение:

Чтобы избежать огромного списка часовых поясов, попросите пользователя сначала выбрать страну, а затем использовать эту информацию для заполнения списка часовых поясов.

Файл заполнить.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; 

Надеюсь, это поможет вам:)

8 голосов
/ 10 августа 2011

Если вы хотите что-то сделать с zoneinfo, у вас нет другого выбора, кроме как включить сотни записей, потому что именно так работает zoneinfo.Как правило, он имеет по крайней мере одну запись на страну, и существует около 200 стран (согласно Wikipedia ).

То, что я сделал раньше, - это использование 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>';

Это создает список с <optgroup> элементами, поэтому список будет по крайней мере логически разделен на области.

5 голосов
/ 18 октября 2011

Я придумал динамическое самообновляющееся решение, которое не требует никаких таблиц поиска ( 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;
}

Существует множество часовых поясов, которые разделяют точныес теми же смещениями и временами перехода на летнее время (Europe/Dublin, Europe/Lisbon и Europe/London и многие другие), мой алгоритм группирует эти зоны (используя специальную запись в ключах массива dst?|offset|timestamp) в первом идентификаторе часового пояса этой группы иобъединяет гуманизированные преобразования последнего (обычно городского уровня) сегмента идентификатора часового пояса:

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
)

Конечно, конкатенация города все еще довольно длинная, но список уникальных (фактических) часовых поясов сократился с 414 (или 415, если мы рассматриваем негеографический UTC) до 75 - что является довольно хорошим IMO и, кажется, отражает список «нормализованных» часовых поясов, который Windows использует (также 75).

У этого автоматического подхода есть две большие проблемы:

  1. выбранный идентификатор часового пояса для группы городов является первым в алфавитном порядке, это означает, что для (UTC) Канарские, Дублинские, Фарерские, Гернси, Остров Мэн, Джерси, Лиссабон, Лондон, Мадейра значение часового пояса будет Atlantic/Canary - хотя в этом не должно быть ничего плохого, оно будетболее разумно выбрать идентификатор часового пояса, связанный с большим городом (например, Europe/London)
  2. объединение городов, безусловно, является самой большой проблемой, их слишком много - один из способов решить эту проблемуиспользовать array_slice($cities, 0, $maxCities) перед взрывом, но это не будет учитывать размер города, и для предела 4 Канарские острова, Дублин, Фарерские острова, Гернси, Остров Мэн, Джерси, Лиссабон, Лондон, Мадейра станет Канарскими, Дублинскими, Фарерскими, Гернси вместо более логичного Windows-эквивалента Дублин, Эдинбург, Лиссабон, Лондон .

Это не должно быть очень полезно, но я подумал, что поделюсь - возможно, кто-то еще может улучшить его.

4 голосов
/ 16 августа 2011

В конце концов, я не думаю, что есть хорошее решение для предварительной оценки слишком большого списка часовых поясов ... И связанные с этим проблемы заставили меня принять решение начать с прилично-размер класса.Я публикую это здесь, так как некоторые люди были заинтересованы в этом.Надеюсь, мое решение поможет кому-то еще.

Что оно делает:

  • Позволяет создать список часовых поясов из всего списка внутри PHP

  • Позволяет вам создать красивый список из ваших собственных предварительно запеченных определений, вычисляя текущие смещения UTC.

  • Позволяет создать список по стране.

  • Дедуплицирует часовые пояса с тем же сокращением.Обратите внимание, что я не провел большого исследования, чтобы увидеть, есть ли в списке дубликаты, от которых мне не следует избавляться.Он достаточно умен, чтобы знать, что даже если два часовых пояса могут сообщать об одной и той же аббревиатуре (скажем, MST для Аризоны), он дополнительно укажет, поддерживает ли часовой пояс DST в любое время года.

  • Выводит либо достаточно настраиваемый HTML (без перехода к шаблону маршрута), либо JSON для Ajax или встроенного JavaScript.

  • Вычисляет текущее смещение часового пояса для любого заданного часового пояса.Обратите внимание, что это не статично ... оно будет меняться в течение года.

  • Обеспечивает большое количество сахара для реального использования.

Чего он не делает:

  • Отделение стран или континентов через OptGroup.Поскольку я уже делаю сортировку по смещению UTC, казалось, что это вызовет больше путаницы, чем облегчения.

Вещи, которые я мог бы добавить однажды:

  • Создайте HTML из шаблона вместо встроенного, как это.

  • Дополнительные селекторы, такие как выбор массива стран вместо одного.

  • Группировка, но, как я уже упоминал выше, я не могу этим заниматься.

  • Код довольно влажный.Существуют незначительные расхождения в стиле кодирования.

  • Возможность указать «предпочтительный» список дружественных имен.Например, хотя «Доусон» является вполне жизнеспособным кандидатом для включения в список «городов» для ФДТ, поскольку он входит в основную группу PST и проводит летнее время ... город с населением чуть более 1000 жителей не долженвыиграл у Лос-Анджелеса, Сан-Франциско, Сиэтла или Ванкувера только потому, что он сначала отображается лексически.

  • Необработанный вывод, так что никто не привязан к простым методам toSelect и toJson.

В общем, оно должно соответствовать моим потребностям как есть.Он правильно обрабатывает все часовые пояса, с которыми я знаком, и все те, которые мне нужны для моего текущего проекта.К сожалению, мой объем знаний сосредоточен в основном в США и Западной Европе.

Это идеально?Возможно нет.Рад слышать о любых проблемах / ошибках / улучшениях, которые могут возникнуть у кого-либо с этим кодом, так как он может оказаться в моей постоянной библиотеке.Если кто-то думает, что я высокий ... дай мне знать это тоже.В этом и заключался весь смысл этого вопроса - найти лучший способ точного представления селектора часового пояса, который на самом деле несколько полезен для конечных пользователей.

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;
    }
}

Вот так.Вот примеры использования (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');

А вот выходной код из примеров в 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>
4 голосов
/ 03 августа 2011

Мне показалось, что это отличный ресурс: http://randomdrake.com/2008/08/06/time-zone-abbreviation-difficulties-with-php/

3 голосов
/ 16 августа 2011

Возможно, мы могли бы использовать предварительно сгенерированный список, подобный представленному здесь: http://www.ultramegatech.com/blog/2009/04/working-with-time-zones-in-php

3 голосов
/ 03 августа 2011

Я могу придумать несколько вариантов.

Сначала нужно заполнить весь список выбора и использовать плагин jQuery, например Chosen , чтобы заполнить весь список.Это может быть отстой, так как ваши пользователи могут даже не знать все часовые пояса.

Второй вариант - использовать JS, чтобы получить местное время браузера в скрытом поле ввода, и объединить его со знанием IP-адресов пользователей, чтобы попытаться прозрачно определить местоположение пользователей.Вы даже можете отобразить их предполагаемое местоположение на карте и спросить их, правильно ли вы указали время.

Третий вариант - спросить пользователя, где он живет.Страна и почтовый индекс, вероятно, должно быть достаточно, чтобы получить часовой пояс для большинства пользователей.В крайних случаях вы можете попросить пользователя уточнить.

Лично я бы, вероятно, соединил первое и второе решение, чтобы в списке выбора отобразились регионы, к которым, по вашему мнению, пользователь ближе всего.

2 голосов
/ 26 октября 2011

Подумав (и протестировав) много об этой проблеме, я пришел к выводу, что нельзя использовать короткие списки часовых поясов из-за простого факта, что правила часовых поясов являются динамическими (больше, чем вы могли ожидать - 4 часовых пояса будутих правила были изменены в этом месяце) и группы / объединения / сокращения являются статическими.

Вот код подхода, который я решил придерживаться:

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);
}
  • поддерживает фильтрацию пострана и континент одновременно (как в США / Америке или США / Тихоокеанском регионе)
  • стандартное (необработанное) смещение динамически рассчитывается для каждого часового пояса
  • часовые пояса упорядочены сначала по их смещению, а затем по ихlocation
  • идентификатор часового пояса преобразуется в удобочитаемое представление

Демонстрация в http://www.ideone.com/VYWtw и http://jsfiddle.net/hSxa8/embedded/result/.

1 голос
/ 07 октября 2013

На это уже давно дан ответ, но я не был удовлетворен ни одним из ответов, которые заставляли пользователей просматривать все записи TZ, которые знает php. Вместо этого я создал более краткий список, который отображает время, например, восточное время, в Америку / Нью-Йорк и включает специальные записи для странных мест, таких как Аризона.

Запись Github: https://github.com/ryanzor/timezone-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
}
?>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...