From 2ab376a06829ed82c4e81716538113cc362c5950 Mon Sep 17 00:00:00 2001 From: gzhegow <6562680@gmail.com> Date: Fri, 6 Dec 2024 02:23:55 +0300 Subject: [PATCH] main --- README.md | 351 +++++--- src/Calendar.php | 249 +++--- src/CalendarFactory.php | 11 + src/CalendarFactoryInterface.php | 8 + src/CalendarInterface.php | 100 ++- src/{Type.php => CalendarType.php} | 27 +- src/Exception/Exception.php | 81 ++ src/Exception/ExceptionInterface.php | 8 + src/Exception/LogicException.php | 81 ++ src/Exception/RuntimeException.php | 81 ++ src/Lib.php | 878 +++++++++++++++----- src/Struct/{ => PHP7}/DateInterval.php | 11 +- src/Struct/{ => PHP7}/DateTime.php | 28 +- src/Struct/{ => PHP7}/DateTimeImmutable.php | 28 +- src/Struct/{ => PHP7}/DateTimeInterface.php | 2 +- src/Struct/{ => PHP7}/DateTimeZone.php | 7 +- src/Struct/PHP8/DateInterval.php | 80 ++ src/Struct/PHP8/DateTime.php | 114 +++ src/Struct/PHP8/DateTimeImmutable.php | 114 +++ src/Struct/PHP8/DateTimeInterface.php | 8 + src/Struct/PHP8/DateTimeZone.php | 50 ++ test.php | 330 +++++--- 22 files changed, 2077 insertions(+), 570 deletions(-) create mode 100644 src/CalendarFactory.php create mode 100644 src/CalendarFactoryInterface.php rename src/{Type.php => CalendarType.php} (65%) create mode 100644 src/Exception/Exception.php create mode 100644 src/Exception/ExceptionInterface.php create mode 100644 src/Exception/LogicException.php create mode 100644 src/Exception/RuntimeException.php rename src/Struct/{ => PHP7}/DateInterval.php (86%) rename src/Struct/{ => PHP7}/DateTime.php (76%) rename src/Struct/{ => PHP7}/DateTimeImmutable.php (76%) rename src/Struct/{ => PHP7}/DateTimeInterface.php (62%) rename src/Struct/{ => PHP7}/DateTimeZone.php (81%) create mode 100644 src/Struct/PHP8/DateInterval.php create mode 100644 src/Struct/PHP8/DateTime.php create mode 100644 src/Struct/PHP8/DateTimeImmutable.php create mode 100644 src/Struct/PHP8/DateTimeInterface.php create mode 100644 src/Struct/PHP8/DateTimeZone.php diff --git a/README.md b/README.md index 8520110..2827902 100644 --- a/README.md +++ b/README.md @@ -2,25 +2,10 @@ ## Что это -В PHP работать с датами это сущий ад. -Каждое действие требует преобразовывать к дате в три-четыре строки, каждое может выбросить исключение, каждое светится в PHPStorm как предупреждение... +Этот пакет сделан, чтобы сделать работу с датами более удобной. -Этот пакет сделан, чтобы сделать работу с датами более удобной. Плюс в нем можно посмотреть, как сделать грамотное наследование имеющищегося в коробке PHP ужаса, чтобы этим управлять или добавить своё. - -## Old good times - -Сначала ты просто пользуешься классами Date и уверен, что нужно просто "правильно готовить". - -Потом ты знакомишься с Immutable, понимаешь, что сложность удваивается, пока не поймешь когда какое нужно выбирать. Начинаешь приводить одно в другое, и натыкаться на заботливо расставленные мины в виде исключений и обратной совместимости версий PHP. - -Потом знакомишься с фреймворком Laravel, где используется пакет Carbon на уровне ядра, который на инициализацию со своими регулярками тратит 60 мс. Хочешь избавиться от него, и не можешь. Потом сможешь) - -Когда наконец избавился, хочешь, чтобы в API твоя дата выглядела так, чтобы Frontend её мог без проблем парсить, и чтобы при этом не пришлось настраивать целый `symfony/serializer`. -Начинаешь наследоваться от имеющихся классов и понимаешь, что половина функций по прежнему возвращает встроенные объекты и сериализацию твою игнорирует. - -К сожалению, даты совмещают как работу над ними, так и хранение состояния внутри, и постоянно косячат по микросекундам. Нужно быть трижды осторожным, чтобы написать стабильный код. - -Это история про то, что ООП это та еще беда... Иногда нужно просто делать как в Javascript - наращивать объекты функционалом, использовать композицию... И тут же понимать, что композиция отвязывает тебя от Структур данных, и привязывает к Поведению. В общем танцы с бубном каждый день. +- Позволяет передавать в конструкторы дат строки без необходимости создавать объект ради объекта +- В нем можно посмотреть, как сделать грамотное наследование имеющищегося в коробке PHP ужаса, чтобы этим управлять или добавить своё. ## Установка @@ -33,16 +18,13 @@ composer require gzhegow/calendar; ```php настраиваем PHP ini_set('memory_limit', '32M'); + // > настраиваем обработку ошибок error_reporting(E_ALL); set_error_handler(function ($errno, $errstr, $errfile, $errline) { @@ -50,111 +32,242 @@ set_error_handler(function ($errno, $errstr, $errfile, $errline) { throw new \ErrorException($errstr, -1, $errno, $errfile, $errline); } }); -set_exception_handler(function ($e) { - var_dump(Lib::php_dump($e)); - var_dump($e->getMessage()); - var_dump(($e->getFile() ?? '{file}') . ': ' . ($e->getLine() ?? '{line}')); +set_exception_handler(function (\Throwable $e) { + $current = $e; + do { + echo "\n"; + + echo \Gzhegow\Calendar\Lib::debug_var_dump($current) . PHP_EOL; + echo $current->getMessage() . PHP_EOL; + + foreach ( $e->getTrace() as $traceItem ) { + echo "{$traceItem['file']} : {$traceItem['line']}" . PHP_EOL; + } + + echo PHP_EOL; + } while ( $current = $current->getPrevious() ); die(); }); -// > создаем календарь -$calendar = new Calendar(); +// > добавляем несколько функция для тестирования +function _dump($value, ...$values) : void +{ + echo \Gzhegow\Calendar\Lib::debug_line([ 'with_ids' => false, 'with_objects' => false ], $value, ...$values); +} -// > можно изменить классы дат на свои собственные реализации -// \Gzhegow\Calendar\Type::setInstance(new \Gzhegow\Calendar\Type()); - -// > создаем дату -$tests[ '_calendar_date' ] = $calendar->parseDateTime($datetime = 'now', $formats = null, $timezoneIfParsed = null); -Lib::assert_true('is_a', [ $tests[ '_calendar_date' ], DateTime::class ]); - -// > создаем/распознаем дату -$tests[ '_calendar_date_immutable' ] = $calendar->parseDateTimeImmutable($datetime = 'now', $formats = null, $timezoneIfParsed = null); -Lib::assert_true('is_a', [ $tests[ '_calendar_date_immutable' ], DateTimeImmutable::class ]); - -// > проводим действия над датой, чтобы убедится что Immutable работает -$tests[ '_calendar_date_immutable_add' ] = $tests[ '_calendar_date_immutable' ]->add(new \DateInterval('P1D')); -$tests[ '_calendar_date_immutable_sub' ] = $tests[ '_calendar_date_immutable' ]->sub(new \DateInterval('P1D')); -$tests[ '_calendar_date_immutable_modify' ] = $tests[ '_calendar_date_immutable' ]->modify('+ 10 hours'); -Lib::assert_true('is_a', [ $tests[ '_calendar_date_immutable_add' ], DateTimeImmutable::class ]); -Lib::assert_true('is_a', [ $tests[ '_calendar_date_immutable_sub' ], DateTimeImmutable::class ]); -Lib::assert_true('is_a', [ $tests[ '_calendar_date_immutable_modify' ], DateTimeImmutable::class ]); -if ($tests[ '_calendar_date_immutable_add' ] === $tests[ '_calendar_date_immutable_sub' ]) throw new \RuntimeException(); -if ($tests[ '_calendar_date_immutable_sub' ] === $tests[ '_calendar_date_immutable_modify' ]) throw new \RuntimeException(); -if ($tests[ '_calendar_date_immutable_add' ] === $tests[ '_calendar_date_immutable_modify' ]) throw new \RuntimeException(); - -// > создает дату "сейчас", просто alias для _calendar_date($date) -$tests[ '_calendar_now' ] = $calendar->now($timezone = null); -$tests[ '_calendar_now_immutable' ] = $calendar->nowImmutable($timezone = null); -Lib::assert_true('is_a', [ $tests[ '_calendar_now' ], DateTime::class ]); -Lib::assert_true('is_a', [ $tests[ '_calendar_now_immutable' ], DateTimeImmutable::class ]); - -// > создает/распознает временную зону -$tests[ '_calendar_timezone' ] = $calendar->parseDateTimeZone($timezone = 'UTC'); -Lib::assert_true('is_a', [ $tests[ '_calendar_timezone' ], DateTimeZone::class ]); -Lib::assert_true(function () use ($tests) { - return 'UTC' === $tests[ '_calendar_timezone' ]->getName(); -}); +function _dump_ln($value, ...$values) : void +{ + echo \Gzhegow\Calendar\Lib::debug_line([ 'with_ids' => false, 'with_objects' => false ], $value, ...$values) . PHP_EOL; +} -// > создает/распознает интервал -$tests[ '_calendar_interval' ] = $calendar->parseDateInterval($interval = 'P0D', $formats = null); -Lib::assert_true('is_a', [ $tests[ '_calendar_interval' ], DateInterval::class ]); -Lib::assert_true(function () use ($tests) { - return 'P0D' === $tests[ '_calendar_interval' ]->jsonSerialize(); -}); +function _assert_call(\Closure $fn, array $expectResult = [], string $expectOutput = null) : void +{ + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1); -// > возвращает разницу между датами -$now = $calendar->nowImmutable(); -$past = $now->modify('- 10 hours'); -$tests[ '_calendar_diff' ] = $calendar->diff($now, $past, $absolute = false); // : ?DateInterval; -Lib::assert_true('is_a', [ $tests[ '_calendar_diff' ], DateInterval::class ]); -Lib::assert_true(function () use ($tests) { - return 'PT10H' === $tests[ '_calendar_diff' ]->jsonSerialize(); -}); + $expect = (object) []; + + if (count($expectResult)) { + $expect->result = $expectResult[ 0 ]; + } + + if (null !== $expectOutput) { + $expect->output = $expectOutput; + } + + $status = \Gzhegow\Calendar\Lib::assert_call($trace, $fn, $expect, $error, STDOUT); -var_dump(json_encode($tests, JSON_PRETTY_PRINT)); - -// string(730) "{ -// "_calendar_date": "2024-05-09T19:47:39.074+03:00", -// "_calendar_date_immutable": "2024-05-09T19:47:39.074+03:00", -// "_calendar_date_immutable_add": "2024-05-10T19:47:39.074+03:00", -// "_calendar_date_immutable_sub": "2024-05-08T19:47:39.074+03:00", -// "_calendar_date_immutable_modify": "2024-05-10T05:47:39.074+03:00", -// "_calendar_now": "2024-05-09T19:47:39.074+03:00", -// "_calendar_now_immutable": "2024-05-09T19:47:39.074+03:00", -// "_calendar_timezone": "UTC", -// "_calendar_interval": "P0D", -// "_calendar_diff": "PT10H" -// }" - -$dump = []; -foreach ( $tests as $i => $test ) { - $dump[ $i ] = Lib::php_dump($test); + if (! $status) { + throw new \Gzhegow\Calendar\Exception\LogicException(); + } } -var_dump($dump); - -// array(13) { -// ["_calendar_date"]=> -// string(48) "{ object(Gzhegow\Calendar\Struct\DateTime # 8) }" -// ["_calendar_date_immutable"]=> -// string(57) "{ object(Gzhegow\Calendar\Struct\DateTimeImmutable # 9) }" -// ["_calendar_date_immutable_add"]=> -// string(58) "{ object(Gzhegow\Calendar\Struct\DateTimeImmutable # 10) }" -// ["_calendar_date_immutable_sub"]=> -// string(58) "{ object(Gzhegow\Calendar\Struct\DateTimeImmutable # 11) }" -// ["_calendar_date_immutable_modify"]=> -// string(57) "{ object(Gzhegow\Calendar\Struct\DateTimeImmutable # 7) }" -// ["_calendar_now"]=> -// string(49) "{ object(Gzhegow\Calendar\Struct\DateTime # 12) }" -// ["_calendar_now_immutable"]=> -// string(58) "{ object(Gzhegow\Calendar\Struct\DateTimeImmutable # 13) }" -// string(58) "{ object(Gzhegow\Calendar\Struct\DateTimeImmutable # 15) }" -// ["_calendar_timezone"]=> -// string(53) "{ object(Gzhegow\Calendar\Struct\DateTimeZone # 16) }" -// ["_calendar_interval"]=> -// string(53) "{ object(Gzhegow\Calendar\Struct\DateInterval # 17) }" -// ["_calendar_diff"]=> -// string(53) "{ object(Gzhegow\Calendar\Struct\DateInterval # 20) }" -// } + + +// >>> ЗАПУСКАЕМ! + +// > сначала всегда фабрика +$factory = new \Gzhegow\Calendar\CalendarFactory(); + +// > создаем календарь +$calendar = $factory->newCalendar(); + +// > можно изменить классы дат на свои собственные реализации +// $calendarType = new \Gzhegow\Calendar\CalendarType(); +// \Gzhegow\Calendar\CalendarType::setInstance($calendarType); + + +// > TEST +// > создаем дату, временную зону и интервал +$fn = function () use ($calendar) { + _dump_ln('TEST 1'); + + $result = $calendar->dateTime($datetime = 'now', $timezone = null); + _dump_ln(get_class($result)); + + $result = $calendar->dateTimeImmutable($datetime = 'now', $timezone = null); + _dump_ln(get_class($result)); + + $result = $calendar->dateTimeZone($timezone = 'UTC'); + _dump_ln(get_class($result)); + + $result = $calendar->dateInterval($duration = 'P1D'); + _dump_ln(get_class($result)); + + _dump(''); +}; +_assert_call($fn, [], PHP_VERSION_ID >= 80000 + ? << TEST +// > распознаем дату, временную зону и интервал +$fn = function () use ($calendar) { + _dump_ln('TEST 2'); + + $result = $calendar->parseDateTime($datetime = '1970-01-01 00:00:00', $formats = [ 'Y-m-d H:i:s' ], $timezoneIfParsed = 'UTC'); + _dump_ln(get_class($result)); + + $result = $calendar->parseDateTimeImmutable($datetime = '1970-01-01 00:00:00', $formats = [ 'Y-m-d H:i:s' ], $timezoneIfParsed = 'UTC'); + _dump_ln(get_class($result)); + + $result = $calendar->parseDateTimeZone($timezone = 'UTC'); + _dump_ln(get_class($result)); + + $result = $calendar->parseDateInterval($interval = 'P0D', $formats = null); + _dump_ln(get_class($result)); + + _dump(''); +}; +_assert_call($fn, [], PHP_VERSION_ID >= 80000 + ? << TEST +// > проводим действия над датой +$fn = function () use ($calendar) { + _dump_ln('TEST 3'); + + $dt = $calendar->parseDateTimeImmutable($datetime = 'now', $formats = null, $timezoneIfParsed = null); + _dump_ln(get_class($dt)); + + $result = $dt->modify('+ 10 hours'); + _dump_ln(get_class($result)); + $result = $result->diff($dt); + _dump_ln(get_class($result)); + _dump_ln(json_encode($result)); + + $result = $dt->add(new \DateInterval('P1D')); + _dump_ln(get_class($result)); + $result = $result->diff($dt); + _dump_ln(get_class($result)); + _dump_ln(json_encode($result)); + _dump_ln((bool) $result->invert); + + $result = $dt->sub(new \DateInterval('P1D')); + _dump_ln(get_class($result)); + $result = $result->diff($dt); + _dump_ln(get_class($result)); + _dump_ln(json_encode($result)); + _dump_ln((bool) $result->invert); + + _dump(''); +}; +_assert_call($fn, [], PHP_VERSION_ID >= 80000 + ? << TEST +// > считаем разницу времени +$fn = function () use ($calendar) { + _dump_ln('TEST 4'); + + $now = $calendar->nowImmutable(); + + $past = $now->modify('- 10 hours'); + + $result = $calendar->diff($now, $past, $absolute = false); + _dump_ln(get_class($result)); + _dump_ln('"PT10H"' === json_encode($result)); + + _dump(''); +}; +_assert_call($fn, [], PHP_VERSION_ID >= 80000 + ? <<fnDateFormatter = $fnDateFormatter; + } + + /** + * @param callable|null $fnDateIntervalFormatter + */ + public function setFnDateIntervalFormatter(?callable $fnDateIntervalFormatter) : void + { + $this->fnDateIntervalFormatter = $fnDateIntervalFormatter; + } + + + /** + * @return \DateTimeZone + */ + public function getTimezoneDefault() : \DateTimeZone + { + return $this->timezoneDefault; + } + + /** + * @param string|\DateTimeZone $timezoneDefault + */ + public function setTimezoneDefault($timezoneDefault) : void + { + if (null === ($_timezoneDefault = $this->parseDateTimeZone($timezoneDefault))) { + throw new LogicException( + 'Invalid `timezoneDefault` passed: ' . Lib::debug_dump($timezoneDefault) + ); + } + + $this->timezoneDefault = $timezoneDefault; + } + + /** + * @param array|string[] $parseFormatsDefault + */ + public function setParseFormatsDefault(array $parseFormatsDefault) : void + { + $this->parseDateTimeFormatsDefault = $parseFormatsDefault; + } + + public function newDateTime($datetime = 'now', $timezone = null) : \DateTime { try { - $dtClass = Type::dateTime(); + $dtClass = CalendarType::dateTime(); $dt = new $dtClass($datetime, $timezone); } catch ( \Throwable $e ) { - throw new \LogicException( - 'Invalid input: ' . Lib::php_dump([ $datetime, $timezone ]), + throw new LogicException( + 'Invalid input: ' . Lib::debug_dump([ $datetime, $timezone ]), -1, $e ); } @@ -127,24 +178,24 @@ public function newDateTime($datetime = 'now', $timezone = null) : \DateTime return $dt; } - public function newDateTimeFromInterface($object) : \DateTime + protected function newDateTimeFromInterface($object) : \DateTime { try { - $dtClass = Type::dateTime(); + $dtClass = CalendarType::dateTime(); $dt = $dtClass::{'createFromInterface'}($object); $dt = $dt ?: null; } catch ( \Throwable $e ) { - throw new \LogicException( - 'Invalid `object`: ' . Lib::php_dump($object), + throw new LogicException( + 'Invalid `object`: ' . Lib::debug_dump($object), -1, $e ); } if (null === $dt) { - throw new \LogicException( - 'Invalid `object`: ' . Lib::php_dump($object), + throw new LogicException( + 'Invalid `object`: ' . Lib::debug_dump($object), -1 ); } @@ -152,24 +203,24 @@ public function newDateTimeFromInterface($object) : \DateTime return $dt; } - public function newDateTimeFromFormat($format, $datetime = 'now', $timezone = null) : \DateTime + protected function newDateTimeFromFormat($format, $datetime = 'now', $timezone = null) : \DateTime { try { - $dtClass = Type::dateTime(); + $dtClass = CalendarType::dateTime(); $dt = $dtClass::{'createFromFormat'}($format, $datetime, $timezone); $dt = $dt ?: null; } catch ( \Throwable $e ) { - throw new \LogicException( - 'Invalid input: ' . Lib::php_dump([ $format, $datetime, $timezone ]), + throw new LogicException( + 'Invalid input: ' . Lib::debug_dump([ $format, $datetime, $timezone ]), -1, $e ); } if (null === $dt) { - throw new \LogicException( - 'Invalid input: ' . Lib::php_dump([ $format, $datetime, $timezone ]), + throw new LogicException( + 'Invalid input: ' . Lib::debug_dump([ $format, $datetime, $timezone ]), -1 ); } @@ -181,12 +232,12 @@ public function newDateTimeFromFormat($format, $datetime = 'now', $timezone = nu public function newDateTimeImmutable($datetime = 'now', $timezone = null) : \DateTimeImmutable { try { - $dtClass = Type::dateTimeImmutable(); + $dtClass = CalendarType::dateTimeImmutable(); $dt = new $dtClass($datetime, $timezone); } catch ( \Throwable $e ) { - throw new \LogicException( - 'Invalid input: ' . Lib::php_dump([ $datetime, $timezone ]), + throw new LogicException( + 'Invalid input: ' . Lib::debug_dump([ $datetime, $timezone ]), -1, $e ); } @@ -194,24 +245,24 @@ public function newDateTimeImmutable($datetime = 'now', $timezone = null) : \Dat return $dt; } - public function newDateTimeImmutableFromInterface($instanceInterface) : \DateTimeImmutable + protected function newDateTimeImmutableFromInterface($instanceInterface) : \DateTimeImmutable { try { - $dtClass = Type::dateTimeImmutable(); + $dtClass = CalendarType::dateTimeImmutable(); $dt = $dtClass::{'createFromInterface'}($instanceInterface); $dt = $dt ?: null; } catch ( \Throwable $e ) { - throw new \LogicException( - 'Invalid `instance`: ' . Lib::php_dump($instanceInterface), + throw new LogicException( + 'Invalid `instance`: ' . Lib::debug_dump($instanceInterface), -1, $e ); } if (null === $dt) { - throw new \LogicException( - 'Invalid `instance`: ' . Lib::php_dump($instanceInterface), + throw new LogicException( + 'Invalid `instance`: ' . Lib::debug_dump($instanceInterface), -1 ); } @@ -219,24 +270,24 @@ public function newDateTimeImmutableFromInterface($instanceInterface) : \DateTim return $dt; } - public function newDateTimeImmutableFromFormat($format, $dtFormattedString, $timezoneIfParsed = null) : \DateTimeImmutable + protected function newDateTimeImmutableFromFormat($format, $dtFormattedString, $timezoneIfParsed = null) : \DateTimeImmutable { try { - $dtClass = Type::dateTimeImmutable(); + $dtClass = CalendarType::dateTimeImmutable(); $dt = $dtClass::{'createFromFormat'}($format, $dtFormattedString, $timezoneIfParsed); $dt = $dt ?: null; } catch ( \Throwable $e ) { - throw new \LogicException( - 'Invalid input: ' . Lib::php_dump([ $format, $dtFormattedString, $timezoneIfParsed ]), + throw new LogicException( + 'Invalid input: ' . Lib::debug_dump([ $format, $dtFormattedString, $timezoneIfParsed ]), -1, $e ); } if (null === $dt) { - throw new \LogicException( - 'Invalid input: ' . Lib::php_dump([ $format, $dtFormattedString, $timezoneIfParsed ]), + throw new LogicException( + 'Invalid input: ' . Lib::debug_dump([ $format, $dtFormattedString, $timezoneIfParsed ]), -1 ); } @@ -248,12 +299,12 @@ public function newDateTimeImmutableFromFormat($format, $dtFormattedString, $tim public function newDateTimeZone($timezone = 'UTC') : \DateTimeZone { try { - $tzClass = Type::dateTimeZone(); + $tzClass = CalendarType::dateTimeZone(); $tz = new $tzClass($timezone); } catch ( \Throwable $e ) { - throw new \LogicException( - 'Invalid input: ' . Lib::php_dump([ $timezone ]), + throw new LogicException( + 'Invalid input: ' . Lib::debug_dump([ $timezone ]), -1, $e ); } @@ -261,24 +312,24 @@ public function newDateTimeZone($timezone = 'UTC') : \DateTimeZone return $tz; } - public function newDateTimeZoneFromInstance($instance) : \DateTimeZone + protected function newDateTimeZoneFromInstance($instance) : \DateTimeZone { try { - $tzClass = Type::dateTimeZone(); + $tzClass = CalendarType::dateTimeZone(); $tz = $tzClass::{'createFromInstance'}($instance); $tz = $tz ?: null; } catch ( \Throwable $e ) { - throw new \LogicException( - 'Invalid `instance`: ' . Lib::php_dump($instance), + throw new LogicException( + 'Invalid `instance`: ' . Lib::debug_dump($instance), -1, $e ); } if (null === $tz) { - throw new \LogicException( - 'Invalid `instance`: ' . Lib::php_dump($instance), + throw new LogicException( + 'Invalid `instance`: ' . Lib::debug_dump($instance), -1 ); } @@ -290,12 +341,12 @@ public function newDateTimeZoneFromInstance($instance) : \DateTimeZone public function newDateInterval($duration = 'P0D') : \DateInterval { try { - $dtiClass = Type::dateInterval(); + $dtiClass = CalendarType::dateInterval(); $dti = new $dtiClass($duration); } catch ( \Throwable $e ) { - throw new \LogicException( - 'Invalid input: ' . Lib::php_dump([ $duration ]), + throw new LogicException( + 'Invalid input: ' . Lib::debug_dump([ $duration ]), -1, $e ); } @@ -303,25 +354,25 @@ public function newDateInterval($duration = 'P0D') : \DateInterval return $dti; } - public function newDateIntervalFromInstance($instance) + protected function newDateIntervalFromInstance($instance) { try { - $dtiClass = Type::dateInterval(); + $dtiClass = CalendarType::dateInterval(); $dti = $dtiClass::{'createFromInstance'}($instance); // > gzhegow, convert false to null $dti = $dti ?: null; } catch ( \Throwable $e ) { - throw new \LogicException( - 'Invalid `instance`: ' . Lib::php_dump($instance), + throw new LogicException( + 'Invalid `instance`: ' . Lib::debug_dump($instance), -1, $e ); } if (null === $dti) { - throw new \LogicException( - 'Invalid `instance`: ' . Lib::php_dump($instance), + throw new LogicException( + 'Invalid `instance`: ' . Lib::debug_dump($instance), -1 ); } @@ -329,25 +380,25 @@ public function newDateIntervalFromInstance($instance) return $dti; } - public function newDateIntervalFromDateString($dtiString) + protected function newDateIntervalFromDateString($dtiString) { try { - $dtiClass = Type::dateInterval(); + $dtiClass = CalendarType::dateInterval(); $dti = $dtiClass::{'createFromDateString'}($dtiString); // > gzhegow, convert false to null $dti = $dti ?: null; } catch ( \Throwable $e ) { - throw new \LogicException( - 'Invalid `dtiString`: ' . Lib::php_dump($dtiString), + throw new LogicException( + 'Invalid `dtiString`: ' . Lib::debug_dump($dtiString), -1, $e ); } if (null === $dti) { - throw new \LogicException( - 'Invalid `dtiString`: ' . Lib::php_dump($dtiString), + throw new LogicException( + 'Invalid `dtiString`: ' . Lib::debug_dump($dtiString), -1 ); } @@ -356,50 +407,17 @@ public function newDateIntervalFromDateString($dtiString) } - /** - * @param string|\DateTimeZone $timezoneDefault - */ - public function setTimezoneDefault($timezoneDefault) : void - { - $timezoneDefault = $this->parseDateTimeZone($timezoneDefault); - - $this->timezoneDefault = $timezoneDefault; - } - - - /** - * @param array|string[] $parseFormatsDefault - */ - public function setParseFormatsDefault(array $parseFormatsDefault) : void - { - $this->parseDateTimeFormatsDefault = $parseFormatsDefault; - } - - - /** - * @param callable|null $fnDateFormatter - */ - public function setFnDateFormatter(?callable $fnDateFormatter) : void - { - $this->fnDateFormatter = $fnDateFormatter; - } - - /** - * @param callable|null $fnDateIntervalFormatter - */ - public function setFnDateIntervalFormatter(?callable $fnDateIntervalFormatter) : void - { - $this->fnDateIntervalFormatter = $fnDateIntervalFormatter; - } - - public function dateTime($datetime = 'now', $timezone = null) : \DateTime { if ('' === $timezone) { $timezone = $this->timezoneDefault; - } elseif ($timezone) { - $timezone = $this->parseDateTimeZone($timezone); + } elseif (null !== $timezone) { + if (null === ($timezone = $this->parseDateTimeZone($timezone))) { + throw new LogicException( + 'Invalid `timezone` passed: ' . Lib::debug_dump($timezone) + ); + } } $dt = $this->newDateTime($datetime, $timezone); @@ -412,8 +430,12 @@ public function dateTimeImmutable($datetime = 'now', $timezone = null) : \DateTi if ('' === $timezone) { $timezone = $this->timezoneDefault; - } elseif ($timezone) { - $timezone = $this->parseDateTimeZone($timezone); + } elseif (null !== $timezone) { + if (null === ($timezone = $this->parseDateTimeZone($timezone))) { + throw new LogicException( + 'Invalid `timezone` passed: ' . Lib::debug_dump($timezone) + ); + } } $dt = $this->newDateTimeImmutable($datetime, $timezone); @@ -425,9 +447,6 @@ public function dateTimeZone($timezone = 'UTC') : \DateTimeZone { if ('' === $timezone) { $timezone = $this->timezoneDefault; - - } elseif ($timezone) { - $timezone = $this->parseDateTimeZone($timezone); } $tz = $this->newDateTimeZone($timezone); @@ -499,8 +518,8 @@ public function parseDateTime($datetime, array $formats = null, $timezoneIfParse } } - throw new \LogicException( - 'UNSUPPORTED_DATE_TIME: ' . Lib::php_dump($datetime), + throw new LogicException( + 'UNSUPPORTED_DATE_TIME: ' . Lib::debug_dump($datetime), -1 ); } @@ -550,13 +569,13 @@ public function parseDateTimeImmutable($datetime, array $formats = null, $timezo } } - throw new \LogicException( - 'UNSUPPORTED_DATE_TIME: ' . Lib::php_dump($datetime) + throw new LogicException( + 'UNSUPPORTED_DATE_TIME: ' . Lib::debug_dump($datetime) ); } - public function parseDateTimeFromNum($num, $timezoneIfParsed = null) : ?\DateTime + protected function parseDateTimeFromNum($num, $timezoneIfParsed = null) : ?\DateTime { if (null === ($_num = Lib::parse_num($num))) { return null; @@ -584,7 +603,7 @@ public function parseDateTimeFromNum($num, $timezoneIfParsed = null) : ?\DateTim return $dt; } - public function parseDateTimeFromString($datetime, $timezoneIfParsed = null) : ?\DateTime + protected function parseDateTimeFromString($datetime, $timezoneIfParsed = null) : ?\DateTime { if (null === ($_string = Lib::parse_astring($datetime))) { return null; @@ -602,7 +621,7 @@ public function parseDateTimeFromString($datetime, $timezoneIfParsed = null) : ? return $dt; } - public function parseDateTimeFromStringByFormat(string $format, $datetime, $timezoneIfParsed = null) : ?\DateTime + protected function parseDateTimeFromStringByFormat(string $format, $datetime, $timezoneIfParsed = null) : ?\DateTime { if (null === ($_string = Lib::parse_astring($datetime))) { return null; @@ -643,13 +662,13 @@ public function parseDateTimeZone($timezone) : ?\DateTimeZone } } - throw new \LogicException( - 'UNSUPPORTED_DATE_TIME_ZONE: ' . Lib::php_dump($timezone), + throw new LogicException( + 'UNSUPPORTED_DATE_TIME_ZONE: ' . Lib::debug_dump($timezone), -1 ); } - public function parseDateTimeZoneFromStringTimezone($timezone) : ?\DateTimeZone + protected function parseDateTimeZoneFromStringTimezone($timezone) : ?\DateTimeZone { if (null === ($_timezone = Lib::parse_astring($timezone))) { return null; @@ -708,13 +727,13 @@ public function parseDateInterval($interval, array $formats = null) : ?\DateInte } } - throw new \LogicException( - 'UNSUPPORTED_DATE_INTERVAL: ' . Lib::php_dump($interval), + throw new LogicException( + 'UNSUPPORTED_DATE_INTERVAL: ' . Lib::debug_dump($interval), -1 ); } - public function parseDateIntervalFromInt($int) : ?\DateInterval + protected function parseDateIntervalFromInt($int) : ?\DateInterval { if (null === ($_int = Lib::parse_int($int))) { return null; @@ -747,7 +766,7 @@ public function parseDateIntervalFromInt($int) : ?\DateInterval return $interval; } - public function parseDateIntervalFromStringDuration($duration) : ?\DateInterval + protected function parseDateIntervalFromStringDuration($duration) : ?\DateInterval { if (null === ($_string = Lib::parse_astring($duration))) { return null; @@ -763,7 +782,7 @@ public function parseDateIntervalFromStringDuration($duration) : ?\DateInterval return $interval; } - public function parseDateIntervalFromStringByFormat(string $format, $datetime) : ?\DateInterval + protected function parseDateIntervalFromStringByFormat(string $format, $datetime) : ?\DateInterval { if (null === ($_string = Lib::parse_astring($datetime))) { return null; @@ -785,7 +804,7 @@ public function parseDateIntervalFromStringByFormat(string $format, $datetime) : return $interval; } - public function parseDateIntervalFromStringDatetime($datetime) : ?\DateInterval + protected function parseDateIntervalFromStringDatetime($datetime) : ?\DateInterval { if (null === ($_string = Lib::parse_astring($datetime))) { return null; diff --git a/src/CalendarFactory.php b/src/CalendarFactory.php new file mode 100644 index 0000000..0fb65c1 --- /dev/null +++ b/src/CalendarFactory.php @@ -0,0 +1,11 @@ + @@ -48,7 +43,9 @@ public static function dateTimeZone() : string */ protected function _dateInterval() : string { - return DateInterval::class; + return PHP_VERSION_ID >= 80000 + ? \Gzhegow\Calendar\Struct\PHP8\DateInterval::class + : \Gzhegow\Calendar\Struct\PHP7\DateInterval::class; } /** @@ -56,7 +53,9 @@ protected function _dateInterval() : string */ protected function _dateTime() : string { - return DateTime::class; + return PHP_VERSION_ID >= 80000 + ? \Gzhegow\Calendar\Struct\PHP8\DateTime::class + : \Gzhegow\Calendar\Struct\PHP7\DateTime::class; } /** @@ -64,7 +63,9 @@ protected function _dateTime() : string */ protected function _dateTimeImmutable() : string { - return DateTimeImmutable::class; + return PHP_VERSION_ID >= 80000 + ? \Gzhegow\Calendar\Struct\PHP8\DateTimeImmutable::class + : \Gzhegow\Calendar\Struct\PHP7\DateTimeImmutable::class; } /** @@ -72,7 +73,9 @@ protected function _dateTimeImmutable() : string */ protected function _dateTimeZone() : string { - return DateTimeZone::class; + return PHP_VERSION_ID >= 80000 + ? \Gzhegow\Calendar\Struct\PHP8\DateTimeZone::class + : \Gzhegow\Calendar\Struct\PHP7\DateTimeZone::class; } @@ -81,7 +84,9 @@ protected function _dateTimeZone() : string */ public static function getInstance() : self { - return static::$instance = static::$instance ?? new static(); + return static::$instance = null + ?? static::$instance + ?? new static(); } public static function setInstance(self $instance) : void diff --git a/src/Exception/Exception.php b/src/Exception/Exception.php new file mode 100644 index 0000000..21c0b37 --- /dev/null +++ b/src/Exception/Exception.php @@ -0,0 +1,81 @@ + $v ) { + if (property_exists($this, $k)) { + $this->{$k} = $v; + } + } + + parent::__construct($this->message, $this->code, $this->previous); + } + + + /** + * @var \Throwable[] + */ + protected $previousList = []; + + /** + * @return \Throwable[] + */ + public function getPreviousList() : array + { + return $this->previousList; + } + + /** + * @return static + */ + public function setPreviousList(array $previousList) + { + $this->previousList = []; + + foreach ( $previousList as $previous ) { + $this->addPrevious($previous); + } + + return $this; + } + + /** + * @return static + */ + public function addPrevious(\Throwable $previous) // : static + { + $this->previousList[] = $previous; + + return $this; + } +} diff --git a/src/Exception/ExceptionInterface.php b/src/Exception/ExceptionInterface.php new file mode 100644 index 0000000..29348e3 --- /dev/null +++ b/src/Exception/ExceptionInterface.php @@ -0,0 +1,8 @@ + $v ) { + if (property_exists($this, $k)) { + $this->{$k} = $v; + } + } + + parent::__construct($this->message, $this->code, $this->previous); + } + + + /** + * @var \Throwable[] + */ + protected $previousList = []; + + /** + * @return \Throwable[] + */ + public function getPreviousList() : array + { + return $this->previousList; + } + + /** + * @return static + */ + public function setPreviousList(array $previousList) + { + $this->previousList = []; + + foreach ( $previousList as $previous ) { + $this->addPrevious($previous); + } + + return $this; + } + + /** + * @return static + */ + public function addPrevious(\Throwable $previous) // : static + { + $this->previousList[] = $previous; + + return $this; + } +} diff --git a/src/Exception/RuntimeException.php b/src/Exception/RuntimeException.php new file mode 100644 index 0000000..c940606 --- /dev/null +++ b/src/Exception/RuntimeException.php @@ -0,0 +1,81 @@ + $v ) { + if (property_exists($this, $k)) { + $this->{$k} = $v; + } + } + + parent::__construct($this->message, $this->code, $this->previous); + } + + + /** + * @var \Throwable[] + */ + protected $previousList = []; + + /** + * @return \Throwable[] + */ + public function getPreviousList() : array + { + return $this->previousList; + } + + /** + * @return static + */ + public function setPreviousList(array $previousList) + { + $this->previousList = []; + + foreach ( $previousList as $previous ) { + $this->addPrevious($previous); + } + + return $this; + } + + /** + * @return static + */ + public function addPrevious(\Throwable $previous) // : static + { + $this->previousList[] = $previous; + + return $this; + } +} diff --git a/src/Lib.php b/src/Lib.php index 58f8f2f..f909352 100644 --- a/src/Lib.php +++ b/src/Lib.php @@ -5,127 +5,160 @@ class Lib { /** - * > gzhegow, выводит короткую и наглядную форму содержимого переменной в виде строки + * @return object{ errors: array } */ - public static function php_dump($value, int $maxlen = null) : string + public static function php_errors() : object { - if (! is_iterable($value)) { - if (is_object($value)) { - if (! method_exists($value, '__debugInfo')) { - $_value = '{ object(' . get_class($value) . ' # ' . spl_object_id($value) . ') }'; + static $stack; - } else { - ob_start(); - var_dump($value); - $_value = ob_get_clean(); + $stack = $stack + ?? new class { + public $errors = []; + }; - $_value = '{ object(' . get_class($value) . ' # ' . spl_object_id($value) . '): ' . $_value . ' }'; - } + return $stack; + } - } elseif (is_string($value)) { - $_value = '' - . '{ ' - . 'string(' . strlen($value) . ')' - . ' "' - . ($maxlen - ? (substr($value, 0, $maxlen) . '...') - : $value - ) - . '"' - . ' }'; + /** + * @return object{ list: array } + */ + public static function php_errors_current() : object + { + $stack = static::php_errors(); - } else { - $_value = null - ?? (($value === null) ? '{ NULL }' : null) - ?? (($value === false) ? '{ FALSE }' : null) - ?? (($value === true) ? '{ TRUE }' : null) - ?? (is_resource($value) ? ('{ resource(' . gettype($value) . ' # ' . ((int) $value) . ') }') : null) - // - ?? (is_int($value) ? (var_export($value, 1)) : null) // INF - ?? (is_float($value) ? (var_export($value, 1)) : null) // NAN - // - ?? null; - } + $errors = end($stack->errors); - } else { - $_value = []; - foreach ( $value as $k => $v ) { - $_value[ $k ] = null - ?? (is_array($v) ? '{ array(' . count($v) . ') }' : null) - // ! recursion - ?? static::php_dump($v, $maxlen); - } + return $errors; + } + + /** + * @return object{ list: array } + */ + public static function php_errors_new() : object + { + $errors = new class { + public $list = []; + }; + + return $errors; + } - ob_start(); - var_dump($_value); - $_value = ob_get_clean(); + /** + * @return object{ list: array } + */ + public static function php_errors_start(object &$errors = null) : object + { + $stack = static::php_errors(); + + $errors = static::php_errors_new(); + $stack->errors[] = $errors; + + return $errors; + } - if (is_object($value)) { - $_value = '{ iterable(' . get_class($value) . ' # ' . spl_object_id($value) . '): ' . $_value . ' }'; + public static function php_errors_end(?object $until) : array + { + $stack = static::php_errors(); + + $errors = static::php_errors_new(); + + while ( count($stack->errors) ) { + $current = array_pop($stack->errors); + + foreach ( $current->list as $error ) { + $errors->list[] = $error; } - $_value = trim($_value); - $_value = preg_replace('/\s+/', ' ', $_value); + if ($current === $until) { + break; + } } - if (null === $_value) { - throw static::php_throwable([ 'Unable to ' . __FUNCTION__ . '()', 'var' => $value ]); - } + return $errors->list; + } - return $_value; + public static function php_error($error, $result = null) // : mixed + { + $current = static::php_errors_current(); + + $current->list[] = $error; + + return $result; } + /** - * > gzhegow, перебрасывает исключение на "тихое", если из библиотеки внутреннее постоянно подсвечивается в PHPStorm + * @param callable|array|object|class-string $mixed + * + * @param array{0: class-string, 1: string}|null $resultArray + * @param callable|string|null $resultString * - * @return \LogicException|\RuntimeException|null + * @return array{0: class-string|object, 1: string}|null */ - public static function php_throwable($error = null, ...$errors) : ?object + public static function php_method_exists( + $mixed, $method = null, + array &$resultArray = null, string &$resultString = null + ) : ?array { - if (is_a($error, \Closure::class)) { - $error = $error(...$errors); + $resultArray = null; + $resultString = null; + + $method = $method ?? ''; + + $_class = null; + $_object = null; + $_method = null; + if (is_object($mixed)) { + $_object = $mixed; + + } elseif (is_array($mixed)) { + $list = array_values($mixed); + + [ $classOrObject, $_method ] = $list + [ '', '' ]; + + is_object($classOrObject) + ? ($_object = $classOrObject) + : ($_class = $classOrObject); + + } elseif (is_string($mixed)) { + [ $_class, $_method ] = explode('::', $mixed) + [ '', '' ]; + + $_method = $_method ?? $method; } - if ( - is_a($error, \LogicException::class) - || is_a($error, \RuntimeException::class) - ) { - return $error; + if (isset($_method) && ! is_string($_method)) { + return null; } - $throwErrors = static::php_throwable_args($error, ...$errors); + if ($_object) { + if ($_object instanceof \Closure) { + return null; + } + + if (method_exists($_object, $_method)) { + $class = get_class($_object); - $message = $throwErrors[ 'message' ] ?? __FUNCTION__; - $code = $throwErrors[ 'code' ] ?? -1; - $previous = $throwErrors[ 'previous' ] ?? null; + $resultArray = [ $class, $_method ]; + $resultString = $class . '::' . $_method; - return $previous - ? new \RuntimeException($message, $code, $previous) - : new \LogicException($message, $code); + return [ $_object, $_method ]; + } + + } elseif ($_class) { + if (method_exists($_class, $_method)) { + $resultArray = [ $_class, $_method ]; + $resultString = $_class . '::' . $_method; + + return [ $_class, $_method ]; + } + } + + return null; } - /** - * > gzhegow, парсит ошибки для передачи результата в конструктор исключения - * - * @return array{ - * messageList: string[], - * codeList: int[], - * previousList: string[], - * messageCodeList: array[], - * messageDataList: array[], - * message: ?string, - * code: ?int, - * previous: ?string, - * messageCode: ?string, - * messageData: ?array, - * messageObject: ?object, - * __unresolved: array, - * } - */ - public static function php_throwable_args($arg = null, ...$args) : array - { - array_unshift($args, $arg); + public static function php_throwable_args(...$args) : array + { $len = count($args); $messageList = null; @@ -137,46 +170,48 @@ public static function php_throwable_args($arg = null, ...$args) : array $__unresolved = []; for ( $i = 0; $i < $len; $i++ ) { - $a = $args[ $i ]; + $arg = $args[ $i ]; - if (is_a($a, \Throwable::class)) { - $previousList[ $i ] = $a; + if (is_a($arg, \Throwable::class)) { + $previousList[ $i ] = $arg; continue; } if ( - is_array($a) - || is_a($a, \stdClass::class) + is_array($arg) + || is_a($arg, \stdClass::class) ) { - $messageDataList[ $i ] = (array) $a; + $messageData = (array) $arg; - if ('' !== ($messageString = (string) $messageDataList[ $i ][ 0 ])) { - $messageList[ $i ] = $messageString; + $messageString = isset($messageData[ 0 ]) + ? (string) $messageData[ 0 ] + : ''; - unset($messageDataList[ $i ][ 0 ]); + if ('' !== $messageString) { + unset($messageData[ 0 ]); - if (! $messageDataList[ $i ]) { - unset($messageDataList[ $i ]); - } + $messageList[ $i ] = $messageString; } + $messageDataList[ $i ] = $messageData; + continue; } - if (is_int($a)) { - $codeList[ $i ] = $a; + if (is_int($arg)) { + $codeList[ $i ] = $arg; continue; } - if ('' !== ($vString = (string) $a)) { + if ('' !== ($vString = (string) $arg)) { $messageList[ $i ] = $vString; continue; } - $__unresolved[ $i ] = $a; + $__unresolved[ $i ] = $arg; } for ( $i = 0; $i < $len; $i++ ) { @@ -222,54 +257,573 @@ public static function php_throwable_args($arg = null, ...$args) : array } + /** - * > gzhegow, вызывает произвольный колбэк с аргументами, если результат NULL - бросает исключение - * > _assert('_filter_strlen', [ $input ], 'Переменная `input` должна быть не-пустой строкой') - * - * @param callable|null $fn + * @return array{ + * 0: array, + * 1: array + * } + */ + public static function array_kwargs(array $src = null) : array + { + if (! isset($src)) return []; + + $list = []; + $dict = []; + + foreach ( $src as $idx => $val ) { + is_int($idx) + ? ($list[ $idx ] = $val) + : ($dict[ $idx ] = $val); + } + + return [ $list, $dict ]; + } + + + + /** + * @param callable $fn + * @param object{ microtime?: float, output?: string, result?: mixed }|null $except + * @param array|null $error + * @param resource|null $stdout */ - public static function assert($fn, array $args = [], $error = '', ...$errors) + public static function assert_call( + array $trace, + $fn, object $except = null, array &$error = null, + $stdout = null + ) : bool { - $result = ($fn === null) - ? $fn - : call_user_func_array($fn, $args); - - if (null === $result) { - if (('' === $error) || (null === $error)) { - if (! $errors) { - $error = [ '[ ' . __FUNCTION__ . ' ] ' . static::php_dump($fn), $args ]; + $error = null; + + $microtime = microtime(true); + + ob_start(); + $result = $fn(); + $output = ob_get_clean(); + + if (property_exists($except, 'result')) { + if ($except->result !== $result) { + $microtime = round(microtime(true) - $microtime, 6); + + static::debug_diff_var_dump($except->result, $result, $diff); + + $error = [ + 'Test result check failed', + [ + 'result' => $result, + 'expect' => $except->result, + 'diff' => $diff, + 'microtime' => $microtime, + 'file' => $trace[ 0 ][ 'file' ], + 'line' => $trace[ 0 ][ 'line' ], + ], + ]; + + if (null !== $stdout) { + fwrite($stdout, '------' . PHP_EOL); + fwrite($stdout, '[ ERROR ] Test result check failed. ' . $microtime . 's' . PHP_EOL); + fwrite($stdout, $trace[ 0 ][ 'file' ] . ' / ' . $trace[ 0 ][ 'line' ] . PHP_EOL); + fwrite($stdout, $diff . PHP_EOL); + fwrite($stdout, '------' . PHP_EOL); } + + return false; } + } + + if (property_exists($except, 'output')) { + if (static::debug_diff($except->output, $output, $diff)) { + $microtime = round(microtime(true) - $microtime, 6); + + $error = [ + 'Test result check failed', + [ + 'output' => $output, + 'expect' => $except->output, + 'diff' => $diff, + 'microtime' => $microtime, + 'file' => $trace[ 0 ][ 'file' ], + 'line' => $trace[ 0 ][ 'line' ], + ], + ]; + + if (null !== $stdout) { + fwrite($stdout, '------' . PHP_EOL); + fwrite($stdout, '[ ERROR ] Test output check failed. ' . $microtime . 's' . PHP_EOL); + fwrite($stdout, $trace[ 0 ][ 'file' ] . ' / ' . $trace[ 0 ][ 'line' ] . PHP_EOL); + fwrite($stdout, $diff . PHP_EOL); + fwrite($stdout, '------' . PHP_EOL); + } - throw static::php_throwable($error, ...$errors); + return false; + } } - return $result; + $microtime = round(microtime(true) - $microtime, 6); + + if (property_exists($except, 'microtime')) { + if ($except->microtime < $microtime) { + $diff = $microtime - $except->microtime; + + $error = [ + 'Test result check failed', + [ + 'microtime' => $microtime, + 'expect' => $except->microtime, + 'diff' => $diff, + 'file' => $trace[ 0 ][ 'file' ], + 'line' => $trace[ 0 ][ 'line' ], + ], + ]; + + if (null !== $stdout) { + fwrite($stdout, '------' . PHP_EOL); + fwrite($stdout, '[ ERROR ] Test microtime check failed. ' . $microtime . 's' . PHP_EOL); + fwrite($stdout, $trace[ 0 ][ 'file' ] . ' / ' . $trace[ 0 ][ 'line' ] . PHP_EOL); + fwrite($stdout, $diff . PHP_EOL); + fwrite($stdout, '------' . PHP_EOL); + } + + return false; + } + } + + if (null !== $stdout) { + fwrite($stdout, '[ OK ] Test success. ' . $microtime . 's' . PHP_EOL); + } + + return true; + } + + + + public static function debug_diff(string $a, string $b, string &$result = null) : bool + { + $result = null; + + static::os_eol($a, $aLines); + static::os_eol($b, $bLines); + + $cnt = max( + count($aLines), + count($bLines) + ); + + $lines = []; + + $isDiff = false; + + for ( $i = 0; $i < $cnt; $i++ ) { + $aLine = ($aLines[ $i ] ?? '{ NULL }') ?: '""'; + $bLine = ($bLines[ $i ] ?? '{ NULL }') ?: '""'; + + if ($aLine === $bLine) { + $lines[] = $aLine; + + continue; + } + + $lines[] = '--- ' . $aLine; + $lines[] = '+++ ' . $bLine; + + $isDiff = true; + } + + $result = implode(PHP_EOL, $lines); + + return $isDiff; } /** - * > gzhegow, вызывает произвольный колбэк с аргументами, если колбэк вернул не TRUE, бросает исключение, иначе $args[0] - * > _assert_true('is_int', [ $input ], 'Переменная `input` должна быть числом') - * - * @param callable|bool $fn + * @param mixed $a + * @param mixed $b */ - public static function assert_true($fn, array $args = [], $error = '', ...$errors) // : mixed + public static function debug_diff_var_dump($a, $b, string &$result = null) : bool + { + ob_start(); + var_dump($a); + $aString = ob_get_clean(); + + ob_start(); + var_dump($b); + $bString = ob_get_clean(); + + $isDiff = static::debug_diff($aString, $bString, $result); + + return $isDiff; + } + + public static function debug_dump($value, ...$values) : string + { + $output = static::debug_line([ 'with_ids' => true, 'with_objects' => false ], $value, ...$values); + + return $output; + } + + public static function debug_line(array $options, $value, ...$values) : string + { + array_unshift($values, $value); + + $valueExports = []; + foreach ( $values as $i => $v ) { + $line = static::debug_var_print($v, $options); + + $line = trim($line); + $line = preg_replace('/\s+/', ' ', $line); + + $valueExports[ $i ] = $line; + } + + $output = implode(' | ', $valueExports); + + return $output; + } + + public static function debug_text(array $options, $value, ...$values) : string + { + array_unshift($values, $value); + + $valueExports = []; + foreach ( $values as $i => $v ) { + $line = static::debug_var_print($v, $options); + + $valueExports[ $i ] = $line; + } + + $output = implode(PHP_EOL . PHP_EOL, $valueExports); + + return $output; + } + + public static function debug_var_dump($var, array $options = []) : string + { + $maxlen = $options[ 'maxlen' ] ?? null; + $withArrays = $options[ 'with_arrays' ] ?? true; + $withIds = $options[ 'with_ids' ] ?? true; + + if ($maxlen < 1) $maxlen = null; + + $type = null; + $dump = null; + + if (is_iterable($var)) { + if (is_object($var)) { + $id = $withIds + ? ' # ' . spl_object_id($var) + : ''; + + $type = 'iterable(' . get_class($var) . $id . ')'; + + } else { + $type = 'array(' . count($var) . ')'; + + if ($withArrays) { + $dump = []; + + foreach ( $var as $i => $v ) { + // ! recursion + $dump[ $i ] = static::debug_var_dump( + $v, + [] + + [ 'with_arrays' => false ] + + $options + ); + } + + $dump = var_export($dump, true); + } + } + + } else { + if (is_object($var)) { + $id = $withIds + ? ' # ' . spl_object_id($var) + : ''; + + $type = 'object(' . get_class($var) . $id . ')'; + + if (method_exists($var, '__debugInfo')) { + ob_start(); + var_dump($var); + $dump = ob_get_clean(); + } + + } elseif (is_string($var)) { + $type = 'string(' . strlen($var) . ')'; + + $dump = "\"{$var}\""; + + } elseif (is_resource($var)) { + $id = $withIds + ? ' # ' . ((int) $var) + : ''; + + $type = '{ resource(' . get_resource_type($var) . $id . ') }'; + + } else { + $type = null + ?? (($var === null) ? '{ NULL }' : null) + ?? (($var === false) ? '{ FALSE }' : null) + ?? (($var === true) ? '{ TRUE }' : null) + // + ?? (is_int($var) ? (var_export($var, 1)) : null) // INF + ?? (is_float($var) ? (var_export($var, 1)) : null) // NAN + // + ?? null; + } + } + + $_value = $type; + if (null !== $dump) { + if (null !== $maxlen) { + $dump = explode("\n", $dump); + + foreach ( $dump as $i => $v ) { + $_v = $v; + + $_v = trim($_v); + $_v = substr($_v, 0, $maxlen) . '...'; + + $dump[ $i ] = $_v; + } + + $dump = implode(PHP_EOL, $dump); + } + + $_value = "{$type} : {$dump}"; + } + + $_value = "{ {$_value} }"; + + return $_value; + } + + public static function debug_var_export($var, array $options = []) : string + { + $indent = $options[ 'indent' ] ?? " "; + $newline = $options[ 'newline' ] ?? PHP_EOL; + + switch ( gettype($var) ) { + case "NULL": + $result = "NULL"; + break; + + case "boolean": + $result = ($var === true) ? "TRUE" : "FALSE"; + break; + + case "string": + $result = '"' . addcslashes($var, "\\\$\"\r\n\t\v\f") . '"'; + break; + + case "array": + $keys = array_keys($var); + + foreach ( $keys as $key ) { + if (is_string($key)) { + $isList = false; + + break; + } + } + $isList = $isList ?? true; + + $isListIndexed = $isList + && ($keys === range(0, count($var) - 1)); + + $lines = []; + foreach ( $var as $key => $value ) { + $line = $indent; + + if (! $isListIndexed) { + $line .= is_string($key) ? "\"{$key}\"" : $key; + $line .= " => "; + } + + // ! recursion + $line .= static::debug_var_export($value, $options); + + $lines[] = $line; + } + + $result = "[" + . $newline + . implode("," . $newline, $lines) . $newline + . $indent . "]"; + + break; + + default: + $result = var_export($var, true); + + break; + } + + return $result; + } + + public static function debug_var_print($var, array $options = []) : string { - $bool = is_bool($fn) - ? $fn - : (bool) call_user_func_array($fn, $args); - - if (true !== $bool) { - if (('' === $error) || (null === $error)) { - if (! $errors) { - $error = [ '[ ' . __FUNCTION__ . ' ] ' . static::php_dump($fn), $args ]; + $indent = $options[ 'indent' ] ?? " "; + $newline = $options[ 'newline' ] ?? PHP_EOL; + $withArrays = $options[ 'with_arrays' ] ?? true; + $withIds = $options[ 'with_ids' ] ?? true; + $withObjects = $options[ 'with_objects' ] ?? true; + + switch ( gettype($var) ) { + case "string": + $result = '"' . $var . '"'; + break; + + case "array": + if (! $withArrays) { + $result = 'array(' . count($var) . ')'; + + } else { + $keys = array_keys($var); + + foreach ( $keys as $k ) { + if (is_string($k)) { + $isList = false; + + break; + } + } + $isList = $isList ?? true; + + $isListIndexed = $isList + && ($keys === range(0, count($var) - 1)); + + $lines = []; + foreach ( $var as $k => $v ) { + $line = $indent; + + if (! $isListIndexed) { + $line .= is_string($k) ? "\"{$k}\"" : $k; + $line .= " => "; + } + + // ! recursion + $line .= static::debug_var_print($v, [ 'with_arrays' => false ] + $options); + + $lines[] = $line; + } + + $result = "[" + . $newline + . implode("," . $newline, $lines) . $newline + . $indent . "]"; + } + + break; + + case "object": + if ($withObjects) { + $result = var_export($var, true); + + } else { + $id = $withIds + ? ' # ' . spl_object_id($var) + : null; + + $result = '{ object(' . get_class($var) . $id . ') }'; + } + + break; + + case "resource": + if ($withObjects) { + $result = var_export($var, true); + + } else { + $id = $withIds + ? ' # ' . spl_object_id($var) + : null; + + $result = '{ resource(' . get_resource_type($var) . $id . ') }'; } + + break; + + default: + $result = static::debug_var_export($var, $options); + + break; + } + + return $result; + } + + + + /** + * @param string[]|null $lines + */ + public static function os_eol(string $str, array &$lines = null) : string + { + $lines = null; + + $array = explode("\n", $str); + + foreach ( $array as $i => $line ) { + $array[ $i ] = rtrim($line, "\r"); + } + + $lines = $array; + + $output = implode("\n", $array); + + return $output; + } + + + + public static function parse_string($value) : ?string + { + if (is_string($value)) { + return $value; + } + + if ( + (null === $value) + || is_array($value) + || is_resource($value) + ) { + return null; + } + + if (is_object($value)) { + if (method_exists($value, '__toString')) { + $_value = (string) $value; + + return $_value; } - throw static::php_throwable($error, ...$errors); + return null; + } + + $_value = $value; + $status = @settype($_value, 'string'); + + if ($status) { + return $_value; + } + + return null; + } + + public static function parse_astring($value) : ?string + { + if (null === ($_value = static::parse_string($value))) { + return null; } - return $args[ 0 ] ?? null; + if ('' === $_value) { + return null; + } + + return $_value; } @@ -368,52 +922,4 @@ public static function parse_num($value) // : ?int|float return null; } - - - public static function parse_string($value) : ?string - { - if (is_string($value)) { - return $value; - } - - if ( - is_null($value) - || is_array($value) - || is_resource($value) - ) { - return null; - } - - if (is_object($value)) { - if (method_exists($value, '__toString')) { - $_value = (string) $value; - - return $_value; - } - - return null; - } - - $_value = $value; - $status = @settype($_value, 'string'); - - if ($status) { - return $_value; - } - - return null; - } - - public static function parse_astring($value) : ?string - { - if (null === ($_value = static::parse_string($value))) { - return null; - } - - if ('' === $_value) { - return null; - } - - return $_value; - } } diff --git a/src/Struct/DateInterval.php b/src/Struct/PHP7/DateInterval.php similarity index 86% rename from src/Struct/DateInterval.php rename to src/Struct/PHP7/DateInterval.php index 5dcf3be..be184f0 100644 --- a/src/Struct/DateInterval.php +++ b/src/Struct/PHP7/DateInterval.php @@ -1,8 +1,9 @@ getMessage(), $e->getCode(), $e); + throw new RuntimeException($e->getMessage(), $e->getCode(), $e); } return $dt; @@ -48,22 +50,22 @@ public static function createFromInterface($object) public static function createFromFormat($format, $datetime, $timezone = null) { if (null === Lib::parse_astring($format)) { - throw new \LogicException( - 'The `format` should be a non-empty string: ' . Lib::php_dump($format) + throw new LogicException( + 'The `format` should be a non-empty string: ' . Lib::debug_dump($format) ); } if (null === Lib::parse_astring($datetime)) { - throw new \LogicException( - 'The `datetime` should be a non-empty string: ' . Lib::php_dump($datetime) + throw new LogicException( + 'The `datetime` should be a non-empty string: ' . Lib::debug_dump($datetime) ); } if (null !== $timezone) { if (! is_a($timezone, \DateTimeZone::class)) { - throw new \LogicException('The `object` should be instance of: ' + throw new LogicException('The `object` should be instance of: ' . \DateTimeZone::class - . ' / ' . Lib::php_dump($timezone) + . ' / ' . Lib::debug_dump($timezone) ); } } @@ -79,7 +81,7 @@ public static function createFromFormat($format, $datetime, $timezone = null) ; } catch ( \Throwable $e ) { - throw new \RuntimeException($e->getMessage(), $e->getCode(), $e); + throw new RuntimeException($e->getMessage(), $e->getCode(), $e); } return $dt; @@ -90,7 +92,7 @@ public function diff($targetObject, $absolute = false) : \DateInterval { $interval = parent::diff($targetObject, $absolute); - $dtiClass = Type::dateInterval(); + $dtiClass = CalendarType::dateInterval(); $interval = $dtiClass::{'createFromInstance'}($interval); return $interval; diff --git a/src/Struct/DateTimeImmutable.php b/src/Struct/PHP7/DateTimeImmutable.php similarity index 76% rename from src/Struct/DateTimeImmutable.php rename to src/Struct/PHP7/DateTimeImmutable.php index 8d903e2..7526a8d 100644 --- a/src/Struct/DateTimeImmutable.php +++ b/src/Struct/PHP7/DateTimeImmutable.php @@ -1,10 +1,12 @@ getMessage(), $e->getCode(), $e); + throw new RuntimeException($e->getMessage(), $e->getCode(), $e); } return $dt; @@ -48,22 +50,22 @@ public static function createFromInterface($object) public static function createFromFormat($format, $datetime, $timezone = null) { if (null === Lib::parse_astring($format)) { - throw new \LogicException( - 'The `format` should be a non-empty string: ' . Lib::php_dump($format) + throw new LogicException( + 'The `format` should be a non-empty string: ' . Lib::debug_dump($format) ); } if (null === Lib::parse_astring($datetime)) { - throw new \LogicException( - 'The `datetime` should be a non-empty string: ' . Lib::php_dump($datetime) + throw new LogicException( + 'The `datetime` should be a non-empty string: ' . Lib::debug_dump($datetime) ); } if (null !== $timezone) { if (! is_a($timezone, \DateTimeZone::class)) { - throw new \LogicException('The `object` should be instance of: ' + throw new LogicException('The `object` should be instance of: ' . \DateTimeZone::class - . ' / ' . Lib::php_dump($timezone) + . ' / ' . Lib::debug_dump($timezone) ); } } @@ -79,7 +81,7 @@ public static function createFromFormat($format, $datetime, $timezone = null) ; } catch ( \Throwable $e ) { - throw new \RuntimeException($e->getMessage(), $e->getCode(), $e); + throw new RuntimeException($e->getMessage(), $e->getCode(), $e); } return $dt; @@ -90,7 +92,7 @@ public function diff($targetObject, $absolute = false) : \DateInterval { $dti = parent::diff($targetObject, $absolute); - $dtiClass = Type::dateInterval(); + $dtiClass = CalendarType::dateInterval(); $dti = $dtiClass::{'createFromInstance'}($dti); return $dti; diff --git a/src/Struct/DateTimeInterface.php b/src/Struct/PHP7/DateTimeInterface.php similarity index 62% rename from src/Struct/DateTimeInterface.php rename to src/Struct/PHP7/DateTimeInterface.php index 1a6d004..8b4ec2b 100644 --- a/src/Struct/DateTimeInterface.php +++ b/src/Struct/PHP7/DateTimeInterface.php @@ -1,6 +1,6 @@ $value ) { + $this->{$key} = $value; + } + })->call($interval = new static('P0D'), (array) $object); + + return $interval; + } + + /** + * @return static + */ + public static function createFromDateString($datetime) + { + if (null === Lib::parse_astring($datetime)) { + throw new LogicException( + 'The `datetime` should be a non-empty string: ' . Lib::debug_dump($datetime) + ); + } + + $dti = parent::createFromDateString($datetime); + + (function ($state) { + foreach ( $state as $key => $value ) { + $this->{$key} = $value; + } + })->call($interval = new static('P0D'), (array) $dti); + + return $interval; + } + + + public function jsonSerialize() : mixed + { + // $dti = new \DateInterval(); + // var_dump($dti, $var = json_encode($dti)); + // + // > string(88) "{"y":0,"m":0,"d":0,"h":0,"i":10,"s":0,"f":0,"invert":0,"days":false,"from_string":false}" + // + // vs + // + // > string(5) "PT10M" + + $search = [ 'S0F', 'M0S', 'H0M', 'DT0H', 'M0D', 'P0Y', 'Y0M', 'P0M' ]; + $replace = [ 'S', 'M', 'H', 'DT', 'M', 'P', 'Y', 'P' ]; + + $result = $this->format('P%yY%mM%dDT%hH%iM%sS%fF'); + $result = str_replace($search, $replace, $result); + $result = rtrim($result, 'PT') ?: 'P0D'; + + return $result ?: null; + } +} diff --git a/src/Struct/PHP8/DateTime.php b/src/Struct/PHP8/DateTime.php new file mode 100644 index 0000000..e86ac5d --- /dev/null +++ b/src/Struct/PHP8/DateTime.php @@ -0,0 +1,114 @@ +format('u'), 6, '0'); + + try { + $dt = (new static('now', $object->getTimezone())) + ->setTimestamp($object->getTimestamp()) + ->modify("+ {$microseconds} microseconds") + ; + } + catch ( \Throwable $e ) { + throw new RuntimeException($e->getMessage(), $e->getCode(), $e); + } + + return $dt; + } + + + /** + * @return static + */ + public static function createFromFormat($format, $datetime, $timezone = null) + { + if (null === Lib::parse_astring($format)) { + throw new LogicException( + 'The `format` should be a non-empty string: ' . Lib::debug_dump($format) + ); + } + + if (null === Lib::parse_astring($datetime)) { + throw new LogicException( + 'The `datetime` should be a non-empty string: ' . Lib::debug_dump($datetime) + ); + } + + if (null !== $timezone) { + if (! is_a($timezone, \DateTimeZone::class)) { + throw new LogicException('The `object` should be instance of: ' + . \DateTimeZone::class + . ' / ' . Lib::debug_dump($timezone) + ); + } + } + + $dt = parent::createFromFormat($format, $datetime, $timezone); + + $microseconds = str_pad($dt->format('u'), 6, '0'); + + try { + $dt = (new static('now', $dt->getTimezone())) + ->setTimestamp($dt->getTimestamp()) + ->modify("+ {$microseconds} microseconds") + ; + } + catch ( \Throwable $e ) { + throw new RuntimeException($e->getMessage(), $e->getCode(), $e); + } + + return $dt; + } + + + public function diff($targetObject, $absolute = false) : \DateInterval + { + $interval = parent::diff($targetObject, $absolute); + + $dtiClass = CalendarType::dateInterval(); + $interval = $dtiClass::{'createFromInstance'}($interval); + + return $interval; + } + + + public function jsonSerialize() : mixed + { + // var_dump($date, $var = json_encode($date)); + // + // > string(72) "{"date":"1970-01-01 00:00:00.000000","timezone_type":3,"timezone":"UTC"}" + // + // vs + // + // > string(29) "2024-04-08T08:42:04.037+00:00" + + return $this->format(Calendar::FORMAT_JAVASCRIPT_MILLISECONDS); + } +} diff --git a/src/Struct/PHP8/DateTimeImmutable.php b/src/Struct/PHP8/DateTimeImmutable.php new file mode 100644 index 0000000..ad7c6fe --- /dev/null +++ b/src/Struct/PHP8/DateTimeImmutable.php @@ -0,0 +1,114 @@ +format('u'), 6, '0'); + + try { + $dt = (new static('now', $object->getTimezone())) + ->setTimestamp($object->getTimestamp()) + ->modify("+ {$microseconds} microseconds") + ; + } + catch ( \Throwable $e ) { + throw new RuntimeException($e->getMessage(), $e->getCode(), $e); + } + + return $dt; + } + + + /** + * @return static + */ + public static function createFromFormat($format, $datetime, $timezone = null) + { + if (null === Lib::parse_astring($format)) { + throw new LogicException( + 'The `format` should be a non-empty string: ' . Lib::debug_dump($format) + ); + } + + if (null === Lib::parse_astring($datetime)) { + throw new LogicException( + 'The `datetime` should be a non-empty string: ' . Lib::debug_dump($datetime) + ); + } + + if (null !== $timezone) { + if (! is_a($timezone, \DateTimeZone::class)) { + throw new LogicException('The `object` should be instance of: ' + . \DateTimeZone::class + . ' / ' . Lib::debug_dump($timezone) + ); + } + } + + $dt = parent::createFromFormat($format, $datetime, $timezone); + + $microseconds = str_pad($dt->format('u'), 6, '0'); + + try { + $dt = (new static('now', $dt->getTimezone())) + ->setTimestamp($dt->getTimestamp()) + ->modify("+ {$microseconds} microseconds") + ; + } + catch ( \Throwable $e ) { + throw new RuntimeException($e->getMessage(), $e->getCode(), $e); + } + + return $dt; + } + + + public function diff($targetObject, $absolute = false) : \DateInterval + { + $dti = parent::diff($targetObject, $absolute); + + $dtiClass = CalendarType::dateInterval(); + $dti = $dtiClass::{'createFromInstance'}($dti); + + return $dti; + } + + + public function jsonSerialize() : mixed + { + // var_dump($date, $var = json_encode($date)); + // + // > string(72) "{"date":"1970-01-01 00:00:00.000000","timezone_type":3,"timezone":"UTC"}" + // + // vs + // + // > string(29) "2024-04-08T08:42:04.037+00:00" + + return $this->format(Calendar::FORMAT_JAVASCRIPT_MILLISECONDS); + } +} diff --git a/src/Struct/PHP8/DateTimeInterface.php b/src/Struct/PHP8/DateTimeInterface.php new file mode 100644 index 0000000..b70760a --- /dev/null +++ b/src/Struct/PHP8/DateTimeInterface.php @@ -0,0 +1,8 @@ + $value ) { + $this->{$key} = $value; + } + })->call($tz = new static('UTC'), (array) $object); + + return $tz; + } + + + public function jsonSerialize() : mixed + { + // var_dump($tz, $var = json_encode($tz)); + // + // > string(72) "{"timezone_type":3,"timezone":"UTC"}" + // + // vs + // + // > string(29) "UTC" + + return $this->getName(); + } +} diff --git a/test.php b/test.php index b36ced7..1a007d6 100644 --- a/test.php +++ b/test.php @@ -1,15 +1,12 @@ настраиваем PHP ini_set('memory_limit', '32M'); + // > настраиваем обработку ошибок error_reporting(E_ALL); set_error_handler(function ($errno, $errstr, $errfile, $errline) { @@ -17,110 +14,241 @@ throw new \ErrorException($errstr, -1, $errno, $errfile, $errline); } }); -set_exception_handler(function ($e) { - var_dump(Lib::php_dump($e)); - var_dump($e->getMessage()); - var_dump(($e->getFile() ?? '{file}') . ': ' . ($e->getLine() ?? '{line}')); +set_exception_handler(function (\Throwable $e) { + $current = $e; + do { + echo "\n"; + + echo \Gzhegow\Calendar\Lib::debug_var_dump($current) . PHP_EOL; + echo $current->getMessage() . PHP_EOL; + + foreach ( $e->getTrace() as $traceItem ) { + echo "{$traceItem['file']} : {$traceItem['line']}" . PHP_EOL; + } + + echo PHP_EOL; + } while ( $current = $current->getPrevious() ); die(); }); -// > создаем календарь -$calendar = new Calendar(); +// > добавляем несколько функция для тестирования +function _dump($value, ...$values) : void +{ + echo \Gzhegow\Calendar\Lib::debug_line([ 'with_ids' => false, 'with_objects' => false ], $value, ...$values); +} -// > можно изменить классы дат на свои собственные реализации -// \Gzhegow\Calendar\Type::setInstance(new \Gzhegow\Calendar\Type()); - -// > создаем дату -$tests[ '_calendar_date' ] = $calendar->parseDateTime($datetime = 'now', $formats = null, $timezoneIfParsed = null); -Lib::assert_true('is_a', [ $tests[ '_calendar_date' ], DateTime::class ]); - -// > создаем/распознаем дату -$tests[ '_calendar_date_immutable' ] = $calendar->parseDateTimeImmutable($datetime = 'now', $formats = null, $timezoneIfParsed = null); -Lib::assert_true('is_a', [ $tests[ '_calendar_date_immutable' ], DateTimeImmutable::class ]); - -// > проводим действия над датой, чтобы убедится что Immutable работает -$tests[ '_calendar_date_immutable_add' ] = $tests[ '_calendar_date_immutable' ]->add(new \DateInterval('P1D')); -$tests[ '_calendar_date_immutable_sub' ] = $tests[ '_calendar_date_immutable' ]->sub(new \DateInterval('P1D')); -$tests[ '_calendar_date_immutable_modify' ] = $tests[ '_calendar_date_immutable' ]->modify('+ 10 hours'); -Lib::assert_true('is_a', [ $tests[ '_calendar_date_immutable_add' ], DateTimeImmutable::class ]); -Lib::assert_true('is_a', [ $tests[ '_calendar_date_immutable_sub' ], DateTimeImmutable::class ]); -Lib::assert_true('is_a', [ $tests[ '_calendar_date_immutable_modify' ], DateTimeImmutable::class ]); -if ($tests[ '_calendar_date_immutable_add' ] === $tests[ '_calendar_date_immutable_sub' ]) throw new \RuntimeException(); -if ($tests[ '_calendar_date_immutable_sub' ] === $tests[ '_calendar_date_immutable_modify' ]) throw new \RuntimeException(); -if ($tests[ '_calendar_date_immutable_add' ] === $tests[ '_calendar_date_immutable_modify' ]) throw new \RuntimeException(); - -// > создает дату "сейчас", просто alias для _calendar_date($date) -$tests[ '_calendar_now' ] = $calendar->now($timezone = null); -$tests[ '_calendar_now_immutable' ] = $calendar->nowImmutable($timezone = null); -Lib::assert_true('is_a', [ $tests[ '_calendar_now' ], DateTime::class ]); -Lib::assert_true('is_a', [ $tests[ '_calendar_now_immutable' ], DateTimeImmutable::class ]); - -// > создает/распознает временную зону -$tests[ '_calendar_timezone' ] = $calendar->parseDateTimeZone($timezone = 'UTC'); -Lib::assert_true('is_a', [ $tests[ '_calendar_timezone' ], DateTimeZone::class ]); -Lib::assert_true(function () use ($tests) { - return 'UTC' === $tests[ '_calendar_timezone' ]->getName(); -}); +function _dump_ln($value, ...$values) : void +{ + echo \Gzhegow\Calendar\Lib::debug_line([ 'with_ids' => false, 'with_objects' => false ], $value, ...$values) . PHP_EOL; +} -// > создает/распознает интервал -$tests[ '_calendar_interval' ] = $calendar->parseDateInterval($interval = 'P0D', $formats = null); -Lib::assert_true('is_a', [ $tests[ '_calendar_interval' ], DateInterval::class ]); -Lib::assert_true(function () use ($tests) { - return 'P0D' === $tests[ '_calendar_interval' ]->jsonSerialize(); -}); +function _assert_call(\Closure $fn, array $expectResult = [], string $expectOutput = null) : void +{ + $trace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS, 1); -// > возвращает разницу между датами -$now = $calendar->nowImmutable(); -$past = $now->modify('- 10 hours'); -$tests[ '_calendar_diff' ] = $calendar->diff($now, $past, $absolute = false); // : ?DateInterval; -Lib::assert_true('is_a', [ $tests[ '_calendar_diff' ], DateInterval::class ]); -Lib::assert_true(function () use ($tests) { - return 'PT10H' === $tests[ '_calendar_diff' ]->jsonSerialize(); -}); + $expect = (object) []; -var_dump(json_encode($tests, JSON_PRETTY_PRINT)); - -// string(730) "{ -// "_calendar_date": "2024-05-09T19:47:39.074+03:00", -// "_calendar_date_immutable": "2024-05-09T19:47:39.074+03:00", -// "_calendar_date_immutable_add": "2024-05-10T19:47:39.074+03:00", -// "_calendar_date_immutable_sub": "2024-05-08T19:47:39.074+03:00", -// "_calendar_date_immutable_modify": "2024-05-10T05:47:39.074+03:00", -// "_calendar_now": "2024-05-09T19:47:39.074+03:00", -// "_calendar_now_immutable": "2024-05-09T19:47:39.074+03:00", -// "_calendar_timezone": "UTC", -// "_calendar_interval": "P0D", -// "_calendar_diff": "PT10H" -// }" - -$dump = []; -foreach ( $tests as $i => $test ) { - $dump[ $i ] = Lib::php_dump($test); + if (count($expectResult)) { + $expect->result = $expectResult[ 0 ]; + } + + if (null !== $expectOutput) { + $expect->output = $expectOutput; + } + + $status = \Gzhegow\Calendar\Lib::assert_call($trace, $fn, $expect, $error, STDOUT); + + if (! $status) { + throw new \Gzhegow\Calendar\Exception\LogicException(); + } } -var_dump($dump); - -// array(13) { -// ["_calendar_date"]=> -// string(48) "{ object(Gzhegow\Calendar\Struct\DateTime # 8) }" -// ["_calendar_date_immutable"]=> -// string(57) "{ object(Gzhegow\Calendar\Struct\DateTimeImmutable # 9) }" -// ["_calendar_date_immutable_add"]=> -// string(58) "{ object(Gzhegow\Calendar\Struct\DateTimeImmutable # 10) }" -// ["_calendar_date_immutable_sub"]=> -// string(58) "{ object(Gzhegow\Calendar\Struct\DateTimeImmutable # 11) }" -// ["_calendar_date_immutable_modify"]=> -// string(57) "{ object(Gzhegow\Calendar\Struct\DateTimeImmutable # 7) }" -// ["_calendar_now"]=> -// string(49) "{ object(Gzhegow\Calendar\Struct\DateTime # 12) }" -// ["_calendar_now_immutable"]=> -// string(58) "{ object(Gzhegow\Calendar\Struct\DateTimeImmutable # 13) }" -// string(58) "{ object(Gzhegow\Calendar\Struct\DateTimeImmutable # 15) }" -// ["_calendar_timezone"]=> -// string(53) "{ object(Gzhegow\Calendar\Struct\DateTimeZone # 16) }" -// ["_calendar_interval"]=> -// string(53) "{ object(Gzhegow\Calendar\Struct\DateInterval # 17) }" -// ["_calendar_diff"]=> -// string(53) "{ object(Gzhegow\Calendar\Struct\DateInterval # 20) }" -// } + + +// >>> ЗАПУСКАЕМ! + +// > сначала всегда фабрика +$factory = new \Gzhegow\Calendar\CalendarFactory(); + +// > создаем календарь +$calendar = $factory->newCalendar(); + +// > можно изменить классы дат на свои собственные реализации +// $calendarType = new \Gzhegow\Calendar\CalendarType(); +// \Gzhegow\Calendar\CalendarType::setInstance($calendarType); + + +// > TEST +// > создаем дату, временную зону и интервал +$fn = function () use ($calendar) { + _dump_ln('TEST 1'); + + $result = $calendar->dateTime($datetime = 'now', $timezone = null); + _dump_ln(get_class($result)); + + $result = $calendar->dateTimeImmutable($datetime = 'now', $timezone = null); + _dump_ln(get_class($result)); + + $result = $calendar->dateTimeZone($timezone = 'UTC'); + _dump_ln(get_class($result)); + + $result = $calendar->dateInterval($duration = 'P1D'); + _dump_ln(get_class($result)); + + _dump(''); +}; +_assert_call($fn, [], PHP_VERSION_ID >= 80000 + ? << TEST +// > распознаем дату, временную зону и интервал +$fn = function () use ($calendar) { + _dump_ln('TEST 2'); + + $result = $calendar->parseDateTime($datetime = '1970-01-01 00:00:00', $formats = [ 'Y-m-d H:i:s' ], $timezoneIfParsed = 'UTC'); + _dump_ln(get_class($result)); + + $result = $calendar->parseDateTimeImmutable($datetime = '1970-01-01 00:00:00', $formats = [ 'Y-m-d H:i:s' ], $timezoneIfParsed = 'UTC'); + _dump_ln(get_class($result)); + + $result = $calendar->parseDateTimeZone($timezone = 'UTC'); + _dump_ln(get_class($result)); + + $result = $calendar->parseDateInterval($interval = 'P0D', $formats = null); + _dump_ln(get_class($result)); + + _dump(''); +}; +_assert_call($fn, [], PHP_VERSION_ID >= 80000 + ? << TEST +// > проводим действия над датой +$fn = function () use ($calendar) { + _dump_ln('TEST 3'); + + $dt = $calendar->parseDateTimeImmutable($datetime = 'now', $formats = null, $timezoneIfParsed = null); + _dump_ln(get_class($dt)); + + $result = $dt->modify('+ 10 hours'); + _dump_ln(get_class($result)); + $result = $result->diff($dt); + _dump_ln(get_class($result)); + _dump_ln(json_encode($result)); + + $result = $dt->add(new \DateInterval('P1D')); + _dump_ln(get_class($result)); + $result = $result->diff($dt); + _dump_ln(get_class($result)); + _dump_ln(json_encode($result)); + _dump_ln((bool) $result->invert); + + $result = $dt->sub(new \DateInterval('P1D')); + _dump_ln(get_class($result)); + $result = $result->diff($dt); + _dump_ln(get_class($result)); + _dump_ln(json_encode($result)); + _dump_ln((bool) $result->invert); + + _dump(''); +}; +_assert_call($fn, [], PHP_VERSION_ID >= 80000 + ? << TEST +// > считаем разницу времени +$fn = function () use ($calendar) { + _dump_ln('TEST 4'); + + $now = $calendar->nowImmutable(); + + $past = $now->modify('- 10 hours'); + + $result = $calendar->diff($now, $past, $absolute = false); + _dump_ln(get_class($result)); + _dump_ln('"PT10H"' === json_encode($result)); + + _dump(''); +}; +_assert_call($fn, [], PHP_VERSION_ID >= 80000 + ? <<