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
  • Loading branch information
niemyjski committed Sep 8, 2023
1 parent e95fcc0 commit 0ac1687
Show file tree
Hide file tree
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();
Expand All @@ -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()),
Expand Down
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) {
Expand All @@ -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;

Expand Down
10 changes: 10 additions & 0 deletions tests/Exceptionless.DateTimeExtensions.Tests/DateTimeRangeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
};
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
};
}
}
Expand Down

0 comments on commit 0ac1687

Please sign in to comment.