Skip to content

Commit fff5118

Browse files
authored
Merge pull request #1618 from Kocal/feature/datepoint-support
Add support for Symfony's DatePoint in DateHandler, close #1617
2 parents b02a6c0 + be3590d commit fff5118

File tree

6 files changed

+132
-1
lines changed

6 files changed

+132
-1
lines changed

.github/workflows/ci.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ jobs:
4545
if: ${{ matrix.composer-stability }}
4646
run: composer config minimum-stability ${{ matrix.composer-stability }}
4747

48+
- name: Install symfony/clock for PHP >= 8.1
49+
if: ${{ matrix.php-version >= '8.1' }}
50+
run: composer require --dev symfony/clock:"^6.4 || ^7.0 || ^8.0" --no-update
51+
4852
- name: Install dependencies with Composer
4953
uses: ramsey/composer-install@v3
5054
with:

.github/workflows/static-analysis.yaml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,10 @@ jobs:
3434
tools: "cs2pr"
3535
extensions: pdo_sqlite
3636

37+
- name: Install symfony/clock for PHP >= 8.1
38+
if: ${{ matrix.php-version >= '8.1' }}
39+
run: composer require --dev symfony/clock:"^6.4 || ^7.0 || ^8.0" --no-update
40+
3741
- name: "Install dependencies with Composer"
3842
uses: "ramsey/composer-install@v3"
3943
with:

phpstan/ignore-by-php-version.neon.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
<?php declare(strict_types = 1);
22

33
use PHPUnit\Framework\Attributes\DataProvider;
4+
use Symfony\Component\Clock\DatePoint;
45
use Symfony\Component\Uid\UuidV7;
56

67
$includes = [];
@@ -28,7 +29,9 @@
2829
if(!class_exists(UuidV7::class)) {
2930
$includes[] = __DIR__ . '/no-uuid-7.neon';
3031
}
31-
32+
if (!class_exists(DatePoint::class)) {
33+
$includes[] = __DIR__ . '/no-datepoint.neon';
34+
}
3235

3336
$config = [];
3437
$config['includes'] = $includes;

phpstan/no-datepoint.neon

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
parameters:
2+
ignoreErrors:
3+
- '~Call to static method createFromInterface\(\) on an unknown class Symfony\\Component\\Clock\\DatePoint.~'

src/Handler/DateHandler.php

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
use JMS\Serializer\Visitor\DeserializationVisitorInterface;
1212
use JMS\Serializer\Visitor\SerializationVisitorInterface;
1313
use JMS\Serializer\XmlSerializationVisitor;
14+
use Symfony\Component\Clock\DatePoint;
1415

1516
/**
1617
* @phpstan-import-type TypeArray from Type
@@ -45,6 +46,11 @@ public static function getSubscribingMethods()
4546
$methods = [];
4647
$types = [\DateTime::class, \DateTimeImmutable::class, \DateInterval::class];
4748

49+
// Add Symfony's DatePoint if available (introduced in Symfony 6.4)
50+
if (class_exists(DatePoint::class)) {
51+
$types[] = DatePoint::class;
52+
}
53+
4854
foreach (['json', 'xml'] as $format) {
4955
foreach ($types as $type) {
5056
$methods[] = [
@@ -156,6 +162,20 @@ public function serializeDateInterval(SerializationVisitorInterface $visitor, \D
156162
return $visitor->visitString($iso8601DateIntervalString, $type);
157163
}
158164

165+
/**
166+
* @param TypeArray $type
167+
*
168+
* @return \DOMCdataSection|\DOMText|mixed
169+
*/
170+
public function serializeSymfonyComponentClockDatePoint(
171+
SerializationVisitorInterface $visitor,
172+
\DateTimeImmutable $date,
173+
array $type,
174+
SerializationContext $context
175+
) {
176+
return $this->serializeDateTimeInterface($visitor, $date, $type, $context);
177+
}
178+
159179
/**
160180
* @param mixed $data
161181
*/
@@ -244,6 +264,32 @@ public function deserializeDateIntervalFromJson(DeserializationVisitorInterface
244264
return $this->parseDateInterval($data);
245265
}
246266

267+
/**
268+
* @param mixed $data
269+
* @param TypeArray $type
270+
*/
271+
public function deserializeSymfonyComponentClockDatePointFromXml(DeserializationVisitorInterface $visitor, $data, array $type): ?\DateTimeInterface
272+
{
273+
if ($this->isDataXmlNull($data)) {
274+
return null;
275+
}
276+
277+
return $this->parseDateTimeAsDatePoint($data, $type);
278+
}
279+
280+
/**
281+
* @param mixed $data
282+
* @param TypeArray $type
283+
*/
284+
public function deserializeSymfonyComponentClockDatePointFromJson(DeserializationVisitorInterface $visitor, $data, array $type): ?\DateTimeInterface
285+
{
286+
if (empty($data)) {
287+
return null;
288+
}
289+
290+
return $this->parseDateTimeAsDatePoint($data, $type);
291+
}
292+
247293
/**
248294
* @param mixed $data
249295
* @param TypeArray $type
@@ -279,6 +325,15 @@ private function parseDateTime($data, array $type, bool $immutable = false): \Da
279325
));
280326
}
281327

328+
/**
329+
* @param mixed $data
330+
* @param TypeArray $type
331+
*/
332+
private function parseDateTimeAsDatePoint($data, array $type): \DateTimeInterface
333+
{
334+
return DatePoint::createFromInterface($this->parseDateTime($data, $type));
335+
}
336+
282337
private function parseDateInterval(string $data): \DateInterval
283338
{
284339
$dateInterval = null;

tests/Handler/DateHandlerTest.php

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
use PHPUnit\Framework\Attributes\DataProvider;
1313
use PHPUnit\Framework\Attributes\DoesNotPerformAssertions;
1414
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Clock\DatePoint;
1516

1617
class DateHandlerTest extends TestCase
1718
{
@@ -188,4 +189,65 @@ public function testDefaultFormat()
188189
$this->handler->deserializeDateTimeFromJson($visitor, '2017-06-18T17:32:11Z', $type),
189190
);
190191
}
192+
193+
public function testDatePointSerializationJson()
194+
{
195+
if (!class_exists(DatePoint::class)) {
196+
self::markTestSkipped('Symfony Clock component is not available');
197+
}
198+
199+
$context = $this->getMockBuilder(SerializationContext::class)->getMock();
200+
201+
$visitor = $this->getMockBuilder(SerializationVisitorInterface::class)->getMock();
202+
$visitor->method('visitString')->with('2017-06-18');
203+
204+
$datePoint = new DatePoint('2017-06-18 14:30:59', $this->timezone);
205+
$type = ['name' => DatePoint::class, 'params' => ['Y-m-d']];
206+
$this->handler->serializeSymfonyComponentClockDatePoint($visitor, $datePoint, $type, $context);
207+
}
208+
209+
public function testDatePointDeserializationJson()
210+
{
211+
if (!class_exists(DatePoint::class)) {
212+
self::markTestSkipped('Symfony Clock component is not available');
213+
}
214+
215+
$visitor = new JsonDeserializationVisitor();
216+
217+
$type = ['name' => DatePoint::class, 'params' => ['Y-m-d', '', 'Y-m-d|']];
218+
$result = $this->handler->deserializeSymfonyComponentClockDatePointFromJson($visitor, '2017-06-18', $type);
219+
220+
self::assertInstanceOf(DatePoint::class, $result);
221+
self::assertEquals('2017-06-18', $result->format('Y-m-d'));
222+
}
223+
224+
public function testDatePointDeserializationReturnsNullForEmptyString()
225+
{
226+
if (!class_exists(DatePoint::class)) {
227+
self::markTestSkipped('Symfony Clock component is not available');
228+
}
229+
230+
$visitor = new JsonDeserializationVisitor();
231+
232+
$type = ['name' => DatePoint::class, 'params' => ['Y-m-d']];
233+
self::assertNull($this->handler->deserializeSymfonyComponentClockDatePointFromJson($visitor, '', $type));
234+
}
235+
236+
public function testDatePointWithTimezone()
237+
{
238+
if (!class_exists(DatePoint::class)) {
239+
self::markTestSkipped('Symfony Clock component is not available');
240+
}
241+
242+
$visitor = new JsonDeserializationVisitor();
243+
244+
$timestamp = (string) time();
245+
$timezone = 'Europe/Brussels';
246+
$type = ['name' => DatePoint::class, 'params' => ['U', $timezone]];
247+
248+
$result = $this->handler->deserializeSymfonyComponentClockDatePointFromJson($visitor, $timestamp, $type);
249+
250+
self::assertInstanceOf(DatePoint::class, $result);
251+
self::assertEquals($timezone, $result->getTimezone()->getName());
252+
}
191253
}

0 commit comments

Comments
 (0)