ldc-mesbr-app/tests/Unit/Shared/Helpers/DatesHelperTest.php
2026-10-12 17:08:07 +02:00

281 lines
9.2 KiB
PHP

<?php
declare(strict_types=1);
namespace Tests\Unit\Shared\Helpers;
use Carbon\CarbonImmutable;
use Shared\Helpers\DatesHelper;
use Shared\Helpers\Exceptions\InvalidDateFormatException;
covers(DatesHelper::class, InvalidDateFormatException::class);
describe('dates helper', function (): void {
afterEach(function (): void {
CarbonImmutable::setTestNow();
});
describe('isIn', function (): void {
it('returns true when the current date is within the range', function (): void {
// GIVEN
CarbonImmutable::setTestNow('2026-06-15T12:00:00Z');
$from = CarbonImmutable::parse('2026-06-01T00:00:00Z');
$to = CarbonImmutable::parse('2026-06-30T23:59:59Z');
// WHEN
$result = DatesHelper::isIn($from, $to);
// THEN
expect($result)->toBeTrue();
});
it('returns false when the current date is outside the range', function (): void {
// GIVEN
CarbonImmutable::setTestNow('2026-06-15T12:00:00Z');
$from = CarbonImmutable::parse('2026-07-01T00:00:00Z');
$to = CarbonImmutable::parse('2026-07-31T23:59:59Z');
// WHEN
$result = DatesHelper::isIn($from, $to);
// THEN
expect($result)->toBeFalse();
});
it('returns true when only "from" is provided and current date is after', function (): void {
// GIVEN
CarbonImmutable::setTestNow('2026-06-15T12:00:00Z');
$from = CarbonImmutable::parse('2026-06-01T00:00:00Z');
// WHEN
$result = DatesHelper::isIn($from, null);
// THEN
expect($result)->toBeTrue();
});
it('returns false when only "from" is provided and current date is before', function (): void {
// GIVEN
CarbonImmutable::setTestNow('2026-06-15T12:00:00Z');
$from = CarbonImmutable::parse('2026-07-01T00:00:00Z');
// WHEN
$result = DatesHelper::isIn($from, null);
// THEN
expect($result)->toBeFalse();
});
it('returns true when only "to" is provided and current date is before', function (): void {
// GIVEN
CarbonImmutable::setTestNow('2026-06-15T12:00:00Z');
$to = CarbonImmutable::parse('2026-06-30T23:59:59Z');
// WHEN
$result = DatesHelper::isIn(null, $to);
// THEN
expect($result)->toBeTrue();
});
it('returns false when only "to" is provided and current date is after', function (): void {
// GIVEN
CarbonImmutable::setTestNow('2026-06-15T12:00:00Z');
$to = CarbonImmutable::parse('2026-05-31T23:59:59Z');
// WHEN
$result = DatesHelper::isIn(null, $to);
// THEN
expect($result)->toBeFalse();
});
it('returns false when both boundaries are null', function (): void {
// GIVEN
CarbonImmutable::setTestNow('2026-06-15T12:00:00Z');
// WHEN
$result = DatesHelper::isIn(null, null);
// THEN
expect($result)->toBeFalse();
});
});
describe('toRfc3339 & toNullableRfc3339', function (): void {
it('formats an immutable datetime to UTC RFC3339 format', function (): void {
// GIVEN
$datetime = new \DateTimeImmutable('2026-06-15 14:00:00', new \DateTimeZone('+02:00'));
// WHEN
$result = DatesHelper::toRfc3339($datetime);
// THEN
expect($result)->toBe('2026-06-15T12:00:00+00:00');
});
it('formats a mutable datetime to UTC RFC3339 without mutating the original object', function (): void {
// GIVEN
$datetime = new \DateTime('2026-06-15 14:00:00', new \DateTimeZone('+02:00'));
// WHEN
$result = DatesHelper::toRfc3339($datetime);
// THEN
expect($result)->toBe('2026-06-15T12:00:00+00:00')
->and($datetime->getTimezone()->getName())->toBe('+02:00');
});
it('formats a string date directly to RFC3339', function (): void {
// GIVEN
$dateString = '2026-06-15';
// WHEN
$result = DatesHelper::toRfc3339($dateString);
// THEN
expect($result)->toBe('2026-06-15T00:00:00+00:00');
});
it('returns null when passing null to toNullableRfc3339', function (): void {
// GIVEN
$datetime = null;
// WHEN
$result = DatesHelper::toNullableRfc3339($datetime);
// THEN
expect($result)->toBeNull();
});
it('formats a valid date through toNullableRfc3339', function (): void {
// GIVEN
$dateString = '2026-06-15';
// WHEN
$result = DatesHelper::toNullableRfc3339($dateString);
// THEN
expect($result)->toBe('2026-06-15T00:00:00+00:00');
});
});
describe('parseDateTime', function (): void {
it('parses french date format (Y-m-d) at midnight UTC', function (): void {
// GIVEN
$input = '2026-04-10';
// WHEN
$result = DatesHelper::parseDateTime($input);
// THEN
expect($result->getTimezone()->getName())->toBe('UTC')
->and($result->format('Y-m-d H:i:s'))->toBe('2026-04-10 00:00:00');
});
it('parses inverted french date format (d-m-Y) at midnight UTC', function (): void {
// GIVEN
$input = '10-04-2026';
// WHEN
$result = DatesHelper::parseDateTime($input);
// THEN
expect($result->getTimezone()->getName())->toBe('UTC')
->and($result->format('Y-m-d H:i:s'))->toBe('2026-04-10 00:00:00');
});
it('parses ISO8601 string ending with Z', function (): void {
// GIVEN
$input = '2026-04-10T15:30:00Z';
// WHEN
$result = DatesHelper::parseDateTime($input);
// THEN
expect($result->getTimezone()->getName())->toBe('UTC')
->and($result->format('Y-m-d H:i:s'))->toBe('2026-04-10 15:30:00');
});
it('parses RFC3339 string with positive timezone offset converted to UTC', function (): void {
// GIVEN
$input = '2026-04-10T16:30:00+02:00';
// WHEN
$result = DatesHelper::parseDateTime($input);
// THEN
expect($result->getTimezone()->getName())->toBe('UTC')
->and($result->format('Y-m-d H:i:s'))->toBe('2026-04-10 14:30:00');
});
it('parses RFC3339 string with negative timezone offset converted to UTC', function (): void {
// GIVEN
$input = '2026-04-10T10:30:00-04:00';
// WHEN
$result = DatesHelper::parseDateTime($input);
// THEN
expect($result->getTimezone()->getName())->toBe('UTC')
->and($result->format('Y-m-d H:i:s'))->toBe('2026-04-10 14:30:00');
});
it('parses RFC3339 string with microseconds', function (): void {
// GIVEN
$input = '2026-04-10T14:30:00.123456Z';
// WHEN
$result = DatesHelper::parseDateTime($input);
// THEN
expect($result->getTimezone()->getName())->toBe('UTC')
->and($result->format('Y-m-d H:i:s'))->toBe('2026-04-10 14:30:00');
});
it('falls back to native date parser when given standard english format', function (): void {
// GIVEN
$input = '2026/04/10 14:00:00';
// WHEN
$result = DatesHelper::parseDateTime($input);
// THEN
expect($result->getTimezone()->getName())->toBe('UTC')
->and($result->format('Y-m-d H:i:s'))->toBe('2026-04-10 14:00:00');
});
it('throws an exception when parsing an invalid date string', function (): void {
// GIVEN
$invalidInput = 'not-a-valid-date';
// WHEN / THEN
expect(fn (): \DateTimeInterface => DatesHelper::parseDateTime($invalidInput))
->toThrow(InvalidDateFormatException::class);
});
it('throws an exception when parsing an empty date string', function (): void {
// GIVEN
$emptyInput = ' ';
// WHEN / THEN
expect(fn (): \DateTimeInterface => DatesHelper::parseDateTime($emptyInput))
->toThrow(InvalidDateFormatException::class);
});
});
describe('now', function (): void {
it('returns the current UTC time as CarbonImmutable', function (): void {
// GIVEN
CarbonImmutable::setTestNow('2026-06-15T12:00:00Z');
// WHEN
$now = DatesHelper::now();
// THEN
expect($now)->toBeInstanceOf(CarbonImmutable::class)
->and($now->getTimezone()->getName())->toBe('UTC')
->and($now->format(\DateTimeInterface::ATOM))->toBe('2026-06-15T12:00:00+00:00');
});
});
});