From f1766534eabc32c9f6dc3447d3484d4e402cb295 Mon Sep 17 00:00:00 2001 From: Russ Cam Date: Wed, 5 Aug 2020 11:59:48 +1000 Subject: [PATCH] Fix Iso8601 format with varying fractional values Relates: #4910 The PR introduced a fix to support basic offset formats when deserializing Iso8601, but the fix would only work with 7 fractional second values. This commit fixes the implementation to support any number of fractional second values. --- .../Utf8Json/Formatters/DateTimeFormatter.cs | 14 +- tests/Tests.Reproduce/GitHubIssue4876.cs | 137 ++++++++++++++++-- 2 files changed, 129 insertions(+), 22 deletions(-) diff --git a/src/Elasticsearch.Net/Utf8Json/Formatters/DateTimeFormatter.cs b/src/Elasticsearch.Net/Utf8Json/Formatters/DateTimeFormatter.cs index 94a0d44da84..81459713495 100644 --- a/src/Elasticsearch.Net/Utf8Json/Formatters/DateTimeFormatter.cs +++ b/src/Elasticsearch.Net/Utf8Json/Formatters/DateTimeFormatter.cs @@ -359,16 +359,15 @@ public DateTime Deserialize(ref JsonReader reader, IJsonFormatterResolver format } else if (i < to && array[i] == '-' || array[i] == '+') { - if (len != 30 && len != 32 && len != 33) goto ERROR; - + var offLen = to - i; + if (offLen != 3 && offLen != 5 && offLen != 6) goto ERROR; kind = DateTimeKind.Local; var minus = array[i++] == '-'; - var h = (array[i++] - (byte)'0') * 10 + (array[i++] - (byte)'0'); var m = 0; if (i < to) { - if (len == 33) + if (offLen == 6) { if (array[i] != ':') goto ERROR; i++; @@ -713,15 +712,14 @@ public DateTimeOffset Deserialize(ref JsonReader reader, IJsonFormatterResolver if (i < to && array[i] == '-' || array[i] == '+') { - if (len != 30 && len != 32 && len != 33) goto ERROR; - + var offLen = to - i; + if (offLen != 3 && offLen != 5 && offLen != 6) goto ERROR; var minus = array[i++] == '-'; - var h = (array[i++] - (byte)'0') * 10 + (array[i++] - (byte)'0'); var m = 0; if (i < to) { - if (len == 33) + if (offLen == 6) { if (array[i] != ':') goto ERROR; i++; diff --git a/tests/Tests.Reproduce/GitHubIssue4876.cs b/tests/Tests.Reproduce/GitHubIssue4876.cs index c39980e6d25..7f5e61a3030 100644 --- a/tests/Tests.Reproduce/GitHubIssue4876.cs +++ b/tests/Tests.Reproduce/GitHubIssue4876.cs @@ -19,58 +19,167 @@ public class GitHubIssue4876 [U] public void CanDeserializeExtendedFormatOffsetIso8601DateTime() => - AssertDateTime("{\"timestamp\":\"2020-07-31T12:29:29.4425068+10:00\"}"); + AssertDateTime("2020-07-31T12:29:29.4425068+10:00", 637317593694425068); [U] public void CanDeserializeBasicFormatOffsetWithMinutesIso8601DateTime() => - AssertDateTime("{\"timestamp\":\"2020-07-31T12:29:29.4425068+1000\"}"); + AssertDateTime("2020-07-31T12:29:29.4425068+1000", 637317593694425068); [U] public void CanDeserializeBasicFormatOffsetIso8601DateTime() => - AssertDateTime("{\"timestamp\":\"2020-07-31T12:29:29.4425068+10\"}"); + AssertDateTime("2020-07-31T12:29:29.4425068+10", 637317593694425068); [U] public void ThrowExceptionWhenInvalidBasicFormatOffset() { - Action action = () => AssertDateTime("{\"timestamp\":\"2020-07-31T12:29:29.4425068+100\"}"); + Action action = () => AssertDateTime("2020-07-31T12:29:29.4425068+100", 637317593694425068); action.Should().Throw(); } [U] public void ThrowExceptionWhenInvalidBasicFormatOffset2() { - Action action = () => AssertDateTime("{\"timestamp\":\"2020-07-31T12:29:29.4425068-10000\"}"); + Action action = () => AssertDateTime("2020-07-31T12:29:29.4425068-10000", 637317593694425068); action.Should().Throw(); } [U] public void CanDeserializeExtendedFormatOffsetIso8601DateTimeOffset() => - AssertDateTimeOffset("{\"timestamp\":\"2020-07-31T12:29:29.4425068+10:00\"}"); + AssertDateTimeOffset("2020-07-31T12:29:29.4425068+10:00", 637317593694425068); [U] public void CanDeserializeBasicFormatOffsetWithMinutesIso8601DateTimeOffset() => - AssertDateTimeOffset("{\"timestamp\":\"2020-07-31T12:29:29.4425068+1000\"}"); + AssertDateTimeOffset("2020-07-31T12:29:29.4425068+1000", 637317593694425068); [U] public void CanDeserializeBasicFormatOffsetIso8601DateTimeOffset() => - AssertDateTimeOffset("{\"timestamp\":\"2020-07-31T12:29:29.4425068+10\"}"); + AssertDateTimeOffset("2020-07-31T12:29:29.4425068+10", 637317593694425068); - private void AssertDateTime(string json) + [U] + public void CanDeserializeNoFractionalSeconds() + { + var ticks = 637321081960000000; + AssertDateTime("2020-08-04T13:23:16+10:00", ticks); + AssertDateTime("2020-08-04T13:23:16+1000", ticks); + AssertDateTime("2020-08-04T13:23:16+10", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16+10:00", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16+1000", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16+10", ticks); + } + + [U] + public void CanDeserializeOneFractionalSecond() + { + var ticks = 637321081967000000; + AssertDateTime("2020-08-04T13:23:16.7+10:00", ticks); + AssertDateTime("2020-08-04T13:23:16.7+1000", ticks); + AssertDateTime("2020-08-04T13:23:16.7+10", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.7+10:00", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.7+1000", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.7+10", ticks); + } + + [U] + public void CanDeserializeTwoFractionalSeconds() + { + var ticks = 637321081967200000; + AssertDateTime("2020-08-04T13:23:16.72+10:00", ticks); + AssertDateTime("2020-08-04T13:23:16.72+1000", ticks); + AssertDateTime("2020-08-04T13:23:16.72+10", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.72+10:00", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.72+1000", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.72+10", ticks); + } + + [U] + public void CanDeserializeThreeFractionalSeconds() + { + var ticks = 637321081967220000; + AssertDateTime("2020-08-04T13:23:16.722+10:00", ticks); + AssertDateTime("2020-08-04T13:23:16.722+1000", ticks); + AssertDateTime("2020-08-04T13:23:16.722+10", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.722+10:00", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.722+1000", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.722+10", ticks); + } + + [U] + public void CanDeserializeFourFractionalSeconds() + { + var ticks = 637321081967220000; + AssertDateTime("2020-08-04T13:23:16.7220+10:00", ticks); + AssertDateTime("2020-08-04T13:23:16.7220+1000", ticks); + AssertDateTime("2020-08-04T13:23:16.7220+10", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.7220+10:00", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.7220+1000", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.7220+10", ticks); + } + + [U] + public void CanDeserializeFiveFractionalSeconds() + { + var ticks = 637321081967220100; + AssertDateTime("2020-08-04T13:23:16.72201+10:00", ticks); + AssertDateTime("2020-08-04T13:23:16.72201+1000", ticks); + AssertDateTime("2020-08-04T13:23:16.72201+10", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.72201+10:00", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.72201+1000", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.72201+10", ticks); + } + + [U] + public void CanDeserializeSixFractionalSeconds() + { + var ticks = 637321081967220110; + AssertDateTime("2020-08-04T13:23:16.722011+10:00", ticks); + AssertDateTime("2020-08-04T13:23:16.722011+1000", ticks); + AssertDateTime("2020-08-04T13:23:16.722011+10", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.722011+10:00", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.722011+1000", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.722011+10", ticks); + } + + [U] + public void CanDeserializeSevenFractionalSeconds() + { + var ticks = 637321081967220111; + AssertDateTime("2020-08-04T13:23:16.7220111+10:00", ticks); + AssertDateTime("2020-08-04T13:23:16.7220111+1000", ticks); + AssertDateTime("2020-08-04T13:23:16.7220111+10", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.7220111+10:00", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.7220111+1000", ticks); + AssertDateTimeOffset("2020-08-04T13:23:16.7220111+10", ticks); + } + + [U] + public void CanDeserializeMoreThanSevenFractionalSeconds() + { + var ticks = 637321081967220111; + var digits = new Random().Next(); + AssertDateTime($"2020-08-04T13:23:16.7220111{digits}+10:00", ticks); + AssertDateTime($"2020-08-04T13:23:16.7220111{digits}+1000", ticks); + AssertDateTime($"2020-08-04T13:23:16.7220111{digits}+10", ticks); + AssertDateTimeOffset($"2020-08-04T13:23:16.7220111{digits}+10:00", ticks); + AssertDateTimeOffset($"2020-08-04T13:23:16.7220111{digits}+1000", ticks); + AssertDateTimeOffset($"2020-08-04T13:23:16.7220111{digits}+10", ticks); + } + + private void AssertDateTime(string json, long expectedTicks) { - var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); + var stream = new MemoryStream(Encoding.UTF8.GetBytes($"{{\"timestamp\":\"{json}\"}}")); var document = _client.SourceSerializer.Deserialize(stream); document.Timestamp.Should().Be( - new DateTime(637317593694425068, DateTimeKind.Utc).ToLocalTime()); + new DateTime(expectedTicks, DateTimeKind.Utc).ToLocalTime()); } - private void AssertDateTimeOffset(string json) + private void AssertDateTimeOffset(string json, long expectedTicks) { - var stream = new MemoryStream(Encoding.UTF8.GetBytes(json)); + var stream = new MemoryStream(Encoding.UTF8.GetBytes($"{{\"timestamp\":\"{json}\"}}")); var document = _client.SourceSerializer.Deserialize(stream); document.Timestamp.Should().Be( - new DateTimeOffset(637317593694425068, TimeSpan.Zero)); + new DateTimeOffset(expectedTicks, TimeSpan.Zero)); } public class Document