Skip to content

Commit

Permalink
Add support for RFC822 timezone formatting for HeaderDateParser. (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
JohnTasler authored Jun 14, 2021
1 parent bfdb0c7 commit ac2bda4
Show file tree
Hide file tree
Showing 2 changed files with 94 additions and 9 deletions.
70 changes: 62 additions & 8 deletions Usenet/Nntp/Parsers/HeaderDateParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ internal static class HeaderDateParser
dateTime = Regex.Replace(dateTime, @"\s+:\s+", ":");

string[] dateTimeParts = dateTime.Split(new[] {' ', '\n', '\r', '\t'}, StringSplitOptions.RemoveEmptyEntries);
if (dateTimeParts.Length != 5)
if (dateTimeParts.Length != 5 && (dateTimeParts.Length != 6 || dateTimeParts[5] != "(UTC)"))
{
throw new FormatException(Resources.Nntp.BadHeaderDateFormat);
}
Expand Down Expand Up @@ -77,8 +77,8 @@ private static int GetCentury(int year, int month, int day)
{
DateTime today = DateTime.UtcNow.Date;
int currentCentury = today.Year / 100;
return new DateTime(currentCentury * 100 + year, month, day, 0, 0, 0, DateTimeKind.Utc) > today
? currentCentury - 1
return new DateTime(currentCentury * 100 + year, month, day, 0, 0, 0, DateTimeKind.Utc) > today
? currentCentury - 1
: currentCentury;
}

Expand Down Expand Up @@ -106,16 +106,70 @@ private static void ParseTime(string value, out int hour, out int minute, out in

private static TimeSpan ParseZone(string value)
{
int zoneSign = value[0] == '-' ? -1 : 1;
if (!int.TryParse(value, out int zone))
// The time zone must be as specified in RFC822, https://tools.ietf.org/html/rfc822#section-5

if (!short.TryParse(value, out short zone))
{
switch (value)
{
// UTC is not specified in RFC822, but allowing it since it is commonly used
case "UTC":
case "UT":
case "GMT":
case "Z":
break;

case "EDT":
zone = -0400;
break;

case "EST":
case "CDT":
zone = -0500;
break;

case "CST":
case "MDT":
zone = -0600;
break;

case "MST":
case "PDT":
zone = -0700;
break;

case "PST":
zone = -0800;
break;

case "A":
zone = -0100;
break;

case "N":
zone = +0100;
break;

case "M":
zone = -1200;
break;

case "Y":
zone = +1200;
break;

default:
throw new FormatException(Resources.Nntp.BadHeaderDateFormat);
}
}
else if (-9999 > zone || zone > 9999)
{
// todo: parse obs-zone
throw new FormatException(Resources.Nntp.BadHeaderDateFormat);
}
zone = Math.Abs(zone);

int minute = zone % 100;
int hour = zone / 100;
return TimeSpan.FromMinutes(zoneSign * (hour * 60 + minute));
return TimeSpan.FromMinutes(hour * 60 + minute);
}
}
}
33 changes: 32 additions & 1 deletion UsenetTests/Nntp/Parsers/HeaderDateParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ public class HeaderDateParserTests
{
new object[] {"01 May 2017 13:55:33 +0000", new DateTimeOffset(2017, 5, 1, 13, 55, 33, TimeSpan.Zero)},
new object[] {"01 May 2017 13:55:33 -0000", new DateTimeOffset(2017, 5, 1, 13, 55, 33, TimeSpan.Zero)},
new object[] {"01 May 2017 13:55:33 +0000 (UTC)", new DateTimeOffset(2017, 5, 1, 13, 55, 33, TimeSpan.Zero)},
new object[] {"01 May 2017 13:55:33 -0000 (UTC)", new DateTimeOffset(2017, 5, 1, 13, 55, 33, TimeSpan.Zero)},
new object[] {"01 May 2017 13:55:33 +0100", new DateTimeOffset(2017, 5, 1, 13, 55, 33, TimeSpan.FromHours(1))},
new object[] {"01 May 2017 13:55:33 -0100", new DateTimeOffset(2017, 5, 1, 13, 55, 33, TimeSpan.FromHours(-1))},

Expand All @@ -31,7 +33,29 @@ public class HeaderDateParserTests
new object[] {"1 Nov 2017 00:00:00 +0000", new DateTimeOffset(2017, 11, 1, 0, 0, 0, TimeSpan.Zero)},
new object[] {"1 Dec 2017 00:00:00 +0000", new DateTimeOffset(2017, 12, 1, 0, 0, 0, TimeSpan.Zero)},

new object[] {"01 May 2017 13 : 55 : 33 +0000", new DateTimeOffset(2017, 5, 1, 13, 55, 33, TimeSpan.Zero)}
// Valid timezone formats
new object[] {"1 Jan 2020 00:00:00 UTC", new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.Zero)},
new object[] {"1 Jan 2020 00:00:00 UT", new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.Zero)},
new object[] {"1 Jan 2020 00:00:00 GMT", new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.Zero)},
new object[] {"1 Jan 2020 00:00:00 Z", new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.Zero)},
new object[] {"1 Jan 2020 00:00:00 EDT", new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.FromHours(-04))},
new object[] {"1 Jan 2020 00:00:00 EST", new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.FromHours(-05))},
new object[] {"1 Jan 2020 00:00:00 CDT", new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.FromHours(-05))},
new object[] {"1 Jan 2020 00:00:00 CST", new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.FromHours(-06))},
new object[] {"1 Jan 2020 00:00:00 MDT", new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.FromHours(-06))},
new object[] {"1 Jan 2020 00:00:00 MST", new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.FromHours(-07))},
new object[] {"1 Jan 2020 00:00:00 PDT", new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.FromHours(-07))},
new object[] {"1 Jan 2020 00:00:00 PST", new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.FromHours(-08))},
new object[] {"1 Jan 2020 00:00:00 A", new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.FromHours(-01))},
new object[] {"1 Jan 2020 00:00:00 N", new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.FromHours(+01))},
new object[] {"1 Jan 2020 00:00:00 M", new DateTimeOffset(2020, 1, 1, 0, 0, 0, TimeSpan.FromHours(-12))},
};

public static IEnumerable<object[]> TimezoneParseFailureData = new[]
{
new object[] {"1 Jan 2020 00:00:00 BAD", typeof(FormatException)},
new object[] {"1 Jan 2020 00:00:00 -10000", typeof(FormatException)},
new object[] {"1 Jan 2020 00:00:00 +10000", typeof(FormatException)},
};

public static IEnumerable<object[]> CenturyData = new[]
Expand All @@ -49,6 +73,13 @@ public void HeaderDateShouldBeParsedCorrectly(string headerDate, DateTimeOffset
Assert.Equal(expectedDateTime, actualDateTime);
}

[Theory]
[MemberData(nameof(TimezoneParseFailureData))]
public void HeaderDateShouldBeNotBeParsedCorrectly(string headerDate, Type exceptionType)
{
Assert.Throws(exceptionType, () => HeaderDateParser.Parse(headerDate));
}

[Fact]
public void ObsoleteTwoDigitYearBeforeCurrentDateShouldBeParsedCorrectly()
{
Expand Down

0 comments on commit ac2bda4

Please sign in to comment.