Skip to content

Commit

Permalink
feat: add datetime constructor with timezone handling
Browse files Browse the repository at this point in the history
It is now possible to use the following signature to map to a datetime:
`array{datetime: non-empty-string|int, timezone: \DateTimeZone}`.

```php
(new \CuyZ\Valinor\MapperBuilder())
    ->mapper()
    ->map(\DateTimeInterface::class, [
        'datetime' => '2024-03-28T21:12:27+00:00',
        'timezone' => 'America/New_York',
    ]);
```
  • Loading branch information
romm committed Apr 12, 2024
1 parent b5c460c commit c087e75
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 9 deletions.
22 changes: 19 additions & 3 deletions src/Mapper/Object/DateTimeFormatConstructor.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@
use DateTimeImmutable;
use DateTimeInterface;

use DateTimeZone;

use function is_array;

/**
* Can be given to {@see MapperBuilder::registerConstructor()} to describe which
* date formats should be allowed during mapping.
Expand Down Expand Up @@ -44,15 +48,27 @@ public function __construct(string $format, string ...$formats)

/**
* @param class-string<DateTime|DateTimeImmutable> $className
* @param non-empty-string|int $value
* @param non-empty-string|int|array{datetime: non-empty-string|int, timezone: DateTimeZone} $value
*/
#[DynamicConstructor]
public function __invoke(string $className, string|int $value): DateTimeInterface
public function __invoke(string $className, string|int|array $value): DateTimeInterface
{
if (is_array($value)) {
$datetime = $value['datetime'];
$timezone = $value['timezone'];
} else {
$datetime = $value;
$timezone = null;
}

foreach ($this->formats as $format) {
$date = $className::createFromFormat($format, (string)$value) ?: null;
$date = $className::createFromFormat($format, (string)$datetime) ?: null;

if ($date) {
if ($timezone) {
$date = $date->setTimezone($timezone);
}

return $date;
}
}
Expand Down
27 changes: 21 additions & 6 deletions tests/Integration/Mapping/Object/DateTimeMappingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,17 +10,32 @@

final class DateTimeMappingTest extends IntegrationTestCase
{
public function test_default_datetime_constructor_cannot_be_used(): void
public function test_default_datetime_constructor_can_be_used_with_valid_rfc_3339(): void
{
try {
$this->mapperBuilder()
$result = $this->mapperBuilder()
->mapper()
->map(DateTimeInterface::class, ['datetime' => '2022/08/05', 'timezone' => 'Europe/Paris']);
} catch (MappingError $exception) {
$error = $exception->node()->messages()[0];
->map(DateTimeInterface::class, ['datetime' => '2024-03-27T21:12:27+00:00', 'timezone' => 'Europe/Paris']);
} catch (MappingError $error) {
$this->mappingFail($error);
}

self::assertSame('2024-03-27T22:12:27+01:00', $result->format(DATE_ATOM));
self::assertSame('Europe/Paris', $result->getTimezone()->getName());
}

self::assertSame('1607027306', $error->code());
public function test_default_datetime_constructor_can_be_used_with_valid_timestamp(): void
{
try {
$result = $this->mapperBuilder()
->mapper()
->map(DateTimeInterface::class, ['datetime' => 1711573053, 'timezone' => 'Europe/Paris']);
} catch (MappingError $error) {
$this->mappingFail($error);
}

self::assertSame('2024-03-27T21:57:33+01:00', $result->format(DATE_ATOM));
self::assertSame('Europe/Paris', $result->getTimezone()->getName());
}

public function test_default_date_constructor_with_valid_rfc_3339_format_source_returns_datetime(): void
Expand Down

0 comments on commit c087e75

Please sign in to comment.