Skip to content

Commit

Permalink
Updated date parser to support a full 8601 date with zulu
Browse files Browse the repository at this point in the history
niemyjski committed Sep 8, 2023

Verified

This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
1 parent e95fcc0 commit 0ac1687
Showing 6 changed files with 57 additions and 30 deletions.
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System;
using System.Globalization;
using System.Text.RegularExpressions;

namespace Exceptionless.DateTimeExtensions.FormatParsers {
[Priority(30)]
public class ExplicitDateFormatParser : IFormatParser {
private static readonly Regex _parser = new(@"^\s*(?<date>\d{4}-\d{2}-\d{2}(?:T(?:\d{2}\:\d{2}\:\d{2}|\d{2}\:\d{2}|\d{2}))?)\s*$");
private static readonly Regex _parser = new(@"^\s*(?<date>\d{4}-\d{2}-\d{2}(?:T(?:\d{2}\:\d{2}\:\d{2}(?:\.\d{3})?|\d{2}\:\d{2}|\d{2})Z?)?)\s*$");

public DateTimeRange Parse(string content, DateTimeOffset relativeBaseTime) {
content = content.Trim();
@@ -18,10 +19,13 @@ public DateTimeRange Parse(string content, DateTimeOffset relativeBaseTime) {
if (value.Length == 16)
value += ":00";

if (!DateTimeOffset.TryParse(value, out var date))
// NOTE: AssumeUniversal here because this might parse a date (E.G., 03/22/2023). If no offset is specified, we assume it's UTC.
if (!DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var date))
return null;

date = date.ChangeOffset(relativeBaseTime.Offset);
if (relativeBaseTime.Offset != date.Offset)
date = date.ChangeOffset(relativeBaseTime.Offset);

return content.Length switch {
10 => new DateTimeRange(date, date.EndOfDay()),
13 => new DateTimeRange(date, date.EndOfHour()),
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System;
using System.Globalization;
using System.Text.RegularExpressions;

namespace Exceptionless.DateTimeExtensions.FormatParsers.PartParsers {
[Priority(50)]
public class ExplicitDatePartParser : IPartParser {
private static readonly Regex _parser = new(@"\G(?<date>\d{4}-\d{2}-\d{2}(?:T(?:\d{2}\:\d{2}\:\d{2}|\d{2}\:\d{2}|\d{2}))?)");
private static readonly Regex _parser = new(@"\G(?<date>\d{4}-\d{2}-\d{2}(?:T(?:\d{2}\:\d{2}\:\d{2}(?:\.\d{3})?|\d{2}\:\d{2}|\d{2})Z?)?)");
public Regex Regex => _parser;

public DateTimeOffset? Parse(Match match, DateTimeOffset relativeBaseTime, bool isUpperLimit) {
@@ -14,10 +15,13 @@ public class ExplicitDatePartParser : IPartParser {
if (value.Length == 16)
value += ":00";

if (!DateTimeOffset.TryParse(value, out var date))
// NOTE: AssumeUniversal here because this might parse a date (E.G., 03/22/2023). If no offset is specified, we assume it's UTC.
if (!DateTimeOffset.TryParse(value, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out var date))
return null;

date = date.ChangeOffset(relativeBaseTime.Offset);
if (relativeBaseTime.Offset != date.Offset)
date = date.ChangeOffset(relativeBaseTime.Offset);

if (!isUpperLimit)
return date;

10 changes: 10 additions & 0 deletions tests/Exceptionless.DateTimeExtensions.Tests/DateTimeRangeTests.cs
Original file line number Diff line number Diff line change
@@ -42,6 +42,16 @@ public void CanParseIntoLocalTime() {
Assert.Equal(new DateTime(2016, 12, 28, 6, 30, 0, DateTimeKind.Utc), localRange.UtcEnd);
}

[Fact]
public void CanParse8601() {
const string time = "2023-12-28T05:00:00.000Z-2023-12-28T05:30:00.000Z";
var range = DateTimeRange.Parse(time, DateTimeOffset.UtcNow);
Assert.Equal(new DateTime(2023, 12, 28, 5, 0, 0, DateTimeKind.Utc), range.Start);
Assert.Equal(new DateTime(2023, 12, 28, 5, 30, 0, DateTimeKind.Utc), range.End);
Assert.Equal(new DateTime(2023, 12, 28, 5, 0, 0, DateTimeKind.Utc), range.UtcStart);
Assert.Equal(new DateTime(2023, 12, 28, 5, 30, 0, DateTimeKind.Utc), range.UtcEnd);
}

[Theory]
[MemberData(nameof(Inputs))]
public void CanParseNamedRanges(string input, DateTime start, DateTime end) {
Original file line number Diff line number Diff line change
@@ -17,15 +17,17 @@ public void ParseInput(string input, DateTime? start, DateTime? end) {
public static IEnumerable<object[]> Inputs {
get {
return new[] {
new object[] { "2014-02-01", _now.Change(null, 2, 1).StartOfDay(), _now.Change(null, 2, 1).EndOfDay() },
new object[] { "2014-02-01T05", _now.Change(null, 2, 1, 5).StartOfHour(), _now.Change(null, 2, 1, 5).EndOfHour() },
new object[] { "2014-02-01T05:30", _now.Change(null, 2, 1, 5, 30).StartOfMinute(), _now.Change(null, 2, 1, 5, 30).EndOfMinute() },
new object[] { "2014-02-01T05:30:20", _now.Change(null, 2, 1, 5, 30, 20).StartOfSecond(), _now.Change(null, 2, 1, 5, 30, 20).EndOfSecond() },
new object[] { "2014-11-06", _now.Change(null, 11, 6).StartOfDay(), _now.Change(null, 11, 6).EndOfDay() },
new object[] { "2014-12-24", _now.Change(null, 12, 24).StartOfDay(), _now.Change(null, 12, 24).EndOfDay() },
new object[] { "2014-12-45", null, null },
new object[] { "blah", null, null },
new object[] { "blah blah", null, null }
new object[] { "2014-02-01", _now.Change(null, 2, 1).StartOfDay(), _now.Change(null, 2, 1).EndOfDay() },
new object[] { "2014-02-01T05", _now.Change(null, 2, 1, 5).StartOfHour(), _now.Change(null, 2, 1, 5).EndOfHour() },
new object[] { "2014-02-01T05:30", _now.Change(null, 2, 1, 5, 30).StartOfMinute(), _now.Change(null, 2, 1, 5, 30).EndOfMinute() },
new object[] { "2014-02-01T05:30:20", _now.Change(null, 2, 1, 5, 30, 20).StartOfSecond(), _now.Change(null, 2, 1, 5, 30, 20).EndOfSecond() },
new object[] { "2014-02-01T05:30:20.000", _now.Change(null, 2, 1, 5, 30, 20).StartOfSecond(), _now.Change(null, 2, 1, 5, 30, 20).EndOfSecond() },
new object[] { "2014-02-01T05:30:20.000Z", _now.Change(null, 2, 1, 5, 30, 20).StartOfSecond(), _now.Change(null, 2, 1, 5, 30, 20).EndOfSecond() },
new object[] { "2014-11-06", _now.Change(null, 11, 6).StartOfDay(), _now.Change(null, 11, 6).EndOfDay() },
new object[] { "2014-12-24", _now.Change(null, 12, 24).StartOfDay(), _now.Change(null, 12, 24).EndOfDay() },
new object[] { "2014-12-45", null, null },
new object[] { "blah", null, null },
new object[] { "blah blah", null, null }
};
}
}
Original file line number Diff line number Diff line change
@@ -17,7 +17,10 @@ static FormatParserTestsBase() {

public void ValidateInput(IFormatParser parser, string input, DateTime? start, DateTime? end) {
_logger.LogInformation("Input: {Input}, Now: {Now}, Start: {Start}, End: {End}", input, _now, start, end);

var range = parser.Parse(input, _now);
_logger.LogInformation("Parsed range: Start: {Start}, End: {End}", range?.Start, range?.End);

if (range == null) {
Assert.Null(start);
Assert.Null(end);
Original file line number Diff line number Diff line change
@@ -17,21 +17,25 @@ public void ParseInput(string input, bool isUpperLimit, DateTimeOffset? expected
public static IEnumerable<object[]> Inputs {
get {
return new[] {
new object[] { "2014-02-01", false, _now.Change(null, 2, 1).StartOfDay() },
new object[] { "2014-02-01", true, _now.Change(null, 2, 1).EndOfDay() },
new object[] { "2014-02-01T05", false, _now.Change(null, 2, 1, 5).StartOfHour() },
new object[] { "2014-02-01T05", true, _now.Change(null, 2, 1, 5).EndOfHour() },
new object[] { "2014-02-01T05:30", false, _now.Change(null, 2, 1, 5, 30).StartOfMinute() },
new object[] { "2014-02-01T05:30", true, _now.Change(null, 2, 1, 5, 30).EndOfMinute() },
new object[] { "2014-02-01T05:30:20", false, _now.Change(null, 2, 1, 5, 30, 20).StartOfSecond() },
new object[] { "2014-02-01T05:30:20", true, _now.Change(null, 2, 1, 5, 30, 20).StartOfSecond() },
new object[] { "2014-11-06", false, _now.Change(null, 11, 6).StartOfDay() },
new object[] { "2014-11-06", true, _now.Change(null, 11, 6).EndOfDay() },
new object[] { "2014-12-24", false, _now.Change(null, 12, 24).StartOfDay() },
new object[] { "2014-12-24", true, _now.Change(null, 12, 24).EndOfDay() },
new object[] { "2014-12-45", true, null },
new object[] { "blah", false, null },
new object[] { "blah blah", true, null }
new object[] { "2014-02-01", false, _now.Change(null, 2, 1).StartOfDay() },
new object[] { "2014-02-01", true, _now.Change(null, 2, 1).EndOfDay() },
new object[] { "2014-02-01T05", false, _now.Change(null, 2, 1, 5).StartOfHour() },
new object[] { "2014-02-01T05", true, _now.Change(null, 2, 1, 5).EndOfHour() },
new object[] { "2014-02-01T05:30", false, _now.Change(null, 2, 1, 5, 30).StartOfMinute() },
new object[] { "2014-02-01T05:30", true, _now.Change(null, 2, 1, 5, 30).EndOfMinute() },
new object[] { "2014-02-01T05:30:20", false, _now.Change(null, 2, 1, 5, 30, 20).StartOfSecond() },
new object[] { "2014-02-01T05:30:20", true, _now.Change(null, 2, 1, 5, 30, 20).StartOfSecond() },
new object[] { "2014-02-01T05:30:20.000", false, _now.Change(null, 2, 1, 5, 30, 20).StartOfSecond() },
new object[] { "2014-02-01T05:30:20.999", true, _now.Change(null, 2, 1, 5, 30, 20).EndOfSecond() },
new object[] { "2014-02-01T05:30:20.000Z", false, _now.Change(null, 2, 1, 5, 30, 20).StartOfSecond() },
new object[] { "2014-02-01T05:30:20.999Z", true, _now.Change(null, 2, 1, 5, 30, 20).EndOfSecond() },
new object[] { "2014-11-06", false, _now.Change(null, 11, 6).StartOfDay() },
new object[] { "2014-11-06", true, _now.Change(null, 11, 6).EndOfDay() },
new object[] { "2014-12-24", false, _now.Change(null, 12, 24).StartOfDay() },
new object[] { "2014-12-24", true, _now.Change(null, 12, 24).EndOfDay() },
new object[] { "2014-12-45", true, null },
new object[] { "blah", false, null },
new object[] { "blah blah", true, null }
};
}
}

0 comments on commit 0ac1687

Please sign in to comment.