Как преобразовать имя часового пояса в смещение часового пояса и наоборот в PHP

У меня есть база данных, полная пользователей, у каждого из которых есть столбец timezone, хранящийся в виде строки (например, America/New_York). (И я знаю, что хранить их как смещения в базе данных имеет смысл, но это невозможно сделать для этого проекта). У меня есть форма, в которой пользователь может изменить свой часовой пояс. Вот мой первый подход:

У меня есть это заполнение раскрывающегося списка (value => display):

protected $timezones = [
    '+00:00' => '(UTC +00:00) Western European Time',
    '+01:00' => '(UTC +01:00) Central European Time',
    '+02:00' => '(UTC +02:00) Eastern European Time',
    '+03:00' => '(UTC +03:00) Further-Eastern European Time',
    '+04:00' => '(UTC +04:00) Gulf Standard Time',
    '+05:00' => '(UTC +05:00) Pakistan Standard Time',
    '+05:30' => '(UTC +05:30) Indian Standard Time',
    '+05:45' => '(UTC +05:45) Nepal Time',
    '+06:00' => '(UTC +06:00) British Indian Ocean Time',
    '+07:00' => '(UTC +07:00) Thailand Standard Time',
    '+08:00' => '(UTC +08:00) Australian Western Standard Time',
    '+09:00' => '(UTC +09:00) Japan Standard Time',
    '+10:00' => '(UTC +10:00) Australian Eastern Standard Time',
    '+10:30' => '(UTC +10:30) Australian Central Daylight Savings Time',
    '+11:00' => '(UTC +11:00) Australian Eastern Daylight Savings Time',
    '+12:00' => '(UTC +12:00) New Zealand Standard Time',
    '+13:00' => '(UTC +13:00) New Zealand Daylight Time',
    '-01:00' => '(UTC -01:00) Eastern Greenland Time',
    '-02:00' => '(UTC -02:00) Brasilia Summer Time',
    '-03:00' => '(UTC -03:00) Atlantic Daylight Time',
    '-04:00' => '(UTC -04:00) Atlantic Standard Time',
    '-05:00' => '(UTC -05:00) Central Daylight Time',
    '-06:00' => '(UTC -06:00) Central Standard Time',
    '-07:00' => '(UTC -07:00) Pacific Daylight Time',
    '-08:00' => '(UTC -08:00) Pacific Standard Time',
    '-09:00' => '(UTC -09:00) Alaska Standard Time',
    '-10:00' => '(UTC -10:00) Hawaii-Aleutian Standard Time',
    '-11:00' => '(UTC -11:00) Niue Time',
];

Эта функция для преобразования строки часового пояса в смещение:

 /**
 * convert timezone string to offset
 *   e.g. "America/New_York" to "-04:00"
 *
 * @param string $timezone time zone represented as a string
 *
 * @return mixed string or null
 */
protected function convertStringToOffset($timezone)
{
    $time = new \DateTime('now', new DateTimeZone($timezone));
    if ($time) {
        return $time->format('P');
    }
}

И эта функция для преобразования смещения в строку:

/**
 * convert timezone offset to string
 *    e.g. "-04:00" to "America/New_York"
 *
 * @param string $offset time zone represented as a numerical offset
 *
 * @return string time zone represented as a string
 */
protected function convertOffsetToString($offset)
{
    // Calculate seconds from offset
    list($hours, $minutes) = explode(':', $offset);
    $seconds = $hours * 60 * 60 + $minutes * 60;
    // Get timezone name from seconds
    $tz = timezone_name_from_abbr('', $seconds, 1);
    // Workaround for bug #44780
    if ($tz === false) {
        $tz = timezone_name_from_abbr('', $seconds, 0);
    }
    return $tz;
}
  1. Итак, при загрузке страницы я бы вытащил часовой пояс из базы данных и преобразовал его в смещение. Например, America/New_York будет преобразовано в -04:00 и будет выбрано (UTC -04:00) Atlantic Standard Time.

  2. Теперь пользователь выберет другой часовой пояс и отправит форму. Затем я преобразовал бы смещение в строку, а затем сохранил бы ее в базе данных.

Я столкнулся с проблемой:

  • $this->convertOffsetToString('+03:00'); возвращает Europe/Helsinki
  • $this->convertOffsetToString('+04:00'); возвращает Europe/Moscow

но!

  • $this->convertStringToOffset('Europe/Moscow'); возвращает +03:00
  • $this->convertStringToOffset('Europe/Helsinki'); возвращает +03:00

Таким образом, если бы пользователь зашел в форму и имел часовой пояс Europe/Moscow в базе данных, мы бы преобразовали строку в ее смещение, получив +03:00, и было бы выбрано '(UTC +03:00) Further-Eastern European Time'.

Проблема: два разных смещения (например, +03:00 и +04:00) будут преобразованы в два разных строковых часовых пояса. Эти два разных строковых часовых пояса будут преобразованы в одно и то же смещение! (например, +03:00). Может ли кто-нибудь предложить безопасное масштабируемое решение для решения этой проблемы?


person Oneil Kwangwanh    schedule 22.09.2016    source источник
comment
TL; DR Вы не можете преобразовать смещение в имя. Многие часовые пояса имеют общие смещения.   -  person Álvaro González    schedule 22.09.2016


Ответы (1)


Прежде всего,

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

Нет, это не так. Смещения меняются. Многие часовые пояса применяют переход на летнее время в течение части года, и даты/время, в которые они меняются, различаются.

Вы должны сохранить часовой пояс пользователя в виде строки из этот список, а затем использовать его для создания экземпляр \DateTimeZone. Например:-

$tzString = 'Europe/London';
$tz = new \DateTimeZone($tzString);

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

$tz->getOffset(new \DateTime());

Пример. Обратите внимание, что смещение, полученное в примере, будет варьироваться от 0 до 3600 в зависимости от даты и времени, когда вы его просматриваете.

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

Я бы предположил, что это «безопасное масштабируемое решение» вашей проблемы.

person vascowhite    schedule 23.09.2016
comment
Меняются не только смещения. Нет представления о других частях света, но DSL в Европейском Союзе достигается путем фактического переключения часовых поясов (например, CET зимой и CEST летом). Вот почему синтаксис Europe/London, предлагаемый PHP, так удобен. Я ненавижу выбирать +0100 в панели управления профилем пользователя. - person Álvaro González; 26.09.2016
comment
@ ÁlvaroGonzález Это хороший момент. В Великобритании у нас есть BST летом и GMT зимой. - person vascowhite; 26.09.2016