CarbonTimeZone.php 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320
  1. <?php
  2. /**
  3. * This file is part of the Carbon package.
  4. *
  5. * (c) Brian Nesbitt <brian@nesbot.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Carbon;
  11. use Carbon\Exceptions\InvalidCastException;
  12. use Carbon\Exceptions\InvalidTimeZoneException;
  13. use DateTimeInterface;
  14. use DateTimeZone;
  15. use Throwable;
  16. class CarbonTimeZone extends DateTimeZone
  17. {
  18. public function __construct($timezone = null)
  19. {
  20. parent::__construct(static::getDateTimeZoneNameFromMixed($timezone));
  21. }
  22. protected static function parseNumericTimezone($timezone)
  23. {
  24. if ($timezone <= -100 || $timezone >= 100) {
  25. throw new InvalidTimeZoneException('Absolute timezone offset cannot be greater than 100.');
  26. }
  27. return ($timezone >= 0 ? '+' : '').ltrim($timezone, '+').':00';
  28. }
  29. protected static function getDateTimeZoneNameFromMixed($timezone)
  30. {
  31. if ($timezone === null) {
  32. return date_default_timezone_get();
  33. }
  34. if (\is_string($timezone)) {
  35. $timezone = preg_replace('/^\s*([+-]\d+)(\d{2})\s*$/', '$1:$2', $timezone);
  36. }
  37. if (is_numeric($timezone)) {
  38. return static::parseNumericTimezone($timezone);
  39. }
  40. return $timezone;
  41. }
  42. protected static function getDateTimeZoneFromName(&$name)
  43. {
  44. return @timezone_open($name = (string) static::getDateTimeZoneNameFromMixed($name));
  45. }
  46. /**
  47. * Cast the current instance into the given class.
  48. *
  49. * @param string $className The $className::instance() method will be called to cast the current object.
  50. *
  51. * @return DateTimeZone
  52. */
  53. public function cast(string $className)
  54. {
  55. if (!method_exists($className, 'instance')) {
  56. if (is_a($className, DateTimeZone::class, true)) {
  57. return new $className($this->getName());
  58. }
  59. throw new InvalidCastException("$className has not the instance() method needed to cast the date.");
  60. }
  61. return $className::instance($this);
  62. }
  63. /**
  64. * Create a CarbonTimeZone from mixed input.
  65. *
  66. * @param DateTimeZone|string|int|null $object original value to get CarbonTimeZone from it.
  67. * @param DateTimeZone|string|int|null $objectDump dump of the object for error messages.
  68. *
  69. * @throws InvalidTimeZoneException
  70. *
  71. * @return false|static
  72. */
  73. public static function instance($object = null, $objectDump = null)
  74. {
  75. $tz = $object;
  76. if ($tz instanceof static) {
  77. return $tz;
  78. }
  79. if ($tz === null) {
  80. return new static();
  81. }
  82. if (!$tz instanceof DateTimeZone) {
  83. $tz = static::getDateTimeZoneFromName($object);
  84. }
  85. if ($tz !== false) {
  86. return new static($tz->getName());
  87. }
  88. if (Carbon::isStrictModeEnabled()) {
  89. throw new InvalidTimeZoneException('Unknown or bad timezone ('.($objectDump ?: $object).')');
  90. }
  91. return false;
  92. }
  93. /**
  94. * Returns abbreviated name of the current timezone according to DST setting.
  95. *
  96. * @param bool $dst
  97. *
  98. * @return string
  99. */
  100. public function getAbbreviatedName($dst = false)
  101. {
  102. $name = $this->getName();
  103. foreach ($this->listAbbreviations() as $abbreviation => $zones) {
  104. foreach ($zones as $zone) {
  105. if ($zone['timezone_id'] === $name && $zone['dst'] == $dst) {
  106. return $abbreviation;
  107. }
  108. }
  109. }
  110. return 'unknown';
  111. }
  112. /**
  113. * @alias getAbbreviatedName
  114. *
  115. * Returns abbreviated name of the current timezone according to DST setting.
  116. *
  117. * @param bool $dst
  118. *
  119. * @return string
  120. */
  121. public function getAbbr($dst = false)
  122. {
  123. return $this->getAbbreviatedName($dst);
  124. }
  125. /**
  126. * Get the offset as string "sHH:MM" (such as "+00:00" or "-12:30").
  127. *
  128. * @param DateTimeInterface|null $date
  129. *
  130. * @return string
  131. */
  132. public function toOffsetName(DateTimeInterface $date = null)
  133. {
  134. return static::getOffsetNameFromMinuteOffset(
  135. $this->getOffset($date ?: Carbon::now($this)) / 60
  136. );
  137. }
  138. /**
  139. * Returns a new CarbonTimeZone object using the offset string instead of region string.
  140. *
  141. * @param DateTimeInterface|null $date
  142. *
  143. * @return CarbonTimeZone
  144. */
  145. public function toOffsetTimeZone(DateTimeInterface $date = null)
  146. {
  147. return new static($this->toOffsetName($date));
  148. }
  149. /**
  150. * Returns the first region string (such as "America/Toronto") that matches the current timezone or
  151. * false if no match is found.
  152. *
  153. * @see timezone_name_from_abbr native PHP function.
  154. *
  155. * @param DateTimeInterface|null $date
  156. * @param int $isDst
  157. *
  158. * @return string|false
  159. */
  160. public function toRegionName(DateTimeInterface $date = null, $isDst = 1)
  161. {
  162. $name = $this->getName();
  163. $firstChar = substr($name, 0, 1);
  164. if ($firstChar !== '+' && $firstChar !== '-') {
  165. return $name;
  166. }
  167. $date = $date ?: Carbon::now($this);
  168. // Integer construction no longer supported since PHP 8
  169. // @codeCoverageIgnoreStart
  170. try {
  171. $offset = @$this->getOffset($date) ?: 0;
  172. } catch (Throwable $e) {
  173. $offset = 0;
  174. }
  175. // @codeCoverageIgnoreEnd
  176. $name = @timezone_name_from_abbr('', $offset, $isDst);
  177. if ($name) {
  178. return $name;
  179. }
  180. foreach (timezone_identifiers_list() as $timezone) {
  181. if (Carbon::instance($date)->tz($timezone)->getOffset() === $offset) {
  182. return $timezone;
  183. }
  184. }
  185. return false;
  186. }
  187. /**
  188. * Returns a new CarbonTimeZone object using the region string instead of offset string.
  189. *
  190. * @param DateTimeInterface|null $date
  191. *
  192. * @return CarbonTimeZone|false
  193. */
  194. public function toRegionTimeZone(DateTimeInterface $date = null)
  195. {
  196. $tz = $this->toRegionName($date);
  197. if ($tz !== false) {
  198. return new static($tz);
  199. }
  200. if (Carbon::isStrictModeEnabled()) {
  201. throw new InvalidTimeZoneException('Unknown timezone for offset '.$this->getOffset($date ?: Carbon::now($this)).' seconds.');
  202. }
  203. return false;
  204. }
  205. /**
  206. * Cast to string (get timezone name).
  207. *
  208. * @return string
  209. */
  210. public function __toString()
  211. {
  212. return $this->getName();
  213. }
  214. /**
  215. * Return the type number:
  216. *
  217. * Type 1; A UTC offset, such as -0300
  218. * Type 2; A timezone abbreviation, such as GMT
  219. * Type 3: A timezone identifier, such as Europe/London
  220. */
  221. public function getType(): int
  222. {
  223. return preg_match('/"timezone_type";i:(\d)/', serialize($this), $match) ? (int) $match[1] : 3;
  224. }
  225. /**
  226. * Create a CarbonTimeZone from mixed input.
  227. *
  228. * @param DateTimeZone|string|int|null $object
  229. *
  230. * @return false|static
  231. */
  232. public static function create($object = null)
  233. {
  234. return static::instance($object);
  235. }
  236. /**
  237. * Create a CarbonTimeZone from int/float hour offset.
  238. *
  239. * @param float $hourOffset number of hour of the timezone shift (can be decimal).
  240. *
  241. * @return false|static
  242. */
  243. public static function createFromHourOffset(float $hourOffset)
  244. {
  245. return static::createFromMinuteOffset($hourOffset * Carbon::MINUTES_PER_HOUR);
  246. }
  247. /**
  248. * Create a CarbonTimeZone from int/float minute offset.
  249. *
  250. * @param float $minuteOffset number of total minutes of the timezone shift.
  251. *
  252. * @return false|static
  253. */
  254. public static function createFromMinuteOffset(float $minuteOffset)
  255. {
  256. return static::instance(static::getOffsetNameFromMinuteOffset($minuteOffset));
  257. }
  258. /**
  259. * Convert a total minutes offset into a standardized timezone offset string.
  260. *
  261. * @param float $minutes number of total minutes of the timezone shift.
  262. *
  263. * @return string
  264. */
  265. public static function getOffsetNameFromMinuteOffset(float $minutes): string
  266. {
  267. $minutes = round($minutes);
  268. $unsignedMinutes = abs($minutes);
  269. return ($minutes < 0 ? '-' : '+').
  270. str_pad((string) floor($unsignedMinutes / 60), 2, '0', STR_PAD_LEFT).
  271. ':'.
  272. str_pad((string) ($unsignedMinutes % 60), 2, '0', STR_PAD_LEFT);
  273. }
  274. }