<?php
/**
 * Abstract class for helping with timezones; specifically converting strings to DateTime objects.
 *
 * @copyright   2014 Perforce Software. All rights reserved.
 * @license     Please see LICENSE.txt in top-level folder of this distribution.
 * @version     <release>/<patch>
 */

namespace P4\Time;

class Time
{
    // translation map derived from http://unicode.org/repos/cldr/trunk/common/supplemental/windowsZones.xml
    // using the script collateral/scripts/timezonemap.php
    // we filter for php supported zones and prefer territory 001 where possible.
    protected static $tzWindowsToPhp    = array(
        'afghanistan standard time'       => 'Asia/Kabul',
        'alaskan standard time'           => 'America/Anchorage',
        'arabian standard time'           => 'Asia/Dubai',
        'arabic standard time'            => 'Asia/Baghdad',
        'arab standard time'              => 'Asia/Riyadh',
        'atlantic standard time'          => 'America/Halifax',
        'aus central standard time'       => 'Australia/Darwin',
        'aus eastern standard time'       => 'Australia/Sydney',
        'azerbaijan standard time'        => 'Asia/Baku',
        'azores standard time'            => 'Atlantic/Azores',
        'bahia standard time'             => 'America/Bahia',
        'bangladesh standard time'        => 'Asia/Dhaka',
        'canada central standard time'    => 'America/Regina',
        'cape verde standard time'        => 'Atlantic/Cape_Verde',
        'caucasus standard time'          => 'Asia/Yerevan',
        'cen. australia standard time'    => 'Australia/Adelaide',
        'central america standard time'   => 'America/Guatemala',
        'central asia standard time'      => 'Asia/Almaty',
        'central brazilian standard time' => 'America/Cuiaba',
        'central european standard time'  => 'Europe/Warsaw',
        'central europe standard time'    => 'Europe/Budapest',
        'central pacific standard time'   => 'Pacific/Guadalcanal',
        'central standard time'           => 'America/Chicago',
        'central standard time (mexico)'  => 'America/Mexico_City',
        'china standard time'             => 'Asia/Shanghai',
        'e. africa standard time'         => 'Africa/Nairobi',
        'e. australia standard time'      => 'Australia/Brisbane',
        'e. south america standard time'  => 'America/Sao_Paulo',
        'eastern standard time'           => 'America/New_York',
        'egypt standard time'             => 'Africa/Cairo',
        'ekaterinburg standard time'      => 'Asia/Yekaterinburg',
        'fiji standard time'              => 'Pacific/Fiji',
        'fle standard time'               => 'Europe/Kiev',
        'georgian standard time'          => 'Asia/Tbilisi',
        'gmt standard time'               => 'Europe/London',
        'greenland standard time'         => 'America/Godthab',
        'greenwich standard time'         => 'Atlantic/Reykjavik',
        'gtb standard time'               => 'Europe/Bucharest',
        'hawaiian standard time'          => 'Pacific/Honolulu',
        'iran standard time'              => 'Asia/Tehran',
        'israel standard time'            => 'Asia/Jerusalem',
        'jordan standard time'            => 'Asia/Amman',
        'kaliningrad standard time'       => 'Europe/Kaliningrad',
        'korea standard time'             => 'Asia/Seoul',
        'libya standard time'             => 'Africa/Tripoli',
        'magadan standard time'           => 'Asia/Magadan',
        'mauritius standard time'         => 'Indian/Mauritius',
        'middle east standard time'       => 'Asia/Beirut',
        'montevideo standard time'        => 'America/Montevideo',
        'morocco standard time'           => 'Africa/Casablanca',
        'mountain standard time'          => 'America/Denver',
        'mountain standard time (mexico)' => 'America/Chihuahua',
        'myanmar standard time'           => 'Asia/Rangoon',
        'n. central asia standard time'   => 'Asia/Novosibirsk',
        'namibia standard time'           => 'Africa/Windhoek',
        'newfoundland standard time'      => 'America/St_Johns',
        'new zealand standard time'       => 'Pacific/Auckland',
        'north asia east standard time'   => 'Asia/Irkutsk',
        'north asia standard time'        => 'Asia/Krasnoyarsk',
        'pacific sa standard time'        => 'America/Santiago',
        'pacific standard time'           => 'America/Los_Angeles',
        'pacific standard time (mexico)'  => 'America/Santa_Isabel',
        'pakistan standard time'          => 'Asia/Karachi',
        'paraguay standard time'          => 'America/Asuncion',
        'romance standard time'           => 'Europe/Paris',
        'russian standard time'           => 'Europe/Moscow',
        'sa eastern standard time'        => 'America/Cayenne',
        'samoa standard time'             => 'Pacific/Apia',
        'sa pacific standard time'        => 'America/Bogota',
        'sa western standard time'        => 'America/La_Paz',
        'se asia standard time'           => 'Asia/Bangkok',
        'singapore standard time'         => 'Asia/Singapore',
        'south africa standard time'      => 'Africa/Johannesburg',
        'sri lanka standard time'         => 'Asia/Colombo',
        'syria standard time'             => 'Asia/Damascus',
        'taipei standard time'            => 'Asia/Taipei',
        'tasmania standard time'          => 'Australia/Hobart',
        'tokyo standard time'             => 'Asia/Tokyo',
        'tonga standard time'             => 'Pacific/Tongatapu',
        'turkey standard time'            => 'Europe/Istanbul',
        'ulaanbaatar standard time'       => 'Asia/Ulaanbaatar',
        'us mountain standard time'       => 'America/Phoenix',
        'utc+12'                          => 'Pacific/Tarawa',
        'utc-02'                          => 'America/Noronha',
        'utc-11'                          => 'Pacific/Pago_Pago',
        'venezuela standard time'         => 'America/Caracas',
        'vladivostok standard time'       => 'Asia/Vladivostok',
        'w. australia standard time'      => 'Australia/Perth',
        'w. central africa standard time' => 'Africa/Lagos',
        'w. europe standard time'         => 'Europe/Berlin',
        'west asia standard time'         => 'Asia/Tashkent',
        'west pacific standard time'      => 'Pacific/Port_Moresby',
        'yakutsk standard time'           => 'Asia/Yakutsk',
    );

    /**
     * This method attempts to turn the passed timezone name/id into a DateTimeZone object.
     * The passed zone can be a windows timezone name, timezone abbreviation or
     * supported long-form PHP timezone.
     *
     * If we are not able to get the passed zone into a value DateTimeZone will recognize,
     * an exception will be thrown.
     *
     * @param   string          $name       the name or abbreviated timezone to turn into a date time zone
     * @param   string|int|null $offset     optional - the offset in minutes e.g. "-0800" or -800
     * @return  \DateTimeZone   an object representing the specified timezone
     * @throws  \Exception      if the passed timezone isn't parsable
     */
    public static function toDateTimeZone($name, $offset = null)
    {
        // if we got a windows timezone name, map it over to the PHP compatible value
        if (isset(static::$tzWindowsToPhp[strtolower($name)])) {
            $name = static::$tzWindowsToPhp[strtolower($name)];
        }

        // the 'daylight' version of windows time still map to the same olson value; check that too
        if (isset(static::$tzWindowsToPhp[str_replace('daylight', 'standard', strtolower($name))])) {
            $name = static::$tzWindowsToPhp[str_replace('daylight', 'standard', strtolower($name))];
        }

        // if we have a known abbreviation, resolve it into a full timezone name
        if (array_key_exists(strtolower($name), array_change_key_case(timezone_abbreviations_list(), CASE_LOWER))) {
            // if we have an offset it is expected to be in the format -430 or "+0800"
            // convert this value from hours and minutes to seconds
            $gmtOffset = null;
            if ($offset) {
                $gmtOffset  = ($offset % 100) * 60;
                $gmtOffset += ((int) ($offset / 100)) * 60 * 60;
            }

            $name = timezone_name_from_abbr($name, $gmtOffset);
        }

        return new \DateTimeZone($name);
    }
}