Skip to content

Commit

Permalink
Fix DateTime mutation tests
Browse files Browse the repository at this point in the history
  • Loading branch information
veewee committed Aug 30, 2024
1 parent 590b58a commit 59cddb9
Show file tree
Hide file tree
Showing 11 changed files with 444 additions and 140 deletions.
198 changes: 101 additions & 97 deletions composer.lock

Large diffs are not rendered by default.

7 changes: 5 additions & 2 deletions config/infection.json.dist
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@
},
"DecrementInteger": {
"ignore": [
"Psl\\DataStructure\\PriorityQueue::peek"
"Psl\\DataStructure\\PriorityQueue::peek",
"Psl\\DateTime\\TemporalConvenienceMethodsTrait::since"
]
},
"FunctionCallRemoval": {
Expand All @@ -42,7 +43,8 @@
},
"IncrementInteger": {
"ignore": [
"Psl\\DataStructure\\PriorityQueue::peek"
"Psl\\DataStructure\\PriorityQueue::peek",
"Psl\\DateTime\\TemporalConvenienceMethodsTrait::since"
]
},
"LogicalNot": {
Expand All @@ -58,6 +60,7 @@
},
"Throw_": {
"ignore": [
"Psl\\DateTime\\DateTime::__construct",
"Psl\\File\\ReadHandle::__construct",
"Psl\\File\\WriteHandle::__construct",
"Psl\\File\\ReadWriteHandle::__construct",
Expand Down
9 changes: 4 additions & 5 deletions src/Psl/DateTime/DateTimeConvenienceMethodsTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -398,15 +398,15 @@ public function plusMonths(int $months): static
return $this;
}

if (0 > $months) {
if ($months < 1) {
return $this->minusMonths(-$months);
}

$plus_years = Math\div($months, MONTHS_PER_YEAR);
$months_left = $months - ($plus_years * MONTHS_PER_YEAR);
$target_month = $this->getMonth() + $months_left;

if ($target_month > 12) {
if ($target_month > MONTHS_PER_YEAR) {
$plus_years++;
$target_month = $target_month - MONTHS_PER_YEAR;
}
Expand Down Expand Up @@ -438,7 +438,7 @@ public function minusMonths(int $months): static
return $this;
}

if (0 > $months) {
if ($months < 1) {
return $this->plusMonths(-$months);
}

Expand Down Expand Up @@ -616,10 +616,9 @@ public function toString(null|DateStyle $date_style = null, null|TimeStyle $time
$timestamp = $this->getTimestamp();

/**
* @psalm-suppress InvalidOperand
* @psalm-suppress ImpureMethodCall
*/
return Internal\create_intl_date_formatter($date_style, $time_style, null, $timezone ?? $this->getTimezone(), $locale)
->format($timestamp->getSeconds() + ($timestamp->getNanoseconds() / NANOSECONDS_PER_SECOND));
->format($timestamp->getSeconds());
}
}
29 changes: 13 additions & 16 deletions src/Psl/DateTime/Duration.php
Original file line number Diff line number Diff line change
Expand Up @@ -674,27 +674,24 @@ public function toString(int $max_decimals = 3): string
$sec_sign = $this->seconds < 0 || $this->nanoseconds < 0 ? '-' : '';
$sec = Math\abs($this->seconds);

/** @var list<array{string, string}> $values */
$values = [
[((string) $this->hours), 'hour(s)'],
[((string) $this->minutes), 'minute(s)'],
[$sec_sign . ((string) $sec) . $decimal_part, 'second(s)'],
];

// $end is the sizeof($values), use static value for better performance.
$end = 3;
while ($end > 0 && $values[$end - 1][0] === '0') {
--$end;
$containsHours = $this->hours !== 0;
$containsMinutes = $this->minutes !== 0;
$concatenatedSeconds = $sec_sign . ((string) $sec) . $decimal_part;
$containsSeconds = $concatenatedSeconds !== '0';

/** @var list<string> $output */
$output = [];
if ($containsHours) {
$output[] = ((string) $this->hours) . ' hour(s)';
}

$start = 0;
while ($start < $end && $values[$start][0] === '0') {
++$start;
if ($containsMinutes || ($containsHours && $containsSeconds)) {
$output[] = ((string) $this->minutes) . ' minute(s)';
}

$output = [];
for ($i = $start; $i < $end; ++$i) {
$output[] = $values[$i][0] . ' ' . $values[$i][1];
if ($containsSeconds) {
$output[] = $concatenatedSeconds . ' second(s)';
}

return ([] === $output) ? '0 second(s)' : Str\join($output, ', ');
Expand Down
12 changes: 11 additions & 1 deletion tests/unit/Collection/MutableSetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ public function testArrayAccess(): void

static::assertTrue(isset($set['foo']));
static::assertSame('foo', $set['foo']);

unset($set['foo']);
static::assertFalse(isset($set['foo']));

Expand Down Expand Up @@ -191,6 +191,16 @@ public function testOffsetGetThrowsForInvalidOffsetType(): void
$set[false];
}

public function testFromArrayKeysConstructor()
{
$set = MutableSet::fromArrayKeys(['foo' => 1, 'bar' => 1, 'baz' => 1]);

static::assertCount(3, $set);
static::assertTrue($set->contains('foo'));
static::assertTrue($set->contains('bar'));
static::assertTrue($set->contains('baz'));
}

/**
* @template T of array-key
*
Expand Down
10 changes: 10 additions & 0 deletions tests/unit/Collection/SetTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,14 @@ protected function createFromList(array $items): Set
{
return Set::fromArray($items);
}

public function testFromArrayKeysConstructor()
{
$set = Set::fromArrayKeys(['foo' => 1, 'bar' => 1, 'baz' => 1]);

static::assertCount(3, $set);
static::assertTrue($set->contains('foo'));
static::assertTrue($set->contains('bar'));
static::assertTrue($set->contains('baz'));
}
}
151 changes: 132 additions & 19 deletions tests/unit/DateTime/DateTimeTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,19 @@
namespace Psl\Tests\Unit\DateTime;

use PHPUnit\Framework\TestCase;
use Psl\DateTime\DateStyle;
use Psl\DateTime\DateTime;
use Psl\DateTime\Exception\UnexpectedValueException;
use Psl\DateTime\FormatPattern;
use Psl\DateTime\Meridiem;
use Psl\DateTime\Month;
use Psl\DateTime\TimeStyle;
use Psl\DateTime\Timezone;
use Psl\DateTime\Weekday;
use Psl\Json;
use Psl\Locale\Locale;

use function Psl\DateTime\Internal\create_intl_date_formatter;
use function time;

final class DateTimeTest extends TestCase
Expand Down Expand Up @@ -59,18 +64,20 @@ public function testFromParts(): void

static::assertSame(Timezone::UTC, $datetime->getTimezone());
static::assertSame(2024, $datetime->getYear());
static::assertSame(24, $datetime->getYearShort());
static::assertSame(2, $datetime->getMonth());
static::assertSame(4, $datetime->getDay());
static::assertSame(Weekday::Sunday, $datetime->getWeekday());
static::assertSame(14, $datetime->getHours());
static::assertSame(0, $datetime->getMinutes());
static::assertSame(0, $datetime->getSeconds());
static::assertSame(1, $datetime->getNanoseconds());
static::assertSame([2024, 2, 4, 14, 0, 0, 1,], $datetime->getParts());
}

public function testFromPartsWithDefaults(): void
{
$datetime = DateTime::fromParts(Timezone::UTC, 2024, Month::February, 4, );
$datetime = DateTime::fromParts(Timezone::UTC, 2024, Month::February, 4,);

static::assertSame(Timezone::UTC, $datetime->getTimezone());
static::assertSame(2024, $datetime->getYear());
Expand All @@ -83,22 +90,81 @@ public function testFromPartsWithDefaults(): void
static::assertSame(0, $datetime->getNanoseconds());
}

public function testFromPartsWithInvalidComponent(): void
{

/**
* @dataProvider provideInvalidComponentParts
*/
public function testFromPartsWithInvalidComponent(
string $expectedMessage,
int $year,
int $month,
int $day,
int $hours,
int $minutes,
int $seconds,
int $nanoseconds
): void {
$this->expectException(UnexpectedValueException::class);
$this->expectExceptionMessage('Unexpected hours value encountered. Provided "999", but the calendar expects "15". Ensure the hour falls within a 24-hour day.');
$this->expectExceptionMessage($expectedMessage);

DateTime::fromParts(Timezone::UTC, 2024, Month::February, 4, 999, 0, 0, 1);
DateTime::fromParts(Timezone::UTC, $year, $month, $day, $hours, $minutes, $seconds, $nanoseconds);
}

public function fromString(): void
public static function provideInvalidComponentParts(): array
{
$datetime = DateTime::fromParts(Timezone::default(), 2024, Month::February, 4, 14, 0, 0, 0);
return [
['Unexpected year value encountered. Provided "0", but the calendar expects "1". Check the year for accuracy and ensure it\'s within the supported range.', 0, 1, 1, 0, 0, 0, 0],
['Unexpected month value encountered. Provided "0", but the calendar expects "12". Ensure the month is within the 1-12 range and matches the specific year context.', 2024, 0, 1, 0, 0, 0, 0],
['Unexpected day value encountered. Provided "0", but the calendar expects "31". Ensure the day is valid for the given month and year, considering variations like leap years.', 2024, 1, 0, 0, 0, 0, 0],
['Unexpected hours value encountered. Provided "-1", but the calendar expects "23". Ensure the hour falls within a 24-hour day.', 2024, 1, 1, -1, 0, 0, 0],
['Unexpected minutes value encountered. Provided "-1", but the calendar expects "59". Check the minutes value for errors and ensure it\'s within the 0-59 range.', 2024, 1, 1, 0, -1, 0, 0],
['Unexpected seconds value encountered. Provided "59", but the calendar expects "-1". Ensure the seconds are correct and within the 0-59 range.', 2024, 1, 1, 0, 0, -1, 0],
];
}

public function testFromString(): void
{
$timezone = Timezone::EuropeBrussels;
$datetime = DateTime::fromParts($timezone, 2024, Month::February, 4, 14, 0, 0, 0);

$string = $datetime->toString();
$parsed = DateTime::fromString($string);
$parsed = DateTime::fromString($string, timezone: $timezone);

static::assertEquals($datetime->getTimestamp(), $parsed->getTimestamp());
static::assertSame($datetime->getTimezone(), $parsed->getTimezone());
static::assertSame($string, $parsed->toString());
}

public function testToString(): void
{
$datetime = DateTime::fromParts(Timezone::default(), 2024, Month::February, 4, 14, 0, 0, 0);

static::assertSame('4 Feb 2024, 14:00:00', $datetime->toString());
static::assertSame('04/02/2024, 14:00:00', $datetime->toString(date_style: DateStyle::Short));
static::assertSame('4 Feb 2024, 14:00:00 Greenwich Mean Time', $datetime->toString(time_style: TimeStyle::Full));
static::assertSame('4 Feb 2024, 15:00:00', $datetime->toString(timezone: TimeZone::EuropeBrussels));

// Formatting depends on version of intl - so compare with intl version instead of hardcoding a label:
static::assertSame(
create_intl_date_formatter(locale: Locale::DutchBelgium)->format($datetime->getTimestamp()->getSeconds()),
$datetime->toString(locale: Locale::DutchBelgium)
);
}

public function testFormat(): void
{
$datetime = DateTime::fromParts(Timezone::default(), 2024, Month::February, 4, 14, 0, 0, 0);

static::assertSame('4 Feb 2024, 14:00:00', $datetime->format());
static::assertSame('02/04/2024', $datetime->format(pattern: FormatPattern::American));
static::assertSame('02/04/2024', $datetime->format(pattern: FormatPattern::American->value));
static::assertSame('4 Feb 2024, 15:00:00', $datetime->format(timezone: TimeZone::EuropeBrussels));

// Formatting depends on version of intl - so compare with intl version instead of hardcoding a label:
static::assertSame(
create_intl_date_formatter(locale: Locale::DutchBelgium)->format($datetime->getTimestamp()->getSeconds()),
$datetime->toString(locale: Locale::DutchBelgium)
);
}

public function testParse(): void
Expand All @@ -121,7 +187,6 @@ public function testParseWithTimezone(): void

static::assertEquals($datetime->getTimestamp(), $parsed->getTimestamp());
static::assertSame($datetime->getTimezone(), $parsed->getTimezone());

}

public function testWithDate(): void
Expand Down Expand Up @@ -173,18 +238,32 @@ public function testGetEra()

public function testGetCentury()
{
$datetime = DateTime::fromParts(Timezone::default(), 2024, Month::February, 4, 14, 0, 0, 0);
static::assertSame(20, DateTime::fromParts(Timezone::default(), 1999, Month::February, 4, 14)->getCentury());
static::assertSame(21, DateTime::fromParts(Timezone::default(), 2000, Month::February, 4, 14)->getCentury());
}

static::assertSame(21, $datetime->getCentury());
public static function provideTwelveHours()
{
yield [0, 12, Meridiem::AnteMeridiem];
yield [1, 1, Meridiem::AnteMeridiem];
yield [2, 2, Meridiem::AnteMeridiem];
yield [11, 11, Meridiem::AnteMeridiem];
yield [12, 12, Meridiem::PostMeridiem];
yield [13, 1, Meridiem::PostMeridiem];
yield [14, 2, Meridiem::PostMeridiem];
yield [23, 11, Meridiem::PostMeridiem];
}

public function testGetTwelveHours()
/**
* @dataProvider provideTwelveHours
*/
public function testGetTwelveHours(int $hour, $expectedTwelveHour, $expectedMeridiem)
{
$datetime = DateTime::fromParts(Timezone::default(), 2024, Month::February, 4, 14, 0, 0, 0);
$datetime = DateTime::fromParts(Timezone::default(), 2024, Month::February, 4, $hour, 0, 0, 0);
[$hours, $meridiem] = $datetime->getTwelveHours();

static::assertSame(2, $hours);
static::assertSame(Meridiem::PostMeridiem, $meridiem);
static::assertSame($expectedTwelveHour, $hours);
static::assertSame($expectedMeridiem, $meridiem);
}

public function testGetIsoWeek(): void
Expand Down Expand Up @@ -221,6 +300,12 @@ public function testPlusMethods(): void
$new = $datetime->plusMonths(1);
static::assertSame(3, $new->getMonth());

$new = $datetime->plusMonths(0);
static::assertSame($datetime, $new);

$new = $datetime->plusMonths(-1);
static::assertSame(1, $new->getMonth());

$new = $datetime->plusDays(1);
static::assertSame(5, $new->getDay());

Expand Down Expand Up @@ -285,6 +370,12 @@ public function testMinusMethods(): void
$new = $datetime->minusMonths(1);
static::assertSame(1, $new->getMonth());

$new = $datetime->minusMonths(0);
static::assertSame($datetime, $new);

$new = $datetime->minusMonths(-1);
static::assertSame(3, $new->getMonth());

$new = $datetime->minusDays(1);
static::assertSame(3, $new->getDay());

Expand Down Expand Up @@ -412,9 +503,31 @@ public function testWithTime()
$date = DateTime::todayAt(14, 0);
$new = $date->withTime(15, 0);

self::assertSame(15, $new->getHours());
self::assertSame(0, $new->getMinutes());
self::assertSame(0, $new->getSeconds());
self::assertSame(0, $new->getNanoseconds());
static::assertSame(15, $new->getHours());
static::assertSame(0, $new->getMinutes());
static::assertSame(0, $new->getSeconds());
static::assertSame(0, $new->getNanoseconds());
}

public function testTimezoneInfo()
{
$timeZone = Timezone::EuropeBrussels;
$date = DateTime::fromParts($timeZone, 2024, 01, 01);

static::assertSame(!$timeZone->getDaylightSavingTimeOffset($date)->isZero(), $date->isDaylightSavingTime());
static::assertEquals($timeZone->getOffset($date), $date->getTimezoneOffset());
}

public function testConvertTimeZone()
{
$date = DateTime::fromParts(Timezone::EuropeBrussels, 2024, 01, 01, 1);
$converted = $date->convertToTimezone($london = Timezone::EuropeLondon);

static::assertSame($london, $converted->getTimezone());
static::assertSame($date->getTimestamp(), $converted->getTimestamp());
static::assertSame($date->getYear(), $converted->getYear());
static::assertSame($date->getMonth(), $converted->getMonth());
static::assertSame($date->getDay(), $converted->getDay());
static::assertSame(0, $converted->getHours());
}
}
Loading

0 comments on commit 59cddb9

Please sign in to comment.