Skip to content

Commit 24ffa6c

Browse files
committed
Support Symfony DatePoint
1 parent b0252b6 commit 24ffa6c

File tree

5 files changed

+213
-1
lines changed

5 files changed

+213
-1
lines changed

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@
4848
"phpstan/phpstan-phpunit": "^1.0",
4949
"phpunit/phpunit": "^10.4",
5050
"squizlabs/php_codesniffer": "^3.5",
51-
"symfony/cache": "^5.4 || ^6.0 || ^7.0"
51+
"symfony/cache": "^5.4 || ^6.0 || ^7.0",
52+
"symfony/clock": "^7.2"
5253
},
5354
"conflict": {
5455
"doctrine/annotations": "<1.12 || >=3.0"

lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use ReflectionNamedType;
3232
use ReflectionProperty;
3333

34+
use Symfony\Component\Clock\DatePoint;
3435
use function array_filter;
3536
use function array_key_exists;
3637
use function array_keys;
@@ -2734,6 +2735,9 @@ private function validateAndCompleteTypedFieldMapping(array $mapping): array
27342735
case DateTimeImmutable::class:
27352736
$mapping['type'] = Type::DATE_IMMUTABLE;
27362737
break;
2738+
case DatePoint::class:
2739+
$mapping['type'] = Type::DATE_POINT;
2740+
break;
27372741
case 'array':
27382742
$mapping['type'] = Type::HASH;
27392743
break;
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\ODM\MongoDB\Types;
6+
7+
use DateTimeInterface;
8+
use Symfony\Component\Clock\DatePoint;
9+
10+
class DatePointType extends DateType
11+
{
12+
/** @return DatePoint */
13+
public static function getDateTime($value): DateTimeInterface
14+
{
15+
return DatePoint::createFromInterface(parent::getDateTime($value));
16+
}
17+
18+
/**
19+
* @param mixed $current
20+
*
21+
* @return DateTimeInterface
22+
*/
23+
public function getNextVersion($current)
24+
{
25+
return new DatePoint();
26+
}
27+
}

lib/Doctrine/ODM/MongoDB/Types/Type.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ abstract class Type
3030
public const STRING = 'string';
3131
public const DATE = 'date';
3232
public const DATE_IMMUTABLE = 'date_immutable';
33+
public const DATE_POINT = 'date_point';
3334
public const KEY = 'key';
3435
public const TIMESTAMP = 'timestamp';
3536
public const BINDATA = 'bin';
@@ -70,6 +71,7 @@ abstract class Type
7071
self::STRING => Types\StringType::class,
7172
self::DATE => Types\DateType::class,
7273
self::DATE_IMMUTABLE => Types\DateImmutableType::class,
74+
self::DATE_POINT => Types\DatePointType::class,
7375
self::KEY => Types\KeyType::class,
7476
self::TIMESTAMP => Types\TimestampType::class,
7577
self::BINDATA => Types\BinDataType::class,
Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Doctrine\ODM\MongoDB\Tests\Types;
6+
7+
use DateTime;
8+
use DateTimeImmutable;
9+
use DateTimeZone;
10+
use Doctrine\ODM\MongoDB\Types\DateImmutableType;
11+
use Doctrine\ODM\MongoDB\Types\Type;
12+
use InvalidArgumentException;
13+
use MongoDB\BSON\UTCDateTime;
14+
use PHPUnit\Framework\Attributes\DataProvider;
15+
use PHPUnit\Framework\TestCase;
16+
use stdClass;
17+
18+
use function assert;
19+
use function date;
20+
use function strtotime;
21+
22+
use const PHP_INT_SIZE;
23+
24+
class DatePointTypeTest extends TestCase
25+
{
26+
public function testGetDateTime(): void
27+
{
28+
$type = Type::getType(Type::DATE_POINT);
29+
assert($type instanceof DateImmutableType);
30+
31+
$timestamp = 100000000.001;
32+
$dateTime = $type->getDateTime($timestamp);
33+
self::assertEquals($timestamp, $dateTime->format('U.u'));
34+
35+
$mongoDate = new UTCDateTime(100000000001);
36+
$dateTime = $type->getDateTime($mongoDate);
37+
self::assertEquals($timestamp, $dateTime->format('U.u'));
38+
}
39+
40+
public function testConvertToDatabaseValue(): void
41+
{
42+
$type = Type::getType(Type::DATE_POINT);
43+
44+
self::assertNull($type->convertToDatabaseValue(null), 'null is not converted');
45+
46+
$mongoDate = new UTCDateTime();
47+
self::assertSame($mongoDate, $type->convertToDatabaseValue($mongoDate), 'MongoDate objects are not converted');
48+
49+
$timestamp = 100000000.123;
50+
$dateTime = DateTimeImmutable::createFromFormat('U.u', (string) $timestamp);
51+
$mongoDate = new UTCDateTime(100000000123);
52+
self::assertEquals($mongoDate, $type->convertToDatabaseValue($dateTime), 'DateTimeImmutable objects are converted to MongoDate objects');
53+
self::assertEquals($mongoDate, $type->convertToDatabaseValue($timestamp), 'Numeric timestamps are converted to MongoDate objects');
54+
self::assertEquals($mongoDate, $type->convertToDatabaseValue('' . $timestamp), 'String dates are converted to MongoDate objects');
55+
self::assertEquals($mongoDate, $type->convertToDatabaseValue($mongoDate), 'MongoDate objects are converted to MongoDate objects');
56+
self::assertEquals(null, $type->convertToDatabaseValue(null), 'null are converted to null');
57+
}
58+
59+
public function testConvertDateTime(): void
60+
{
61+
$type = Type::getType(Type::DATE);
62+
63+
$timestamp = 100000000.123;
64+
$mongoDate = new UTCDateTime(100000000123);
65+
66+
$dateTime = DateTime::createFromFormat('U.u', (string) $timestamp);
67+
self::assertEquals($mongoDate, $type->convertToDatabaseValue($dateTime), 'DateTime objects are converted to MongoDate objects');
68+
}
69+
70+
public function testConvertOldDate(): void
71+
{
72+
$type = Type::getType(Type::DATE_POINT);
73+
74+
$date = new DateTimeImmutable('1900-01-01 00:00:00.123', new DateTimeZone('UTC'));
75+
$timestamp = '-2208988800.123';
76+
self::assertEquals($type->convertToDatabaseValue($timestamp), $type->convertToDatabaseValue($date));
77+
}
78+
79+
/** @param mixed $value */
80+
#[DataProvider('provideInvalidDateValues')]
81+
public function testConvertToDatabaseValueWithInvalidValues($value): void
82+
{
83+
$type = Type::getType(Type::DATE_POINT);
84+
$this->expectException(InvalidArgumentException::class);
85+
$type->convertToDatabaseValue($value);
86+
}
87+
88+
public static function provideInvalidDateValues(): array
89+
{
90+
return [
91+
'array' => [[]],
92+
'string' => ['whatever'],
93+
'bool' => [false],
94+
'object' => [new stdClass()],
95+
'invalid string' => ['foo'],
96+
];
97+
}
98+
99+
/** @param mixed $input */
100+
#[DataProvider('provideDatabaseToPHPValues')]
101+
public function testConvertToPHPValue($input, DateTimeImmutable $output): void
102+
{
103+
$type = Type::getType(Type::DATE_POINT);
104+
$return = $type->convertToPHPValue($input);
105+
106+
self::assertInstanceOf('DateTimeImmutable', $return);
107+
$this->assertTimestampEquals($output, $return);
108+
}
109+
110+
public function testConvertToPHPValueDoesNotConvertNull(): void
111+
{
112+
$type = Type::getType(Type::DATE_POINT);
113+
114+
self::assertNull($type->convertToPHPValue(null));
115+
}
116+
117+
/** @param mixed $input */
118+
#[DataProvider('provideDatabaseToPHPValues')]
119+
public function testClosureToPHP($input, DateTimeImmutable $output): void
120+
{
121+
$type = Type::getType(Type::DATE_POINT);
122+
123+
$return = (static function ($value) use ($type) {
124+
$return = null;
125+
eval($type->closureToPHP());
126+
127+
return $return;
128+
})($input);
129+
130+
// @phpstan-ignore-next-line
131+
self::assertInstanceOf(DateTimeImmutable::class, $return);
132+
$this->assertTimestampEquals($output, $return);
133+
}
134+
135+
public static function provideDatabaseToPHPValues(): array
136+
{
137+
$yesterday = strtotime('yesterday');
138+
$mongoDate = new UTCDateTime($yesterday * 1000);
139+
$dateTime = new DateTimeImmutable('@' . $yesterday);
140+
141+
return [
142+
[$dateTime, $dateTime],
143+
[$mongoDate, $dateTime],
144+
[$yesterday, $dateTime],
145+
[date('c', $yesterday), $dateTime],
146+
[new UTCDateTime(100000000123), DateTimeImmutable::createFromFormat('U.u', '100000000.123')],
147+
];
148+
}
149+
150+
public function test32bit1900Date(): void
151+
{
152+
if (PHP_INT_SIZE !== 4) {
153+
$this->markTestSkipped('Platform is not 32-bit');
154+
}
155+
156+
$type = Type::getType(Type::DATE_POINT);
157+
$this->expectException(InvalidArgumentException::class);
158+
$type->convertToDatabaseValue('1900-01-01');
159+
}
160+
161+
public function test64bit1900Date(): void
162+
{
163+
if (PHP_INT_SIZE !== 8) {
164+
$this->markTestSkipped('Platform is not 64-bit');
165+
}
166+
167+
$type = Type::getType(Type::DATE_POINT);
168+
$return = $type->convertToDatabaseValue('1900-01-01');
169+
170+
self::assertInstanceOf(UTCDateTime::class, $return);
171+
self::assertEquals(new UTCDateTime(strtotime('1900-01-01') * 1000), $return);
172+
}
173+
174+
private function assertTimestampEquals(DateTimeImmutable $expected, DateTimeImmutable $actual): void
175+
{
176+
self::assertEquals($expected->format('U.u'), $actual->format('U.u'));
177+
}
178+
}

0 commit comments

Comments
 (0)