Date Routines Test Cases

pull/686/head
Qstick 3 years ago
parent 6b886b938c
commit b05d8c930d

@ -0,0 +1,54 @@
using System;
using System.Collections;
using NUnit.Framework;
using NzbDrone.Core.Parser;
using NzbDrone.Core.Test.Framework;
namespace NzbDrone.Core.Test.ParserTests
{
[TestFixture]
public class DateTimeRoutinesFixture : CoreTest
{
public static IEnumerable DateTimeTestCases
{
get
{
yield return new TestCaseData(@"Member since: 10-Feb-2008").Returns(new DateTime(2008, 2, 10, 0, 0, 0));
yield return new TestCaseData(@"Last Update: 18:16 11 Feb '08 ").Returns(new DateTime(2008, 2, 11, 18, 16, 0));
yield return new TestCaseData(@"date Tue, Feb 10, 2008 at 11:06 AM").Returns(new DateTime(2008, 2, 10, 11, 06, 0));
yield return new TestCaseData(@"see at 12/31/2007 14:16:32").Returns(new DateTime(2007, 12, 31, 14, 16, 32));
yield return new TestCaseData(@"sack finish 14:16:32 November 15 2008, 1-144 app").Returns(new DateTime(2008, 11, 15, 14, 16, 32));
yield return new TestCaseData(@"Genesis Message - Wed 04 Feb 08 - 19:40").Returns(new DateTime(2008, 2, 4, 19, 40, 0));
yield return new TestCaseData(@"The day 07/31/07 14:16:32 is ").Returns(new DateTime(2007, 7, 31, 14, 16, 32));
yield return new TestCaseData(@"Shipping is on us until December 24, 2008 within the U.S. ").Returns(new DateTime(2008, 12, 24, 0, 0, 0));
yield return new TestCaseData(@" 2008 within the U.S. at 14:16:32").Returns(new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, 14, 16, 32));
yield return new TestCaseData(@"5th November, 1994, 8:15:30 pm").Returns(new DateTime(1994, 11, 5, 20, 15, 30));
yield return new TestCaseData(@"7 boxes January 31 , 14:16:32.").Returns(new DateTime(DateTime.Now.Year, 1, 31, 14, 16, 32));
yield return new TestCaseData(@"the blue sky of Sept 30th 2008 14:16:32").Returns(new DateTime(2008, 9, 30, 14, 16, 32));
yield return new TestCaseData(@" e.g. 1997-07-16T19:20:30+01:00").Returns(new DateTime(1997, 7, 16, 19, 20, 30));
yield return new TestCaseData(@"Apr 1st, 2008 14:16:32 tufa 6767").Returns(new DateTime(2008, 4, 1, 14, 16, 32));
yield return new TestCaseData(@"wait for 07/31/07 14:16:32").Returns(new DateTime(2007, 7, 31, 14, 16, 32));
yield return new TestCaseData(@"later 12.31.08 and before 1.01.09").Returns(new DateTime(2008, 12, 31, 0, 0, 0));
yield return new TestCaseData(@"Expires: Sept 30th 2008 14:16:32").Returns(new DateTime(2008, 9, 30, 14, 16, 32));
yield return new TestCaseData(@"Offer expires Apr 1st, 2007, 14:16:32").Returns(new DateTime(2007, 4, 1, 14, 16, 32));
yield return new TestCaseData(@"Expires 14:16:32 January 31.").Returns(new DateTime(DateTime.Now.Year, 1, 31, 14, 16, 32));
yield return new TestCaseData(@"Expires 14:16:32 January 31-st.").Returns(new DateTime(DateTime.Now.Year, 1, 31, 14, 16, 32));
yield return new TestCaseData(@"Expires 23rd January 2010.").Returns(new DateTime(2010, 1, 23, 0, 0, 0));
yield return new TestCaseData(@"Expires January 22nd, 2010.").Returns(new DateTime(2010, 1, 22, 0, 0, 0));
yield return new TestCaseData(@"Expires DEC 22, 2010.").Returns(new DateTime(2010, 12, 22, 0, 0, 0));
yield return new TestCaseData(@"Version: 1.0.0.692 6/1/2010 2:28:04 AM ").Returns(new DateTime(2010, 6, 1, 2, 28, 4));
yield return new TestCaseData(@"Version: 1.0.0.692 04/21/11 12:30am ").Returns(new DateTime(2011, 4, 21, 00, 30, 00));
yield return new TestCaseData(@"Version: 1.0.0.692 04/21/11 12:30pm ").Returns(new DateTime(2011, 4, 21, 12, 30, 00));
yield return new TestCaseData(@"Version: Thu Aug 06 22:32:15 MDT 2009 ").Returns(new DateTime(2009, 8, 6, 22, 32, 15));
}
}
[TestCaseSource("DateTimeTestCases")]
public DateTime should_parse_date(string date)
{
DateTimeRoutines.TryParseDateOrTime(date, DateTimeRoutines.DateTimeFormat.USDate, out var parsedDateTime);
return parsedDateTime.DateTime;
}
}
}

@ -21,6 +21,9 @@ namespace NzbDrone.Core.Parser
public readonly DateTime DateTime; public readonly DateTime DateTime;
public readonly bool IsDateFound; public readonly bool IsDateFound;
public readonly bool IsTimeFound; public readonly bool IsTimeFound;
public readonly TimeSpan UtcOffset;
public readonly bool IsUtcOffsetFound;
public DateTime UtcDateTime;
internal ParsedDateTime(int index_of_date, int length_of_date, int index_of_time, int length_of_time, DateTime date_time) internal ParsedDateTime(int index_of_date, int length_of_date, int index_of_time, int length_of_time, DateTime date_time)
{ {
@ -31,6 +34,46 @@ namespace NzbDrone.Core.Parser
DateTime = date_time; DateTime = date_time;
IsDateFound = index_of_date > -1; IsDateFound = index_of_date > -1;
IsTimeFound = index_of_time > -1; IsTimeFound = index_of_time > -1;
UtcOffset = new TimeSpan(25, 0, 0);
IsUtcOffsetFound = false;
UtcDateTime = new DateTime(1, 1, 1);
}
internal ParsedDateTime(int index_of_date, int length_of_date, int index_of_time, int length_of_time, DateTime date_time, TimeSpan utc_offset)
{
IndexOfDate = index_of_date;
LengthOfDate = length_of_date;
IndexOfTime = index_of_time;
LengthOfTime = length_of_time;
DateTime = date_time;
IsDateFound = index_of_date > -1;
IsTimeFound = index_of_time > -1;
UtcOffset = utc_offset;
IsUtcOffsetFound = Math.Abs(utc_offset.TotalHours) < 12;
if (!IsUtcOffsetFound)
{
UtcDateTime = new DateTime(1, 1, 1);
}
else
{
if (index_of_date < 0)
{
//to avoid negative date exception when date is undefined
var ts = date_time.TimeOfDay + utc_offset;
if (ts < new TimeSpan(0))
{
UtcDateTime = new DateTime(1, 1, 2) + ts;
}
else
{
UtcDateTime = new DateTime(1, 1, 1) + ts;
}
}
else
{
UtcDateTime = date_time + utc_offset;
}
}
} }
} }
@ -78,7 +121,7 @@ namespace NzbDrone.Core.Parser
} }
var date_time = new DateTime(DefaultDate.Year, DefaultDate.Month, DefaultDate.Day, parsed_time.DateTime.Hour, parsed_time.DateTime.Minute, parsed_time.DateTime.Second); var date_time = new DateTime(DefaultDate.Year, DefaultDate.Month, DefaultDate.Day, parsed_time.DateTime.Hour, parsed_time.DateTime.Minute, parsed_time.DateTime.Second);
parsed_date_time = new ParsedDateTime(-1, -1, parsed_time.IndexOfTime, parsed_time.LengthOfTime, date_time); parsed_date_time = new ParsedDateTime(-1, -1, parsed_time.IndexOfTime, parsed_time.LengthOfTime, date_time, parsed_time.UtcOffset);
} }
else else
{ {
@ -90,7 +133,7 @@ namespace NzbDrone.Core.Parser
else else
{ {
var date_time = new DateTime(parsed_date.DateTime.Year, parsed_date.DateTime.Month, parsed_date.DateTime.Day, parsed_time.DateTime.Hour, parsed_time.DateTime.Minute, parsed_time.DateTime.Second); var date_time = new DateTime(parsed_date.DateTime.Year, parsed_date.DateTime.Month, parsed_date.DateTime.Day, parsed_time.DateTime.Hour, parsed_time.DateTime.Minute, parsed_time.DateTime.Second);
parsed_date_time = new ParsedDateTime(parsed_date.IndexOfDate, parsed_date.LengthOfDate, parsed_time.IndexOfTime, parsed_time.LengthOfTime, date_time); parsed_date_time = new ParsedDateTime(parsed_date.IndexOfDate, parsed_date.LengthOfDate, parsed_time.IndexOfTime, parsed_time.LengthOfTime, date_time, parsed_time.UtcOffset);
} }
} }
@ -101,72 +144,137 @@ namespace NzbDrone.Core.Parser
{ {
parsed_time = null; parsed_time = null;
string time_zone_r;
if (default_format == DateTimeFormat.USDate)
{
time_zone_r = @"(?:\s*(?'time_zone'UTC|GMT|CST|EST))?";
}
else
{
time_zone_r = @"(?:\s*(?'time_zone'UTC|GMT))?";
}
Match m; Match m;
if (parsed_date != null && parsed_date.IndexOfDate > -1) if (parsed_date != null && parsed_date.IndexOfDate > -1)
{ {
//look around the found date //look around the found date
//look for <date> [h]h:mm[:ss] [PM/AM] //look for <date> hh:mm:ss <UTC offset>
m = Regex.Match(str.Substring(parsed_date.IndexOfDate + parsed_date.LengthOfDate), @"(?<=^\s*,?\s+|^\s*at\s*|^\s*[T\-]\s*)(?'hour'\d{1,2})\s*:\s*(?'minute'\d{2})\s*(?::\s*(?'second'\d{2}))?(?:\s*(?'ampm'AM|am|PM|pm))?(?=$|[^\d\w])", RegexOptions.Compiled); m = Regex.Match(str.Substring(parsed_date.IndexOfDate + parsed_date.LengthOfDate), @"(?<=^\s*,?\s+|^\s*at\s*|^\s*[T\-]\s*)(?'hour'\d{2})\s*:\s*(?'minute'\d{2})\s*:\s*(?'second'\d{2})\s+(?'offset_sign'[\+\-])(?'offset_hh'\d{2}):?(?'offset_mm'\d{2})(?=$|[^\d\w])", RegexOptions.Compiled);
if (!m.Success)
{
//look for <date> [h]h:mm[:ss] [PM/AM] [UTC/GMT]
m = Regex.Match(str.Substring(parsed_date.IndexOfDate + parsed_date.LengthOfDate), @"(?<=^\s*,?\s+|^\s*at\s*|^\s*[T\-]\s*)(?'hour'\d{1,2})\s*:\s*(?'minute'\d{2})\s*(?::\s*(?'second'\d{2}))?(?:\s*(?'ampm'AM|am|PM|pm))?" + time_zone_r + @"(?=$|[^\d\w])", RegexOptions.Compiled);
}
if (!m.Success)
{
//look for [h]h:mm:ss [PM/AM] [UTC/GMT] <date>
m = Regex.Match(str.Substring(0, parsed_date.IndexOfDate), @"(?<=^|[^\d])(?'hour'\d{1,2})\s*:\s*(?'minute'\d{2})\s*(?::\s*(?'second'\d{2}))?(?:\s*(?'ampm'AM|am|PM|pm))?" + time_zone_r + @"(?=$|[\s,]+)", RegexOptions.Compiled);
}
if (!m.Success) if (!m.Success)
{ {
//look for [h]h:mm:ss <date> //look for [h]h:mm:ss [PM/AM] [UTC/GMT] within <date>
m = Regex.Match(str.Substring(0, parsed_date.IndexOfDate), @"(?<=^|[^\d])(?'hour'\d{1,2})\s*:\s*(?'minute'\d{2})\s*(?::\s*(?'second'\d{2}))?(?:\s*(?'ampm'AM|am|PM|pm))?(?=$|[\s,]+)", RegexOptions.Compiled); m = Regex.Match(str.Substring(parsed_date.IndexOfDate, parsed_date.LengthOfDate), @"(?<=^|[^\d])(?'hour'\d{1,2})\s*:\s*(?'minute'\d{2})\s*(?::\s*(?'second'\d{2}))?(?:\s*(?'ampm'AM|am|PM|pm))?" + time_zone_r + @"(?=$|[\s,]+)", RegexOptions.Compiled);
} }
} }
else else
{ {
//look anywere within string //look anywhere within string
//look for [h]h:mm[:ss] [PM/AM] //look for hh:mm:ss <UTC offset>
m = Regex.Match(str, @"(?<=^|\s+|\s*T\s*)(?'hour'\d{1,2})\s*:\s*(?'minute'\d{2})\s*(?::\s*(?'second'\d{2}))?(?:\s*(?'ampm'AM|am|PM|pm))?(?=$|[^\d\w])", RegexOptions.Compiled); m = Regex.Match(str, @"(?<=^|\s+|\s*T\s*)(?'hour'\d{2})\s*:\s*(?'minute'\d{2})\s*:\s*(?'second'\d{2})\s+(?'offset_sign'[\+\-])(?'offset_hh'\d{2}):?(?'offset_mm'\d{2})?(?=$|[^\d\w])", RegexOptions.Compiled);
if (!m.Success)
{
//look for [h]h:mm[:ss] [PM/AM] [UTC/GMT]
m = Regex.Match(str, @"(?<=^|\s+|\s*T\s*)(?'hour'\d{1,2})\s*:\s*(?'minute'\d{2})\s*(?::\s*(?'second'\d{2}))?(?:\s*(?'ampm'AM|am|PM|pm))?" + time_zone_r + @"(?=$|[^\d\w])", RegexOptions.Compiled);
}
} }
if (m.Success) if (!m.Success)
{ {
try return false;
}
//try
//{
var hour = int.Parse(m.Groups["hour"].Value);
if (hour < 0 || hour > 23)
{
return false;
}
var minute = int.Parse(m.Groups["minute"].Value);
if (minute < 0 || minute > 59)
{
return false;
}
var second = 0;
if (!string.IsNullOrEmpty(m.Groups["second"].Value))
{
second = int.Parse(m.Groups["second"].Value);
if (second < 0 || second > 59)
{ {
var hour = int.Parse(m.Groups["hour"].Value); return false;
if (hour < 0 || hour > 23) }
{ }
return false;
}
var minute = int.Parse(m.Groups["minute"].Value); if (string.Compare(m.Groups["ampm"].Value, "PM", true) == 0 && hour < 12)
if (minute < 0 || minute > 59) {
{ hour += 12;
return false; }
} else if (string.Compare(m.Groups["ampm"].Value, "AM", true) == 0 && hour == 12)
{
hour -= 12;
}
var second = 0; var date_time = new DateTime(1, 1, 1, hour, minute, second);
if (!string.IsNullOrEmpty(m.Groups["second"].Value))
{
second = int.Parse(m.Groups["second"].Value);
if (second < 0 || second > 59)
{
return false;
}
}
if (string.Compare(m.Groups["ampm"].Value, "PM", true) == 0 && hour < 12) if (m.Groups["offset_hh"].Success)
{ {
hour += 12; var offset_hh = int.Parse(m.Groups["offset_hh"].Value);
} var offset_mm = 0;
else if (string.Compare(m.Groups["ampm"].Value, "AM", true) == 0 && hour == 12) if (m.Groups["offset_mm"].Success)
{ {
hour -= 12; offset_mm = int.Parse(m.Groups["offset_mm"].Value);
} }
var date_time = new DateTime(1, 1, 1, hour, minute, second); var utc_offset = new TimeSpan(offset_hh, offset_mm, 0);
parsed_time = new ParsedDateTime(-1, -1, m.Index, m.Length, date_time); if (m.Groups["offset_sign"].Value == "-")
{
utc_offset = -utc_offset;
} }
catch
parsed_time = new ParsedDateTime(-1, -1, m.Index, m.Length, date_time, utc_offset);
return true;
}
if (m.Groups["time_zone"].Success)
{
TimeSpan utc_offset;
switch (m.Groups["time_zone"].Value)
{ {
return false; case "UTC":
case "GMT":
utc_offset = new TimeSpan(0, 0, 0);
break;
case "CST":
utc_offset = new TimeSpan(-6, 0, 0);
break;
case "EST":
utc_offset = new TimeSpan(-5, 0, 0);
break;
default:
throw new Exception("Time zone: " + m.Groups["time_zone"].Value + " is not defined.");
} }
parsed_time = new ParsedDateTime(-1, -1, m.Index, m.Length, date_time, utc_offset);
return true; return true;
} }
return false; parsed_time = new ParsedDateTime(-1, -1, m.Index, m.Length, date_time);
return true;
} }
public static bool TryParseDate(this string str, DateTimeFormat default_format, out ParsedDateTime parsed_date) public static bool TryParseDate(this string str, DateTimeFormat default_format, out ParsedDateTime parsed_date)
@ -230,6 +338,12 @@ namespace NzbDrone.Core.Parser
m = Regex.Match(str, @"(?:^|[^\d\w])(?'year'\d{4})\s+(?'month'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[uarychilestmbro]*\s+(?'day'\d{1,2})(?:-?st|-?th|-?rd|-?nd)?(?=$|[^\d\w])", RegexOptions.Compiled | RegexOptions.IgnoreCase); m = Regex.Match(str, @"(?:^|[^\d\w])(?'year'\d{4})\s+(?'month'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[uarychilestmbro]*\s+(?'day'\d{1,2})(?:-?st|-?th|-?rd|-?nd)?(?=$|[^\d\w])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
} }
if (!m.Success)
{
//look for month dd hh:mm:ss MDT|UTC yyyy
m = Regex.Match(str, @"(?:^|[^\d\w])(?'month'Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[uarychilestmbro]*\s+(?'day'\d{1,2})\s+\d{2}\:\d{2}\:\d{2}\s+(?:MDT|UTC)\s+(?'year'\d{4})(?=$|[^\d\w])", RegexOptions.Compiled | RegexOptions.IgnoreCase);
}
if (!m.Success) if (!m.Success)
{ {
//look for month dd [yyyy] //look for month dd [yyyy]

@ -104,10 +104,10 @@ namespace NzbDrone.Core.Parser
{ {
var dtFormat = format == "UK" ? var dtFormat = format == "UK" ?
DateTimeRoutines.DateTimeFormat.UKDate : DateTimeRoutines.DateTimeFormat.UKDate :
DateTimeRoutines.DateTimeFormat.UKDate; DateTimeRoutines.DateTimeFormat.USDate;
if (DateTimeRoutines.TryParseDateOrTime( if (DateTimeRoutines.TryParseDateOrTime(
str, dtFormat, out var dt)) str, dtFormat, out DateTimeRoutines.ParsedDateTime dt))
{ {
return dt.DateTime; return dt.DateTime;
} }

Loading…
Cancel
Save