From fc22ea832d7211e76b818631df3a9a35a29a8e3d Mon Sep 17 00:00:00 2001 From: Pavol Kirschbaum Date: Mon, 20 Dec 2021 16:59:37 +0100 Subject: [PATCH 1/3] feat: Add php 8.1 support; Drop php 7.3 and 7.4 support --- .github/workflows/continuous-integration.yml | 6 +- composer.json | 6 +- src/DateInterval.php | 74 +++++---- src/DateTime.php | 153 +++++++------------ src/DateTimeFactory.php | 40 ++--- src/DateTimeFactoryInterface.php | 36 +---- src/DateTimeInterface.php | 51 +++---- tests/src/DateIntervalTest.php | 4 +- tests/src/DateTimeTest.php | 1 + 9 files changed, 136 insertions(+), 235 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 5912136..ab0b32c 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -17,7 +17,7 @@ jobs: - name: Setup PHP uses: shivammathur/setup-php@v2 with: - php-version: 7.4 + php-version: 8.1 coverage: none - name: Get composer cache directory @@ -52,7 +52,7 @@ jobs: uses: shivammathur/setup-php@v2 with: coverage: none - php-version: 7.4 + php-version: 8.1 - name: Get composer cache directory id: composer-cache @@ -77,7 +77,7 @@ jobs: strategy: matrix: - php-versions: ['7.3', '7.4', '8.0'] + php-versions: ['8.0', '8.1'] steps: - name: Checkout code diff --git a/composer.json b/composer.json index 845b29b..1e5d91a 100644 --- a/composer.json +++ b/composer.json @@ -22,14 +22,14 @@ "issues": "https://github.com/pauci/datetime/issues" }, "require": { - "php": "^7.3 | ^8.0", + "php": "~8.0.0||~8.1.0", "ext-json": "*" }, "require-dev": { "php-parallel-lint/php-parallel-lint": "^1.2", "phpstan/extension-installer": "^1.0", - "phpstan/phpstan": "^0.12.58", - "phpstan/phpstan-phpunit": "^0.12.16", + "phpstan/phpstan": "^1.2", + "phpstan/phpstan-phpunit": "^1.0", "phpunit/phpunit": "^9.5", "squizlabs/php_codesniffer": "^3.5" }, diff --git a/src/DateInterval.php b/src/DateInterval.php index ae6c38a..a1af49b 100644 --- a/src/DateInterval.php +++ b/src/DateInterval.php @@ -1,18 +1,17 @@ y = $dateInterval->y; $interval->m = $dateInterval->m; $interval->d = $dateInterval->d; @@ -21,17 +20,11 @@ public static function fromDateInterval(\DateInterval $dateInterval): self $interval->s = $dateInterval->s; $interval->invert = $dateInterval->invert; $interval->days = $dateInterval->days; + return $interval; } /** - * @param int $years - * @param int $months - * @param int $days - * @param int $hours - * @param int $minutes - * @param int $seconds - * @return DateInterval * @throws \Exception */ public static function fromParts( @@ -41,76 +34,79 @@ public static function fromParts( int $hours = 0, int $minutes = 0, int $seconds = 0 - ): self { - $interval = new self('P0D'); + ): static { + $interval = new static('P0D'); $interval->y = $years; $interval->m = $months; $interval->d = $days; $interval->h = $hours; $interval->i = $minutes; $interval->s = $seconds; + return $interval; } + final public function __construct(string $duration) + { + parent::__construct($duration); + } + /** - * @param string $interval - * @return DateInterval * @throws \Exception */ - public static function fromString(string $interval): self + public static function fromString(string $interval): static { - return new self($interval); + return new static($interval); } - /** - * @return string - */ public function __toString(): string { return $this->toString(); } - /** - * @return string - */ public function jsonSerialize(): string { return $this->toString(); } - /** - * @return string - */ public function toString(): string { - $dateString = ''; + $datePart = ''; + if ($this->y !== 0) { - $dateString .= $this->y . 'Y'; + $datePart .= $this->y . 'Y'; } + if ($this->m !== 0) { - $dateString .= $this->m . 'M'; + $datePart .= $this->m . 'M'; } + if ($this->d !== 0) { - $dateString .= $this->d . 'D'; + $datePart .= $this->d . 'D'; } - $timeString = ''; + $timePart = ''; + if ($this->h !== 0) { - $timeString .= $this->h . 'H'; + $timePart .= $this->h . 'H'; } + if ($this->i !== 0) { - $timeString .= $this->i . 'M'; + $timePart .= $this->i . 'M'; } + if ($this->s !== 0) { - $timeString .= $this->s . 'S'; + $timePart .= $this->s . 'S'; } - if ($timeString === '') { - if ($dateString === '') { + if ($timePart === '') { + if ($datePart === '') { return 'P0D'; } - return 'P' . $dateString; + + return 'P' . $datePart; } - return 'P' . $dateString . 'T' . $timeString; + + return 'P' . $datePart . 'T' . $timePart; } } diff --git a/src/DateTime.php b/src/DateTime.php index 02553e7..505d51c 100644 --- a/src/DateTime.php +++ b/src/DateTime.php @@ -1,189 +1,152 @@ now(); } - /** - * @return DateTimeInterface - */ public static function microsecondsNow(): DateTimeInterface { return self::getFactory()->microsecondsNow(); } - /** - * @param string $time - * @param DateTimeZone|null $timezone - * @return DateTimeInterface - */ public static function fromString(string $time, DateTimeZone $timezone = null): DateTimeInterface { return self::getFactory()->fromString($time, $timezone); } - /** - * @param string $format - * @param string $time - * @param DateTimeZone|null $timezone - * @return DateTimeInterface - */ public static function fromFormat(string $format, string $time, DateTimeZone $timezone = null): DateTimeInterface { return self::getFactory()->fromFormat($format, $time, $timezone); } - /** - * @param int $timestamp - * @param DateTimeZone|null $timezone - * @return DateTimeInterface - */ public static function fromTimestamp(int $timestamp, DateTimeZone $timezone = null): DateTimeInterface { return self::getFactory()->fromTimestamp($timestamp, $timezone); } - /** - * @param float $timestamp - * @param DateTimeZone|null $timezone - * @return DateTimeInterface - */ public static function fromFloatTimestamp(float $timestamp, DateTimeZone $timezone = null): DateTimeInterface { return self::getFactory()->fromFloatTimestamp($timestamp, $timezone); } - /** - * @param \DateTimeInterface $dateTime - * @return DateTimeInterface - */ public static function fromDateTime(\DateTimeInterface $dateTime): DateTimeInterface { return self::getFactory()->fromDateTime($dateTime); } - /** - * @param string $format - * @param string $time - * @param DateTimeZone|null $timezone - * @return DateTimeInterface - */ - public static function createFromFormat($format, $time, DateTimeZone $timezone = null): DateTimeInterface - { - return self::fromFormat($format, $time, $timezone); + #[\ReturnTypeWillChange] + public static function createFromFormat( + string $format, + string $datetime, + DateTimeZone $timezone = null + ): DateTimeInterface { + return self::fromFormat($format, $datetime, $timezone); } - /** - * @param \DateTime $dateTime - * @return DateTimeInterface - */ - public static function createFromMutable($dateTime): DateTimeInterface + #[\ReturnTypeWillChange] + public static function createFromMutable(\DateTime $object): DateTimeInterface { - return self::fromDateTime($dateTime); + return self::fromDateTime($object); } /** - * @param \DateTimeInterface $dateTime - * @param bool $absolute - * @return DateInterval * @throws \Exception */ - public function diff($dateTime, $absolute = false): DateInterval + public function diff(\DateTimeInterface $targetObject, bool $absolute = false): DateInterval { return DateInterval::fromDateInterval( - parent::diff($dateTime, $absolute) + parent::diff($targetObject, $absolute) ); } - /** - * @param DateTimeInterface $dateTime - * @return bool - */ + public function add(\DateInterval $interval): static + { + return parent::add($interval); + } + + public function sub(\DateInterval $interval): static + { + return parent::sub($interval); + } + + public function modify(string $modifier): static|false + { + return parent::modify($modifier); + } + + public function setDate(int $year, int $month, int $day): static + { + return parent::setDate($year, $month, $day); + } + + public function setISODate(int $year, int $week, int $dayOfWeek = 1): static + { + return parent::setISODate($year, $week, $dayOfWeek); + } + + public function setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0): static + { + return parent::setTime($hour, $minute, $second, $microsecond); + } + + public function setTimestamp(int $timestamp): static + { + return parent::setTimestamp($timestamp); + } + + public function setTimezone(\DateTimeZone $timezone): static + { + return parent::setTimezone($timezone); + } + public function equals(DateTimeInterface $dateTime): bool { return $this == $dateTime; } - /** - * @return DateTimeInterface - */ public function inDefaultTimezone(): DateTimeInterface { return $this->setTimezone(new DateTimeZone(date_default_timezone_get())); } - /** - * @return string - */ public function toString(): string { return $this->format($this->getFormat()); } - /** - * @return string - */ private function getFormat(): string { - return $this->format('u') === '000000' ? \DateTime::ATOM : 'Y-m-d\TH:i:s.uP'; + return $this->format('u') === '000000' + ? \DateTimeInterface::ATOM + : 'Y-m-d\TH:i:s.uP'; } - /** - * @return string - */ public function __toString(): string { return $this->toString(); } - /** - * @return string - */ public function jsonSerialize(): string { return $this->toString(); diff --git a/src/DateTimeFactory.php b/src/DateTimeFactory.php index cafaa0a..c34b658 100644 --- a/src/DateTimeFactory.php +++ b/src/DateTimeFactory.php @@ -1,4 +1,5 @@ getMessage(); - if (0 === strpos($message, 'DateTimeImmutable::__construct(): ')) { + + if (str_starts_with($message, 'DateTimeImmutable::__construct(): ')) { $message = substr($message, 34); } - throw new Exception\InvalidTimeStringException($message, $e->getCode(), $e); + + throw new Exception\InvalidTimeStringException($message, previous: $e); } } /** - * @param string $format - * @param string $time - * @param DateTimeZone|null $timezone - * @return DateTimeInterface - * @throws \InvalidArgumentException + * @throws Exception\InvalidTimeStringException */ public function fromFormat(string $format, string $time, DateTimeZone $timezone = null): DateTimeInterface { @@ -55,22 +52,12 @@ public function fromFormat(string $format, string $time, DateTimeZone $timezone return $this->fromDateTime($dateTime); } - /** - * @param int $timestamp - * @param DateTimeZone|null $timezone - * @return DateTimeInterface - */ public function fromTimestamp(int $timestamp, DateTimeZone $timezone = null): DateTimeInterface { return $this->fromString('@' . $timestamp) ->setTimezone($timezone ?? $this->getDefaultTimezone()); } - /** - * @param float $timestamp - * @param DateTimeZone|null $timezone - * @return DateTimeInterface - */ public function fromFloatTimestamp(float $timestamp, DateTimeZone $timezone = null): DateTimeInterface { $integerPart = (int) floor($timestamp); @@ -78,21 +65,18 @@ public function fromFloatTimestamp(float $timestamp, DateTimeZone $timezone = nu $time = date('Y-m-d H:i:s.' . $fractionalPart, $integerPart); $dateTime = $this->fromString($time); + return $timezone ? $dateTime->setTimezone($timezone) : $dateTime; } - /** - * @param \DateTimeInterface $dateTime - * @return DateTimeInterface - */ public function fromDateTime(\DateTimeInterface $dateTime): DateTimeInterface { - return $this->fromString($dateTime->format('Y-m-d H:i:s.u'), $dateTime->getTimezone()); + return $this->fromString( + $dateTime->format('Y-m-d H:i:s.u'), + $dateTime->getTimezone() + ); } - /** - * @return DateTimeZone - */ private function getDefaultTimezone(): DateTimeZone { return new DateTimeZone(date_default_timezone_get()); diff --git a/src/DateTimeFactoryInterface.php b/src/DateTimeFactoryInterface.php index bb6b37e..a769803 100644 --- a/src/DateTimeFactoryInterface.php +++ b/src/DateTimeFactoryInterface.php @@ -1,4 +1,5 @@ i); self::assertEquals($parts[5], $interval->s); - // Test for extreme case when diff between winter and summer time returns interval with negative hour + // Test for extreme case when diff between winter and daylight saving time returns interval with negative hour $phpDate1 = new \DateTimeImmutable('2016-11-22 11:00:00'); $phpDate2 = new \DateTimeImmutable('2016-08-22 12:00:00'); $phpInterval = $phpDate1->diff($phpDate2); $interval = DateInterval::fromDateInterval($phpInterval); - self::assertEquals('P3MT-1H', (string) $interval); + self::assertEquals('P2M30DT23H', (string) $interval); } /** diff --git a/tests/src/DateTimeTest.php b/tests/src/DateTimeTest.php index af79c04..4a19a91 100644 --- a/tests/src/DateTimeTest.php +++ b/tests/src/DateTimeTest.php @@ -319,6 +319,7 @@ public function testSerialize(): void $serialized = serialize($dateTime); $unserializedDateTime = unserialize($serialized, ['allowed_classes' => [DateTime::class]]); + self::assertInstanceOf(DateTime::class, $unserializedDateTime); self::assertEquals($dateTime, $unserializedDateTime); self::assertEquals($dateTime->getTimezone(), $unserializedDateTime->getTimezone()); } From e5139e251a72a7364345b45db3ec5da93f92c672 Mon Sep 17 00:00:00 2001 From: Pavol Kirschbaum Date: Mon, 20 Dec 2021 16:59:37 +0100 Subject: [PATCH 2/3] test: Diff with negative hour was fixed in php 8.1 --- tests/src/DateIntervalTest.php | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/tests/src/DateIntervalTest.php b/tests/src/DateIntervalTest.php index 52656ad..a78771c 100644 --- a/tests/src/DateIntervalTest.php +++ b/tests/src/DateIntervalTest.php @@ -38,8 +38,16 @@ public function testFromDateInterval(string $intervalSpec, array $parts): void self::assertEquals($parts[3], $interval->h); self::assertEquals($parts[4], $interval->i); self::assertEquals($parts[5], $interval->s); + } - // Test for extreme case when diff between winter and daylight saving time returns interval with negative hour + /** + * Test for extreme case when diff between winter and daylight saving time returns interval with negative hour + */ + public function testFromDateIntervalWithNegativeHour(): void + { + if (PHP_VERSION_ID >= 80100) { + self::markTestSkipped(); + } $phpDate1 = new \DateTimeImmutable('2016-11-22 11:00:00'); $phpDate2 = new \DateTimeImmutable('2016-08-22 12:00:00'); $phpInterval = $phpDate1->diff($phpDate2); From 6eb6559b71134071bee625110ef2b6fb44284aeb Mon Sep 17 00:00:00 2001 From: Pavol Kirschbaum Date: Mon, 20 Dec 2021 16:59:37 +0100 Subject: [PATCH 3/3] test: Improvements --- src/DateTimeFactory.php | 4 ---- tests/src/DateIntervalTest.php | 15 +++++++-------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/src/DateTimeFactory.php b/src/DateTimeFactory.php index c34b658..eede497 100644 --- a/src/DateTimeFactory.php +++ b/src/DateTimeFactory.php @@ -28,10 +28,6 @@ public function fromString(string $time, DateTimeZone $timezone = null): DateTim } catch (\Exception $e) { $message = $e->getMessage(); - if (str_starts_with($message, 'DateTimeImmutable::__construct(): ')) { - $message = substr($message, 34); - } - throw new Exception\InvalidTimeStringException($message, previous: $e); } } diff --git a/tests/src/DateIntervalTest.php b/tests/src/DateIntervalTest.php index a78771c..8623746 100644 --- a/tests/src/DateIntervalTest.php +++ b/tests/src/DateIntervalTest.php @@ -43,18 +43,17 @@ public function testFromDateInterval(string $intervalSpec, array $parts): void /** * Test for extreme case when diff between winter and daylight saving time returns interval with negative hour */ - public function testFromDateIntervalWithNegativeHour(): void + public function testFromDateIntervalBetweenWinterAndDst(): void { - if (PHP_VERSION_ID >= 80100) { - self::markTestSkipped(); - } - $phpDate1 = new \DateTimeImmutable('2016-11-22 11:00:00'); - $phpDate2 = new \DateTimeImmutable('2016-08-22 12:00:00'); - $phpInterval = $phpDate1->diff($phpDate2); + $winterTime = new \DateTimeImmutable('2016-11-22 11:00:00'); + $dstTime = new \DateTimeImmutable('2016-08-22 12:00:00'); + $phpInterval = $winterTime->diff($dstTime); $interval = DateInterval::fromDateInterval($phpInterval); - self::assertEquals('P2M30DT23H', (string) $interval); + $expected = PHP_VERSION_ID >= 80100 ? 'P2M30DT23H' : 'P3MT-1H'; + + self::assertEquals($expected, (string) $interval); } /**