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