diff --git a/src/ChronosDate.php b/src/ChronosDate.php index b51d911..ab63951 100644 --- a/src/ChronosDate.php +++ b/src/ChronosDate.php @@ -300,7 +300,7 @@ public static function diffFormatter(?DifferenceFormatterInterface $formatter = */ public function add(DateInterval $interval): static { - if ($interval->f > 0 || $interval->s > 0 || $interval->i > 0 || $interval->h > 0) { + if ($interval->f != 0 || $interval->s !== 0 || $interval->i !== 0 || $interval->h !== 0) { throw new InvalidArgumentException('Cannot add intervals with time components'); } $new = clone $this; @@ -319,7 +319,7 @@ public function add(DateInterval $interval): static */ public function sub(DateInterval $interval): static { - if ($interval->f > 0 || $interval->s > 0 || $interval->i > 0 || $interval->h > 0) { + if ($interval->f != 0 || $interval->s !== 0 || $interval->i !== 0 || $interval->h !== 0) { throw new InvalidArgumentException('Cannot subtract intervals with time components'); } $new = clone $this; @@ -338,7 +338,21 @@ public function sub(DateInterval $interval): static */ public function modify(string $modifier): static { - if (preg_match('/hour|minute|second/', $modifier)) { + $parsed = date_parse($modifier); + $relative = $parsed['relative'] ?? []; + $timestampSeconds = 0; + if (preg_match('/(?:^|\s)@(-?\d+)(?:\.\d+)?(?:\s|$)/', $modifier, $matches)) { + $timestampSeconds = (int)$matches[1]; + } + if ( + ($relative['hour'] ?? 0) !== 0 || + ($relative['minute'] ?? 0) !== 0 || + ($relative['second'] ?? 0) !== $timestampSeconds || + preg_match( + '/(?add($interval); } + public function testAddRaiseErrorOnNegativeTime(): void + { + $interval = DateInterval::createFromDateString('-1 hour'); + $date = ChronosDate::create(2001, 1, 1); + $this->expectException(InvalidArgumentException::class); + $date->add($interval); + } + public function testSubFullDay(): void { $interval = DateInterval::createFromDateString('1 day'); @@ -53,6 +61,14 @@ public function testSubIgnoreTime(): void $date->sub($interval); } + public function testSubRaiseErrorOnNegativeTime(): void + { + $interval = DateInterval::createFromDateString('-1 hour'); + $date = ChronosDate::create(2001, 1, 1); + $this->expectException(InvalidArgumentException::class); + $date->sub($interval); + } + public function testAddDay(): void { $this->assertSame(1, ChronosDate::create(1975, 5, 31)->addDays(1)->day); @@ -90,4 +106,76 @@ public function testModifyTimeComponentError(): void $this->expectException(InvalidArgumentException::class); $date->modify('10 seconds'); } + + public function testModifyOrdinalWeekday(): void + { + $date = ChronosDate::create(2026, 9, 1); + $new = $date->modify('second monday of this month'); + $this->assertSame('2026-09-14', $new->toDateString()); + } + + public function testModifyUppercaseTimeComponentError(): void + { + $date = ChronosDate::create(2001, 1, 1); + $this->expectException(InvalidArgumentException::class); + $date->modify('10 SECONDS'); + } + + public function testModifyClockTimeIgnored(): void + { + $date = ChronosDate::create(2001, 1, 1); + $new = $date->modify('12:00'); + $this->assertSame('2001-01-01', $new->toDateString()); + } + + public function testModifyTimeComponentWrappingDayError(): void + { + $date = ChronosDate::create(2001, 1, 1); + $this->expectException(InvalidArgumentException::class); + $date->modify('+24 hours'); + } + + public function testModifyFractionalTimeComponentError(): void + { + $date = ChronosDate::create(2001, 1, 1); + $this->expectException(InvalidArgumentException::class); + $date->modify('1 millisecond'); + } + + public function testModifyAbbreviatedFractionalTimeComponentError(): void + { + $date = ChronosDate::create(2001, 1, 1); + $this->expectException(InvalidArgumentException::class); + $date->modify('-1 msec'); + } + + public function testModifyTimestamp(): void + { + $date = ChronosDate::create(2001, 1, 1); + $new = $date->modify('@86400'); + $this->assertSame('1970-01-02', $new->toDateString()); + } + + public function testModifyTimestampWithTimezone(): void + { + $date = ChronosDate::create(2001, 1, 1); + $new = $date->modify('@1 UTC'); + $this->assertSame('1970-01-01', $new->toDateString()); + } + + public function testModifyTimestampWithDateComponent(): void + { + $date = ChronosDate::create(2001, 1, 1); + $new = $date->modify('@86400 +1 day'); + $this->assertSame('1970-01-03', $new->toDateString()); + } + + public function testModifyAcrossSkippedMidnight(): void + { + $this->withTimezone('America/Havana', function (): void { + $date = new ChronosDate('2023-03-11'); + $new = $date->modify('+1 day'); + $this->assertSame('2023-03-12', $new->toDateString()); + }); + } }