Skip to content

Code to parse datetime ranges sent from alexa #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions AlexaSkillsKit.Lib/AlexaSkillsKit.Lib.csproj
Original file line number Diff line number Diff line change
@@ -56,6 +56,9 @@
<Compile Include="Authentication\SpeechletRequestValidationResult.cs" />
<Compile Include="Authentication\SpeechletRequestSignatureVerifier.cs" />
<Compile Include="Authentication\SpeechletRequestTimestampVerifier.cs" />
<Compile Include="DateRangeParser\AlexaDateRange.cs" />
<Compile Include="DateRangeParser\AlexaDateRangeConverter.cs" />
<Compile Include="DateRangeParser\AlexaDateRangeConvertException.cs" />
<Compile Include="HttpHelpers.cs" />
<Compile Include="Json\SpeechletRequestEnvelope.cs" />
<Compile Include="Json\SpeechletResponseEnvelope.cs" />
@@ -91,6 +94,7 @@
<None Include="app.config" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
22 changes: 22 additions & 0 deletions AlexaSkillsKit.Lib/DateRangeParser/AlexaDateRange.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System;

namespace AlexaSkillsKit.DateRangeParser
{
public class AlexaDateRange
{
public DateTime StartDate { get; }

public DateTime EndDate { get; }

// ReSharper disable once UnusedAutoPropertyAccessor.Global
// ReSharper disable once MemberCanBePrivate.Global
public string RangeType { get; }

public AlexaDateRange(DateTime startDate, DateTime endDate, string rangeType)
{
StartDate = startDate;
EndDate = endDate;
RangeType = rangeType;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;
using System.Runtime.Serialization;

namespace AlexaSkillsKit.DateRangeParser
{
[Serializable]
public class AlexaDateRangeConvertException : Exception
{
public AlexaDateRangeConvertException()
{
}

public AlexaDateRangeConvertException(string message) : base(message)
{
}

public AlexaDateRangeConvertException(string message, Exception innerException) : base(message, innerException)
{
}

protected AlexaDateRangeConvertException(SerializationInfo info, StreamingContext context) : base(info, context)
{
}
}
}
165 changes: 165 additions & 0 deletions AlexaSkillsKit.Lib/DateRangeParser/AlexaDateRangeConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
using System;
using System.Diagnostics;
using System.Text.RegularExpressions;

namespace AlexaSkillsKit.DateRangeParser
{
public static class AlexaDateRangeConverter
{
private const string Year = "year";
private const string Week = "week";
private const string Month = "month";
private const string Day = "day";
private const string Quarter = "quarter";

private static readonly string DecadeRegX = $"^(?<{Year}>\\d{{3}})X$";
private static readonly string WeekRegX = $"^(?<{Year}>\\d{{4}})-W(?<{Week}>\\d+)$";
private static readonly string WeekendRegX = $"^(?<{Year}>\\d{{4}})-W(?<{Week}>\\d+)-WE$";
private static readonly string QuarterRegX = $"^(?<{Year}>\\d{{4}})-Q(?<{Quarter}>[1234])$";
private static readonly string MonthRegX = $"^(?<{Year}>\\d{{4}})-(?<{Month}>\\d+)$";
private static readonly string DateRegX = $"^(?<{Year}>\\d{{4}})-(?<{Month}>\\d+)-(?<{Day}>\\d+)$";

private static AlexaDateRange ConvertDecade(Match match, TimeSpan offset)
{
if (!match.Success) throw new AlexaDateRangeConvertException("Expected successful regex match.");
var yearValue = System.Convert.ToInt32(match.Groups[Year].Value);
return new AlexaDateRange(
CreateDate(yearValue * 10, 1, 1, offset, false),
CreateDate((yearValue * 10) + 9, 12, 31, offset, true),
"Decade");
}

private static AlexaDateRange ConvertMonth(Match match, TimeSpan offset)
{
if (!match.Success) throw new AlexaDateRangeConvertException("Expected successful regex match.");
var yearValue = System.Convert.ToInt32(match.Groups[Year].Value);
var monthValue = System.Convert.ToInt32(match.Groups[Month].Value);

var start = CreateDate(yearValue, monthValue, 1, offset, false);
var end = CreateDate(yearValue, monthValue, 1, offset, true).AddMonths(1).AddDays(-1);
return new AlexaDateRange(start, end, "Month");
}

private static AlexaDateRange ConvertDate(Match match, TimeSpan offset)
{
if (!match.Success) throw new AlexaDateRangeConvertException("Expected successful regex match.");
var yearValue = System.Convert.ToInt32(match.Groups[Year].Value);
var monthValue = System.Convert.ToInt32(match.Groups[Month].Value);
var dayValue = System.Convert.ToInt32(match.Groups[Day].Value);

var start = CreateDate(yearValue, monthValue, dayValue, offset, false);
var end = CreateDate(yearValue, monthValue, dayValue, offset, true);
return new AlexaDateRange(start, end, "Date");
}

private static AlexaDateRange ConvertWeek(Match match, TimeSpan offset)
{
if (!match.Success) throw new AlexaDateRangeConvertException("Expected successful regex match.");
var yearValue = System.Convert.ToInt32(match.Groups[Year].Value);
var weekValue = System.Convert.ToInt32(match.Groups[Week].Value);

var start = CreateDate(yearValue, 1, 1, offset, false);
while (start.DayOfWeek != DayOfWeek.Monday)
start = start.AddDays(1);
start = start.AddDays(7 * (weekValue - 1));
var end = CreateDate(start.Year, start.Month, start.Day, offset, true).AddDays(6);

return new AlexaDateRange(start, end, "Week");
}

private static AlexaDateRange ConvertWeekend(Match match, TimeSpan offset)
{
var week = ConvertWeek(match, offset);
return new AlexaDateRange(week.StartDate.AddDays(5), week.EndDate, "Weekend");
}

private static AlexaDateRange ConvertQuarter(Match match, TimeSpan offset)
{
if (!match.Success) throw new AlexaDateRangeConvertException("Expected successful regex match.");
var yearValue = System.Convert.ToInt32(match.Groups[Year].Value);
var quarterValue = System.Convert.ToInt32(match.Groups[Quarter].Value);
var months = new[] { 1, 4, 7, 10 };
var start = CreateDate(yearValue, months[quarterValue - 1], 1, offset, false);
var end = CreateDate(yearValue, months[quarterValue - 1], 1, offset, true).AddMonths(3).AddDays(-1);
return new AlexaDateRange(start, end, "Quarter");
}

private static DateTime CreateDate(int year, int month, int day, TimeSpan offset, bool endOfDay)
{
var tm = new DateTime(2017, 1, 1, 0, 0, 0).Add(-offset);
var tz = new DateTime(
year, month, day,
tm.Hour, tm.Minute, tm.Second, tm.Millisecond,
DateTimeKind.Utc);
return endOfDay ? tz.Add(new TimeSpan(0, 23, 59, 59, 999)) : tz;
}

public class RegExTry
{
private readonly string _regEx;
private readonly Func<Match, TimeSpan, AlexaDateRange> _converter;
public string Name { get; set; }

public AlexaDateRange TryConvert(string dateString, TimeSpan offset)
{
if (string.IsNullOrEmpty(dateString)) throw new ArgumentNullException(nameof(dateString));

var regex = new Regex(_regEx);
var match = regex.Match(dateString);
if (!match.Success)
return null;

var converted = _converter(match, offset);
Debug.WriteLine($"Found match for {Name}, date range {converted.StartDate} to {converted.EndDate}");
return converted;
}

public RegExTry(string regEx, Func<Match, TimeSpan, AlexaDateRange> converter, string name)
{
_regEx = regEx;
_converter = converter;
Name = name;
}
}

public static readonly RegExTry[] RegexesToTry =
{
new RegExTry(DecadeRegX, ConvertDecade, "Decade"),
new RegExTry(WeekRegX, ConvertWeek, "Week"),
new RegExTry(WeekendRegX, ConvertWeekend, "Weekend"),
new RegExTry(MonthRegX, ConvertMonth, "Month"),
new RegExTry(DateRegX, ConvertDate, "Date"),
new RegExTry(QuarterRegX, ConvertQuarter, "Quarter"),
};

/// <summary>
/// Convert an Alexa date format to a date range.
/// </summary>
/// <param name="date">The date string from Alexa</param>
/// <returns>AlexaDateRange</returns>
public static AlexaDateRange Convert(string date)
{
return Convert(date, TimeSpan.Zero);
}

/// <summary>
/// Convert an Alexa date format to a date range.
/// </summary>
/// <param name="date">The date string from Alexa</param>
/// <param name="offset">The timezone offset from UTC time</param>
/// <returns>AlexaDateRange</returns>
public static AlexaDateRange Convert(string date, TimeSpan offset)
{
if (string.IsNullOrEmpty(date)) throw new ArgumentNullException(nameof(date));

foreach (var rex in RegexesToTry)
{
AlexaDateRange gotRange;
if ((gotRange = rex.TryConvert(date, offset)) != null)
return gotRange;
}

return null;
}
}
}
1 change: 1 addition & 0 deletions AlexaSkillsKit.Tests/AlexaSkillsKit.Tests.csproj
Original file line number Diff line number Diff line change
@@ -66,6 +66,7 @@
</Choose>
<ItemGroup>
<Compile Include="Authentication\SignatureVerifierTests.cs" />
<Compile Include="DateRangeParser\AlexaDateConvertTests.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
</ItemGroup>
<ItemGroup>
101 changes: 101 additions & 0 deletions AlexaSkillsKit.Tests/DateRangeParser/AlexaDateConvertTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
using System;
using System.Linq;
using AlexaSkillsKit.DateRangeParser;
using Xunit;

namespace AlexaSkillsKit.Tests.DateRangeParser
{
public class AmazonDateConvertTests
{
[Fact]
public void TestDateRangeConverters()
{
var r = AlexaDateRangeConverter.Convert("201X");
Assert.True(new DateTime(2010, 1, 1, 0, 0, 0) == r.StartDate);

r = AlexaDateRangeConverter.Convert("2017-W01");
Assert.True(new DateTime(2017, 1, 2, 0, 0, 0) == r.StartDate);

r = AlexaDateRangeConverter.Convert("2017-01");
Assert.True(new DateTime(2017, 1, 1, 0, 0, 0) == r.StartDate);

r = AlexaDateRangeConverter.Convert("2017-02");
Assert.True(new DateTime(2017, 2, 1, 0, 0, 0) == r.StartDate);

r = AlexaDateRangeConverter.Convert("2016-Q4");
Assert.True(new DateTime(2016, 10, 1, 0, 0, 0) == r.StartDate);

r = AlexaDateRangeConverter.Convert("2017-W02-WE");
Assert.True(new DateTime(2017, 1, 14, 0, 0, 0) == r.StartDate);
}

[Fact]
public void TestDecade()
{
var rexTry = AlexaDateRangeConverter.RegexesToTry.First(rt => rt.Name == "Decade");
var r = rexTry.TryConvert("201X", TimeSpan.Zero);
Assert.True(new DateTime(2010, 1, 1, 0, 0, 0) == r.StartDate);
Assert.True(new DateTime(2019, 12, 31, 23, 59, 59, 999) == r.EndDate);
}

[Fact]
public void TestWeek()
{
var rexTry = AlexaDateRangeConverter.RegexesToTry.First(rt => rt.Name == "Week");
var r = rexTry.TryConvert("2017-W01", TimeSpan.Zero);
Assert.True(new DateTime(2017, 1, 2, 0, 0, 0) == r.StartDate);
Assert.True(new DateTime(2017, 1, 8, 23, 59, 59, 999) == r.EndDate);

rexTry = AlexaDateRangeConverter.RegexesToTry.First(rt => rt.Name == "Week");
r = rexTry.TryConvert("2017-W02", TimeSpan.Zero);
Assert.True(new DateTime(2017, 1, 9, 0, 0, 0) == r.StartDate);
Assert.True(new DateTime(2017, 1, 15, 23, 59, 59, 999) == r.EndDate);
}

[Fact]
public void TestWeekend()
{
var rexTry = AlexaDateRangeConverter.RegexesToTry.First(rt => rt.Name == "Weekend");
var r = rexTry.TryConvert("2017-W01-WE", TimeSpan.Zero);
Assert.True(new DateTime(2017, 1, 7, 0, 0, 0) == r.StartDate);
Assert.True(new DateTime(2017, 1, 8, 23, 59, 59, 999) == r.EndDate);

rexTry = AlexaDateRangeConverter.RegexesToTry.First(rt => rt.Name == "Weekend");
r = rexTry.TryConvert("2017-W02-WE", TimeSpan.Zero);
Assert.True(new DateTime(2017, 1, 14, 0, 0, 0) == r.StartDate);
Assert.True(new DateTime(2017, 1, 15, 23, 59, 59, 999) == r.EndDate);
}

[Fact]
public void TestMonth()
{
var rexTry = AlexaDateRangeConverter.RegexesToTry.First(rt => rt.Name == "Month");
var r = rexTry.TryConvert("2017-01", TimeSpan.Zero);
Assert.True(new DateTime(2017, 1, 1, 0, 0, 0) == r.StartDate);
Assert.True(new DateTime(2017, 1, 31, 23, 59, 59, 999) == r.EndDate);

rexTry = AlexaDateRangeConverter.RegexesToTry.First(rt => rt.Name == "Month");
r = rexTry.TryConvert("2017-02", TimeSpan.Zero);
Assert.True(new DateTime(2017, 2, 1, 0, 0, 0) == r.StartDate);
Assert.True(new DateTime(2017, 2, 28, 23, 59, 59, 999) == r.EndDate);
}

[Fact]
public void TestDate()
{
var rexTry = AlexaDateRangeConverter.RegexesToTry.First(rt => rt.Name == "Date");
var r = rexTry.TryConvert("2017-01-02", TimeSpan.Zero);
Assert.True(new DateTime(2017, 1, 2, 0, 0, 0) == r.StartDate);
Assert.True(new DateTime(2017, 1, 2, 23, 59, 59, 999) == r.EndDate);
}

[Fact]
public void TestQuarter()
{
var rexTry = AlexaDateRangeConverter.RegexesToTry.First(rt => rt.Name == "Quarter");
var r = rexTry.TryConvert("2016-Q4", TimeSpan.Zero);
Assert.True(new DateTime(2016, 10, 1, 0, 0, 0) == r.StartDate);
Assert.True(new DateTime(2016, 12, 31, 23, 59, 59, 999) == r.EndDate);
}
}
}