From 555dd7240e2acfc1561b5cbb42202b5b307b4768 Mon Sep 17 00:00:00 2001 From: Gustavo Freze Date: Sat, 24 Oct 2026 12:40:27 -0300 Subject: [PATCH 1/2] build: Update the dependencies to their latest versions. --- composer.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/composer.json b/composer.json index e6b7afb..fefefb9 100644 --- a/composer.json +++ b/composer.json @@ -35,10 +35,10 @@ "tiny-blocks/value-object": "^5.0" }, "require-dev": { - "ergebnis/composer-normalize": "^2.52", - "infection/infection": "^0.34", - "phpstan/phpstan": "^2.2", - "phpunit/phpunit": "^13.2", + "ergebnis/composer-normalize": "^2.54", + "infection/infection": "^0.35", + "phpstan/phpstan": "^2.3", + "phpunit/phpunit": "^13.4", "slevomat/coding-standard": "^8.31", "squizlabs/php_codesniffer": "^4.0" }, @@ -79,7 +79,7 @@ "test-file": "@php ./vendor/bin/phpunit --configuration phpunit.xml --no-coverage --filter", "tests": [ "@php -d memory_limit=2G ./vendor/bin/phpunit --configuration phpunit.xml tests", - "@php ./vendor/bin/infection --threads=max --logger-html=reports/coverage/mutation-report.html --coverage=reports/coverage" + "@php ./vendor/bin/infection --threads=max --logger-html=reports/coverage/mutation-report.html" ] } } From ae10815e442785ab2c1fedbe77d8a62e92ad7074 Mon Sep 17 00:00:00 2001 From: Gustavo Freze Date: Sat, 24 Oct 2026 12:57:11 -0300 Subject: [PATCH 2/2] refactor: Order members and parameters as the conformance hooks require. --- src/DayOfWeek.php | 2 +- .../Decoders/OffsetDateTimeDecoder.php | 2 +- tests/Models/Event.php | 2 +- tests/Unit/DurationTest.php | 110 ++++++++-------- tests/Unit/InstantSerializationTest.php | 4 +- tests/Unit/InstantTest.php | 66 +++++----- tests/Unit/LocalDateTest.php | 120 +++++++++--------- tests/Unit/PeriodTest.php | 68 +++++----- tests/Unit/TimezoneTest.php | 44 +++---- 9 files changed, 209 insertions(+), 209 deletions(-) diff --git a/src/DayOfWeek.php b/src/DayOfWeek.php index 898e626..8415b34 100644 --- a/src/DayOfWeek.php +++ b/src/DayOfWeek.php @@ -62,7 +62,7 @@ public function isWeekend(): bool * - Monday->distanceTo(Monday) returns 0 * * @param DayOfWeek $other The target day of the week. - * @return int The number of days forward from this day to the other (0–6). + * @return int The number of days forward from this day to the other (0 to 6). */ public function distanceTo(DayOfWeek $other): int { diff --git a/src/Internal/Decoders/OffsetDateTimeDecoder.php b/src/Internal/Decoders/OffsetDateTimeDecoder.php index 9554e0a..954bd92 100644 --- a/src/Internal/Decoders/OffsetDateTimeDecoder.php +++ b/src/Internal/Decoders/OffsetDateTimeDecoder.php @@ -10,8 +10,8 @@ final readonly class OffsetDateTimeDecoder implements Decoder { private const string FORMAT = 'Y-m-d\TH:i:sP'; - private const string FORMAT_MICRO = 'Y-m-d\TH:i:s.uP'; private const string PATTERN = '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:Z|[+\-]\d{2}:\d{2})$/'; + private const string FORMAT_MICRO = 'Y-m-d\TH:i:s.uP'; private const string PATTERN_MICRO = '/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+(?:Z|[+\-]\d{2}:\d{2})$/'; public function decode(string $value): ?DateTimeImmutable diff --git a/tests/Models/Event.php b/tests/Models/Event.php index 005575e..00ce70a 100644 --- a/tests/Models/Event.php +++ b/tests/Models/Event.php @@ -8,7 +8,7 @@ final readonly class Event { - public function __construct(public Instant $occurredAt, public string $name) + public function __construct(public string $name, public Instant $occurredAt) { } } diff --git a/tests/Unit/DurationTest.php b/tests/Unit/DurationTest.php index da1b6d5..ac9f638 100644 --- a/tests/Unit/DurationTest.php +++ b/tests/Unit/DurationTest.php @@ -19,6 +19,17 @@ public function testFromMinutesWhenZeroThenIsZero(): void self::assertTrue($duration->isZero()); } + public function testEqualsWhenSameAmountThenIsTrue(): void + { + /** @Given the same amount of time reached by two units */ + $one = Duration::fromMinutes(minutes: 2); + $other = Duration::fromSeconds(seconds: 120); + + /** @Then they are equal by value and share the hash code */ + self::assertTrue($one->equals(other: $other)); + self::assertSame($one->hashCode(), $other->hashCode()); + } + public function testZeroThenHoldsNoSecondsAndIsZero(): void { /** @When creating a zero Duration */ @@ -55,6 +66,17 @@ public function testToDaysWhenHasRemainderThenTruncates(): void self::assertSame(1, $result); } + public function testEqualsWhenDifferentAmountThenIsFalse(): void + { + /** @Given two durations one second apart */ + $one = Duration::fromSeconds(seconds: 120); + $other = Duration::fromSeconds(seconds: 121); + + /** @Then they are not equal and the hash codes differ */ + self::assertFalse($one->equals(other: $other)); + self::assertNotSame($one->hashCode(), $other->hashCode()); + } + public function testFromDaysWhenZeroThenHoldsZeroSeconds(): void { /** @When creating a Duration from zero days */ @@ -418,39 +440,6 @@ public function testIsGreaterThanWhenDurationsAreEqualThenReturnsFalse(): void self::assertFalse($result); } - public function testDivideWhenAppointmentSplitIntoSlotsThenSlotCountIsExact(): void - { - /** @Given a Duration of 90 minutes */ - $appointmentDuration = Duration::fromMinutes(minutes: 90); - - /** @And a slot size of 30 minutes */ - $slotSize = Duration::fromMinutes(minutes: 30); - - /** @When dividing the appointment by the slot size */ - $slotCount = $appointmentDuration->divide(other: $slotSize); - - /** @Then the slot count should be 3 */ - self::assertSame(3, $slotCount); - } - - public function testFromMinutesWhenReconstructedFromSlotCountThenMatchesOriginal(): void - { - /** @Given a Duration of 90 minutes */ - $appointmentDuration = Duration::fromMinutes(minutes: 90); - - /** @And a slot size of 30 minutes */ - $slotSize = Duration::fromMinutes(minutes: 30); - - /** @And the slot count obtained by dividing the appointment by the slot size */ - $slotCount = $appointmentDuration->divide(other: $slotSize); - - /** @When reconstructing a Duration from the slot count and slot size */ - $reconstructed = Duration::fromMinutes(minutes: ($slotCount * $slotSize->toMinutes())); - - /** @Then it should match the original appointment duration */ - self::assertSame($appointmentDuration->toSeconds(), $reconstructed->toSeconds()); - } - public function testPlusAndMinusWhenAppliedWithSameAmountThenAreInverse(): void { /** @Given a Duration of 45 minutes */ @@ -481,6 +470,21 @@ public function testMinusWhenResultWouldBeNegativeThenThrowsInvalidSeconds(): vo $tenMinutes->minus(other: $thirtyMinutes); } + public function testDivideWhenAppointmentSplitIntoSlotsThenSlotCountIsExact(): void + { + /** @Given a Duration of 90 minutes */ + $appointmentDuration = Duration::fromMinutes(minutes: 90); + + /** @And a slot size of 30 minutes */ + $slotSize = Duration::fromMinutes(minutes: 30); + + /** @When dividing the appointment by the slot size */ + $slotCount = $appointmentDuration->divide(other: $slotSize); + + /** @Then the slot count should be 3 */ + self::assertSame(3, $slotCount); + } + public function testFromSecondsWhenPositiveValueThenHoldsThatNumberOfSeconds(): void { /** @When creating a Duration from 1800 seconds */ @@ -493,6 +497,24 @@ public function testFromSecondsWhenPositiveValueThenHoldsThatNumberOfSeconds(): self::assertFalse($duration->isZero()); } + public function testFromMinutesWhenReconstructedFromSlotCountThenMatchesOriginal(): void + { + /** @Given a Duration of 90 minutes */ + $appointmentDuration = Duration::fromMinutes(minutes: 90); + + /** @And a slot size of 30 minutes */ + $slotSize = Duration::fromMinutes(minutes: 30); + + /** @And the slot count obtained by dividing the appointment by the slot size */ + $slotCount = $appointmentDuration->divide(other: $slotSize); + + /** @When reconstructing a Duration from the slot count and slot size */ + $reconstructed = Duration::fromMinutes(minutes: ($slotCount * $slotSize->toMinutes())); + + /** @Then it should match the original appointment duration */ + self::assertSame($appointmentDuration->toSeconds(), $reconstructed->toSeconds()); + } + public function testDivideWhenDivisorIsZeroDurationFromSecondsThenThrowsInvalidSeconds(): void { /** @Given a Duration of 30 minutes */ @@ -527,26 +549,4 @@ public function testFactoriesWhenSameDurationExpressedDifferentlyThenProduceSame self::assertSame($fromMinutes->toSeconds(), $fromHours->toSeconds()); self::assertSame($fromHours->toSeconds(), $fromDays->toSeconds()); } - - public function testEqualsWhenSameAmountThenIsTrue(): void - { - /** @Given the same amount of time reached by two units */ - $one = Duration::fromMinutes(minutes: 2); - $other = Duration::fromSeconds(seconds: 120); - - /** @Then they are equal by value and share the hash code */ - self::assertTrue($one->equals(other: $other)); - self::assertSame($one->hashCode(), $other->hashCode()); - } - - public function testEqualsWhenDifferentAmountThenIsFalse(): void - { - /** @Given two durations one second apart */ - $one = Duration::fromSeconds(seconds: 120); - $other = Duration::fromSeconds(seconds: 121); - - /** @Then they are not equal and the hash codes differ */ - self::assertFalse($one->equals(other: $other)); - self::assertNotSame($one->hashCode(), $other->hashCode()); - } } diff --git a/tests/Unit/InstantSerializationTest.php b/tests/Unit/InstantSerializationTest.php index fc59f84..2bdaed3 100644 --- a/tests/Unit/InstantSerializationTest.php +++ b/tests/Unit/InstantSerializationTest.php @@ -18,13 +18,13 @@ public function testToArrayWhenInstantFieldThenEmitsIso8601Seconds(): void $mapper = Mapper::create(); /** @And an event carrying an instant and a name */ - $event = new Event(occurredAt: Instant::fromString(value: '2026-02-17T10:30:00+00:00'), name: 'launch'); + $event = new Event(name: 'launch', occurredAt: Instant::fromString(value: '2026-02-17T10:30:00+00:00')); /** @When the event is serialized to an array */ $array = $mapper->toArray(source: $event); /** @Then the instant is written as a second-precision ISO 8601 string */ - self::assertSame(['occurredAt' => '2026-02-17T10:30:00+00:00', 'name' => 'launch'], $array); + self::assertSame(['name' => 'launch', 'occurredAt' => '2026-02-17T10:30:00+00:00'], $array); } public function testToObjectWhenInstantFromOffsetStringThenNormalizesToUtc(): void diff --git a/tests/Unit/InstantTest.php b/tests/Unit/InstantTest.php index 348c191..2d1a1ce 100644 --- a/tests/Unit/InstantTest.php +++ b/tests/Unit/InstantTest.php @@ -24,6 +24,17 @@ public function testNowThenTimezoneIsUtc(): void self::assertSame('UTC', $instant->toDateTimeImmutable()->getTimezone()->getName()); } + public function testEqualsWhenSameMomentThenIsTrue(): void + { + /** @Given two Instants built separately from the same moment */ + $one = Instant::fromString(value: '2026-02-17T08:27:21.106011+00:00'); + $other = Instant::fromString(value: '2026-02-17T08:27:21.106011+00:00'); + + /** @Then they are equal by value and share the hash code */ + self::assertTrue($one->equals(other: $other)); + self::assertSame($one->hashCode(), $other->hashCode()); + } + public function testIsAfterWhenLaterThenReturnsTrue(): void { /** @Given a later Instant */ @@ -131,6 +142,17 @@ public function testNowThenProducesOrderedUtcInstances(): void ); } + public function testEqualsWhenDifferentMomentThenIsFalse(): void + { + /** @Given two Instants one microsecond apart */ + $one = Instant::fromString(value: '2026-02-17T08:27:21.106011+00:00'); + $other = Instant::fromString(value: '2026-02-17T08:27:21.106012+00:00'); + + /** @Then they are not equal and the hash codes differ */ + self::assertFalse($one->equals(other: $other)); + self::assertNotSame($one->hashCode(), $other->hashCode()); + } + public function testNowThenIso8601HasNoFractionalSeconds(): void { /** @Given an Instant created from now */ @@ -436,6 +458,17 @@ public function testPlusWhenCrossesDayBoundaryThenReturnsNextDay(): void self::assertSame('2026-02-18T00:30:00+00:00', $shifted->toIso8601()); } + public function testEqualsWhenSameMomentInAnotherOffsetThenIsTrue(): void + { + /** @Given two Instants for the same moment written in different offsets */ + $one = Instant::fromString(value: '2026-02-17T08:27:21+00:00'); + $other = Instant::fromString(value: '2026-02-17T05:27:21-03:00'); + + /** @Then they are equal by value, because the state is normalized to UTC */ + self::assertTrue($one->equals(other: $other)); + self::assertSame($one->hashCode(), $other->hashCode()); + } + public function testFromStringWhenDatabaseFormatThenTimezoneIsUtc(): void { /** @Given a database date-time string */ @@ -1306,37 +1339,4 @@ public static function validDatabaseStringsDataProvider(): array ] ]; } - - public function testEqualsWhenSameMomentThenIsTrue(): void - { - /** @Given two Instants built separately from the same moment */ - $one = Instant::fromString(value: '2026-02-17T08:27:21.106011+00:00'); - $other = Instant::fromString(value: '2026-02-17T08:27:21.106011+00:00'); - - /** @Then they are equal by value and share the hash code */ - self::assertTrue($one->equals(other: $other)); - self::assertSame($one->hashCode(), $other->hashCode()); - } - - public function testEqualsWhenSameMomentInAnotherOffsetThenIsTrue(): void - { - /** @Given two Instants for the same moment written in different offsets */ - $one = Instant::fromString(value: '2026-02-17T08:27:21+00:00'); - $other = Instant::fromString(value: '2026-02-17T05:27:21-03:00'); - - /** @Then they are equal by value, because the state is normalized to UTC */ - self::assertTrue($one->equals(other: $other)); - self::assertSame($one->hashCode(), $other->hashCode()); - } - - public function testEqualsWhenDifferentMomentThenIsFalse(): void - { - /** @Given two Instants one microsecond apart */ - $one = Instant::fromString(value: '2026-02-17T08:27:21.106011+00:00'); - $other = Instant::fromString(value: '2026-02-17T08:27:21.106012+00:00'); - - /** @Then they are not equal and the hash codes differ */ - self::assertFalse($one->equals(other: $other)); - self::assertNotSame($one->hashCode(), $other->hashCode()); - } } diff --git a/tests/Unit/LocalDateTest.php b/tests/Unit/LocalDateTest.php index 8a4aff3..a3a3ac0 100644 --- a/tests/Unit/LocalDateTest.php +++ b/tests/Unit/LocalDateTest.php @@ -107,6 +107,17 @@ public function testIsBeforeWhenEqualThenFalse(): void self::assertFalse($isBefore); } + public function testEqualsWhenSameDateThenIsTrue(): void + { + /** @Given two LocalDates built separately from the same date */ + $one = LocalDate::fromString(value: '2026-02-17'); + $other = LocalDate::of(year: 2026, month: 2, day: 17); + + /** @Then they are equal by value and share the hash code */ + self::assertTrue($one->equals(other: $other)); + self::assertSame($one->hashCode(), $other->hashCode()); + } + public function testIsAfterOrEqualWhenAfterThenTrue(): void { /** @Given a later date */ @@ -207,6 +218,17 @@ public function testOfWhenDayZeroThenInvalidLocalDate(): void LocalDate::of(year: 2026, month: 1, day: 0); } + public function testEqualsWhenDifferentDateThenIsFalse(): void + { + /** @Given two LocalDates one day apart */ + $one = LocalDate::fromString(value: '2026-02-17'); + $other = LocalDate::fromString(value: '2026-02-18'); + + /** @Then they are not equal and the hash codes differ */ + self::assertFalse($one->equals(other: $other)); + self::assertNotSame($one->hashCode(), $other->hashCode()); + } + public function testOfWhenYearZeroThenInvalidLocalDate(): void { /** @Then an exception indicating invalid components should be thrown */ @@ -980,6 +1002,19 @@ public function testPlusMonthsWhenTargetIsCommonCenturyThenClampsToTwentyEight() self::assertSame('1900-02-28', $shifted->toIso8601()); } + public function testComparisonWhenSameDateBuiltApartThenNeitherPrecedesTheOther(): void + { + /** @Given the same date reached by parsing and by shifting */ + $one = LocalDate::fromString(value: '2026-02-17'); + $other = LocalDate::fromString(value: '2026-02-16')->plusDays(days: 1); + + /** @Then the ordering depends on the date alone, never on when each was built */ + self::assertFalse($one->isAfter(other: $other)); + self::assertFalse($one->isBefore(other: $other)); + self::assertTrue($one->isAfterOrEqual(other: $other)); + self::assertTrue($one->isBeforeOrEqual(other: $other)); + } + public function testPlusMonthsWhenTargetIsFourHundredYearLeapThenClampsToLeapDay(): void { /** @Given the last day of January in a year divisible by four hundred */ @@ -992,18 +1027,6 @@ public function testPlusMonthsWhenTargetIsFourHundredYearLeapThenClampsToLeapDay self::assertSame('2000-02-29', $shifted->toIso8601()); } - public function testInstantToLocalDateWhenJustPastMidnightUtcInWestZoneThenPreviousDate(): void - { - /** @Given an Instant at 00:30 UTC on 2026-02-18 */ - $instant = Instant::fromString(value: '2026-02-18T00:30:00+00:00'); - - /** @When projecting into America/New_York (UTC-5 in February) */ - $localDate = $instant->toLocalDate(zone: Timezone::from(identifier: 'America/New_York')); - - /** @Then the local date is the previous day relative to UTC */ - self::assertSame('2026-02-17', $localDate->toIso8601()); - } - #[DataProvider('civilDateTimeDataProvider')] public function testAtTimeWhenCivilValuesGivenThenAnchorsThemToTheTimelineOfTheZone( string $date, @@ -1041,19 +1064,6 @@ public function testAtTimeWhenCivilTimeFallsInTheSpringForwardGapThenItResolvesF self::assertSame('2026-03-08T07:30:00+00:00', $insideTheGap->toIso8601()); } - public function testAtTimeWhenCivilTimeFallsInTheFallBackOverlapThenItResolvesToTheEarlierOccurrence(): void - { - /** @Given the New York day when 01:30 happens twice, first on -04:00 and then on -05:00 */ - $newYork = Timezone::from(identifier: 'America/New_York'); - - /** @When the ambiguous civil time is anchored in that zone */ - $ambiguous = LocalDate::of(year: 2026, month: 11, day: 1) - ->atTime(time: TimeOfDay::from(hour: 1, minute: 30), zone: $newYork); - - /** @Then it lands on the earlier of the two, the one still on the pre-transition offset */ - self::assertSame('2026-11-01T05:30:00+00:00', $ambiguous->toIso8601()); - } - public function testAtTimeWhenTheInstantIsReadBackInTheSameZoneThenTheCivilDateSurvives(): void { /** @Given a civil date anchored at a time whose UTC day differs from the local one */ @@ -1068,6 +1078,31 @@ public function testAtTimeWhenTheInstantIsReadBackInTheSameZoneThenTheCivilDateS self::assertSame($date->toIso8601(), $roundTripped->toIso8601()); } + public function testInstantToLocalDateWhenJustPastMidnightUtcInWestZoneThenPreviousDate(): void + { + /** @Given an Instant at 00:30 UTC on 2026-02-18 */ + $instant = Instant::fromString(value: '2026-02-18T00:30:00+00:00'); + + /** @When projecting into America/New_York (UTC-5 in February) */ + $localDate = $instant->toLocalDate(zone: Timezone::from(identifier: 'America/New_York')); + + /** @Then the local date is the previous day relative to UTC */ + self::assertSame('2026-02-17', $localDate->toIso8601()); + } + + public function testAtTimeWhenCivilTimeFallsInTheFallBackOverlapThenItResolvesToTheEarlierOccurrence(): void + { + /** @Given the New York day when 01:30 happens twice, first on -04:00 and then on -05:00 */ + $newYork = Timezone::from(identifier: 'America/New_York'); + + /** @When the ambiguous civil time is anchored in that zone */ + $ambiguous = LocalDate::of(year: 2026, month: 11, day: 1) + ->atTime(time: TimeOfDay::from(hour: 1, minute: 30), zone: $newYork); + + /** @Then it lands on the earlier of the two, the one still on the pre-transition offset */ + self::assertSame('2026-11-01T05:30:00+00:00', $ambiguous->toIso8601()); + } + public static function civilDateTimeDataProvider(): array { return [ @@ -1125,39 +1160,4 @@ public static function invalidStringsDataProvider(): array 'Date with leading whitespace' => ['value' => ' 2026-05-23'] ]; } - - public function testEqualsWhenSameDateThenIsTrue(): void - { - /** @Given two LocalDates built separately from the same date */ - $one = LocalDate::fromString(value: '2026-02-17'); - $other = LocalDate::of(year: 2026, month: 2, day: 17); - - /** @Then they are equal by value and share the hash code */ - self::assertTrue($one->equals(other: $other)); - self::assertSame($one->hashCode(), $other->hashCode()); - } - - public function testEqualsWhenDifferentDateThenIsFalse(): void - { - /** @Given two LocalDates one day apart */ - $one = LocalDate::fromString(value: '2026-02-17'); - $other = LocalDate::fromString(value: '2026-02-18'); - - /** @Then they are not equal and the hash codes differ */ - self::assertFalse($one->equals(other: $other)); - self::assertNotSame($one->hashCode(), $other->hashCode()); - } - - public function testComparisonWhenSameDateBuiltApartThenNeitherPrecedesTheOther(): void - { - /** @Given the same date reached by parsing and by shifting */ - $one = LocalDate::fromString(value: '2026-02-17'); - $other = LocalDate::fromString(value: '2026-02-16')->plusDays(days: 1); - - /** @Then the ordering depends on the date alone, never on when each was built */ - self::assertFalse($one->isAfter(other: $other)); - self::assertFalse($one->isBefore(other: $other)); - self::assertTrue($one->isAfterOrEqual(other: $other)); - self::assertTrue($one->isBeforeOrEqual(other: $other)); - } } diff --git a/tests/Unit/PeriodTest.php b/tests/Unit/PeriodTest.php index cfdbe5a..bc59720 100644 --- a/tests/Unit/PeriodTest.php +++ b/tests/Unit/PeriodTest.php @@ -12,6 +12,40 @@ final class PeriodTest extends TestCase { + public function testEqualsWhenSameBoundsThenIsTrue(): void + { + /** @Given two Periods over the same bounds, one of them written in another offset */ + $one = Period::from( + from: Instant::fromString(value: '2026-02-17T08:00:00+00:00'), + to: Instant::fromString(value: '2026-02-17T09:00:00+00:00') + ); + $other = Period::from( + from: Instant::fromString(value: '2026-02-17T05:00:00-03:00'), + to: Instant::fromString(value: '2026-02-17T09:00:00+00:00') + ); + + /** @Then they are equal by value, which only holds because Instant compares by value */ + self::assertTrue($one->equals(other: $other)); + self::assertSame($one->hashCode(), $other->hashCode()); + } + + public function testEqualsWhenDifferentBoundsThenIsFalse(): void + { + /** @Given two Periods that end at different moments */ + $one = Period::from( + from: Instant::fromString(value: '2026-02-17T08:00:00+00:00'), + to: Instant::fromString(value: '2026-02-17T09:00:00+00:00') + ); + $other = Period::from( + from: Instant::fromString(value: '2026-02-17T08:00:00+00:00'), + to: Instant::fromString(value: '2026-02-17T10:00:00+00:00') + ); + + /** @Then they are not equal and the hash codes differ */ + self::assertFalse($one->equals(other: $other)); + self::assertNotSame($one->hashCode(), $other->hashCode()); + } + public function testFromWhenValidRangeThenExposesBoundaries(): void { /** @Given an Instant at the start of the range */ @@ -419,38 +453,4 @@ public function testDurationWhenPeriodCreatedFromStartingAtThenMatchesInputDurat /** @Then the duration should match the input */ self::assertSame($inputDuration->toSeconds(), $duration->toSeconds()); } - - public function testEqualsWhenSameBoundsThenIsTrue(): void - { - /** @Given two Periods over the same bounds, one of them written in another offset */ - $one = Period::from( - from: Instant::fromString(value: '2026-02-17T08:00:00+00:00'), - to: Instant::fromString(value: '2026-02-17T09:00:00+00:00') - ); - $other = Period::from( - from: Instant::fromString(value: '2026-02-17T05:00:00-03:00'), - to: Instant::fromString(value: '2026-02-17T09:00:00+00:00') - ); - - /** @Then they are equal by value, which only holds because Instant compares by value */ - self::assertTrue($one->equals(other: $other)); - self::assertSame($one->hashCode(), $other->hashCode()); - } - - public function testEqualsWhenDifferentBoundsThenIsFalse(): void - { - /** @Given two Periods that end at different moments */ - $one = Period::from( - from: Instant::fromString(value: '2026-02-17T08:00:00+00:00'), - to: Instant::fromString(value: '2026-02-17T09:00:00+00:00') - ); - $other = Period::from( - from: Instant::fromString(value: '2026-02-17T08:00:00+00:00'), - to: Instant::fromString(value: '2026-02-17T10:00:00+00:00') - ); - - /** @Then they are not equal and the hash codes differ */ - self::assertFalse($one->equals(other: $other)); - self::assertNotSame($one->hashCode(), $other->hashCode()); - } } diff --git a/tests/Unit/TimezoneTest.php b/tests/Unit/TimezoneTest.php index 71a17c6..91a3ea6 100644 --- a/tests/Unit/TimezoneTest.php +++ b/tests/Unit/TimezoneTest.php @@ -32,6 +32,17 @@ public function testUtcWhenToStringThenReturnsUtc(): void self::assertSame('UTC', $result); } + public function testEqualsWhenSameIdentifierThenIsTrue(): void + { + /** @Given two Timezones built separately from the same identifier */ + $one = Timezone::from(identifier: 'America/Sao_Paulo'); + $other = Timezone::from(identifier: 'America/Sao_Paulo'); + + /** @Then they are equal by value and share the hash code */ + self::assertTrue($one->equals(other: $other)); + self::assertSame($one->hashCode(), $other->hashCode()); + } + public function testUtcWhenToDateTimeZoneThenNameIsUtc(): void { /** @Given a UTC Timezone */ @@ -58,6 +69,17 @@ public function testFromWhenValidIdentifierThenValueMatches(string $identifier): self::assertSame($identifier, $timezone->toString()); } + public function testEqualsWhenDifferentIdentifierThenIsFalse(): void + { + /** @Given two Timezones with different identifiers */ + $one = Timezone::utc(); + $other = Timezone::from(identifier: 'America/Sao_Paulo'); + + /** @Then they are not equal and the hash codes differ */ + self::assertFalse($one->equals(other: $other)); + self::assertNotSame($one->hashCode(), $other->hashCode()); + } + #[DataProvider('invalidIdentifiersDataProvider')] public function testFromWhenInvalidIdentifierThenInvalidTimezone(string $identifier): void { @@ -109,26 +131,4 @@ public static function invalidIdentifiersDataProvider(): array 'Numeric offset' => ['identifier' => '+00:00'] ]; } - - public function testEqualsWhenSameIdentifierThenIsTrue(): void - { - /** @Given two Timezones built separately from the same identifier */ - $one = Timezone::from(identifier: 'America/Sao_Paulo'); - $other = Timezone::from(identifier: 'America/Sao_Paulo'); - - /** @Then they are equal by value and share the hash code */ - self::assertTrue($one->equals(other: $other)); - self::assertSame($one->hashCode(), $other->hashCode()); - } - - public function testEqualsWhenDifferentIdentifierThenIsFalse(): void - { - /** @Given two Timezones with different identifiers */ - $one = Timezone::utc(); - $other = Timezone::from(identifier: 'America/Sao_Paulo'); - - /** @Then they are not equal and the hash codes differ */ - self::assertFalse($one->equals(other: $other)); - self::assertNotSame($one->hashCode(), $other->hashCode()); - } }