TimeZoneUtil.php 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272
  1. <?php
  2. namespace Sabre\VObject;
  3. use DateTimeZone;
  4. use InvalidArgumentException;
  5. use Sabre\VObject\TimezoneGuesser\FindFromOffset;
  6. use Sabre\VObject\TimezoneGuesser\FindFromTimezoneIdentifier;
  7. use Sabre\VObject\TimezoneGuesser\FindFromTimezoneMap;
  8. use Sabre\VObject\TimezoneGuesser\GuessFromLicEntry;
  9. use Sabre\VObject\TimezoneGuesser\GuessFromMsTzId;
  10. use Sabre\VObject\TimezoneGuesser\TimezoneFinder;
  11. use Sabre\VObject\TimezoneGuesser\TimezoneGuesser;
  12. /**
  13. * Time zone name translation.
  14. *
  15. * This file translates well-known time zone names into "Olson database" time zone names.
  16. *
  17. * @copyright Copyright (C) fruux GmbH (https://fruux.com/)
  18. * @author Frank Edelhaeuser (fedel@users.sourceforge.net)
  19. * @author Evert Pot (http://evertpot.com/)
  20. * @license http://sabre.io/license/ Modified BSD License
  21. */
  22. class TimeZoneUtil
  23. {
  24. /** @var self */
  25. private static $instance = null;
  26. /** @var TimezoneGuesser[] */
  27. private $timezoneGuessers = [];
  28. /** @var TimezoneFinder[] */
  29. private $timezoneFinders = [];
  30. private function __construct()
  31. {
  32. $this->addGuesser('lic', new GuessFromLicEntry());
  33. $this->addGuesser('msTzId', new GuessFromMsTzId());
  34. $this->addFinder('tzid', new FindFromTimezoneIdentifier());
  35. $this->addFinder('tzmap', new FindFromTimezoneMap());
  36. $this->addFinder('offset', new FindFromOffset());
  37. }
  38. private static function getInstance(): self
  39. {
  40. if (null === self::$instance) {
  41. self::$instance = new self();
  42. }
  43. return self::$instance;
  44. }
  45. private function addGuesser(string $key, TimezoneGuesser $guesser): void
  46. {
  47. $this->timezoneGuessers[$key] = $guesser;
  48. }
  49. private function addFinder(string $key, TimezoneFinder $finder): void
  50. {
  51. $this->timezoneFinders[$key] = $finder;
  52. }
  53. /**
  54. * This method will try to find out the correct timezone for an iCalendar
  55. * date-time value.
  56. *
  57. * You must pass the contents of the TZID parameter, as well as the full
  58. * calendar.
  59. *
  60. * If the lookup fails, this method will return the default PHP timezone
  61. * (as configured using date_default_timezone_set, or the date.timezone ini
  62. * setting).
  63. *
  64. * Alternatively, if $failIfUncertain is set to true, it will throw an
  65. * exception if we cannot accurately determine the timezone.
  66. */
  67. private function findTimeZone(string $tzid, ?Component $vcalendar = null, bool $failIfUncertain = false): DateTimeZone
  68. {
  69. foreach ($this->timezoneFinders as $timezoneFinder) {
  70. $timezone = $timezoneFinder->find($tzid, $failIfUncertain);
  71. if (!$timezone instanceof DateTimeZone) {
  72. continue;
  73. }
  74. return $timezone;
  75. }
  76. if ($vcalendar) {
  77. // If that didn't work, we will scan VTIMEZONE objects
  78. foreach ($vcalendar->select('VTIMEZONE') as $vtimezone) {
  79. if ((string) $vtimezone->TZID === $tzid) {
  80. foreach ($this->timezoneGuessers as $timezoneGuesser) {
  81. $timezone = $timezoneGuesser->guess($vtimezone, $failIfUncertain);
  82. if (!$timezone instanceof DateTimeZone) {
  83. continue;
  84. }
  85. return $timezone;
  86. }
  87. }
  88. }
  89. }
  90. if ($failIfUncertain) {
  91. throw new InvalidArgumentException('We were unable to determine the correct PHP timezone for tzid: '.$tzid);
  92. }
  93. // If we got all the way here, we default to whatever has been set as the PHP default timezone.
  94. return new DateTimeZone(date_default_timezone_get());
  95. }
  96. public static function addTimezoneGuesser(string $key, TimezoneGuesser $guesser): void
  97. {
  98. self::getInstance()->addGuesser($key, $guesser);
  99. }
  100. public static function addTimezoneFinder(string $key, TimezoneFinder $finder): void
  101. {
  102. self::getInstance()->addFinder($key, $finder);
  103. }
  104. /**
  105. * @param string $tzid
  106. * @param false $failIfUncertain
  107. *
  108. * @return DateTimeZone
  109. */
  110. public static function getTimeZone($tzid, ?Component $vcalendar = null, $failIfUncertain = false)
  111. {
  112. return self::getInstance()->findTimeZone($tzid, $vcalendar, $failIfUncertain);
  113. }
  114. public static function clean(): void
  115. {
  116. self::$instance = null;
  117. }
  118. // Keeping things for backwards compatibility
  119. /**
  120. * @var array|null
  121. *
  122. * @deprecated
  123. */
  124. public static $map = null;
  125. /**
  126. * List of microsoft exchange timezone ids.
  127. *
  128. * Source: http://msdn.microsoft.com/en-us/library/aa563018(loband).aspx
  129. *
  130. * @deprecated
  131. */
  132. public static $microsoftExchangeMap = [
  133. 0 => 'UTC',
  134. 31 => 'Africa/Casablanca',
  135. // Insanely, id #2 is used for both Europe/Lisbon, and Europe/Sarajevo.
  136. // I'm not even kidding.. We handle this special case in the
  137. // getTimeZone method.
  138. 2 => 'Europe/Lisbon',
  139. 1 => 'Europe/London',
  140. 4 => 'Europe/Berlin',
  141. 6 => 'Europe/Prague',
  142. 3 => 'Europe/Paris',
  143. 69 => 'Africa/Luanda', // This was a best guess
  144. 7 => 'Europe/Athens',
  145. 5 => 'Europe/Bucharest',
  146. 49 => 'Africa/Cairo',
  147. 50 => 'Africa/Harare',
  148. 59 => 'Europe/Helsinki',
  149. 27 => 'Asia/Jerusalem',
  150. 26 => 'Asia/Baghdad',
  151. 74 => 'Asia/Kuwait',
  152. 51 => 'Europe/Moscow',
  153. 56 => 'Africa/Nairobi',
  154. 25 => 'Asia/Tehran',
  155. 24 => 'Asia/Muscat', // Best guess
  156. 54 => 'Asia/Baku',
  157. 48 => 'Asia/Kabul',
  158. 58 => 'Asia/Yekaterinburg',
  159. 47 => 'Asia/Karachi',
  160. 23 => 'Asia/Calcutta',
  161. 62 => 'Asia/Kathmandu',
  162. 46 => 'Asia/Almaty',
  163. 71 => 'Asia/Dhaka',
  164. 66 => 'Asia/Colombo',
  165. 61 => 'Asia/Rangoon',
  166. 22 => 'Asia/Bangkok',
  167. 64 => 'Asia/Krasnoyarsk',
  168. 45 => 'Asia/Shanghai',
  169. 63 => 'Asia/Irkutsk',
  170. 21 => 'Asia/Singapore',
  171. 73 => 'Australia/Perth',
  172. 75 => 'Asia/Taipei',
  173. 20 => 'Asia/Tokyo',
  174. 72 => 'Asia/Seoul',
  175. 70 => 'Asia/Yakutsk',
  176. 19 => 'Australia/Adelaide',
  177. 44 => 'Australia/Darwin',
  178. 18 => 'Australia/Brisbane',
  179. 76 => 'Australia/Sydney',
  180. 43 => 'Pacific/Guam',
  181. 42 => 'Australia/Hobart',
  182. 68 => 'Asia/Vladivostok',
  183. 41 => 'Asia/Magadan',
  184. 17 => 'Pacific/Auckland',
  185. 40 => 'Pacific/Fiji',
  186. 67 => 'Pacific/Tongatapu',
  187. 29 => 'Atlantic/Azores',
  188. 53 => 'Atlantic/Cape_Verde',
  189. 30 => 'America/Noronha',
  190. 8 => 'America/Sao_Paulo', // Best guess
  191. 32 => 'America/Argentina/Buenos_Aires',
  192. 60 => 'America/Godthab',
  193. 28 => 'America/St_Johns',
  194. 9 => 'America/Halifax',
  195. 33 => 'America/Caracas',
  196. 65 => 'America/Santiago',
  197. 35 => 'America/Bogota',
  198. 10 => 'America/New_York',
  199. 34 => 'America/Indiana/Indianapolis',
  200. 55 => 'America/Guatemala',
  201. 11 => 'America/Chicago',
  202. 37 => 'America/Mexico_City',
  203. 36 => 'America/Edmonton',
  204. 38 => 'America/Phoenix',
  205. 12 => 'America/Denver', // Best guess
  206. 13 => 'America/Los_Angeles', // Best guess
  207. 14 => 'America/Anchorage',
  208. 15 => 'Pacific/Honolulu',
  209. 16 => 'Pacific/Midway',
  210. 39 => 'Pacific/Kwajalein',
  211. ];
  212. /**
  213. * This method will load in all the tz mapping information, if it's not yet
  214. * done.
  215. *
  216. * @deprecated
  217. */
  218. public static function loadTzMaps()
  219. {
  220. if (!is_null(self::$map)) {
  221. return;
  222. }
  223. self::$map = array_merge(
  224. include __DIR__.'/timezonedata/windowszones.php',
  225. include __DIR__.'/timezonedata/lotuszones.php',
  226. include __DIR__.'/timezonedata/exchangezones.php',
  227. include __DIR__.'/timezonedata/php-workaround.php'
  228. );
  229. }
  230. /**
  231. * This method returns an array of timezone identifiers, that are supported
  232. * by DateTimeZone(), but not returned by DateTimeZone::listIdentifiers().
  233. *
  234. * We're not using DateTimeZone::listIdentifiers(DateTimeZone::ALL_WITH_BC) because:
  235. * - It's not supported by some PHP versions as well as HHVM.
  236. * - It also returns identifiers, that are invalid values for new DateTimeZone() on some PHP versions.
  237. * (See timezonedata/php-bc.php and timezonedata php-workaround.php)
  238. *
  239. * @return array
  240. *
  241. * @deprecated
  242. */
  243. public static function getIdentifiersBC()
  244. {
  245. return include __DIR__.'/timezonedata/php-bc.php';
  246. }
  247. }