TimeZoneUtil.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265
  1. <?php
  2. namespace Sabre\VObject;
  3. /**
  4. * Time zone name translation
  5. *
  6. * This file translates well-known time zone names into "Olson database" time zone names.
  7. *
  8. * @copyright Copyright (C) 2007-2014 fruux GmbH (https://fruux.com/).
  9. * @author Frank Edelhaeuser (fedel@users.sourceforge.net)
  10. * @author Evert Pot (http://evertpot.com/)
  11. * @license http://sabre.io/license/ Modified BSD License
  12. */
  13. class TimeZoneUtil {
  14. public static $map = null;
  15. /**
  16. * List of microsoft exchange timezone ids.
  17. *
  18. * Source: http://msdn.microsoft.com/en-us/library/aa563018(loband).aspx
  19. */
  20. public static $microsoftExchangeMap = array(
  21. 0 => 'UTC',
  22. 31 => 'Africa/Casablanca',
  23. // Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo.
  24. // I'm not even kidding.. We handle this special case in the
  25. // getTimeZone method.
  26. 2 => 'Europe/Lisbon',
  27. 1 => 'Europe/London',
  28. 4 => 'Europe/Berlin',
  29. 6 => 'Europe/Prague',
  30. 3 => 'Europe/Paris',
  31. 69 => 'Africa/Luanda', // This was a best guess
  32. 7 => 'Europe/Athens',
  33. 5 => 'Europe/Bucharest',
  34. 49 => 'Africa/Cairo',
  35. 50 => 'Africa/Harare',
  36. 59 => 'Europe/Helsinki',
  37. 27 => 'Asia/Jerusalem',
  38. 26 => 'Asia/Baghdad',
  39. 74 => 'Asia/Kuwait',
  40. 51 => 'Europe/Moscow',
  41. 56 => 'Africa/Nairobi',
  42. 25 => 'Asia/Tehran',
  43. 24 => 'Asia/Muscat', // Best guess
  44. 54 => 'Asia/Baku',
  45. 48 => 'Asia/Kabul',
  46. 58 => 'Asia/Yekaterinburg',
  47. 47 => 'Asia/Karachi',
  48. 23 => 'Asia/Calcutta',
  49. 62 => 'Asia/Kathmandu',
  50. 46 => 'Asia/Almaty',
  51. 71 => 'Asia/Dhaka',
  52. 66 => 'Asia/Colombo',
  53. 61 => 'Asia/Rangoon',
  54. 22 => 'Asia/Bangkok',
  55. 64 => 'Asia/Krasnoyarsk',
  56. 45 => 'Asia/Shanghai',
  57. 63 => 'Asia/Irkutsk',
  58. 21 => 'Asia/Singapore',
  59. 73 => 'Australia/Perth',
  60. 75 => 'Asia/Taipei',
  61. 20 => 'Asia/Tokyo',
  62. 72 => 'Asia/Seoul',
  63. 70 => 'Asia/Yakutsk',
  64. 19 => 'Australia/Adelaide',
  65. 44 => 'Australia/Darwin',
  66. 18 => 'Australia/Brisbane',
  67. 76 => 'Australia/Sydney',
  68. 43 => 'Pacific/Guam',
  69. 42 => 'Australia/Hobart',
  70. 68 => 'Asia/Vladivostok',
  71. 41 => 'Asia/Magadan',
  72. 17 => 'Pacific/Auckland',
  73. 40 => 'Pacific/Fiji',
  74. 67 => 'Pacific/Tongatapu',
  75. 29 => 'Atlantic/Azores',
  76. 53 => 'Atlantic/Cape_Verde',
  77. 30 => 'America/Noronha',
  78. 8 => 'America/Sao_Paulo', // Best guess
  79. 32 => 'America/Argentina/Buenos_Aires',
  80. 60 => 'America/Godthab',
  81. 28 => 'America/St_Johns',
  82. 9 => 'America/Halifax',
  83. 33 => 'America/Caracas',
  84. 65 => 'America/Santiago',
  85. 35 => 'America/Bogota',
  86. 10 => 'America/New_York',
  87. 34 => 'America/Indiana/Indianapolis',
  88. 55 => 'America/Guatemala',
  89. 11 => 'America/Chicago',
  90. 37 => 'America/Mexico_City',
  91. 36 => 'America/Edmonton',
  92. 38 => 'America/Phoenix',
  93. 12 => 'America/Denver', // Best guess
  94. 13 => 'America/Los_Angeles', // Best guess
  95. 14 => 'America/Anchorage',
  96. 15 => 'Pacific/Honolulu',
  97. 16 => 'Pacific/Midway',
  98. 39 => 'Pacific/Kwajalein',
  99. );
  100. /**
  101. * This method will try to find out the correct timezone for an iCalendar
  102. * date-time value.
  103. *
  104. * You must pass the contents of the TZID parameter, as well as the full
  105. * calendar.
  106. *
  107. * If the lookup fails, this method will return the default PHP timezone
  108. * (as configured using date_default_timezone_set, or the date.timezone ini
  109. * setting).
  110. *
  111. * Alternatively, if $failIfUncertain is set to true, it will throw an
  112. * exception if we cannot accurately determine the timezone.
  113. *
  114. * @param string $tzid
  115. * @param Sabre\VObject\Component $vcalendar
  116. * @return DateTimeZone
  117. */
  118. static public function getTimeZone($tzid, Component $vcalendar = null, $failIfUncertain = false) {
  119. // First we will just see if the tzid is a support timezone identifier.
  120. //
  121. // The only exception is if the timezone starts with (. This is to
  122. // handle cases where certain microsoft products generate timezone
  123. // identifiers that for instance look like:
  124. //
  125. // (GMT+01.00) Sarajevo/Warsaw/Zagreb
  126. //
  127. // Since PHP 5.5.10, the first bit will be used as the timezone and
  128. // this method will return just GMT+01:00. This is wrong, because it
  129. // doesn't take DST into account.
  130. if ($tzid[0]!=='(') {
  131. // PHP has a bug that logs PHP warnings even it shouldn't:
  132. // https://bugs.php.net/bug.php?id=67881
  133. //
  134. // That's why we're checking if we'll be able to successfull instantiate
  135. // \DateTimeZone() before doing so. Otherwise we could simply instantiate
  136. // and catch the exception.
  137. $tzIdentifiers = \DateTimeZone::listIdentifiers();
  138. try {
  139. if (
  140. (in_array($tzid, $tzIdentifiers)) ||
  141. (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) ||
  142. (in_array($tzid, self::getIdentifiersBC()))
  143. ) {
  144. return new \DateTimeZone($tzid);
  145. }
  146. } catch(\Exception $e) {
  147. }
  148. }
  149. self::loadTzMaps();
  150. // Next, we check if the tzid is somewhere in our tzid map.
  151. if (isset(self::$map[$tzid])) {
  152. return new \DateTimeZone(self::$map[$tzid]);
  153. }
  154. // Maybe the author was hyper-lazy and just included an offset. We
  155. // support it, but we aren't happy about it.
  156. if (preg_match('/^GMT(\+|-)([0-9]{4})$/', $tzid, $matches)) {
  157. // Note that the path in the source will never be taken from PHP 5.5.10
  158. // onwards. PHP 5.5.10 supports the "GMT+0100" style of format, so it
  159. // already gets returned early in this function. Once we drop support
  160. // for versions under PHP 5.5.10, this bit can be taken out of the
  161. // source.
  162. // @codeCoverageIgnoreStart
  163. return new \DateTimeZone('Etc/GMT' . $matches[1] . ltrim(substr($matches[2],0,2),'0'));
  164. // @codeCoverageIgnoreEnd
  165. }
  166. if ($vcalendar) {
  167. // If that didn't work, we will scan VTIMEZONE objects
  168. foreach($vcalendar->select('VTIMEZONE') as $vtimezone) {
  169. if ((string)$vtimezone->TZID === $tzid) {
  170. // Some clients add 'X-LIC-LOCATION' with the olson name.
  171. if (isset($vtimezone->{'X-LIC-LOCATION'})) {
  172. $lic = (string)$vtimezone->{'X-LIC-LOCATION'};
  173. // Libical generators may specify strings like
  174. // "SystemV/EST5EDT". For those we must remove the
  175. // SystemV part.
  176. if (substr($lic,0,8)==='SystemV/') {
  177. $lic = substr($lic,8);
  178. }
  179. return self::getTimeZone($lic, null, $failIfUncertain);
  180. }
  181. // Microsoft may add a magic number, which we also have an
  182. // answer for.
  183. if (isset($vtimezone->{'X-MICROSOFT-CDO-TZID'})) {
  184. $cdoId = (int)$vtimezone->{'X-MICROSOFT-CDO-TZID'}->getValue();
  185. // 2 can mean both Europe/Lisbon and Europe/Sarajevo.
  186. if ($cdoId===2 && strpos((string)$vtimezone->TZID, 'Sarajevo')!==false) {
  187. return new \DateTimeZone('Europe/Sarajevo');
  188. }
  189. if (isset(self::$microsoftExchangeMap[$cdoId])) {
  190. return new \DateTimeZone(self::$microsoftExchangeMap[$cdoId]);
  191. }
  192. }
  193. }
  194. }
  195. }
  196. if ($failIfUncertain) {
  197. throw new \InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: ' . $tzid);
  198. }
  199. // If we got all the way here, we default to UTC.
  200. return new \DateTimeZone(date_default_timezone_get());
  201. }
  202. /**
  203. * This method will load in all the tz mapping information, if it's not yet
  204. * done.
  205. */
  206. static public function loadTzMaps() {
  207. if (!is_null(self::$map)) return;
  208. self::$map = array_merge(
  209. include __DIR__ . '/timezonedata/windowszones.php',
  210. include __DIR__ . '/timezonedata/lotuszones.php',
  211. include __DIR__ . '/timezonedata/exchangezones.php',
  212. include __DIR__ . '/timezonedata/php-workaround.php'
  213. );
  214. }
  215. /**
  216. * This method returns an array of timezone identifiers, that are supported
  217. * by DateTimeZone(), but not returned by DateTimeZone::listIdentifiers()
  218. *
  219. * We're not using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) because:
  220. * - It's not supported by some PHP versions as well as HHVM.
  221. * - It also returns identifiers, that are invalid values for new DateTimeZone() on some PHP versions.
  222. * (See timezonedata/php-bc.php and timezonedata php-workaround.php)
  223. *
  224. * @return array
  225. */
  226. static public function getIdentifiersBC() {
  227. return include __DIR__ . '/timezonedata/php-bc.php';
  228. }
  229. }