Подтвердить что ты не робот

Лучший способ заполнить поле <SELECT> с помощью часовых поясов

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

То, что я не хочу делать, представляет собой поле выбора с более чем 300 часовыми поясами, и я не хочу создавать фальшивые смещения часового пояса с чем-то вроде UTC-8 (который теряет не только информацию DST, но и фактическую даты, на которые падает DST).

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

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

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


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

4b9b3361

Ответ 1

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>

Ответ 2

Мое решение:

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

Файл 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>

файл 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; 

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

Ответ 3

Если вы хотите делать что-то с 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> элементами, поэтому список будет по крайней мере логически разделен по региону.

Ответ 4

Я придумал динамическое самообновляющееся решение, которое не требует никаких таблиц поиска (выберите демо):

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

Существует много часовых поясов с одинаковыми смещениями и временами DST (например, 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 - что довольно неплохо для ИМО и, похоже, отражает список Windows использует "нормализованные" часовые пояса (также 75).

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

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

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

Ответ 6

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

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

  • Позволяет создать список часовых поясов из всего списка внутри 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>

Ответ 7

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

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

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

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

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

Ответ 9

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

Демо на http://www.ideone.com/VYWtw и http://jsfiddle.net/hSxa8/embedded/result/.

Ответ 11

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

Запись Github: https://github.com/ryanzor/timezone-dropdown

Демонстрация только селектора: http://lifesnow.com/time-zone-dropdown/

<?php

/**
  * 
  * This get 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
}
?>

Ответ 12

Аналогичен ответу @Zubair1, но намного короче, а также показывает текущее время в каждом часовом поясе:

foreach(timezone_identifiers_list() as $tz)
{
    $current_dt = new DateTime('now', new DateTimeZone($tz));

    echo '<option value=' . $tz . '>'
            . str_replace('_', ' ', $tz) . ' - ' // Some timezones contain '_'
            . $current_dt->format('\G\M\TP') // Add the timezone in GMT
            . ' (' . $current_dt->format('H:i') . ')'; // Add the time in $tz
}

Выводится что-то вроде: Europe/Brussels - GMT+02:00 (23:57)
Любой действительный формат (GMT == UTC) может быть использован для часового пояса (и текущего времени, конечно).