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