From d821673f6088a99ee7d1e64c04155d373c6a78c0 Mon Sep 17 00:00:00 2001 From: Sean O'Brien Date: Thu, 1 Aug 2024 16:04:31 -0400 Subject: [PATCH] bugfix: iso8601 timestamp parsing --- .changes/nextrelease/iso-8601-fix.json | 7 +++++++ src/Api/DateTimeResult.php | 10 +++++++++ tests/Api/DateTimeResultTest.php | 29 ++++++++++++++++++++++++++ tests/Api/Parser/JsonParserTest.php | 12 +++++++++++ tests/Api/Parser/XmlParserTest.php | 12 +++++++++++ 5 files changed, 70 insertions(+) create mode 100644 .changes/nextrelease/iso-8601-fix.json diff --git a/.changes/nextrelease/iso-8601-fix.json b/.changes/nextrelease/iso-8601-fix.json new file mode 100644 index 0000000000..9772190285 --- /dev/null +++ b/.changes/nextrelease/iso-8601-fix.json @@ -0,0 +1,7 @@ +[ + { + "type": "bugfix", + "category": "Api", + "description": "Fixes issue with parsing iso8601 timestamps with nanosecond precision in versions 8.0.9 and below." + } +] diff --git a/src/Api/DateTimeResult.php b/src/Api/DateTimeResult.php index cccf55b49d..b296abc491 100644 --- a/src/Api/DateTimeResult.php +++ b/src/Api/DateTimeResult.php @@ -13,6 +13,8 @@ */ class DateTimeResult extends \DateTime implements \JsonSerializable { + private const ISO8601_NANOSECOND_REGEX = '/^(.*\.\d{6})(\d{1,3})(Z|[+-]\d{2}:\d{2})?$/'; + /** * Create a new DateTimeResult from a unix timestamp. * The Unix epoch (or Unix time or POSIX time or Unix @@ -60,6 +62,14 @@ public static function fromISO8601($iso8601Timestamp) throw new ParserException('Invalid timestamp value passed to DateTimeResult::fromISO8601'); } + // Prior to 8.0.10, nanosecond precision is not supported + // Reduces to microsecond precision if nanosecond precision is detected + if (PHP_VERSION_ID < 80010 + && preg_match(self::ISO8601_NANOSECOND_REGEX, $iso8601Timestamp, $matches) + ) { + $iso8601Timestamp = $matches[1] . ($matches[3] ?? ''); + } + return new DateTimeResult($iso8601Timestamp); } diff --git a/tests/Api/DateTimeResultTest.php b/tests/Api/DateTimeResultTest.php index 7afc019d02..f8fe015904 100644 --- a/tests/Api/DateTimeResultTest.php +++ b/tests/Api/DateTimeResultTest.php @@ -49,4 +49,33 @@ public function testJsonSerialzesAsIso8601() $d = DateTimeResult::fromEpoch($t); $this->assertSame('"' . gmdate('c', $t). '"', json_encode($d)); } + + /** + * @return void + * + * @dataProvider nanosecondPrecisionProvider + */ + public function testIso8601NanosecondPrecision($timestamp, $expected) + { + $parsed = DateTimeResult::fromISO8601($timestamp); + $this->assertEquals($expected, (string) $parsed); + } + + public function nanosecondPrecisionProvider() + { + return [ + ['2024-07-31T19:05:47.1234567Z', '2024-07-31T19:05:47+00:00'], + ['2024-07-31T19:05:47.12345678Z', '2024-07-31T19:05:47+00:00'], + ['2024-07-31T19:05:47.123456789Z', '2024-07-31T19:05:47+00:00'], + ['2024-07-31T19:05:47.1234567+02:00', '2024-07-31T19:05:47+02:00'], + ['2024-07-31T19:05:47.12345678+02:00', '2024-07-31T19:05:47+02:00'], + ['2024-07-31T19:05:47.123456789+02:00', '2024-07-31T19:05:47+02:00'], + ['2024-07-31T19:05:47.1234567-05:00', '2024-07-31T19:05:47-05:00'], + ['2024-07-31T19:05:47.12345678-05:00', '2024-07-31T19:05:47-05:00'], + ['2024-07-31T19:05:47.123456789-05:00', '2024-07-31T19:05:47-05:00'], + ['2024-07-31T19:05:47.1234567', '2024-07-31T19:05:47+00:00'], + ['2024-07-31T19:05:47.12345678', '2024-07-31T19:05:47+00:00'], + ['2024-07-31T19:05:47.123456789', '2024-07-31T19:05:47+00:00'] + ]; + } } diff --git a/tests/Api/Parser/JsonParserTest.php b/tests/Api/Parser/JsonParserTest.php index 0c8f3c51d0..8a2c82071b 100644 --- a/tests/Api/Parser/JsonParserTest.php +++ b/tests/Api/Parser/JsonParserTest.php @@ -44,6 +44,18 @@ public function timeStampModelProvider() [(float) 0, "ParseIso8601", "1970-01-01T00:00:00+00:00"], [(float) 0, "ParseUnix", "1970-01-01T00:00:00+00:00"], [(float) 0, "ParseUnknown", '1970-01-01T00:00:00+00:00'], + ['2024-07-31T19:05:47.1234567Z', 'ParseIso8601', '2024-07-31T19:05:47+00:00'], + ['2024-07-31T19:05:47.12345678Z', 'ParseIso8601', '2024-07-31T19:05:47+00:00'], + ['2024-07-31T19:05:47.123456789Z', 'ParseIso8601', '2024-07-31T19:05:47+00:00'], + ['2024-07-31T19:05:47.1234567+02:00', 'ParseIso8601', '2024-07-31T19:05:47+02:00'], + ['2024-07-31T19:05:47.12345678+02:00', 'ParseIso8601', '2024-07-31T19:05:47+02:00'], + ['2024-07-31T19:05:47.123456789+02:00', 'ParseIso8601', '2024-07-31T19:05:47+02:00'], + ['2024-07-31T19:05:47.1234567-05:00', 'ParseIso8601', '2024-07-31T19:05:47-05:00'], + ['2024-07-31T19:05:47.12345678-05:00', 'ParseIso8601', '2024-07-31T19:05:47-05:00'], + ['2024-07-31T19:05:47.123456789-05:00', 'ParseIso8601', '2024-07-31T19:05:47-05:00'], + ['2024-07-31T19:05:47.1234567', 'ParseIso8601', '2024-07-31T19:05:47+00:00'], + ['2024-07-31T19:05:47.12345678', 'ParseIso8601', '2024-07-31T19:05:47+00:00'], + ['2024-07-31T19:05:47.123456789', 'ParseIso8601', '2024-07-31T19:05:47+00:00'] ]; } diff --git a/tests/Api/Parser/XmlParserTest.php b/tests/Api/Parser/XmlParserTest.php index 50190e379f..c8d8b51d18 100644 --- a/tests/Api/Parser/XmlParserTest.php +++ b/tests/Api/Parser/XmlParserTest.php @@ -47,6 +47,18 @@ public function timeStampModelProvider() [(float) 0, "ParseIso8601", "1970-01-01T00:00:00+00:00"], [(float) 0, "ParseUnix", "1970-01-01T00:00:00+00:00"], [(float) 0, "ParseUnknown", '1970-01-01T00:00:00+00:00'], + ['2024-07-31T19:05:47.1234567Z', 'ParseIso8601', '2024-07-31T19:05:47+00:00'], + ['2024-07-31T19:05:47.12345678Z', 'ParseIso8601', '2024-07-31T19:05:47+00:00'], + ['2024-07-31T19:05:47.123456789Z', 'ParseIso8601', '2024-07-31T19:05:47+00:00'], + ['2024-07-31T19:05:47.1234567+02:00', 'ParseIso8601', '2024-07-31T19:05:47+02:00'], + ['2024-07-31T19:05:47.12345678+02:00', 'ParseIso8601', '2024-07-31T19:05:47+02:00'], + ['2024-07-31T19:05:47.123456789+02:00', 'ParseIso8601', '2024-07-31T19:05:47+02:00'], + ['2024-07-31T19:05:47.1234567-05:00', 'ParseIso8601', '2024-07-31T19:05:47-05:00'], + ['2024-07-31T19:05:47.12345678-05:00', 'ParseIso8601', '2024-07-31T19:05:47-05:00'], + ['2024-07-31T19:05:47.123456789-05:00', 'ParseIso8601', '2024-07-31T19:05:47-05:00'], + ['2024-07-31T19:05:47.1234567', 'ParseIso8601', '2024-07-31T19:05:47+00:00'], + ['2024-07-31T19:05:47.12345678', 'ParseIso8601', '2024-07-31T19:05:47+00:00'], + ['2024-07-31T19:05:47.123456789', 'ParseIso8601', '2024-07-31T19:05:47+00:00'] ]; }