blob: f2adee5fd251146f9117ef1c690e1ed94574215a [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 */
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010011
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020012namespace Carbon\Traits;
13
14use Carbon\Carbon;
15use Carbon\CarbonImmutable;
16use Carbon\CarbonInterface;
17use Carbon\Exceptions\InvalidDateException;
18use Carbon\Exceptions\InvalidFormatException;
19use Carbon\Exceptions\OutOfRangeException;
20use Carbon\Translator;
21use Closure;
22use DateTimeInterface;
23use DateTimeZone;
24use Exception;
25use ReturnTypeWillChange;
26
27/**
28 * Trait Creator.
29 *
30 * Static creators.
31 *
32 * Depends on the following methods:
33 *
34 * @method static Carbon|CarbonImmutable getTestNow()
35 */
36trait Creator
37{
38 use ObjectInitialisation;
39
40 /**
41 * The errors that can occur.
42 *
43 * @var array
44 */
45 protected static $lastErrors;
46
47 /**
48 * Create a new Carbon instance.
49 *
50 * Please see the testing aids section (specifically static::setTestNow())
51 * for more on the possibility of this constructor returning a test instance.
52 *
53 * @param DateTimeInterface|string|null $time
54 * @param DateTimeZone|string|null $tz
55 *
56 * @throws InvalidFormatException
57 */
58 public function __construct($time = null, $tz = null)
59 {
60 if ($time instanceof DateTimeInterface) {
61 $time = $this->constructTimezoneFromDateTime($time, $tz)->format('Y-m-d H:i:s.u');
62 }
63
64 if (is_numeric($time) && (!\is_string($time) || !preg_match('/^\d{1,14}$/', $time))) {
65 $time = static::createFromTimestampUTC($time)->format('Y-m-d\TH:i:s.uP');
66 }
67
68 // If the class has a test now set and we are trying to create a now()
69 // instance then override as required
70 $isNow = empty($time) || $time === 'now';
71
72 if (method_exists(static::class, 'hasTestNow') &&
73 method_exists(static::class, 'getTestNow') &&
74 static::hasTestNow() &&
75 ($isNow || static::hasRelativeKeywords($time))
76 ) {
77 static::mockConstructorParameters($time, $tz);
78 }
79
80 // Work-around for PHP bug https://bugs.php.net/bug.php?id=67127
81 if (!str_contains((string) .1, '.')) {
82 $locale = setlocale(LC_NUMERIC, '0');
83 setlocale(LC_NUMERIC, 'C');
84 }
85
86 try {
87 parent::__construct($time ?: 'now', static::safeCreateDateTimeZone($tz) ?: null);
88 } catch (Exception $exception) {
89 throw new InvalidFormatException($exception->getMessage(), 0, $exception);
90 }
91
92 $this->constructedObjectId = spl_object_hash($this);
93
94 if (isset($locale)) {
95 setlocale(LC_NUMERIC, $locale);
96 }
97
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +010098 self::setLastErrors(parent::getLastErrors());
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +020099 }
100
101 /**
102 * Get timezone from a datetime instance.
103 *
104 * @param DateTimeInterface $date
105 * @param DateTimeZone|string|null $tz
106 *
107 * @return DateTimeInterface
108 */
109 private function constructTimezoneFromDateTime(DateTimeInterface $date, &$tz)
110 {
111 if ($tz !== null) {
112 $safeTz = static::safeCreateDateTimeZone($tz);
113
114 if ($safeTz) {
115 return $date->setTimezone($safeTz);
116 }
117
118 return $date;
119 }
120
121 $tz = $date->getTimezone();
122
123 return $date;
124 }
125
126 /**
127 * Update constructedObjectId on cloned.
128 */
129 public function __clone()
130 {
131 $this->constructedObjectId = spl_object_hash($this);
132 }
133
134 /**
135 * Create a Carbon instance from a DateTime one.
136 *
137 * @param DateTimeInterface $date
138 *
139 * @return static
140 */
141 public static function instance($date)
142 {
143 if ($date instanceof static) {
144 return clone $date;
145 }
146
147 static::expectDateTime($date);
148
149 $instance = new static($date->format('Y-m-d H:i:s.u'), $date->getTimezone());
150
151 if ($date instanceof CarbonInterface || $date instanceof Options) {
152 $settings = $date->getSettings();
153
154 if (!$date->hasLocalTranslator()) {
155 unset($settings['locale']);
156 }
157
158 $instance->settings($settings);
159 }
160
161 return $instance;
162 }
163
164 /**
165 * Create a carbon instance from a string.
166 *
167 * This is an alias for the constructor that allows better fluent syntax
168 * as it allows you to do Carbon::parse('Monday next week')->fn() rather
169 * than (new Carbon('Monday next week'))->fn().
170 *
171 * @param string|DateTimeInterface|null $time
172 * @param DateTimeZone|string|null $tz
173 *
174 * @throws InvalidFormatException
175 *
176 * @return static
177 */
178 public static function rawParse($time = null, $tz = null)
179 {
180 if ($time instanceof DateTimeInterface) {
181 return static::instance($time);
182 }
183
184 try {
185 return new static($time, $tz);
186 } catch (Exception $exception) {
187 $date = @static::now($tz)->change($time);
188
189 if (!$date) {
190 throw new InvalidFormatException("Could not parse '$time': ".$exception->getMessage(), 0, $exception);
191 }
192
193 return $date;
194 }
195 }
196
197 /**
198 * Create a carbon instance from a string.
199 *
200 * This is an alias for the constructor that allows better fluent syntax
201 * as it allows you to do Carbon::parse('Monday next week')->fn() rather
202 * than (new Carbon('Monday next week'))->fn().
203 *
204 * @param string|DateTimeInterface|null $time
205 * @param DateTimeZone|string|null $tz
206 *
207 * @throws InvalidFormatException
208 *
209 * @return static
210 */
211 public static function parse($time = null, $tz = null)
212 {
213 $function = static::$parseFunction;
214
215 if (!$function) {
216 return static::rawParse($time, $tz);
217 }
218
219 if (\is_string($function) && method_exists(static::class, $function)) {
220 $function = [static::class, $function];
221 }
222
223 return $function(...\func_get_args());
224 }
225
226 /**
227 * Create a carbon instance from a localized string (in French, Japanese, Arabic, etc.).
228 *
229 * @param string $time date/time string in the given language (may also contain English).
230 * @param string|null $locale if locale is null or not specified, current global locale will be
231 * used instead.
232 * @param DateTimeZone|string|null $tz optional timezone for the new instance.
233 *
234 * @throws InvalidFormatException
235 *
236 * @return static
237 */
238 public static function parseFromLocale($time, $locale = null, $tz = null)
239 {
240 return static::rawParse(static::translateTimeString($time, $locale, 'en'), $tz);
241 }
242
243 /**
244 * Get a Carbon instance for the current date and time.
245 *
246 * @param DateTimeZone|string|null $tz
247 *
248 * @return static
249 */
250 public static function now($tz = null)
251 {
252 return new static(null, $tz);
253 }
254
255 /**
256 * Create a Carbon instance for today.
257 *
258 * @param DateTimeZone|string|null $tz
259 *
260 * @return static
261 */
262 public static function today($tz = null)
263 {
264 return static::rawParse('today', $tz);
265 }
266
267 /**
268 * Create a Carbon instance for tomorrow.
269 *
270 * @param DateTimeZone|string|null $tz
271 *
272 * @return static
273 */
274 public static function tomorrow($tz = null)
275 {
276 return static::rawParse('tomorrow', $tz);
277 }
278
279 /**
280 * Create a Carbon instance for yesterday.
281 *
282 * @param DateTimeZone|string|null $tz
283 *
284 * @return static
285 */
286 public static function yesterday($tz = null)
287 {
288 return static::rawParse('yesterday', $tz);
289 }
290
291 /**
292 * Create a Carbon instance for the greatest supported date.
293 *
294 * @return static
295 */
296 public static function maxValue()
297 {
298 if (self::$PHPIntSize === 4) {
299 // 32 bit
300 return static::createFromTimestamp(PHP_INT_MAX); // @codeCoverageIgnore
301 }
302
303 // 64 bit
304 return static::create(9999, 12, 31, 23, 59, 59);
305 }
306
307 /**
308 * Create a Carbon instance for the lowest supported date.
309 *
310 * @return static
311 */
312 public static function minValue()
313 {
314 if (self::$PHPIntSize === 4) {
315 // 32 bit
316 return static::createFromTimestamp(~PHP_INT_MAX); // @codeCoverageIgnore
317 }
318
319 // 64 bit
320 return static::create(1, 1, 1, 0, 0, 0);
321 }
322
323 private static function assertBetween($unit, $value, $min, $max)
324 {
325 if (static::isStrictModeEnabled() && ($value < $min || $value > $max)) {
326 throw new OutOfRangeException($unit, $min, $max, $value);
327 }
328 }
329
330 private static function createNowInstance($tz)
331 {
332 if (!static::hasTestNow()) {
333 return static::now($tz);
334 }
335
336 $now = static::getTestNow();
337
338 if ($now instanceof Closure) {
339 return $now(static::now($tz));
340 }
341
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100342 return $now->avoidMutation()->tz($tz);
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200343 }
344
345 /**
346 * Create a new Carbon instance from a specific date and time.
347 *
348 * If any of $year, $month or $day are set to null their now() values will
349 * be used.
350 *
351 * If $hour is null it will be set to its now() value and the default
352 * values for $minute and $second will be their now() values.
353 *
354 * If $hour is not null then the default values for $minute and $second
355 * will be 0.
356 *
357 * @param int|null $year
358 * @param int|null $month
359 * @param int|null $day
360 * @param int|null $hour
361 * @param int|null $minute
362 * @param int|null $second
363 * @param DateTimeZone|string|null $tz
364 *
365 * @throws InvalidFormatException
366 *
367 * @return static|false
368 */
369 public static function create($year = 0, $month = 1, $day = 1, $hour = 0, $minute = 0, $second = 0, $tz = null)
370 {
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100371 if (\is_string($year) && !is_numeric($year) || $year instanceof DateTimeInterface) {
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200372 return static::parse($year, $tz ?: (\is_string($month) || $month instanceof DateTimeZone ? $month : null));
373 }
374
375 $defaults = null;
376 $getDefault = function ($unit) use ($tz, &$defaults) {
377 if ($defaults === null) {
378 $now = self::createNowInstance($tz);
379
380 $defaults = array_combine([
381 'year',
382 'month',
383 'day',
384 'hour',
385 'minute',
386 'second',
387 ], explode('-', $now->rawFormat('Y-n-j-G-i-s.u')));
388 }
389
390 return $defaults[$unit];
391 };
392
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100393 $year = $year ?? $getDefault('year');
394 $month = $month ?? $getDefault('month');
395 $day = $day ?? $getDefault('day');
396 $hour = $hour ?? $getDefault('hour');
397 $minute = $minute ?? $getDefault('minute');
398 $second = (float) ($second ?? $getDefault('second'));
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200399
400 self::assertBetween('month', $month, 0, 99);
401 self::assertBetween('day', $day, 0, 99);
402 self::assertBetween('hour', $hour, 0, 99);
403 self::assertBetween('minute', $minute, 0, 99);
404 self::assertBetween('second', $second, 0, 99);
405
406 $fixYear = null;
407
408 if ($year < 0) {
409 $fixYear = $year;
410 $year = 0;
411 } elseif ($year > 9999) {
412 $fixYear = $year - 9999;
413 $year = 9999;
414 }
415
416 $second = ($second < 10 ? '0' : '').number_format($second, 6);
417 $instance = static::rawCreateFromFormat('!Y-n-j G:i:s.u', sprintf('%s-%s-%s %s:%02s:%02s', $year, $month, $day, $hour, $minute, $second), $tz);
418
419 if ($fixYear !== null) {
420 $instance = $instance->addYears($fixYear);
421 }
422
423 return $instance;
424 }
425
426 /**
427 * Create a new safe Carbon instance from a specific date and time.
428 *
429 * If any of $year, $month or $day are set to null their now() values will
430 * be used.
431 *
432 * If $hour is null it will be set to its now() value and the default
433 * values for $minute and $second will be their now() values.
434 *
435 * If $hour is not null then the default values for $minute and $second
436 * will be 0.
437 *
438 * If one of the set values is not valid, an InvalidDateException
439 * will be thrown.
440 *
441 * @param int|null $year
442 * @param int|null $month
443 * @param int|null $day
444 * @param int|null $hour
445 * @param int|null $minute
446 * @param int|null $second
447 * @param DateTimeZone|string|null $tz
448 *
449 * @throws InvalidDateException
450 *
451 * @return static|false
452 */
453 public static function createSafe($year = null, $month = null, $day = null, $hour = null, $minute = null, $second = null, $tz = null)
454 {
455 $fields = static::getRangesByUnit();
456
457 foreach ($fields as $field => $range) {
458 if ($$field !== null && (!\is_int($$field) || $$field < $range[0] || $$field > $range[1])) {
459 if (static::isStrictModeEnabled()) {
460 throw new InvalidDateException($field, $$field);
461 }
462
463 return false;
464 }
465 }
466
467 $instance = static::create($year, $month, $day, $hour, $minute, $second, $tz);
468
469 foreach (array_reverse($fields) as $field => $range) {
470 if ($$field !== null && (!\is_int($$field) || $$field !== $instance->$field)) {
471 if (static::isStrictModeEnabled()) {
472 throw new InvalidDateException($field, $$field);
473 }
474
475 return false;
476 }
477 }
478
479 return $instance;
480 }
481
482 /**
483 * Create a new Carbon instance from a specific date and time using strict validation.
484 *
485 * @see create()
486 *
487 * @param int|null $year
488 * @param int|null $month
489 * @param int|null $day
490 * @param int|null $hour
491 * @param int|null $minute
492 * @param int|null $second
493 * @param DateTimeZone|string|null $tz
494 *
495 * @throws InvalidFormatException
496 *
497 * @return static
498 */
499 public static function createStrict(?int $year = 0, ?int $month = 1, ?int $day = 1, ?int $hour = 0, ?int $minute = 0, ?int $second = 0, $tz = null): self
500 {
501 $initialStrictMode = static::isStrictModeEnabled();
502 static::useStrictMode(true);
503
504 try {
505 $date = static::create($year, $month, $day, $hour, $minute, $second, $tz);
506 } finally {
507 static::useStrictMode($initialStrictMode);
508 }
509
510 return $date;
511 }
512
513 /**
514 * Create a Carbon instance from just a date. The time portion is set to now.
515 *
516 * @param int|null $year
517 * @param int|null $month
518 * @param int|null $day
519 * @param DateTimeZone|string|null $tz
520 *
521 * @throws InvalidFormatException
522 *
523 * @return static
524 */
525 public static function createFromDate($year = null, $month = null, $day = null, $tz = null)
526 {
527 return static::create($year, $month, $day, null, null, null, $tz);
528 }
529
530 /**
531 * Create a Carbon instance from just a date. The time portion is set to midnight.
532 *
533 * @param int|null $year
534 * @param int|null $month
535 * @param int|null $day
536 * @param DateTimeZone|string|null $tz
537 *
538 * @throws InvalidFormatException
539 *
540 * @return static
541 */
542 public static function createMidnightDate($year = null, $month = null, $day = null, $tz = null)
543 {
544 return static::create($year, $month, $day, 0, 0, 0, $tz);
545 }
546
547 /**
548 * Create a Carbon instance from just a time. The date portion is set to today.
549 *
550 * @param int|null $hour
551 * @param int|null $minute
552 * @param int|null $second
553 * @param DateTimeZone|string|null $tz
554 *
555 * @throws InvalidFormatException
556 *
557 * @return static
558 */
559 public static function createFromTime($hour = 0, $minute = 0, $second = 0, $tz = null)
560 {
561 return static::create(null, null, null, $hour, $minute, $second, $tz);
562 }
563
564 /**
565 * Create a Carbon instance from a time string. The date portion is set to today.
566 *
567 * @param string $time
568 * @param DateTimeZone|string|null $tz
569 *
570 * @throws InvalidFormatException
571 *
572 * @return static
573 */
574 public static function createFromTimeString($time, $tz = null)
575 {
576 return static::today($tz)->setTimeFromTimeString($time);
577 }
578
579 /**
580 * @param string $format Datetime format
581 * @param string $time
582 * @param DateTimeZone|string|false|null $originalTz
583 *
584 * @return DateTimeInterface|false
585 */
586 private static function createFromFormatAndTimezone($format, $time, $originalTz)
587 {
588 // Work-around for https://bugs.php.net/bug.php?id=75577
589 // @codeCoverageIgnoreStart
590 if (version_compare(PHP_VERSION, '7.3.0-dev', '<')) {
591 $format = str_replace('.v', '.u', $format);
592 }
593 // @codeCoverageIgnoreEnd
594
595 if ($originalTz === null) {
596 return parent::createFromFormat($format, (string) $time);
597 }
598
599 $tz = \is_int($originalTz)
600 ? @timezone_name_from_abbr('', (int) ($originalTz * static::MINUTES_PER_HOUR * static::SECONDS_PER_MINUTE), 1)
601 : $originalTz;
602
603 $tz = static::safeCreateDateTimeZone($tz, $originalTz);
604
605 if ($tz === false) {
606 return false;
607 }
608
609 return parent::createFromFormat($format, (string) $time, $tz);
610 }
611
612 /**
613 * Create a Carbon instance from a specific format.
614 *
615 * @param string $format Datetime format
616 * @param string $time
617 * @param DateTimeZone|string|false|null $tz
618 *
619 * @throws InvalidFormatException
620 *
621 * @return static|false
622 */
623 public static function rawCreateFromFormat($format, $time, $tz = null)
624 {
625 // Work-around for https://bugs.php.net/bug.php?id=80141
626 $format = preg_replace('/(?<!\\\\)((?:\\\\{2})*)c/', '$1Y-m-d\TH:i:sP', $format);
627
628 if (preg_match('/(?<!\\\\)(?:\\\\{2})*(a|A)/', $format, $aMatches, PREG_OFFSET_CAPTURE) &&
629 preg_match('/(?<!\\\\)(?:\\\\{2})*(h|g|H|G)/', $format, $hMatches, PREG_OFFSET_CAPTURE) &&
630 $aMatches[1][1] < $hMatches[1][1] &&
631 preg_match('/(am|pm|AM|PM)/', $time)
632 ) {
633 $format = preg_replace('/^(.*)(?<!\\\\)((?:\\\\{2})*)(a|A)(.*)$/U', '$1$2$4 $3', $format);
634 $time = preg_replace('/^(.*)(am|pm|AM|PM)(.*)$/U', '$1$3 $2', $time);
635 }
636
637 // First attempt to create an instance, so that error messages are based on the unmodified format.
638 $date = self::createFromFormatAndTimezone($format, $time, $tz);
639 $lastErrors = parent::getLastErrors();
640 /** @var \Carbon\CarbonImmutable|\Carbon\Carbon|null $mock */
641 $mock = static::getMockedTestNow($tz);
642
643 if ($mock && $date instanceof DateTimeInterface) {
644 // Set timezone from mock if custom timezone was neither given directly nor as a part of format.
645 // First let's skip the part that will be ignored by the parser.
646 $nonEscaped = '(?<!\\\\)(\\\\{2})*';
647
648 $nonIgnored = preg_replace("/^.*{$nonEscaped}!/s", '', $format);
649
650 if ($tz === null && !preg_match("/{$nonEscaped}[eOPT]/", $nonIgnored)) {
651 $tz = clone $mock->getTimezone();
652 }
653
654 // Set microseconds to zero to match behavior of DateTime::createFromFormat()
655 // See https://bugs.php.net/bug.php?id=74332
656 $mock = $mock->copy()->microsecond(0);
657
658 // Prepend mock datetime only if the format does not contain non escaped unix epoch reset flag.
659 if (!preg_match("/{$nonEscaped}[!|]/", $format)) {
660 $format = static::MOCK_DATETIME_FORMAT.' '.$format;
661 $time = ($mock instanceof self ? $mock->rawFormat(static::MOCK_DATETIME_FORMAT) : $mock->format(static::MOCK_DATETIME_FORMAT)).' '.$time;
662 }
663
664 // Regenerate date from the modified format to base result on the mocked instance instead of now.
665 $date = self::createFromFormatAndTimezone($format, $time, $tz);
666 }
667
668 if ($date instanceof DateTimeInterface) {
669 $instance = static::instance($date);
670 $instance::setLastErrors($lastErrors);
671
672 return $instance;
673 }
674
675 if (static::isStrictModeEnabled()) {
676 throw new InvalidFormatException(implode(PHP_EOL, $lastErrors['errors']));
677 }
678
679 return false;
680 }
681
682 /**
683 * Create a Carbon instance from a specific format.
684 *
685 * @param string $format Datetime format
686 * @param string $time
687 * @param DateTimeZone|string|false|null $tz
688 *
689 * @throws InvalidFormatException
690 *
691 * @return static|false
692 */
693 #[ReturnTypeWillChange]
694 public static function createFromFormat($format, $time, $tz = null)
695 {
696 $function = static::$createFromFormatFunction;
697
698 if (!$function) {
699 return static::rawCreateFromFormat($format, $time, $tz);
700 }
701
702 if (\is_string($function) && method_exists(static::class, $function)) {
703 $function = [static::class, $function];
704 }
705
706 return $function(...\func_get_args());
707 }
708
709 /**
710 * Create a Carbon instance from a specific ISO format (same replacements as ->isoFormat()).
711 *
712 * @param string $format Datetime format
713 * @param string $time
714 * @param DateTimeZone|string|false|null $tz optional timezone
715 * @param string|null $locale locale to be used for LTS, LT, LL, LLL, etc. macro-formats (en by fault, unneeded if no such macro-format in use)
716 * @param \Symfony\Component\Translation\TranslatorInterface $translator optional custom translator to use for macro-formats
717 *
718 * @throws InvalidFormatException
719 *
720 * @return static|false
721 */
722 public static function createFromIsoFormat($format, $time, $tz = null, $locale = 'en', $translator = null)
723 {
724 $format = preg_replace_callback('/(?<!\\\\)(\\\\{2})*(LTS|LT|[Ll]{1,4})/', function ($match) use ($locale, $translator) {
725 [$code] = $match;
726
727 static $formats = null;
728
729 if ($formats === null) {
730 $translator = $translator ?: Translator::get($locale);
731
732 $formats = [
733 'LT' => static::getTranslationMessageWith($translator, 'formats.LT', $locale, 'h:mm A'),
734 'LTS' => static::getTranslationMessageWith($translator, 'formats.LTS', $locale, 'h:mm:ss A'),
735 'L' => static::getTranslationMessageWith($translator, 'formats.L', $locale, 'MM/DD/YYYY'),
736 'LL' => static::getTranslationMessageWith($translator, 'formats.LL', $locale, 'MMMM D, YYYY'),
737 'LLL' => static::getTranslationMessageWith($translator, 'formats.LLL', $locale, 'MMMM D, YYYY h:mm A'),
738 'LLLL' => static::getTranslationMessageWith($translator, 'formats.LLLL', $locale, 'dddd, MMMM D, YYYY h:mm A'),
739 ];
740 }
741
742 return $formats[$code] ?? preg_replace_callback(
743 '/MMMM|MM|DD|dddd/',
744 function ($code) {
745 return mb_substr($code[0], 1);
746 },
747 $formats[strtoupper($code)] ?? ''
748 );
749 }, $format);
750
751 $format = preg_replace_callback('/(?<!\\\\)(\\\\{2})*('.CarbonInterface::ISO_FORMAT_REGEXP.'|[A-Za-z])/', function ($match) {
752 [$code] = $match;
753
754 static $replacements = null;
755
756 if ($replacements === null) {
757 $replacements = [
758 'OD' => 'd',
759 'OM' => 'M',
760 'OY' => 'Y',
761 'OH' => 'G',
762 'Oh' => 'g',
763 'Om' => 'i',
764 'Os' => 's',
765 'D' => 'd',
766 'DD' => 'd',
767 'Do' => 'd',
768 'd' => '!',
769 'dd' => '!',
770 'ddd' => 'D',
771 'dddd' => 'D',
772 'DDD' => 'z',
773 'DDDD' => 'z',
774 'DDDo' => 'z',
775 'e' => '!',
776 'E' => '!',
777 'H' => 'G',
778 'HH' => 'H',
779 'h' => 'g',
780 'hh' => 'h',
781 'k' => 'G',
782 'kk' => 'G',
783 'hmm' => 'gi',
784 'hmmss' => 'gis',
785 'Hmm' => 'Gi',
786 'Hmmss' => 'Gis',
787 'm' => 'i',
788 'mm' => 'i',
789 'a' => 'a',
790 'A' => 'a',
791 's' => 's',
792 'ss' => 's',
793 'S' => '*',
794 'SS' => '*',
795 'SSS' => '*',
796 'SSSS' => '*',
797 'SSSSS' => '*',
798 'SSSSSS' => 'u',
799 'SSSSSSS' => 'u*',
800 'SSSSSSSS' => 'u*',
801 'SSSSSSSSS' => 'u*',
802 'M' => 'm',
803 'MM' => 'm',
804 'MMM' => 'M',
805 'MMMM' => 'M',
806 'Mo' => 'm',
807 'Q' => '!',
808 'Qo' => '!',
809 'G' => '!',
810 'GG' => '!',
811 'GGG' => '!',
812 'GGGG' => '!',
813 'GGGGG' => '!',
814 'g' => '!',
815 'gg' => '!',
816 'ggg' => '!',
817 'gggg' => '!',
818 'ggggg' => '!',
819 'W' => '!',
820 'WW' => '!',
821 'Wo' => '!',
822 'w' => '!',
823 'ww' => '!',
824 'wo' => '!',
825 'x' => 'U???',
826 'X' => 'U',
827 'Y' => 'Y',
828 'YY' => 'y',
829 'YYYY' => 'Y',
830 'YYYYY' => 'Y',
831 'YYYYYY' => 'Y',
832 'z' => 'e',
833 'zz' => 'e',
834 'Z' => 'e',
835 'ZZ' => 'e',
836 ];
837 }
838
839 $format = $replacements[$code] ?? '?';
840
841 if ($format === '!') {
842 throw new InvalidFormatException("Format $code not supported for creation.");
843 }
844
845 return $format;
846 }, $format);
847
848 return static::rawCreateFromFormat($format, $time, $tz);
849 }
850
851 /**
852 * Create a Carbon instance from a specific format and a string in a given language.
853 *
854 * @param string $format Datetime format
855 * @param string $locale
856 * @param string $time
857 * @param DateTimeZone|string|false|null $tz
858 *
859 * @throws InvalidFormatException
860 *
861 * @return static|false
862 */
863 public static function createFromLocaleFormat($format, $locale, $time, $tz = null)
864 {
865 return static::rawCreateFromFormat($format, static::translateTimeString($time, $locale, 'en'), $tz);
866 }
867
868 /**
869 * Create a Carbon instance from a specific ISO format and a string in a given language.
870 *
871 * @param string $format Datetime ISO format
872 * @param string $locale
873 * @param string $time
874 * @param DateTimeZone|string|false|null $tz
875 *
876 * @throws InvalidFormatException
877 *
878 * @return static|false
879 */
880 public static function createFromLocaleIsoFormat($format, $locale, $time, $tz = null)
881 {
882 $time = static::translateTimeString($time, $locale, 'en', CarbonInterface::TRANSLATE_MONTHS | CarbonInterface::TRANSLATE_DAYS | CarbonInterface::TRANSLATE_MERIDIEM);
883
884 return static::createFromIsoFormat($format, $time, $tz, $locale);
885 }
886
887 /**
888 * Make a Carbon instance from given variable if possible.
889 *
890 * Always return a new instance. Parse only strings and only these likely to be dates (skip intervals
891 * and recurrences). Throw an exception for invalid format, but otherwise return null.
892 *
893 * @param mixed $var
894 *
895 * @throws InvalidFormatException
896 *
897 * @return static|null
898 */
899 public static function make($var)
900 {
901 if ($var instanceof DateTimeInterface) {
902 return static::instance($var);
903 }
904
905 $date = null;
906
907 if (\is_string($var)) {
908 $var = trim($var);
909
910 if (!preg_match('/^P[0-9T]/', $var) &&
911 !preg_match('/^R[0-9]/', $var) &&
912 preg_match('/[a-z0-9]/i', $var)
913 ) {
914 $date = static::parse($var);
915 }
916 }
917
918 return $date;
919 }
920
921 /**
922 * Set last errors.
923 *
924 * @param array $lastErrors
925 *
926 * @return void
927 */
928 private static function setLastErrors(array $lastErrors)
929 {
930 static::$lastErrors = $lastErrors;
931 }
932
933 /**
934 * {@inheritdoc}
Matthias Andreas Benkard1ba53812022-12-27 17:32:58 +0100935 *
936 * @return array
Matthias Andreas Benkard7b2a3a12021-08-16 10:57:25 +0200937 */
938 #[ReturnTypeWillChange]
939 public static function getLastErrors()
940 {
941 return static::$lastErrors;
942 }
943}