Skip to content

Commit 66901d1

Browse files
MichaelMWWMichael Wang (Centific Technologies Inc)
and
Michael Wang (Centific Technologies Inc)
authored
[EN DateTimeV2] Support [duration]+starting+[datetime] pattern as date range or date time range (#3178)
* DateTimeForLongerSpanForStarting - Initial implemention * Support duration starting datetime pattern - initial commit * Support duration starting datetime pattern - update test cases not supported attribute --------- Co-authored-by: Michael Wang (Centific Technologies Inc) <[email protected]>
1 parent 18fe661 commit 66901d1

16 files changed

+427
-2
lines changed

.NET/Microsoft.Recognizers.Definitions.Common/English/DateTimeDefinitions.cs

+1
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ public static class DateTimeDefinitions
256256
public const string AroundRegex = @"(?:\b(?:around|circa)\s*?\b)(\s+the)?";
257257
public static readonly string BeforeRegex = $@"((\b{InclusiveModPrepositions}?(?:before|in\s+advance\s+of|prior\s+to|(no\s+later|earlier|sooner)\s+than|ending\s+(with|on)|by|(un)?till?|(?<include>as\s+late\s+as)){InclusiveModPrepositions}?\b\s*?)|(?<!\w|>)((?<include><\s*=)|<))(\s+the)?";
258258
public static readonly string AfterRegex = $@"((\b{InclusiveModPrepositions}?((after(\s+on)?(?!\sfrom)|(?<!no\s+)later\s+than)|((year\s+)?greater\s+than))(?!\s+or\s+equal\s+to){InclusiveModPrepositions}?\b\s*?)|(?<!\w|<)((?<include>>\s*=)|>))(\s+the)?";
259+
public const string StartingRegex = @"(starting|beginning)(\s+)?(?:from|on|with)?";
259260
public const string SinceRegex = @"(?:(?:\b(?:since|after\s+or\s+equal\s+to|(starting|beginning)(\s)?(?:from|on|with)?|as\s+early\s+as|(any\s+time\s+)from)\b\s*?)|(?<!\w|<)(>=))(\s+the)?";
260261
public static readonly string SinceRegexExp = $@"({SinceRegex}|\bfrom(\s+the)?\b)";
261262
public const string AgoRegex = @"\b(ago|earlier|before\s+(?<day>yesterday|today))\b";

.NET/Microsoft.Recognizers.Text.DateTime/English/Extractors/EnglishDatePeriodExtractorConfiguration.cs

+3
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,9 @@ public class EnglishDatePeriodExtractorConfiguration : BaseDateTimeOptionsConfig
175175
public static readonly Regex OfYearRegex =
176176
new Regex(DateTimeDefinitions.OfYearRegex, RegexFlags, RegexTimeOut);
177177

178+
public static readonly Regex StartingRegex =
179+
new Regex(DateTimeDefinitions.StartingRegex, RegexFlags, RegexTimeOut);
180+
178181
private const RegexOptions RegexFlags = RegexOptions.Singleline | RegexOptions.ExplicitCapture;
179182

180183
private static readonly Regex FromTokenRegex =

.NET/Microsoft.Recognizers.Text.DateTime/English/Extractors/EnglishDateTimePeriodExtractorConfiguration.cs

+3
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ public class EnglishDateTimePeriodExtractorConfiguration : BaseDateTimeOptionsCo
6262
public static readonly Regex TasksmodeMealTimeofDayRegex =
6363
new Regex(DateTimeDefinitions.TasksmodeMealTimeofDayRegex, RegexFlags, RegexTimeOut);
6464

65+
public static readonly Regex StartingRegex =
66+
new Regex(DateTimeDefinitions.StartingRegex, RegexFlags, RegexTimeOut);
67+
6568
private const RegexOptions RegexFlags = RegexOptions.Singleline | RegexOptions.ExplicitCapture;
6669

6770
private static readonly Regex[] SimpleCases =

.NET/Microsoft.Recognizers.Text.DateTime/English/Parsers/EnglishDatePeriodParserConfiguration.cs

+3
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ public EnglishDatePeriodParserConfiguration(ICommonDateTimeParserConfiguration c
107107
NowRegex = NowParseRegex;
108108
SpecialDayRegex = EnglishDateExtractorConfiguration.SpecialDayRegex;
109109
TodayNowRegex = new Regex(DateTimeDefinitions.TodayNowRegex, RegexOptions.Singleline, RegexTimeOut);
110+
StartingRegex = EnglishDatePeriodExtractorConfiguration.StartingRegex;
110111

111112
UnitMap = config.UnitMap;
112113
CardinalMap = config.CardinalMap;
@@ -227,6 +228,8 @@ public EnglishDatePeriodParserConfiguration(ICommonDateTimeParserConfiguration c
227228

228229
public Regex OfYearRegex { get; }
229230

231+
public Regex StartingRegex { get; }
232+
230233
Regex ISimpleDatePeriodParserConfiguration.RelativeRegex => RelativeRegex;
231234

232235
Regex IDatePeriodParserConfiguration.NextPrefixRegex => NextPrefixRegex;

.NET/Microsoft.Recognizers.Text.DateTime/English/Parsers/EnglishDateTimePeriodParserConfiguration.cs

+3
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ public EnglishDateTimePeriodParserConfiguration(ICommonDateTimeParserConfigurati
6969
AfterRegex = EnglishDateTimePeriodExtractorConfiguration.AfterRegex;
7070
UnitMap = config.UnitMap;
7171
Numbers = config.Numbers;
72+
StartingRegex = EnglishDateTimePeriodExtractorConfiguration.StartingRegex;
7273

7374
TasksmodeMealTimeofDayRegex = EnglishDateTimePeriodExtractorConfiguration.TasksmodeMealTimeofDayRegex;
7475
}
@@ -143,6 +144,8 @@ public EnglishDateTimePeriodParserConfiguration(ICommonDateTimeParserConfigurati
143144

144145
public Regex TasksmodeMealTimeofDayRegex { get; }
145146

147+
public Regex StartingRegex { get; }
148+
146149
bool IDateTimePeriodParserConfiguration.CheckBothBeforeAfter => DateTimeDefinitions.CheckBothBeforeAfter;
147150

148151
public IImmutableDictionary<string, string> UnitMap { get; }

.NET/Microsoft.Recognizers.Text.DateTime/Extractors/BaseDatePeriodExtractor.cs

+19
Original file line numberDiff line numberDiff line change
@@ -746,6 +746,25 @@ private List<Token> SingleTimePointWithPatterns(string text, List<ExtractResult>
746746
ret.AddRange(GetTokenForRegexMatching(beforeString, EnglishDatePeriodExtractorConfiguration.ForPrefixRegex, extractionResult, inPrefix: true));
747747
}
748748
}
749+
750+
// For cases like xx weeks/days starting (from) a date point
751+
if (this.config as EnglishDatePeriodExtractorConfiguration != null)
752+
{
753+
var match = EnglishDatePeriodExtractorConfiguration.StartingRegex.MatchEnd(beforeString, true);
754+
if (match.Success)
755+
{
756+
var durationERs = this.config.DurationExtractor.Extract(beforeString);
757+
if (durationERs.Count >= 1)
758+
{
759+
var lastDuration = durationERs[durationERs.Count - 1];
760+
string startingWord = beforeString.Substring(beforeString.LastIndexOf(lastDuration.Text, StringComparison.Ordinal) + lastDuration.Text.Length);
761+
if (startingWord.Trim() == match.Value.Trim())
762+
{
763+
ret.Add(new Token(lastDuration.Start ?? 0, (extractionResult.Start ?? 0) + (extractionResult.Length ?? 0)));
764+
}
765+
}
766+
}
767+
}
749768
}
750769
}
751770

.NET/Microsoft.Recognizers.Text.DateTime/Extractors/BaseDateTimePeriodExtractor.cs

+35-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
using System.Collections.Generic;
66
using System.Linq;
77
using System.Text.RegularExpressions;
8-
8+
using Microsoft.Recognizers.Text.DateTime.English;
99
using Microsoft.Recognizers.Text.Utilities;
1010
using DateObject = System.DateTime;
1111

@@ -52,6 +52,9 @@ public List<ExtractResult> Extract(string text, DateObject reference)
5252
tokens.AddRange(MatchDateWithPeriodPrefix(text, reference, new List<ExtractResult>(dateErs)));
5353
tokens.AddRange(MergeDateWithTimePeriodSuffix(text, new List<ExtractResult>(dateErs), new List<ExtractResult>(timeErs)));
5454

55+
// Extracting cases like [duration] starting [datetime]
56+
tokens.AddRange(MatchStartingWithDuration(text, reference));
57+
5558
var ers = Token.MergeAllTokens(tokens, text, ExtractorName);
5659

5760
if ((this.config.Options & DateTimeOptions.EnablePreview) != 0)
@@ -820,5 +823,36 @@ private List<Token> MatchPureNumberCases(string text, Token tok, bool before)
820823

821824
return ret;
822825
}
826+
827+
private List<Token> MatchStartingWithDuration(string text, DateObject reference)
828+
{
829+
var ret = new List<Token>();
830+
831+
if (this.config as EnglishDateTimePeriodExtractorConfiguration != null
832+
&& EnglishDateTimePeriodExtractorConfiguration.StartingRegex.Match(text).Success)
833+
{
834+
var dateTimeERs = this.config.SingleDateTimeExtractor.Extract(text, reference);
835+
foreach (var dateTimeER in dateTimeERs)
836+
{
837+
var beforeString = text.Substring(0, (int)dateTimeER.Start);
838+
var match = EnglishDatePeriodExtractorConfiguration.StartingRegex.MatchEnd(beforeString, true);
839+
if (match.Success)
840+
{
841+
var durationERs = this.config.DurationExtractor.Extract(beforeString);
842+
if (durationERs.Count >= 1)
843+
{
844+
var lastDuration = durationERs[durationERs.Count - 1];
845+
string startingWord = beforeString.Substring(beforeString.LastIndexOf(lastDuration.Text, StringComparison.Ordinal) + lastDuration.Text.Length);
846+
if (startingWord.Trim() == match.Value.Trim())
847+
{
848+
ret.Add(new Token((int)lastDuration.Start, (int)dateTimeER.Start + (int)dateTimeER.Length));
849+
}
850+
}
851+
}
852+
}
853+
}
854+
855+
return ret;
856+
}
823857
}
824858
}

.NET/Microsoft.Recognizers.Text.DateTime/Extractors/BaseMergedDateTimeExtractor.cs

-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
using System.Collections.Generic;
66
using System.Linq;
77
using System.Text.RegularExpressions;
8-
98
using Microsoft.Recognizers.Text.Matcher;
109
using Microsoft.Recognizers.Text.Utilities;
1110
using DateObject = System.DateTime;

.NET/Microsoft.Recognizers.Text.DateTime/Parsers/BaseDatePeriodParser.cs

+52
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,12 @@ private DateTimeResolutionResult ParseBaseDatePeriod(string text, DateObject ref
416416
innerResult = ParseOneWordPeriod(text, referenceDate);
417417
}
418418

419+
// Cases like "x weeks/days starting (from) today/12 sep etc."
420+
if (!innerResult.Success)
421+
{
422+
innerResult = ParseStartingWithDuration(text, referenceDate);
423+
}
424+
419425
if (!innerResult.Success)
420426
{
421427
innerResult = MergeTwoTimePoints(text, referenceDate);
@@ -686,6 +692,52 @@ private DateTimeResolutionResult ParseDatePointWithForPrefix(string text, DateOb
686692
return ret;
687693
}
688694

695+
// Only handle cases like "x weeks/days starting (from) today/tomorrow/some day"
696+
private DateTimeResolutionResult ParseStartingWithDuration(string text, DateObject referenceDate)
697+
{
698+
var ret = new DateTimeResolutionResult();
699+
var dateER = this.config.DateExtractor.Extract(text, referenceDate);
700+
var enConfig = this.config as EnglishDatePeriodParserConfiguration;
701+
702+
if (enConfig != null && enConfig.StartingRegex.Match(text).Success && dateER.Count == 1)
703+
{
704+
var beforeString = text.Substring(0, (int)dateER[0].Start);
705+
706+
if (!string.IsNullOrEmpty(beforeString) && enConfig.StartingRegex.MatchEnd(beforeString, true).Success)
707+
{
708+
var pr = this.config.DateParser.Parse(dateER[0], referenceDate);
709+
var durationER = this.config.DurationExtractor.Extract(beforeString, referenceDate);
710+
711+
if (durationER.Count == 1)
712+
{
713+
var duration = this.config.DurationParser.Parse(durationER[0]);
714+
var durationInSeconds = (double)((DateTimeResolutionResult)duration.Value).PastValue;
715+
716+
DateObject startDate;
717+
DateObject endDate;
718+
719+
startDate = (DateObject)((DateTimeResolutionResult)pr.Value).PastValue;
720+
endDate = startDate.AddSeconds(durationInSeconds);
721+
722+
if (startDate != DateObject.MinValue)
723+
{
724+
var startLuisStr = DateTimeFormatUtil.LuisDate(startDate);
725+
var endLuisStr = DateTimeFormatUtil.LuisDate(endDate);
726+
var durationTimex = ((DateTimeResolutionResult)duration.Value).Timex;
727+
728+
ret.Timex = $"({startLuisStr},{endLuisStr},{durationTimex})";
729+
ret.FutureValue = new Tuple<DateObject, DateObject>(startDate, endDate);
730+
ret.PastValue = new Tuple<DateObject, DateObject>(startDate, endDate);
731+
ret.SubDateTimeEntities = new List<object> { pr, duration };
732+
ret.Success = true;
733+
}
734+
}
735+
}
736+
}
737+
738+
return ret;
739+
}
740+
689741
private DateTimeResolutionResult ParseSingleTimePoint(string text, DateObject referenceDate, DateContext dateContext = null)
690742
{
691743
var ret = new DateTimeResolutionResult();

.NET/Microsoft.Recognizers.Text.DateTime/Parsers/BaseDateTimePeriodParser.cs

+52
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Linq;
88
using System.Reflection;
99
using System.Text.RegularExpressions;
10+
using Microsoft.Recognizers.Text.DateTime.English;
1011
using Microsoft.Recognizers.Text.Utilities;
1112
using DateObject = System.DateTime;
1213

@@ -170,6 +171,12 @@ protected DateTimeResolutionResult InternalParse(string entityText, DateObject r
170171
innerResult = ParseDateWithTimePeriodSuffix(entityText, referenceTime);
171172
}
172173

174+
if (!innerResult.Success)
175+
{
176+
// Parsing cases like [duration] starting [datetime]
177+
innerResult = ParseStartingWithDuration(entityText, referenceTime);
178+
}
179+
173180
if (!innerResult.Success)
174181
{
175182
innerResult = ParseDuration(entityText, referenceTime);
@@ -1378,6 +1385,51 @@ private DateTimeResolutionResult ParseDuration(string text, DateObject reference
13781385
return ret;
13791386
}
13801387

1388+
private DateTimeResolutionResult ParseStartingWithDuration(string text, DateObject referenceTime)
1389+
{
1390+
var ret = new DateTimeResolutionResult();
1391+
var datetimeERs = Config.DateTimeExtractor.Extract(text, referenceTime);
1392+
var enConfig = Config as EnglishDateTimePeriodParserConfiguration;
1393+
1394+
if (enConfig != null && enConfig.StartingRegex.Match(text).Success && datetimeERs.Count == 1)
1395+
{
1396+
var beforeString = text.Substring(0, (int)datetimeERs[0].Start);
1397+
1398+
if (!string.IsNullOrEmpty(beforeString) && enConfig.StartingRegex.MatchEnd(beforeString, true).Success)
1399+
{
1400+
var pr = Config.DateTimeParser.Parse(datetimeERs[0], referenceTime);
1401+
var durationERs = Config.DurationExtractor.Extract(beforeString, referenceTime);
1402+
1403+
if (durationERs.Count == 1)
1404+
{
1405+
var duration = Config.DurationParser.Parse(durationERs[0]);
1406+
var durationInSeconds = (double)((DateTimeResolutionResult)duration.Value).PastValue;
1407+
1408+
DateObject startDate;
1409+
DateObject endDate;
1410+
1411+
startDate = (DateObject)((DateTimeResolutionResult)pr.Value).PastValue;
1412+
endDate = startDate.AddSeconds(durationInSeconds);
1413+
1414+
if (startDate != DateObject.MinValue)
1415+
{
1416+
var startLuisStr = $"{DateTimeFormatUtil.LuisDate(startDate)}{DateTimeFormatUtil.ShortTime(startDate.Hour, startDate.Minute, startDate.Second)}";
1417+
var endLuisStr = $"{DateTimeFormatUtil.LuisDate(endDate)}{DateTimeFormatUtil.ShortTime(endDate.Hour, endDate.Minute, endDate.Second)}";
1418+
var durationTimex = ((DateTimeResolutionResult)duration.Value).Timex;
1419+
1420+
ret.Timex = $"({startLuisStr},{endLuisStr},{durationTimex})";
1421+
ret.FutureValue = new Tuple<DateObject, DateObject>(startDate, endDate);
1422+
ret.PastValue = new Tuple<DateObject, DateObject>(startDate, endDate);
1423+
ret.SubDateTimeEntities = new List<object> { pr, duration };
1424+
ret.Success = true;
1425+
}
1426+
}
1427+
}
1428+
}
1429+
1430+
return ret;
1431+
}
1432+
13811433
// Parse "last minute", "next hour"
13821434
private DateTimeResolutionResult ParseRelativeUnit(string text, DateObject referenceTime)
13831435
{

Patterns/English/English-DateTime.yaml

+2
Original file line numberDiff line numberDiff line change
@@ -613,6 +613,8 @@ BeforeRegex: !nestedRegex
613613
AfterRegex: !nestedRegex
614614
def: ((\b{InclusiveModPrepositions}?((after(\s+on)?(?!\sfrom)|(?<!no\s+)later\s+than)|((year\s+)?greater\s+than))(?!\s+or\s+equal\s+to){InclusiveModPrepositions}?\b\s*?)|(?<!\w|<)((?<include>>\s*=)|>))(\s+the)?
615615
references: [ InclusiveModPrepositions ]
616+
StartingRegex: !simpleRegex
617+
def: (starting|beginning)(\s+)?(?:from|on|with)?
616618
SinceRegex: !simpleRegex
617619
def: (?:(?:\b(?:since|after\s+or\s+equal\s+to|(starting|beginning)(\s)?(?:from|on|with)?|as\s+early\s+as|(any\s+time\s+)from)\b\s*?)|(?<!\w|<)(>=))(\s+the)?
618620
SinceRegexExp: !nestedRegex

Specs/DateTime/English/DatePeriodExtractor.json

+36
Original file line numberDiff line numberDiff line change
@@ -3897,6 +3897,42 @@
38973897
}
38983898
]
38993899
},
3900+
{
3901+
"Input": "Schedule Out of office replies for 3 days starting from next Monday",
3902+
"NotSupported": "java, javascript, python",
3903+
"Results": [
3904+
{
3905+
"Text": "3 days starting from next Monday",
3906+
"Type": "daterange",
3907+
"Start": 35,
3908+
"Length": 32
3909+
}
3910+
]
3911+
},
3912+
{
3913+
"Input": "set ooo for a week starting tomorrow",
3914+
"NotSupported": "java, javascript, python",
3915+
"Results": [
3916+
{
3917+
"Text": "a week starting tomorrow",
3918+
"Type": "daterange",
3919+
"Start": 12,
3920+
"Length": 24
3921+
}
3922+
]
3923+
},
3924+
{
3925+
"Input": "set ooo for 2 weeks starting May 20th",
3926+
"NotSupported": "java, javascript, python",
3927+
"Results": [
3928+
{
3929+
"Text": "2 weeks starting may 20th",
3930+
"Type": "daterange",
3931+
"Start": 12,
3932+
"Length": 25
3933+
}
3934+
]
3935+
},
39003936
{
39013937
"Input": "please schedule a meeting for the week starting on february 4",
39023938
"Results": [

0 commit comments

Comments
 (0)