Skip to content

Commit

Permalink
Fix Iso8601 format with varying fractional values
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
russcam committed Aug 5, 2020
1 parent 2ee3e97 commit f176653
Show file tree
Hide file tree
Showing 2 changed files with 129 additions and 22 deletions.
14 changes: 6 additions & 8 deletions src/Elasticsearch.Net/Utf8Json/Formatters/DateTimeFormatter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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++;
Expand Down Expand Up @@ -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++;
Expand Down
137 changes: 123 additions & 14 deletions tests/Tests.Reproduce/GitHubIssue4876.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<InvalidOperationException>();
}

[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<InvalidOperationException>();
}

[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<Document>(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<Document2>(stream);

document.Timestamp.Should().Be(
new DateTimeOffset(637317593694425068, TimeSpan.Zero));
new DateTimeOffset(expectedTicks, TimeSpan.Zero));
}

public class Document
Expand Down

0 comments on commit f176653

Please sign in to comment.