Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 9 additions & 9 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
"require-dev": {
"brainbits/phpcs-standard": "^8.0.0",
"brainbits/phpstan-rules": "^4.0.0",
"ergebnis/composer-normalize": "^2.47",
"ergebnis/phpstan-rules": "^2.11.0",
"phpstan/phpstan": "^2.1.22",
"phpstan/phpstan-phpunit": "^2.0.7",
"phpstan/phpstan-strict-rules": "^2.0.6",
"phpunit/phpunit": "^12.3.6",
"rector/rector": "^2.1.4",
"squizlabs/php_codesniffer": "^3.13.2",
"thecodingmachine/phpstan-safe-rule": "^1.4.1"
"ergebnis/composer-normalize": "^2.50",
"ergebnis/phpstan-rules": "^2.13.1",
"phpstan/phpstan": "^2.1.40",
"phpstan/phpstan-phpunit": "^2.0.16",
"phpstan/phpstan-strict-rules": "^2.0.10",
"phpunit/phpunit": "^12.5.14",
"rector/rector": "^2.3.8",
"squizlabs/php_codesniffer": "^3.13.5",
"thecodingmachine/phpstan-safe-rule": "^1.4.3"
},
"autoload": {
"psr-4": {
Expand Down
5 changes: 5 additions & 0 deletions src/NotConvertable.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ public static function toDateTime(mixed $value): self
return self::withMessage(sprintf('%s is not convertable to date time object', get_debug_type($value)));
}

public static function toDateTimeZone(mixed $value): self
{
return self::withMessage(sprintf('%s is not convertable to date time zone', get_debug_type($value)));
}

private static function withMessage(string $message): self
{
$me = new self($message);
Expand Down
33 changes: 28 additions & 5 deletions src/TypeGuard.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,19 +118,20 @@ public function asString(mixed $value): string|null
return (string) $value;
}

/** @return DateTimeImmutable */
public function asDateTimeImmutable(mixed $value): DateTimeImmutable|null
public function asDateTimeImmutable(mixed $value, DateTimeZone|string|null $timeZone = null): DateTimeImmutable|null
{
if ($value === null) {
return null;
}

$timeZone = $this->asDateTimeZone($timeZone) ?? $this->timeZone();

if ($value instanceof DateTimeImmutable) {
if ($value->getTimezone()->getName() === $this->timeZone()->getName()) {
if ($value->getTimezone()->getName() === $timeZone->getName()) {
return $value;
}

return $value->setTimezone($this->timeZone());
return $value->setTimezone($timeZone);
}

if ($value instanceof Stringable) {
Expand All @@ -141,7 +142,29 @@ public function asDateTimeImmutable(mixed $value): DateTimeImmutable|null
throw NotConvertable::toDateTime($value);
}

return new DateTimeImmutable(asString($value), $this->timeZone());
return new DateTimeImmutable(asString($value), $timeZone);
}

/** @return ($value is null ? null : DateTimeZone) */
public function asDateTimeZone(mixed $value): DateTimeZone|null
{
if ($value === null) {
return null;
}

if ($value instanceof DateTimeZone) {
return $value;
}

if ($value instanceof Stringable) {
$value = (string) $value;
}

if (!is_string($value)) {
throw NotConvertable::toDateTimeZone($value);
}

return new DateTimeZone($value);
}

/** @return ($value is null ? null : string) */
Expand Down
16 changes: 14 additions & 2 deletions src/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Plook\TypeGuard;

use DateTimeImmutable;
use DateTimeZone;

use function function_exists;

Expand Down Expand Up @@ -49,9 +50,20 @@ function asString(mixed $value): string|null
if (!function_exists('\Plook\TypeGuard\asDateTimeImmutable')) { // @codeCoverageIgnore

/** @return ($value is null ? null : DateTimeImmutable) */
function asDateTimeImmutable(mixed $value): DateTimeImmutable|null
function asDateTimeImmutable(
mixed $value,
DateTimeZone|string|null $timeZone = null,
): DateTimeImmutable|null {
return TypeGuard::instance()->asDateTimeImmutable($value, $timeZone);
}
}

if (!function_exists('\Plook\TypeGuard\asDateTimeZone')) { // @codeCoverageIgnore

/** @return ($value is null ? null : DateTimeZone) */
function asDateTimeZone(mixed $value): DateTimeZone|null
{
return TypeGuard::instance()->asDateTimeImmutable($value);
return TypeGuard::instance()->asDateTimeZone($value);
}
}

Expand Down
57 changes: 57 additions & 0 deletions tests/AsDateTimeImmutableTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,63 @@ public function testDoesNotTouchNull(): void
self::assertNull(asDateTimeImmutable(null));
}

public function testTimezoneParameterOverridesDefaultWithDateTimeZone(): void
{
$result = asDateTimeImmutable('2010-09-08 07:06:05', new DateTimeZone('Australia/Adelaide'));

self::assertInstanceOf(DateTimeImmutable::class, $result);
self::assertSame('2010-09-08T07:06:05+09:30', $result->format('c'));
}

public function testTimezoneParameterOverridesDefaultWithString(): void
{
$result = asDateTimeImmutable('2010-09-08 07:06:05', 'Australia/Adelaide');

self::assertInstanceOf(DateTimeImmutable::class, $result);
self::assertSame('2010-09-08T07:06:05+09:30', $result->format('c'));
}

public function testTimezoneParameterOverridesChangedDefault(): void
{
TypeGuard::instance()->timeZone('Europe/Berlin');

$result = asDateTimeImmutable('2010-09-08 07:06:05', 'Australia/Adelaide');

self::assertInstanceOf(DateTimeImmutable::class, $result);
self::assertSame('2010-09-08T07:06:05+09:30', $result->format('c'));
}

public function testTimezoneParameterConvertsSameDateTimeImmutable(): void
{
$input = new DateTimeImmutable('2010-09-08T07:06:05', new DateTimeZone('Australia/Adelaide'));

$result = asDateTimeImmutable($input, 'Australia/Adelaide');

self::assertInstanceOf(DateTimeImmutable::class, $result);
self::assertSame('Australia/Adelaide', $result->getTimezone()->getName());
self::assertSame('2010-09-08T07:06:05+09:30', $result->format('c'));
}

public function testTimezoneParameterConvertsDifferentDateTimeImmutable(): void
{
$input = new DateTimeImmutable('2010-09-08T07:06:05+00:00');

$result = asDateTimeImmutable($input, new DateTimeZone('Australia/Adelaide'));

self::assertInstanceOf(DateTimeImmutable::class, $result);
self::assertSame('2010-09-08T16:36:05+09:30', $result->format('c'));
}

public function testNullTimezoneParameterUsesDefault(): void
{
TypeGuard::instance()->timeZone('Australia/Adelaide');

$result = asDateTimeImmutable('2010-09-08 07:06:05');

self::assertInstanceOf(DateTimeImmutable::class, $result);
self::assertSame('2010-09-08T07:06:05+09:30', $result->format('c'));
}

public function testOnlyScalarsAreConvertable(): void
{
$this->expectException(NotConvertable::class);
Expand Down
67 changes: 67 additions & 0 deletions tests/AsDateTimeZoneTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php

declare(strict_types=1);

namespace Plook\Tests\TypeGuard;

use DateTimeZone;
use PHPUnit\Framework\Attributes\CoversClass;
use PHPUnit\Framework\Attributes\CoversFunction;
use PHPUnit\Framework\TestCase;
use Plook\Tests\TypeGuard\Helper\StringableString;
use Plook\TypeGuard\NotConvertable;
use Plook\TypeGuard\TypeGuard;

use function basename;
use function Plook\TypeGuard\asDateTimeZone;
use function sprintf;

#[CoversClass(TypeGuard::class)]
#[CoversClass(NotConvertable::class)]
#[CoversFunction('Plook\TypeGuard\asDateTimeZone')]
final class AsDateTimeZoneTest extends TestCase
{
public function testConvertsStrings(): void
{
$dateTimeZone = asDateTimeZone('Europe/Berlin');

self::assertInstanceOf(DateTimeZone::class, $dateTimeZone);
self::assertSame('Europe/Berlin', $dateTimeZone->getName());
}

public function testConvertsStringables(): void
{
$dateTimeZone = asDateTimeZone(new StringableString('Australia/Adelaide'));

self::assertInstanceOf(DateTimeZone::class, $dateTimeZone);
self::assertSame('Australia/Adelaide', $dateTimeZone->getName());
}

public function testReturnsSameDateTimeZone(): void
{
$dateTimeZone = new DateTimeZone('Europe/Berlin');

$result = asDateTimeZone($dateTimeZone);

self::assertSame($dateTimeZone, $result);
}

public function testDoesNotTouchNull(): void
{
self::assertNull(asDateTimeZone(null));
}

public function testOnlyStringsAreConvertable(): void
{
$this->expectException(NotConvertable::class);
$this->expectExceptionMessageMatches(
sprintf(
'/Closure is not convertable to date time zone in %s:%s/',
basename(__FILE__),
__LINE__ + 4,
),
);

asDateTimeZone(static fn (): null => null);
}
}
Loading