Commit c8dc57ee authored by plach's avatar plach

Issue #2926508 by Wim Leers, mpdonadio, joelstein, tacituseu, jhedstrom,...

Issue #2926508 by Wim Leers, mpdonadio, joelstein, tacituseu, jhedstrom, effulgentsia, tedbow, mradcliffe, borisson_, dawehner, larowlan: Add DateTimeNormalizer+TimestampNormalizer, deprecate TimestampItemNormalizer: @DataType-level normalizers are reusable by JSON:API
parent c1f7ffae
......@@ -26,7 +26,7 @@ class Timestamp extends IntegerData implements DateTimeInterface {
* {@inheritdoc}
*/
public function getDateTime() {
if ($this->value) {
if (isset($this->value)) {
return DrupalDateTime::createFromTimestamp($this->value);
}
}
......
......@@ -136,7 +136,7 @@ protected function assertNormalizationEdgeCases($method, Url $url, array $reques
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
$response = $this->request($method, $url, $request_options);
$message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The datetime value '{$value}' is invalid for the format 'Y-m-d'\n";
$message = "The specified date \"$value\" is not in an accepted format: \"Y-m-d\" (date-only).";
$this->assertResourceErrorResponse(422, $message, $response);
// DX: 422 when value is not a valid date.
......@@ -146,7 +146,7 @@ protected function assertNormalizationEdgeCases($method, Url $url, array $reques
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
$response = $this->request($method, $url, $request_options);
$message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The datetime value '{$value}' did not parse properly for the format 'Y-m-d'\n{$fieldName}.0.value: This value should be of the correct primitive type.\n";
$message = "The specified date \"$value\" is not in an accepted format: \"Y-m-d\" (date-only).";
$this->assertResourceErrorResponse(422, $message, $response);
}
}
......
......@@ -90,7 +90,7 @@ protected function getExpectedNormalizedEntity() {
return parent::getExpectedNormalizedEntity() + [
static::$fieldName => [
[
'value' => $this->entity->get(static::$fieldName)->value,
'value' => '2017-03-02T07:02:00+11:00',
],
],
];
......@@ -103,6 +103,24 @@ protected function getNormalizedPostEntity() {
return parent::getNormalizedPostEntity() + [
static::$fieldName => [
[
'value' => static::$dateString . '+00:00',
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPatchEntity() {
return parent::getNormalizedPostEntity() + [
static::$fieldName => [
[
// Omitting the timezone is allowed, this should result in the site's
// timezone being used automatically. This does not make sense, but
// it's how it functioned in the past, so we explicitly test this to
// guarantee backward compatibility. ::getNormalizedPostEntity() tests
// the recommended case, this tests backward compatibility.
'value' => static::$dateString,
],
],
......@@ -136,7 +154,7 @@ protected function assertNormalizationEdgeCases($method, Url $url, array $reques
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
$response = $this->request($method, $url, $request_options);
$message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The datetime value '{$value}' is invalid for the format 'Y-m-d\\TH:i:s'\n";
$message = "The specified date \"$value\" is not in an accepted format: \"Y-m-d\\TH:i:sP\" (RFC 3339), \"Y-m-d\\TH:i:sO\" (ISO 8601), \"Y-m-d\\TH:i:s\" (backward compatibility — deprecated).";
$this->assertResourceErrorResponse(422, $message, $response);
// DX: 422 when date format is incorrect.
......@@ -146,9 +164,29 @@ protected function assertNormalizationEdgeCases($method, Url $url, array $reques
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
$response = $this->request($method, $url, $request_options);
$message = "Unprocessable Entity: validation failed.\n{$fieldName}.0: The datetime value '{$value}' did not parse properly for the format 'Y-m-d\\TH:i:s'\n{$fieldName}.0.value: This value should be of the correct primitive type.\n";
$message = "The specified date \"$value\" is not in an accepted format: \"Y-m-d\\TH:i:sP\" (RFC 3339), \"Y-m-d\\TH:i:sO\" (ISO 8601), \"Y-m-d\\TH:i:s\" (backward compatibility — deprecated).";
$this->assertResourceErrorResponse(422, $message, $response);
// DX: 422 when date value is invalid.
$normalization = $this->getNormalizedPostEntity();
$value = '2017-13-55T20:02:00+00:00';
$normalization[static::$fieldName][0]['value'] = $value;
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
$response = $this->request($method, $url, $request_options);
$message = "The specified date \"$value\" is not in an accepted format: \"Y-m-d\\TH:i:sP\" (RFC 3339), \"Y-m-d\\TH:i:sO\" (ISO 8601), \"Y-m-d\\TH:i:s\" (backward compatibility — deprecated).";
$this->assertResourceErrorResponse(422, $message, $response);
}
}
/**
* {@inheritdoc}
*
* @group legacy
* @expectedDeprecation The provided datetime string format (Y-m-d\TH:i:s) is deprecated and will be removed before Drupal 9.0.0. Use the RFC3339 format instead (Y-m-d\TH:i:sP).
*/
public function testPatch() {
return parent::testPatch();
}
}
<?php
namespace Drupal\Tests\datetime_range\Functional\EntityResource\EntityTest;
use Drupal\Core\Url;
use Drupal\datetime_range\Plugin\Field\FieldType\DateRangeItem;
use Drupal\entity_test\Entity\EntityTest;
use Drupal\field\Entity\FieldConfig;
use Drupal\field\Entity\FieldStorageConfig;
use Drupal\Tests\entity_test\Functional\Rest\EntityTestResourceTestBase;
use Drupal\Tests\rest\Functional\AnonResourceTestTrait;
use GuzzleHttp\RequestOptions;
/**
* Tests the 'daterange' field's normalization.
*
* @group datetime_range
*/
class EntityTestDateRangeTest extends EntityTestResourceTestBase {
use AnonResourceTestTrait;
/**
* The ISO date string to use throughout the test.
*
* @var string
*/
protected static $dateString = '2017-03-01T20:02:00';
/**
* Datetime Range test field name.
*
* @var string
*/
protected static $fieldName = 'field_daterange';
/**
* {@inheritdoc}
*/
public static $modules = ['datetime_range', 'entity_test'];
/**
* {@inheritdoc}
*/
public function setUp() {
parent::setUp();
// Add datetime_range field.
FieldStorageConfig::create([
'field_name' => static::$fieldName,
'type' => 'daterange',
'entity_type' => static::$entityTypeId,
'settings' => ['datetime_type' => DateRangeItem::DATETIME_TYPE_ALLDAY],
])->save();
FieldConfig::create([
'field_name' => static::$fieldName,
'entity_type' => static::$entityTypeId,
'bundle' => $this->entity->bundle(),
])->save();
// Reload entity so that it has the new field.
$this->entity = $this->entityStorage->load($this->entity->id());
$this->entity->set(static::$fieldName, [
'value' => static::$dateString,
'end_value' => static::$dateString,
]);
$this->entity->save();
}
/**
* {@inheritdoc}
*/
protected function createEntity() {
$entity_test = EntityTest::create([
'name' => 'Llama',
'type' => static::$entityTypeId,
]);
$entity_test->setOwnerId(0);
$entity_test->save();
return $entity_test;
}
/**
* {@inheritdoc}
*/
protected function getExpectedNormalizedEntity() {
return parent::getExpectedNormalizedEntity() + [
static::$fieldName => [
[
'value' => '2017-03-02T07:02:00+11:00',
'end_value' => '2017-03-02T07:02:00+11:00',
],
],
];
}
/**
* {@inheritdoc}
*/
protected function getNormalizedPostEntity() {
return parent::getNormalizedPostEntity() + [
static::$fieldName => [
[
'value' => '2017-03-01T20:02:00+00:00',
'end_value' => '2017-03-01T20:02:00+00:00',
],
],
];
}
/**
* {@inheritdoc}
*/
protected function assertNormalizationEdgeCases($method, Url $url, array $request_options) {
parent::assertNormalizationEdgeCases($method, $url, $request_options);
if ($this->entity->getEntityType()->hasKey('bundle')) {
$fieldName = static::$fieldName;
// DX: 422 when 'value' data type is incorrect.
$normalization = $this->getNormalizedPostEntity();
$normalization[static::$fieldName][0]['value'] = [
'2017', '03', '01', '21', '53', '00',
];
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
$response = $this->request($method, $url, $request_options);
$message = "Unprocessable Entity: validation failed.\n{$fieldName}.0.value: This value should be of the correct primitive type.\n";
$this->assertResourceErrorResponse(422, $message, $response);
// DX: 422 when 'end_value' is not specified.
$normalization = $this->getNormalizedPostEntity();
unset($normalization[static::$fieldName][0]['end_value']);
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
$response = $this->request($method, $url, $request_options);
$message = "Unprocessable Entity: validation failed.\n{$fieldName}.0.end_value: This value should not be null.\n";
$this->assertResourceErrorResponse(422, $message, $response);
// DX: 422 when 'end_value' data type is incorrect.
$normalization = $this->getNormalizedPostEntity();
$normalization[static::$fieldName][0]['end_value'] = [
'2017', '03', '01', '21', '53', '00',
];
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
$response = $this->request($method, $url, $request_options);
$message = "Unprocessable Entity: validation failed.\n{$fieldName}.0.end_value: This value should be of the correct primitive type.\n";
$this->assertResourceErrorResponse(422, $message, $response);
// DX: 422 when end date value is invalid.
$normalization = $this->getNormalizedPostEntity();
$value = '2017-13-55T20:02:00+00:00';
$normalization[static::$fieldName][0]['end_value'] = $value;
$request_options[RequestOptions::BODY] = $this->serializer->encode($normalization, static::$format);
$response = $this->request($method, $url, $request_options);
$message = "The specified date \"$value\" is not in an accepted format: \"Y-m-d\\TH:i:sP\" (RFC 3339), \"Y-m-d\\TH:i:sO\" (ISO 8601), \"Y-m-d\\TH:i:s\" (backward compatibility — deprecated).";
$this->assertResourceErrorResponse(422, $message, $response);
// @todo Expand in https://www.drupal.org/project/drupal/issues/2847041.
}
}
}
......@@ -4,15 +4,18 @@
use Drupal\Core\Field\FieldItemInterface;
use Drupal\Core\Field\Plugin\Field\FieldType\TimestampItem;
use Drupal\serialization\Normalizer\TimeStampItemNormalizerTrait;
use Drupal\Core\TypedData\Plugin\DataType\Timestamp;
/**
* Converts values for TimestampItem to and from common formats for hal.
*
* Overrides FieldItemNormalizer to
* - during normalization, add the 'format' key to assist consumers
* - during denormalization, use
* \Drupal\serialization\Normalizer\TimestampNormalizer
*/
class TimestampItemNormalizer extends FieldItemNormalizer {
use TimeStampItemNormalizerTrait;
/**
* {@inheritdoc}
*/
......@@ -22,8 +25,21 @@ class TimestampItemNormalizer extends FieldItemNormalizer {
* {@inheritdoc}
*/
protected function normalizedFieldValues(FieldItemInterface $field_item, $format, array $context) {
$normalized = parent::normalizedFieldValues($field_item, $format, $context);
return $this->processNormalizedValues($normalized);
return parent::normalizedFieldValues($field_item, $format, $context) + [
// 'format' is not a property on Timestamp objects. This is present to
// assist consumers of this data.
'format' => \DateTime::RFC3339,
];
}
/**
* {@inheritdoc}
*/
protected function constructValue($data, $context) {
if (!empty($data['format'])) {
$context['datetime_allowed_formats'] = [$data['format']];
}
return ['value' => $this->serializer->denormalize($data['value'], Timestamp::class, NULL, $context)];
}
}
......@@ -31,6 +31,8 @@ protected function formatExpectedTimestampItemValues($timestamp) {
// \Drupal\serialization\Normalizer\TimestampItemNormalizer will produce.
$date = new \DateTime();
$date->setTimestamp($timestamp);
// Per \Drupal\Core\TypedData\Plugin\DataType\Timestamp::getDateTime(), they
// default to string representations in the UTC timezone.
$date->setTimezone(new \DateTimeZone('UTC'));
// Format is also added to the expected return values.
......
......@@ -58,6 +58,18 @@ services:
# Priority must be higher than serializer.normalizer.field_item and lower
# than hal normalizers.
- { name: normalizer, priority: 8, bc: bc_timestamp_normalizer_unix, bc_config_name: 'serialization.settings' }
serializer.normalizer.timestamp:
class: Drupal\serialization\Normalizer\TimestampNormalizer
arguments: ['@config.factory']
tags:
# Priority must be higher than serializer.normalizer.primitive_data.
- { name: normalizer, priority: 20, bc: bc_timestamp_normalizer_unix, bc_config_name: 'serialization.settings' }
serializer.normalizer.datetimeiso8601:
class: \Drupal\serialization\Normalizer\DateTimeIso8601Normalizer
arguments: ['@config.factory']
tags:
# Priority must be higher than serializer.normalizer.primitive_data.
- { name: normalizer, priority: 20 }
serializer.normalizer.password_field_item:
class: Drupal\serialization\Normalizer\NullNormalizer
arguments: ['Drupal\Core\Field\Plugin\Field\FieldType\PasswordItem']
......
<?php
namespace Drupal\serialization\Normalizer;
use Drupal\Core\TypedData\Plugin\DataType\DateTimeIso8601;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItem;
use Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
/**
* Converts values for the DateTimeIso8601 data type to RFC3339.
*
* @internal
*/
class DateTimeIso8601Normalizer extends DateTimeNormalizer {
/**
* {@inheritdoc}
*/
protected $allowedFormats = [
'RFC 3339' => \DateTime::RFC3339,
'ISO 8601' => \DateTime::ISO8601,
// @todo Remove this in https://www.drupal.org/project/drupal/issues/2958416.
// RFC3339 only covers combined date and time representations. For date-only
// representations, we need to use ISO 8601. There isn't a constant on the
// \DateTime class that we can use, so we have to hardcode the format.
// @see https://en.wikipedia.org/wiki/ISO_8601#Calendar_dates
// @see \Drupal\datetime\Plugin\Field\FieldType\DateTimeItemInterface::DATE_STORAGE_FORMAT
'date-only' => 'Y-m-d',
];
/**
* {@inheritdoc}
*/
protected $supportedInterfaceOrClass = DateTimeIso8601::class;
/**
* {@inheritdoc}
*/
public function normalize($datetime, $format = NULL, array $context = []) {
assert($datetime instanceof DateTimeIso8601);
$field_item = $datetime->getParent();
// @todo Remove this in https://www.drupal.org/project/drupal/issues/2958416.
if ($field_item instanceof DateTimeItem && $field_item->getFieldDefinition()->getFieldStorageDefinition()->getSetting('datetime_type') === DateTimeItem::DATETIME_TYPE_DATE) {
$drupal_date_time = $datetime->getDateTime();
if ($drupal_date_time === NULL) {
return $drupal_date_time;
}
return $drupal_date_time->format($this->allowedFormats['date-only']);
}
return parent::normalize($datetime, $format, $context);
}
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = NULL, array $context = []) {
// @todo Move the date-only handling out of here in https://www.drupal.org/project/drupal/issues/2958416.
$field_definition = isset($context['target_instance'])
? $context['target_instance']->getFieldDefinition()
: (isset($context['field_definition']) ? $context['field_definition'] : NULL);
if ($field_definition === NULL) {
throw new InvalidArgumentException('$context[\'target_instance\'] or $context[\'field_definition\'] must be set to denormalize with the DateTimeIso8601Normalizer');
}
$datetime_type = $field_definition->getSetting('datetime_type');
$is_date_only = $datetime_type === DateTimeItem::DATETIME_TYPE_DATE;
if ($is_date_only) {
$context['datetime_allowed_formats'] = array_intersect_key($this->allowedFormats, ['date-only' => TRUE]);
$datetime = parent::denormalize($data, $class, $format, $context);
if (!$datetime instanceof \DateTime) {
return $datetime;
}
return $datetime->format(DateTimeItemInterface::DATE_STORAGE_FORMAT);
}
$context['datetime_allowed_formats'] = array_diff_key($this->allowedFormats, ['date-only' => TRUE]);
try {
$datetime = parent::denormalize($data, $class, $format, $context);
}
catch (\UnexpectedValueException $e) {
// If denormalization didn't work using any of the actively supported
// formats, try again with the BC format too. Explicitly label it as
// being deprecated and trigger a deprecation error.
$using_deprecated_format = TRUE;
$context['datetime_allowed_formats']['backward compatibility — deprecated'] = DateTimeItemInterface::DATETIME_STORAGE_FORMAT;
$datetime = parent::denormalize($data, $class, $format, $context);
}
if (!$datetime instanceof \DateTime) {
return $datetime;
}
if (isset($using_deprecated_format)) {
@trigger_error('The provided datetime string format (Y-m-d\\TH:i:s) is deprecated and will be removed before Drupal 9.0.0. Use the RFC3339 format instead (Y-m-d\\TH:i:sP).', E_USER_DEPRECATED);
}
$datetime->setTimezone(new \DateTimeZone(DateTimeItemInterface::STORAGE_TIMEZONE));
return $datetime->format(DateTimeItemInterface::DATETIME_STORAGE_FORMAT);
}
}
<?php
namespace Drupal\serialization\Normalizer;
use Drupal\Core\Config\ConfigFactoryInterface;
use Drupal\Core\TypedData\Type\DateTimeInterface;
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
/**
* Converts values for datetime objects to RFC3339 and from common formats.
*
* @internal
*/
class DateTimeNormalizer extends NormalizerBase implements DenormalizerInterface {
/**
* Allowed datetime formats for the denormalizer.
*
* The list is chosen to be unambiguous and language neutral, but also common
* for data interchange.
*
* @var string[]
*
* @see http://php.net/manual/en/datetime.createfromformat.php
*/
protected $allowedFormats = [
'RFC 3339' => \DateTime::RFC3339,
'ISO 8601' => \DateTime::ISO8601,
];
/**
* {@inheritdoc}
*/
protected $supportedInterfaceOrClass = DateTimeInterface::class;
/**
* The system's date configuration.
*
* @var \Drupal\Core\Config\ImmutableConfig
*/
protected $systemDateConfig;
/**
* Constructs a new DateTimeNormalizer instance.
*
* @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
* A config factory for retrieving required config objects.
*/
public function __construct(ConfigFactoryInterface $config_factory) {
$this->systemDateConfig = $config_factory->get('system.date');
}
/**
* {@inheritdoc}
*/
public function normalize($datetime, $format = NULL, array $context = []) {
assert($datetime instanceof DateTimeInterface);
$drupal_date_time = $datetime->getDateTime();
if ($drupal_date_time === NULL) {
return $drupal_date_time;
}
return $drupal_date_time
// Set an explicit timezone. Otherwise, timestamps may end up being
// normalized using the user's preferred timezone. Which would result in
// many variations and complex caching.
// @see \Drupal\Core\Datetime\DrupalDateTime::prepareTimezone()
// @see drupal_get_user_timezone()
->setTimezone($this->getNormalizationTimezone())
->format(\DateTime::RFC3339);
}
/**
* Gets the timezone to be used during normalization.
*
* @see ::normalize
*
* @returns \DateTimeZone
* The timezone to use.
*/
protected function getNormalizationTimezone() {
$default_site_timezone = $this->systemDateConfig->get('timezone.default');
return new \DateTimeZone($default_site_timezone);
}
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = NULL, array $context = []) {
// This only knows how to denormalize datetime strings and timestamps. If
// something else is received, let validation constraints handle this.
if (!is_string($data) && !is_numeric($data)) {
return $data;
}
// Loop through the allowed formats and create a \DateTime from the
// input data if it matches the defined pattern. Since the formats are
// unambiguous (i.e., they reference an absolute time with a defined time
// zone), only one will ever match.
$allowed_formats = isset($context['datetime_allowed_formats'])
? $context['datetime_allowed_formats']
: $this->allowedFormats;
foreach ($allowed_formats as $format) {
$date = \DateTime::createFromFormat($format, $data);
$errors = \DateTime::getLastErrors();
if ($date !== FALSE && empty($errors['errors']) && empty($errors['warnings'])) {
return $date;
}
}
$format_strings = [];
foreach ($allowed_formats as $label => $format) {
$format_strings[] = "\"$format\" ($label)";
}
$formats = implode(', ', $format_strings);
throw new UnexpectedValueException(sprintf('The specified date "%s" is not in an accepted format: %s.', $data, $formats));
}
}
......@@ -4,8 +4,12 @@
use Symfony\Component\Serializer\Exception\UnexpectedValueException;
@trigger_error(__NAMESPACE__ . '\TimeStampItemNormalizerTrait is deprecated in Drupal 8.7.0 and will be removed in Drupal 9.0.0. Use \Drupal\serialization\Normalizer\TimestampNormalizer instead.', E_USER_DEPRECATED);
/**
* A trait for TimestampItem normalization functionality.
*
* @deprecated in 8.7.0, use \Drupal\serialization\Normalizer\TimestampNormalizer instead.
*/
trait TimeStampItemNormalizerTrait {
......
......@@ -3,15 +3,19 @@
namespace Drupal\serialization\Normalizer;
use Drupal\Core\Field\Plugin\Field\FieldType\TimestampItem;
use Symfony\Component\Serializer\Exception\InvalidArgumentException;
use Drupal\Core\TypedData\Plugin\DataType\Timestamp;
/**
* Converts values for TimestampItem to and from common formats.
*
* Overrides FieldItemNormalizer to use \Drupal\serialization\Normalizer\TimestampNormalizer
*
* Overrides FieldItemNormalizer to
* - during normalization, add the 'format' key to assist consumers
* - during denormalization, use \Drupal\serialization\Normalizer\TimestampNormalizer
*/
class TimestampItemNormalizer extends FieldItemNormalizer {
use TimeStampItemNormalizerTrait;
/**
* {@inheritdoc}
*/
......@@ -20,21 +24,22 @@ class TimestampItemNormalizer extends FieldItemNormalizer {
/**
* {@inheritdoc}
*/
public function normalize($field_item, $format = NULL, array $context = []) {
$data = parent::normalize($field_item, $format, $context);
return $this->processNormalizedValues($data);
public function normalize($object, $format = NULL, array $context = []) {
return parent::normalize($object, $format, $context) + [
// 'format' is not a property on Timestamp objects. This is present to
// assist consumers of this data.
'format' => \DateTime::RFC3339,
];
}
/**
* {@inheritdoc}
*/
public function denormalize($data, $class, $format = NULL, array $context = []) {
if (empty($data['value'])) {
throw new InvalidArgumentException('No "value" attribute present');
protected function constructValue($data, $context) {
if (!empty($data['format'])) {
$context['datetime_allowed_formats'] = [$data['format']];
}
return parent::denormalize($data, $class, $format, $context);
return ['value' => $this->serializer->denormalize($data['value'], Timestamp::class, NULL, $context)];
}
}
<?php
namespace Drupal\serialization\Normalizer;
use Drupal\Core\TypedData\Plugin\DataType\Timestamp;
/**
* Converts values for the Timestamp data type to and from common formats.
*
* @internal
*
* Note that \Drupal\Core\TypedData\Plugin\DataType\Timestamp::getDateTime()
* explicitly sets a default timezone of UTC. This ensures the string
* representation generated by DateTimeNormalizer::normalize() is also in UTC.
*/
class TimestampNormalizer extends DateTimeNormalizer {
/**
* {@inheritdoc}
*/
protected $allowedFormats = [
'UNIX timestamp' => 'U',
'ISO 8601' => \DateTime::ISO8601,
'RFC 3339' => \DateTime::RFC3339,
];
/**
* {@inheritdoc}
*/
protected $supportedInterfaceOrClass = Timestamp::class;
/**
* {@inheritdoc}
*/
protected function getNormalizationTimezone() {
return new \DateTimeZone('UTC');