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