private static DateTimeResolutionResult GetAgoLaterResult( DateTimeParseResult durationParseResult, string afterStr, string beforeStr, DateObject referenceTime, IParser numberParser, IDateTimeUtilityConfiguration utilityConfiguration, AgoLaterMode mode, SwiftDayDelegate swiftDay) { var ret = new DateTimeResolutionResult(); var resultDateTime = referenceTime; var timex = durationParseResult.TimexStr; if (((DateTimeResolutionResult)durationParseResult.Value).Mod == Constants.MORE_THAN_MOD) { ret.Mod = Constants.MORE_THAN_MOD; } else if (((DateTimeResolutionResult)durationParseResult.Value).Mod == Constants.LESS_THAN_MOD) { ret.Mod = Constants.LESS_THAN_MOD; } if (MatchingUtil.ContainsAgoLaterIndex(afterStr, utilityConfiguration.AgoRegex)) { var match = utilityConfiguration.AgoRegex.Match(afterStr); var swift = 0; // Handle cases like "3 days before yesterday" if (match.Success && !string.IsNullOrEmpty(match.Groups["day"].Value)) { swift = swiftDay(match.Groups["day"].Value); } else if (utilityConfiguration.CheckBothBeforeAfter && match.Success && !MatchingUtil.ContainsAgoLaterIndexInBeforeString(beforeStr, utilityConfiguration.AgoRegex)) { match = utilityConfiguration.AgoRegex.Match(beforeStr + " " + afterStr); if (match.Success && !string.IsNullOrEmpty(match.Groups["day"].Value)) { swift = swiftDay(match.Groups["day"].Value); } } resultDateTime = DurationParsingUtil.ShiftDateTime(timex, referenceTime.AddDays(swift), false); ((DateTimeResolutionResult)durationParseResult.Value).Mod = Constants.BEFORE_MOD; } else if (utilityConfiguration.CheckBothBeforeAfter && MatchingUtil.ContainsAgoLaterIndexInBeforeString(beforeStr, utilityConfiguration.AgoRegex)) { var match = utilityConfiguration.AgoRegex.Match(beforeStr); var swift = 0; // Handle cases like "3 days before yesterday" if (match.Success && !string.IsNullOrEmpty(match.Groups["day"].Value)) { swift = swiftDay(match.Groups["day"].Value); } resultDateTime = DurationParsingUtil.ShiftDateTime(timex, referenceTime.AddDays(swift), false); ((DateTimeResolutionResult)durationParseResult.Value).Mod = Constants.BEFORE_MOD; } else if (MatchingUtil.ContainsAgoLaterIndex(afterStr, utilityConfiguration.LaterRegex) || MatchingUtil.ContainsTermIndex(beforeStr, utilityConfiguration.InConnectorRegex) || (utilityConfiguration.CheckBothBeforeAfter && MatchingUtil.ContainsAgoLaterIndexInBeforeString(beforeStr, utilityConfiguration.LaterRegex))) { var match = utilityConfiguration.LaterRegex.Match(afterStr); var swift = 0; if (utilityConfiguration.CheckBothBeforeAfter && MatchingUtil.ContainsAgoLaterIndexInBeforeString(beforeStr, utilityConfiguration.LaterRegex) && string.IsNullOrEmpty(match.Groups["day"].Value)) { match = utilityConfiguration.LaterRegex.Match(beforeStr); } // Handle cases like "3 days after tomorrow" if (match.Success && !string.IsNullOrEmpty(match.Groups["day"].Value)) { swift = swiftDay(match.Groups["day"].Value); } var yearMatch = utilityConfiguration.SinceYearSuffixRegex.Match(afterStr); if (yearMatch.Success) { var yearString = yearMatch.Groups[Constants.YearGroupName].Value; var yearEr = new ExtractResult { Text = yearString }; var year = Convert.ToInt32((double)(numberParser.Parse(yearEr).Value ?? 0)); referenceTime = DateObject.MinValue.SafeCreateFromValue(year, 1, 1); } resultDateTime = DurationParsingUtil.ShiftDateTime(timex, referenceTime.AddDays(swift), true); ((DateTimeResolutionResult)durationParseResult.Value).Mod = Constants.AFTER_MOD; } else if (utilityConfiguration.CheckBothBeforeAfter && (MatchingUtil.ContainsAgoLaterIndexInBeforeString(beforeStr, utilityConfiguration.LaterRegex) || MatchingUtil.ContainsAgoLaterIndex(afterStr, utilityConfiguration.InConnectorRegex) || MatchingUtil.ContainsAgoLaterIndex(afterStr, utilityConfiguration.LaterRegex))) { // Check also beforeStr var match = utilityConfiguration.LaterRegex.Match(beforeStr); var swift = 0; if (MatchingUtil.ContainsAgoLaterIndex(afterStr, utilityConfiguration.LaterRegex) && string.IsNullOrEmpty(match.Groups["day"].Value)) { match = utilityConfiguration.LaterRegex.Match(beforeStr); } // Handle cases like "3 days after tomorrow" if (match.Success && !string.IsNullOrEmpty(match.Groups["day"].Value)) { swift = swiftDay(match.Groups["day"].Value); } var yearMatch = utilityConfiguration.SinceYearSuffixRegex.Match(beforeStr); if (yearMatch.Success) { var yearString = yearMatch.Groups[Constants.YearGroupName].Value; var yearEr = new ExtractResult { Text = yearString }; var year = Convert.ToInt32((double)(numberParser.Parse(yearEr).Value ?? 0)); referenceTime = DateObject.MinValue.SafeCreateFromValue(year, 1, 1); } resultDateTime = DurationParsingUtil.ShiftDateTime(timex, referenceTime.AddDays(swift), true); ((DateTimeResolutionResult)durationParseResult.Value).Mod = Constants.AFTER_MOD; } if (resultDateTime != referenceTime) { if (mode.Equals(AgoLaterMode.Date)) { ret.Timex = $"{DateTimeFormatUtil.LuisDate(resultDateTime)}"; } else if (mode.Equals(AgoLaterMode.DateTime)) { ret.Timex = $"{DateTimeFormatUtil.LuisDateTime(resultDateTime)}"; } ret.FutureValue = ret.PastValue = resultDateTime; ret.SubDateTimeEntities = new List <object> { durationParseResult }; ret.Success = true; } return(ret); }
private DateTimeResolutionResult ParseSimpleCases(string text, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); int year = referenceTime.Year, month = referenceTime.Month, day = referenceTime.Day; var trimedText = text.Trim().ToLower(); var match = this.config.PureNumberFromToRegex.Match(trimedText); if (!match.Success) { match = this.config.PureNumberBetweenAndRegex.Match(trimedText); } if (match.Success && match.Index == 0) { // this "from .. to .." pattern is valid if followed by a Date OR "pm" var isValid = false; // get hours var hourGroup = match.Groups["hour"]; var hourStr = hourGroup.Captures[0].Value; if (!this.config.Numbers.TryGetValue(hourStr, out int beginHour)) { beginHour = int.Parse(hourStr); } hourStr = hourGroup.Captures[1].Value; if (!this.config.Numbers.TryGetValue(hourStr, out int endHour)) { endHour = int.Parse(hourStr); } // parse "pm" var leftDesc = match.Groups["leftDesc"].Value; var rightDesc = match.Groups["rightDesc"].Value; var pmStr = match.Groups["pm"].Value; var amStr = match.Groups["am"].Value; var descStr = match.Groups["desc"].Value; // The "ampm" only occurs in time, we don't have to consider it here if (string.IsNullOrEmpty(leftDesc)) { bool rightAmValid = !string.IsNullOrEmpty(rightDesc) && config.UtilityConfiguration.AmDescRegex.Match(rightDesc.ToLower()).Success; bool rightPmValid = !string.IsNullOrEmpty(rightDesc) && config.UtilityConfiguration.PmDescRegex.Match(rightDesc.ToLower()).Success; if (!string.IsNullOrEmpty(amStr) || rightAmValid) { if (endHour >= 12) { endHour -= 12; } if (beginHour >= 12 && beginHour - 12 < endHour) { beginHour -= 12; } // Resolve case like "11 to 3am" if (beginHour < 12 && beginHour > endHour) { beginHour += 12; } isValid = true; } else if (!string.IsNullOrEmpty(pmStr) || rightPmValid) { if (endHour < 12) { endHour += 12; } // Resolve case like "11 to 3pm" if (beginHour + 12 < endHour) { beginHour += 12; } isValid = true; } } if (isValid) { var beginStr = "T" + beginHour.ToString("D2"); var endStr = "T" + endHour.ToString("D2"); if (endHour >= beginHour) { ret.Timex = $"({beginStr},{endStr},PT{endHour - beginHour}H)"; } else { ret.Timex = $"({beginStr},{endStr},PT{endHour - beginHour + 24}H)"; } ret.FutureValue = ret.PastValue = new Tuple <DateObject, DateObject>( DateObject.MinValue.SafeCreateFromValue(year, month, day, beginHour, 0, 0), DateObject.MinValue.SafeCreateFromValue(year, month, day, endHour, 0, 0)); ret.Success = true; return(ret); } } return(ret); }
// Handle cases like "January first", "twenty-two of August" // Handle cases like "20th of next month" private DateTimeResolutionResult ParseNumberWithMonth(string text, DateObject referenceDate) { var ret = new DateTimeResolutionResult(); var trimmedText = text.Trim(); int month = 0, day = 0, year = referenceDate.Year; bool ambiguous = true; var er = this.config.OrdinalExtractor.Extract(trimmedText); if (er.Count == 0) { er = this.config.IntegerExtractor.Extract(trimmedText); } if (er.Count == 0) { return(ret); } var num = Convert.ToInt32((double)(this.config.NumberParser.Parse(er[0]).Value ?? 0)); var match = this.config.MonthRegex.Match(trimmedText); if (match.Success) { month = this.config.MonthOfYear[match.Value.Trim()]; day = num; var suffix = trimmedText.Substring(er[0].Start + er[0].Length ?? 0); var matchYear = this.config.YearSuffix.Match(suffix); if (matchYear.Success) { year = ((BaseDateExtractor)this.config.DateExtractor).GetYearFromText(matchYear); if (year != Constants.InvalidYear) { ambiguous = false; } } } // Handling relative month if (!match.Success) { match = this.config.RelativeMonthRegex.Match(trimmedText); if (match.Success) { var monthStr = match.Groups["order"].Value; var swift = this.config.GetSwiftMonthOrYear(monthStr); month = referenceDate.AddMonths(swift).Month; year = referenceDate.AddMonths(swift).Year; day = num; ambiguous = false; } } // Handling cases like 'second Sunday' if (!match.Success) { match = this.config.WeekDayRegex.Match(trimmedText); if (match.Success) { month = referenceDate.Month; // Resolve the date of wanted week day var wantedWeekDay = this.config.DayOfWeek[match.Groups["weekday"].Value]; var firstDate = DateObject.MinValue.SafeCreateFromValue(referenceDate.Year, referenceDate.Month, 1); var firstWeekDay = (int)firstDate.DayOfWeek; var firstWantedWeekDay = firstDate.AddDays(wantedWeekDay > firstWeekDay ? wantedWeekDay - firstWeekDay : wantedWeekDay - firstWeekDay + 7); var answerDay = firstWantedWeekDay.Day + ((num - 1) * 7); day = answerDay; ambiguous = false; } } if (!match.Success) { return(ret); } // For LUIS format value string var futureDate = DateObject.MinValue.SafeCreateFromValue(year, month, day); var pastDate = DateObject.MinValue.SafeCreateFromValue(year, month, day); if (ambiguous) { ret.Timex = DateTimeFormatUtil.LuisDate(-1, month, day); if (futureDate < referenceDate) { futureDate = futureDate.AddYears(+1); } if (pastDate >= referenceDate) { pastDate = pastDate.AddYears(-1); } } else { ret.Timex = DateTimeFormatUtil.LuisDate(year, month, day); } ret.FutureValue = futureDate; ret.PastValue = pastDate; ret.Success = true; return(ret); }
private DateTimeResolutionResult MergeTwoTimePoints(string text, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); DateTimeParseResult pr1 = null, pr2 = null; bool bothHaveDates = false, beginHasDate = false, endHasDate = false; var timeExtractResults = this.Config.TimeExtractor.Extract(text, referenceTime); var dateTimeExtractResults = this.Config.DateTimeExtractor.Extract(text, referenceTime); if (dateTimeExtractResults.Count == 2) { pr1 = this.Config.DateTimeParser.Parse(dateTimeExtractResults[0], referenceTime); pr2 = this.Config.DateTimeParser.Parse(dateTimeExtractResults[1], referenceTime); bothHaveDates = true; } else if (dateTimeExtractResults.Count == 1 && timeExtractResults.Count == 2) { if (!dateTimeExtractResults[0].IsOverlap(timeExtractResults[0])) { pr1 = this.Config.TimeParser.Parse(timeExtractResults[0], referenceTime); pr2 = this.Config.DateTimeParser.Parse(dateTimeExtractResults[0], referenceTime); endHasDate = true; } else { pr1 = this.Config.DateTimeParser.Parse(dateTimeExtractResults[0], referenceTime); pr2 = this.Config.TimeParser.Parse(timeExtractResults[1], referenceTime); beginHasDate = true; } } else if (dateTimeExtractResults.Count == 1 && timeExtractResults.Count == 1) { if (timeExtractResults[0].Start < dateTimeExtractResults[0].Start) { pr1 = this.Config.TimeParser.Parse(timeExtractResults[0], referenceTime); pr2 = this.Config.DateTimeParser.Parse(dateTimeExtractResults[0], referenceTime); endHasDate = true; } else if (timeExtractResults[0].Start >= dateTimeExtractResults[0].Start + dateTimeExtractResults[0].Length) { pr1 = this.Config.DateTimeParser.Parse(dateTimeExtractResults[0], referenceTime); pr2 = this.Config.TimeParser.Parse(timeExtractResults[0], referenceTime); beginHasDate = true; } else { // If the only TimeExtractResult is part of DateTimeExtractResult, then it should not be handled in this method return(ret); } } else if (timeExtractResults.Count == 2) { // If both ends are Time. then this is a TimePeriod, not a DateTimePeriod return(ret); } else { return(ret); } if (pr1.Value == null || pr2.Value == null) { return(ret); } DateObject futureBegin = (DateObject)((DateTimeResolutionResult)pr1.Value).FutureValue, futureEnd = (DateObject)((DateTimeResolutionResult)pr2.Value).FutureValue; DateObject pastBegin = (DateObject)((DateTimeResolutionResult)pr1.Value).PastValue, pastEnd = (DateObject)((DateTimeResolutionResult)pr2.Value).PastValue; if (bothHaveDates) { if (futureBegin > futureEnd) { futureBegin = pastBegin; } if (pastEnd < pastBegin) { pastEnd = futureEnd; } } if (bothHaveDates) { ret.Timex = $"({pr1.TimexStr},{pr2.TimexStr},PT{Convert.ToInt32((futureEnd - futureBegin).TotalHours)}H)"; // Do nothing } else if (beginHasDate) { futureEnd = DateObject.MinValue.SafeCreateFromValue( futureBegin.Year, futureBegin.Month, futureBegin.Day, futureEnd.Hour, futureEnd.Minute, futureEnd.Second); pastEnd = DateObject.MinValue.SafeCreateFromValue( pastBegin.Year, pastBegin.Month, pastBegin.Day, pastEnd.Hour, pastEnd.Minute, pastEnd.Second); var dateStr = pr1.TimexStr.Split('T')[0]; var durationStr = DateTimeFormatUtil.LuisTimeSpan(futureEnd - futureBegin); ret.Timex = $"({pr1.TimexStr},{dateStr + pr2.TimexStr},{durationStr}"; } else if (endHasDate) { futureBegin = DateObject.MinValue.SafeCreateFromValue( futureEnd.Year, futureEnd.Month, futureEnd.Day, futureBegin.Hour, futureBegin.Minute, futureBegin.Second); pastBegin = DateObject.MinValue.SafeCreateFromValue( pastEnd.Year, pastEnd.Month, pastEnd.Day, pastBegin.Hour, pastBegin.Minute, pastBegin.Second); var dateStr = pr2.TimexStr.Split('T')[0]; var durationStr = DateTimeFormatUtil.LuisTimeSpan(pastEnd - pastBegin); ret.Timex = $"({dateStr + pr1.TimexStr},{pr2.TimexStr},PT{Convert.ToInt32((pastEnd - pastBegin).TotalHours)}H)"; } var ampmStr1 = ((DateTimeResolutionResult)pr1.Value).Comment; var ampmStr2 = ((DateTimeResolutionResult)pr2.Value).Comment; if (!string.IsNullOrEmpty(ampmStr1) && ampmStr1.EndsWith(Constants.Comment_AmPm) && !string.IsNullOrEmpty(ampmStr2) && ampmStr2.EndsWith(Constants.Comment_AmPm)) { ret.Comment = Constants.Comment_AmPm; } if ((this.Config.Options & DateTimeOptions.EnablePreview) != 0) { if (((DateTimeResolutionResult)pr1.Value).TimeZoneResolution != null) { ret.TimeZoneResolution = ((DateTimeResolutionResult)pr1.Value).TimeZoneResolution; } else if (((DateTimeResolutionResult)pr2.Value).TimeZoneResolution != null) { ret.TimeZoneResolution = ((DateTimeResolutionResult)pr2.Value).TimeZoneResolution; } } ret.FutureValue = new Tuple <DateObject, DateObject>(futureBegin, futureEnd); ret.PastValue = new Tuple <DateObject, DateObject>(pastBegin, pastEnd); ret.Success = true; ret.SubDateTimeEntities = new List <object> { pr1, pr2 }; return(ret); }
private DateTimeResolutionResult MergeTwoTimePoints(string text, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); DateTimeParseResult pr1 = null, pr2 = null; var validTimeNumber = false; var ers = this.config.TimeExtractor.Extract(text, referenceTime); if (ers.Count != 2) { if (ers.Count == 1) { var numErs = this.config.IntegerExtractor.Extract(text); foreach (var num in numErs) { int midStrBegin = 0, midStrEnd = 0; // ending number if (num.Start > ers[0].Start + ers[0].Length) { midStrBegin = ers[0].Start + ers[0].Length ?? 0; midStrEnd = num.Start - midStrBegin ?? 0; } else if (num.Start + num.Length < ers[0].Start) { midStrBegin = num.Start + num.Length ?? 0; midStrEnd = ers[0].Start - midStrBegin ?? 0; } // check if the middle string between the time point and the valid number is a connect string. var middleStr = text.Substring(midStrBegin, midStrEnd); var tillMatch = this.config.TillRegex.Match(middleStr); if (tillMatch.Success) { num.Data = null; num.Type = Constants.SYS_DATETIME_TIME; ers.Add(num); validTimeNumber = true; break; } } ers.Sort((x, y) => (x.Start - y.Start ?? 0)); } if (!validTimeNumber) { return(ret); } } pr1 = this.config.TimeParser.Parse(ers[0], referenceTime); pr2 = this.config.TimeParser.Parse(ers[1], referenceTime); if (pr1.Value == null || pr2.Value == null) { return(ret); } var ampmStr1 = ((DateTimeResolutionResult)pr1.Value).Comment; var ampmStr2 = ((DateTimeResolutionResult)pr2.Value).Comment; var beginTime = (DateObject)((DateTimeResolutionResult)pr1.Value).FutureValue; var endTime = (DateObject)((DateTimeResolutionResult)pr2.Value).FutureValue; if (!string.IsNullOrEmpty(ampmStr2) && ampmStr2.EndsWith(Constants.Comment_AmPm) && endTime <= beginTime && endTime.AddHours(12) > beginTime) { endTime = endTime.AddHours(12); ((DateTimeResolutionResult)pr2.Value).FutureValue = endTime; pr2.TimexStr = $"T{endTime.Hour}"; if (endTime.Minute > 0) { pr2.TimexStr = $"{pr2.TimexStr}:{endTime.Minute}"; } } if (!string.IsNullOrEmpty(ampmStr1) && ampmStr1.EndsWith(Constants.Comment_AmPm) && endTime > beginTime.AddHours(12)) { beginTime = beginTime.AddHours(12); ((DateTimeResolutionResult)pr1.Value).FutureValue = beginTime; pr1.TimexStr = $"T{beginTime.Hour}"; if (beginTime.Minute > 0) { pr1.TimexStr = $"{pr1.TimexStr}:{beginTime.Minute}"; } } if (endTime < beginTime) { endTime = endTime.AddDays(1); } var minutes = (endTime - beginTime).Minutes; var hours = (endTime - beginTime).Hours; ret.Timex = $"({pr1.TimexStr},{pr2.TimexStr}," + $"PT{(hours > 0 ? hours + "H" : "")}{(minutes > 0 ? minutes + "M" : "")})"; ret.FutureValue = ret.PastValue = new Tuple <DateObject, DateObject>(beginTime, endTime); ret.Success = true; if (!string.IsNullOrEmpty(ampmStr1) && ampmStr1.EndsWith(Constants.Comment_AmPm) && !string.IsNullOrEmpty(ampmStr2) && ampmStr2.EndsWith(Constants.Comment_AmPm)) { ret.Comment = Constants.Comment_AmPm; } ret.SubDateTimeEntities = new List <object> { pr1, pr2 }; return(ret); }
// Parse "last minute", "next hour" private DateTimeResolutionResult ParseRelativeUnit(string text, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); var match = Config.RelativeTimeUnitRegex.Match(text); if (!match.Success) { match = this.Config.RestOfDateTimeRegex.Match(text); } if (match.Success) { var srcUnit = match.Groups["unit"].Value.ToLower(); var unitStr = Config.UnitMap[srcUnit]; int swiftValue = 1; var prefixMatch = Config.PreviousPrefixRegex.Match(text); if (prefixMatch.Success) { swiftValue = -1; } DateObject beginTime; var endTime = beginTime = referenceTime; var sufixPtTimex = string.Empty; if (Config.UnitMap.ContainsKey(srcUnit)) { switch (unitStr) { case "D": endTime = DateObject.MinValue.SafeCreateFromValue(beginTime.Year, beginTime.Month, beginTime.Day); endTime = endTime.AddDays(1).AddSeconds(-1); sufixPtTimex = "PT" + (endTime - beginTime).TotalSeconds + "S"; break; case "H": beginTime = swiftValue > 0 ? beginTime : referenceTime.AddHours(swiftValue); endTime = swiftValue > 0 ? referenceTime.AddHours(swiftValue) : endTime; sufixPtTimex = "PT1H"; break; case "M": beginTime = swiftValue > 0 ? beginTime : referenceTime.AddMinutes(swiftValue); endTime = swiftValue > 0 ? referenceTime.AddMinutes(swiftValue) : endTime; sufixPtTimex = "PT1M"; break; case "S": beginTime = swiftValue > 0 ? beginTime : referenceTime.AddSeconds(swiftValue); endTime = swiftValue > 0 ? referenceTime.AddSeconds(swiftValue) : endTime; sufixPtTimex = "PT1S"; break; default: return(ret); } ret.Timex = $"({DateTimeFormatUtil.LuisDate(beginTime)}T{DateTimeFormatUtil.LuisTime(beginTime)}," + $"{DateTimeFormatUtil.LuisDate(endTime)}T{DateTimeFormatUtil.LuisTime(endTime)},{sufixPtTimex})"; ret.FutureValue = ret.PastValue = new Tuple <DateObject, DateObject>(beginTime, endTime); ret.Success = true; return(ret); } } return(ret); }
private DateTimeResolutionResult MergeDateWithSingleTimePeriod(string text, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); var trimmedText = text.Trim().ToLower(); var ers = Config.TimePeriodExtractor.Extract(trimmedText, referenceTime); if (ers.Count == 0) { return(ParsePureNumberCases(text, referenceTime)); } else if (ers.Count == 1) { var timePeriodParseResult = Config.TimePeriodParser.Parse(ers[0]); var timePeriodResolutionResult = (DateTimeResolutionResult)timePeriodParseResult.Value; if (timePeriodResolutionResult == null) { return(ParsePureNumberCases(text, referenceTime)); } var periodTimex = timePeriodResolutionResult.Timex; // If it is a range type timex if (TimexUtility.IsRangeTimex(periodTimex)) { var dateResult = this.Config.DateExtractor.Extract(trimmedText.Replace(ers[0].Text, string.Empty), referenceTime); var dateText = trimmedText.Replace(ers[0].Text, string.Empty).Replace(Config.TokenBeforeDate, string.Empty).Trim(); // If only one Date is extracted and the Date text equals to the rest part of source text if (dateResult.Count == 1 && dateText.Equals(dateResult[0].Text)) { string dateTimex; DateObject futureTime; DateObject pastTime; var pr = this.Config.DateParser.Parse(dateResult[0], referenceTime); if (pr.Value != null) { futureTime = (DateObject)((DateTimeResolutionResult)pr.Value).FutureValue; pastTime = (DateObject)((DateTimeResolutionResult)pr.Value).PastValue; dateTimex = pr.TimexStr; } else { return(ParsePureNumberCases(text, referenceTime)); } var rangeTimexComponents = TimexUtility.GetRangeTimexComponents(periodTimex); if (rangeTimexComponents.IsValid) { var beginTimex = TimexUtility.CombineDateAndTimeTimex(dateTimex, rangeTimexComponents.BeginTimex); var endTimex = TimexUtility.CombineDateAndTimeTimex(dateTimex, rangeTimexComponents.EndTimex); ret.Timex = TimexUtility.GenerateDateTimePeriodTimex(beginTimex, endTimex, rangeTimexComponents.DurationTimex); var timePeriodFutureValue = (Tuple <DateObject, DateObject>)timePeriodResolutionResult.FutureValue; var beginTime = timePeriodFutureValue.Item1; var endTime = timePeriodFutureValue.Item2; ret.FutureValue = new Tuple <DateObject, DateObject>( DateObject.MinValue.SafeCreateFromValue( futureTime.Year, futureTime.Month, futureTime.Day, beginTime.Hour, beginTime.Minute, beginTime.Second), DateObject.MinValue.SafeCreateFromValue( futureTime.Year, futureTime.Month, futureTime.Day, endTime.Hour, endTime.Minute, endTime.Second)); ret.PastValue = new Tuple <DateObject, DateObject>( DateObject.MinValue.SafeCreateFromValue( pastTime.Year, pastTime.Month, pastTime.Day, beginTime.Hour, beginTime.Minute, beginTime.Second), DateObject.MinValue.SafeCreateFromValue( pastTime.Year, pastTime.Month, pastTime.Day, endTime.Hour, endTime.Minute, endTime.Second)); if (!string.IsNullOrEmpty(timePeriodResolutionResult.Comment) && timePeriodResolutionResult.Comment.Equals(Constants.Comment_AmPm)) { // AmPm comment is used for later SetParserResult to judge whether this parse result should have two parsing results // Cases like "from 10:30 to 11 on 1/1/2015" should have AmPm comment, as it can be parsed to "10:30am to 11am" and also be parsed to "10:30pm to 11pm" // Cases like "from 10:30 to 3 on 1/1/2015" should not have AmPm comment if (beginTime.Hour < Constants.HalfDayHourCount && endTime.Hour < Constants.HalfDayHourCount) { ret.Comment = Constants.Comment_AmPm; } } ret.Success = true; ret.SubDateTimeEntities = new List <object> { pr, timePeriodParseResult }; return(ret); } } return(ParsePureNumberCases(text, referenceTime)); } } return(ret); }
// Cases like "from 3 to 5pm" or "between 4 and 6am", time point is pure number without colon private DateTimeResolutionResult ParsePureNumCases(string text, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); int year = referenceTime.Year, month = referenceTime.Month, day = referenceTime.Day; var trimmedText = text.Trim().ToLower(); var match = this.config.PureNumberFromToRegex.MatchBegin(trimmedText, trim: true); if (!match.Success) { match = this.config.PureNumberBetweenAndRegex.MatchBegin(trimmedText, trim: true); } if (match.Success) { // this "from .. to .." pattern is valid if followed by a Date OR "pm" var isValid = false; // get hours var hourGroup = match.Groups[Constants.HourGroupName]; var hourStr = hourGroup.Captures[0].Value; var afterHourIndex = hourGroup.Captures[0].Index + hourGroup.Captures[0].Length; // hard to integrate this part into the regex if (afterHourIndex == trimmedText.Length || !trimmedText.Substring(afterHourIndex).Trim().StartsWith(":")) { if (!this.config.Numbers.TryGetValue(hourStr, out int beginHour)) { beginHour = int.Parse(hourStr); } hourStr = hourGroup.Captures[1].Value; afterHourIndex = hourGroup.Captures[1].Index + hourGroup.Captures[1].Length; if (afterHourIndex == trimmedText.Length || !trimmedText.Substring(afterHourIndex).Trim().StartsWith(":")) { if (!this.config.Numbers.TryGetValue(hourStr, out int endHour)) { endHour = int.Parse(hourStr); } // parse "pm" var leftDesc = match.Groups["leftDesc"].Value; var rightDesc = match.Groups["rightDesc"].Value; var matchPmStr = match.Groups[Constants.PmGroupName].Value; var matchAmStr = match.Groups[Constants.AmGroupName].Value; var descStr = match.Groups[Constants.DescGroupName].Value; // The "ampm" only occurs in time, we don't have to consider it here if (string.IsNullOrEmpty(leftDesc)) { var rightAmValid = !string.IsNullOrEmpty(rightDesc) && config.UtilityConfiguration.AmDescRegex.Match(rightDesc.ToLower()).Success; var rightPmValid = !string.IsNullOrEmpty(rightDesc) && config.UtilityConfiguration.PmDescRegex.Match(rightDesc.ToLower()).Success; if (!string.IsNullOrEmpty(matchAmStr) || rightAmValid) { if (endHour >= Constants.HalfDayHourCount) { endHour -= Constants.HalfDayHourCount; } if (beginHour >= Constants.HalfDayHourCount && beginHour - Constants.HalfDayHourCount < endHour) { beginHour -= Constants.HalfDayHourCount; } // Resolve case like "11 to 3am" if (beginHour < Constants.HalfDayHourCount && beginHour > endHour) { beginHour += Constants.HalfDayHourCount; } isValid = true; } else if (!string.IsNullOrEmpty(matchPmStr) || rightPmValid) { if (endHour < Constants.HalfDayHourCount) { endHour += Constants.HalfDayHourCount; } // Resolve case like "11 to 3pm" if (beginHour + Constants.HalfDayHourCount < endHour) { beginHour += Constants.HalfDayHourCount; } isValid = true; } } if (isValid) { var beginStr = "T" + beginHour.ToString("D2"); var endStr = "T" + endHour.ToString("D2"); if (endHour >= beginHour) { ret.Timex = $"({beginStr},{endStr},PT{endHour - beginHour}H)"; } else { ret.Timex = $"({beginStr},{endStr},PT{endHour - beginHour + 24}H)"; } // Try to get the timezone resolution var timeErs = config.TimeExtractor.Extract(trimmedText); foreach (var er in timeErs) { var pr = config.TimeParser.Parse(er, referenceTime); if (((DateTimeResolutionResult)pr.Value).TimeZoneResolution != null) { ret.TimeZoneResolution = ((DateTimeResolutionResult)pr.Value).TimeZoneResolution; break; } } ret.FutureValue = ret.PastValue = new Tuple <DateObject, DateObject>( DateObject.MinValue.SafeCreateFromValue(year, month, day, beginHour, 0, 0), DateObject.MinValue.SafeCreateFromValue(year, month, day, endHour, 0, 0)); ret.Success = true; } } } } return(ret); }
// Cases like "from 3:30 to 5" or "between 3:30am to 6pm", at least one of the time point contains colon private DateTimeResolutionResult ParseSpecificTimeCases(string text, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); int year = referenceTime.Year, month = referenceTime.Month, day = referenceTime.Day; // Handle cases like "from 4:30 to 5" var match = config.SpecificTimeFromToRegex.MatchExact(text, trim: true); if (!match.Success) { // Handle cases like "between 5:10 and 7" match = config.SpecificTimeBetweenAndRegex.MatchExact(text, trim: true); } if (match.Success) { // Cases like "half past seven" are not handled here if (match.Groups[Constants.PrefixGroupName].Success) { return(ret); } // Cases like "4" is different with "4:00" as the Timex is different "T04H" vs "T04H00M" int beginHour; int beginMinute = Constants.InvalidMinute; int beginSecond = Constants.InvalidSecond; int endHour; int endMinute = Constants.InvalidMinute; int endSecond = Constants.InvalidSecond; // Get time1 and time2 var hourGroup = match.Groups[Constants.HourGroupName]; var hourStr = hourGroup.Captures[0].Value; if (config.Numbers.ContainsKey(hourStr)) { beginHour = config.Numbers[hourStr]; } else { beginHour = int.Parse(hourStr); } hourStr = hourGroup.Captures[1].Value; if (config.Numbers.ContainsKey(hourStr)) { endHour = config.Numbers[hourStr]; } else { endHour = int.Parse(hourStr); } var time1StartIndex = match.Groups["time1"].Index; var time1EndIndex = time1StartIndex + match.Groups["time1"].Length; var time2StartIndex = match.Groups["time2"].Index; var time2EndIndex = time2StartIndex + match.Groups["time2"].Length; // Get beginMinute (if exists) and endMinute (if exists) for (int i = 0; i < match.Groups[Constants.MinuteGroupName].Captures.Count; i++) { var minuteCapture = match.Groups[Constants.MinuteGroupName].Captures[i]; if (minuteCapture.Index >= time1StartIndex && minuteCapture.Index + minuteCapture.Length <= time1EndIndex) { beginMinute = int.Parse(minuteCapture.Value); } else if (minuteCapture.Index >= time2StartIndex && minuteCapture.Index + minuteCapture.Length <= time2EndIndex) { endMinute = int.Parse(minuteCapture.Value); } } // Get beginSecond (if exists) and endSecond (if exists) for (int i = 0; i < match.Groups[Constants.SecondGroupName].Captures.Count; i++) { var secondCapture = match.Groups[Constants.SecondGroupName].Captures[i]; if (secondCapture.Index >= time1StartIndex && secondCapture.Index + secondCapture.Length <= time1EndIndex) { beginSecond = int.Parse(secondCapture.Value); } else if (secondCapture.Index >= time2StartIndex && secondCapture.Index + secondCapture.Length <= time2EndIndex) { endSecond = int.Parse(secondCapture.Value); } } // Desc here means descriptions like "am / pm / o'clock" // Get leftDesc (if exists) and rightDesc (if exists) var leftDesc = match.Groups["leftDesc"].Value; var rightDesc = match.Groups["rightDesc"].Value; for (int i = 0; i < match.Groups[Constants.DescGroupName].Captures.Count; i++) { var descCapture = match.Groups[Constants.DescGroupName].Captures[i]; if (descCapture.Index >= time1StartIndex && descCapture.Index + descCapture.Length <= time1EndIndex && string.IsNullOrEmpty(leftDesc)) { leftDesc = descCapture.Value; } else if (descCapture.Index >= time2StartIndex && descCapture.Index + descCapture.Length <= time2EndIndex && string.IsNullOrEmpty(rightDesc)) { rightDesc = descCapture.Value; } } var beginDateTime = DateObject.MinValue.SafeCreateFromValue(year, month, day, beginHour, beginMinute >= 0 ? beginMinute : 0, beginSecond >= 0 ? beginSecond : 0); var endDateTime = DateObject.MinValue.SafeCreateFromValue(year, month, day, endHour, endMinute >= 0 ? endMinute : 0, endSecond >= 0 ? endSecond : 0); var hasLeftAm = !string.IsNullOrEmpty(leftDesc) && leftDesc.ToLower().StartsWith("a"); var hasLeftPm = !string.IsNullOrEmpty(leftDesc) && leftDesc.ToLower().StartsWith("p"); var hasRightAm = !string.IsNullOrEmpty(rightDesc) && rightDesc.ToLower().StartsWith("a"); var hasRightPm = !string.IsNullOrEmpty(rightDesc) && rightDesc.ToLower().StartsWith("p"); var hasLeft = hasLeftAm || hasLeftPm; var hasRight = hasRightAm || hasRightPm; // Both time point has description like 'am' or 'pm' if (hasLeft && hasRight) { if (hasLeftAm) { if (beginHour >= Constants.HalfDayHourCount) { beginDateTime = beginDateTime.AddHours(-Constants.HalfDayHourCount); } } else { if (beginHour < Constants.HalfDayHourCount) { beginDateTime = beginDateTime.AddHours(Constants.HalfDayHourCount); } } if (hasRightAm) { if (endHour > Constants.HalfDayHourCount) { endDateTime = endDateTime.AddHours(-Constants.HalfDayHourCount); } } else { if (endHour < Constants.HalfDayHourCount) { endDateTime = endDateTime.AddHours(Constants.HalfDayHourCount); } } } else if (hasLeft || hasRight) { // one of the time point has description like 'am' or 'pm' if (hasLeftAm) { if (beginHour >= Constants.HalfDayHourCount) { beginDateTime = beginDateTime.AddHours(-Constants.HalfDayHourCount); } if (endHour < Constants.HalfDayHourCount) { if (endDateTime < beginDateTime) { endDateTime = endDateTime.AddHours(Constants.HalfDayHourCount); } } } else if (hasLeftPm) { if (beginHour < Constants.HalfDayHourCount) { beginDateTime = beginDateTime.AddHours(Constants.HalfDayHourCount); } if (endHour < Constants.HalfDayHourCount) { if (endDateTime < beginDateTime) { var span = beginDateTime - endDateTime; endDateTime = endDateTime.AddHours(span.TotalHours >= Constants.HalfDayHourCount ? 24 : Constants.HalfDayHourCount); } } } if (hasRightAm) { if (endHour >= Constants.HalfDayHourCount) { endDateTime = endDateTime.AddHours(-Constants.HalfDayHourCount); } if (beginHour < Constants.HalfDayHourCount) { if (endDateTime < beginDateTime) { beginDateTime = beginDateTime.AddHours(-Constants.HalfDayHourCount); } } } else if (hasRightPm) { if (endHour < Constants.HalfDayHourCount) { endDateTime = endDateTime.AddHours(Constants.HalfDayHourCount); } if (beginHour < Constants.HalfDayHourCount) { if (endDateTime < beginDateTime) { beginDateTime = beginDateTime.AddHours(-Constants.HalfDayHourCount); } else { var span = endDateTime - beginDateTime; if (span.TotalHours > Constants.HalfDayHourCount) { beginDateTime = beginDateTime.AddHours(Constants.HalfDayHourCount); } } } } } // No 'am' or 'pm' indicator else if (beginHour <= Constants.HalfDayHourCount && endHour <= Constants.HalfDayHourCount) { if (beginHour > endHour) { if (beginHour == Constants.HalfDayHourCount) { beginDateTime = beginDateTime.AddHours(-Constants.HalfDayHourCount); } else { endDateTime = endDateTime.AddHours(Constants.HalfDayHourCount); } } ret.Comment = Constants.Comment_AmPm; } if (endDateTime < beginDateTime) { endDateTime = endDateTime.AddHours(24); } var beginStr = DateTimeFormatUtil.ShortTime(beginDateTime.Hour, beginMinute, beginSecond); var endStr = DateTimeFormatUtil.ShortTime(endDateTime.Hour, endMinute, endSecond); ret.Success = true; ret.Timex = $"({beginStr},{endStr},{DateTimeFormatUtil.LuisTimeSpan(endDateTime - beginDateTime)})"; ret.FutureValue = ret.PastValue = new Tuple <DateObject, DateObject>( beginDateTime, endDateTime); ret.SubDateTimeEntities = new List <object>(); // In SplitDateAndTime mode, time points will be get from these SubDateTimeEntities // Cases like "from 4 to 5pm", "4" should not be treated as SubDateTimeEntity if (hasLeft || beginMinute != Constants.InvalidMinute || beginSecond != Constants.InvalidSecond) { var er = new ExtractResult() { Start = time1StartIndex, Length = time1EndIndex - time1StartIndex, Text = text.Substring(time1StartIndex, time1EndIndex - time1StartIndex), Type = $"{Constants.SYS_DATETIME_TIME}", }; var pr = this.config.TimeParser.Parse(er, referenceTime); ret.SubDateTimeEntities.Add(pr); } // Cases like "from 4am to 5", "5" should not be treated as SubDateTimeEntity if (hasRight || endMinute != Constants.InvalidMinute || endSecond != Constants.InvalidSecond) { var er = new ExtractResult { Start = time2StartIndex, Length = time2EndIndex - time2StartIndex, Text = text.Substring(time2StartIndex, time2EndIndex - time2StartIndex), Type = $"{Constants.SYS_DATETIME_TIME}", }; var pr = this.config.TimeParser.Parse(er, referenceTime); ret.SubDateTimeEntities.Add(pr); } ret.Success = true; } return(ret); }
// Merge a Date entity and a Time entity private DateTimeResolutionResult MergeDateAndTime(string text, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); var er1 = this.config.DateExtractor.Extract(text, referenceTime); if (er1.Count == 0) { er1 = this.config.DateExtractor.Extract(this.config.TokenBeforeDate + text, referenceTime); if (er1.Count == 1) { er1[0].Start -= this.config.TokenBeforeDate.Length; } else { return(ret); } } else { // This is to understand if there is an ambiguous token in the text. For some languages (e.g. spanish), // the same word could mean different things (e.g a time in the day or an specific day). if (this.config.ContainsAmbiguousToken(text, er1[0].Text)) { return(ret); } } var er2 = this.config.TimeExtractor.Extract(text, referenceTime); if (er2.Count == 0) { // Here we filter out "morning, afternoon, night..." time entities er2 = this.config.TimeExtractor.Extract(this.config.TokenBeforeTime + text, referenceTime); if (er2.Count == 1) { er2[0].Start -= this.config.TokenBeforeTime.Length; } else if (er2.Count == 0) { // check whether there is a number being used as a time point bool hasTimeNumber = false; var numErs = this.config.IntegerExtractor.Extract(text); if (numErs.Count > 0 && er1.Count == 1) { foreach (var num in numErs) { var middleBegin = er1[0].Start + er1[0].Length ?? 0; var middleEnd = num.Start ?? 0; if (middleBegin > middleEnd) { continue; } var middleStr = text.Substring(middleBegin, middleEnd - middleBegin).Trim(); var match = this.config.DateNumberConnectorRegex.Match(middleStr); if (string.IsNullOrEmpty(middleStr) || match.Success) { num.Type = Constants.SYS_DATETIME_TIME; er2.Add(num); hasTimeNumber = true; } } } if (!hasTimeNumber) { return(ret); } } } // Handle cases like "Oct. 5 in the afternoon at 7:00"; // in this case "5 in the afternoon" will be extracted as a Time entity var correctTimeIdx = 0; while (correctTimeIdx < er2.Count && er2[correctTimeIdx].IsOverlap(er1[0])) { correctTimeIdx++; } if (correctTimeIdx >= er2.Count) { return(ret); } var pr1 = this.config.DateParser.Parse(er1[0], referenceTime.Date); var pr2 = this.config.TimeParser.Parse(er2[correctTimeIdx], referenceTime); if (pr1.Value == null || pr2.Value == null) { return(ret); } var futureDate = (DateObject)((DateTimeResolutionResult)pr1.Value).FutureValue; var pastDate = (DateObject)((DateTimeResolutionResult)pr1.Value).PastValue; var time = (DateObject)((DateTimeResolutionResult)pr2.Value).FutureValue; var hour = time.Hour; var min = time.Minute; var sec = time.Second; // Handle morning, afternoon if (this.config.PMTimeRegex.IsMatch(text) && WithinAfternoonHours(hour)) { hour += Constants.HalfDayHourCount; } else if (this.config.AMTimeRegex.IsMatch(text) && WithinMorningHoursAndNoon(hour, min, sec)) { hour -= Constants.HalfDayHourCount; } var timeStr = pr2.TimexStr; if (timeStr.EndsWith(Constants.Comment_AmPm, StringComparison.Ordinal)) { timeStr = timeStr.Substring(0, timeStr.Length - 4); } timeStr = "T" + hour.ToString("D2") + timeStr.Substring(3); ret.Timex = pr1.TimexStr + timeStr; var val = (DateTimeResolutionResult)pr2.Value; if (hour <= Constants.HalfDayHourCount && !this.config.PMTimeRegex.IsMatch(text) && !this.config.AMTimeRegex.IsMatch(text) && !string.IsNullOrEmpty(val.Comment)) { ret.Comment = Constants.Comment_AmPm; } ret.FutureValue = DateObject.MinValue.SafeCreateFromValue(futureDate.Year, futureDate.Month, futureDate.Day, hour, min, sec); ret.PastValue = DateObject.MinValue.SafeCreateFromValue(pastDate.Year, pastDate.Month, pastDate.Day, hour, min, sec); // Handle case like "Wed Oct 26 15:50:06 2016" which year and month separated by time. var timeSuffix = text.Substring(er2[0].Start + er2[0].Length ?? 0); var matchYear = this.config.YearRegex.Match(timeSuffix); if (matchYear.Success && ((DateObject)((DateTimeResolutionResult)pr1.Value).FutureValue).Year != ((DateObject)((DateTimeResolutionResult)pr1.Value).PastValue).Year) { var year = ((BaseDateExtractor)this.config.DateExtractor).GetYearFromText(matchYear); var dateSuffix = text.Substring(er1[0].Start + er1[0].Length ?? 0); var checkYear = this.config.DateExtractor.GetYearFromText(this.config.YearRegex.Match(dateSuffix)); if (year >= Constants.MinYearNum && year <= Constants.MaxYearNum && year == checkYear) { ret.FutureValue = DateObject.MinValue.SafeCreateFromValue(year, futureDate.Month, futureDate.Day, hour, min, sec); ret.PastValue = DateObject.MinValue.SafeCreateFromValue(year, pastDate.Month, pastDate.Day, hour, min, sec); ret.Timex = year + pr1.TimexStr.Substring(4) + timeStr; } } ret.Success = true; // Change the value of time object pr2.TimexStr = timeStr; if (!string.IsNullOrEmpty(ret.Comment)) { ((DateTimeResolutionResult)pr2.Value).Comment = ret.Comment.Equals(Constants.Comment_AmPm, StringComparison.Ordinal) ? Constants.Comment_AmPm : string.Empty; } // Add the date and time object in case we want to split them ret.SubDateTimeEntities = new List <object> { pr1, pr2 }; // Add timezone ret.TimeZoneResolution = ((DateTimeResolutionResult)pr2.Value).TimeZoneResolution; return(ret); }
private DateTimeResolutionResult ParseTimeOfToday(string text, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); var trimmedText = text.Trim(); int hour = 0, min = 0, sec = 0; string timeStr; var wholeMatch = this.config.SimpleTimeOfTodayAfterRegex.MatchExact(trimmedText, trim: true); if (!wholeMatch.Success) { wholeMatch = this.config.SimpleTimeOfTodayBeforeRegex.MatchExact(trimmedText, trim: true); } if (wholeMatch.Success) { var hourStr = wholeMatch.Groups[Constants.HourGroupName].Value; if (string.IsNullOrEmpty(hourStr)) { hourStr = wholeMatch.Groups["hournum"].Value; hour = this.config.Numbers[hourStr]; } else { hour = int.Parse(hourStr); } timeStr = "T" + hour.ToString("D2"); } else { var ers = this.config.TimeExtractor.Extract(trimmedText, referenceTime); if (ers.Count != 1) { ers = this.config.TimeExtractor.Extract(this.config.TokenBeforeTime + trimmedText, referenceTime); if (ers.Count == 1) { ers[0].Start -= this.config.TokenBeforeTime.Length; } else { return(ret); } } var pr = this.config.TimeParser.Parse(ers[0], referenceTime); if (pr.Value == null) { return(ret); } var time = (DateObject)((DateTimeResolutionResult)pr.Value).FutureValue; hour = time.Hour; min = time.Minute; sec = time.Second; timeStr = pr.TimexStr; } var match = this.config.SpecificTimeOfDayRegex.Match(trimmedText); if (match.Success) { var matchStr = match.Value; // Handle "last", "next" var swift = this.config.GetSwiftDay(matchStr); var date = referenceTime.AddDays(swift).Date; // Handle "morning", "afternoon" hour = this.config.GetHour(matchStr, hour); // In this situation, timeStr cannot end up with "ampm", because we always have a "morning" or "night" if (timeStr.EndsWith(Constants.Comment_AmPm, StringComparison.Ordinal)) { timeStr = timeStr.Substring(0, timeStr.Length - 4); } timeStr = "T" + hour.ToString("D2") + timeStr.Substring(3); ret.Timex = DateTimeFormatUtil.FormatDate(date) + timeStr; ret.FutureValue = ret.PastValue = DateObject.MinValue.SafeCreateFromValue(date.Year, date.Month, date.Day, hour, min, sec); ret.Success = true; return(ret); } return(ret); }
private DateTimeResolutionResult ParseWeekdayOfMonth(string text, DateObject referenceDate) { var ret = new DateTimeResolutionResult(); var trimmedText = text.Trim(); var match = this.config.WeekDayOfMonthRegex.Match(trimmedText); if (!match.Success) { return(ret); } var cardinalStr = match.Groups["cardinal"].Value; var weekdayStr = match.Groups["weekday"].Value; var monthStr = match.Groups["month"].Value; var noYear = false; int year; var cardinal = this.config.IsCardinalLast(cardinalStr) ? 5 : this.config.CardinalMap[cardinalStr]; var weekday = this.config.DayOfWeek[weekdayStr]; int month; if (string.IsNullOrEmpty(monthStr)) { var swift = this.config.GetSwiftMonthOrYear(trimmedText); month = referenceDate.AddMonths(swift).Month; year = referenceDate.AddMonths(swift).Year; } else { month = this.config.MonthOfYear[monthStr]; year = referenceDate.Year; noYear = true; } var value = ComputeDate(cardinal, weekday, month, year); if (value.Month != month) { cardinal -= 1; value = value.AddDays(-7); } var futureDate = value; var pastDate = value; if (noYear && futureDate < referenceDate) { futureDate = ComputeDate(cardinal, weekday, month, year + 1); if (futureDate.Month != month) { futureDate = futureDate.AddDays(-7); } } if (noYear && pastDate >= referenceDate) { pastDate = ComputeDate(cardinal, weekday, month, year - 1); if (pastDate.Month != month) { pastDate = pastDate.AddDays(-7); } } // Here is a very special case, timeX follow future date ret.Timex = $@"XXXX-{month.ToString("D2")}-WXX-{weekday}-#{cardinal}"; ret.FutureValue = futureDate; ret.PastValue = pastDate; ret.Success = true; return(ret); }
private DateTimeResolutionResult Match2Time(Match match, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); int hour = 0, min = 0, second = 0, day = referenceTime.Day, month = referenceTime.Month, year = referenceTime.Year; bool hasMin = false, hasSec = false, hasAm = false, hasPm = false; var engTimeStr = match.Groups["engtime"].Value; if (!string.IsNullOrEmpty(engTimeStr)) { // get hour var hourStr = match.Groups["hournum"].Value.ToLower(); hour = this.config.Numbers[hourStr]; // get minute var minStr = match.Groups["minnum"].Value; var tensStr = match.Groups["tens"].Value; if (!string.IsNullOrEmpty(minStr)) { min = this.config.Numbers[minStr]; if (!string.IsNullOrEmpty(tensStr)) { min += this.config.Numbers[tensStr]; } hasMin = true; } } else { // get hour var hourStr = match.Groups["hour"].Value; if (string.IsNullOrEmpty(hourStr)) { hourStr = match.Groups["hournum"].Value.ToLower(); if (!this.config.Numbers.TryGetValue(hourStr, out hour)) { return(ret); } } else { hour = int.Parse(hourStr); } // get minute var minStr = match.Groups["min"].Value.ToLower(); if (string.IsNullOrEmpty(minStr)) { minStr = match.Groups["minnum"].Value; if (!string.IsNullOrEmpty(minStr)) { min = this.config.Numbers[minStr]; hasMin = true; } var tensStr = match.Groups["tens"].Value; if (!string.IsNullOrEmpty(tensStr)) { min += this.config.Numbers[tensStr]; hasMin = true; } } else { min = int.Parse(minStr); hasMin = true; } // get second var secStr = match.Groups["sec"].Value.ToLower(); if (!string.IsNullOrEmpty(secStr)) { second = int.Parse(secStr); hasSec = true; } } //adjust by desc string var descStr = match.Groups["desc"].Value.ToLower(); if (!string.IsNullOrEmpty(descStr)) { //ampm is a special case in which at 6ampm = at 6 if (config.UtilityConfiguration.AmDescRegex.Match(descStr.ToLower()).Success || config.UtilityConfiguration.AmPmDescRegex.Match(descStr.ToLower()).Success) { if (hour >= 12) { hour -= 12; } if (!config.UtilityConfiguration.AmPmDescRegex.Match(descStr.ToLower()).Success) { hasAm = true; } } else if (config.UtilityConfiguration.PmDescRegex.Match(descStr.ToLower()).Success) { if (hour < 12) { hour += 12; } hasPm = true; } } // adjust min by prefix var timePrefix = match.Groups["prefix"].Value.ToLower(); if (!string.IsNullOrEmpty(timePrefix)) { this.config.AdjustByPrefix(timePrefix, ref hour, ref min, ref hasMin); } // adjust hour by suffix var timeSuffix = match.Groups["suffix"].Value.ToLower(); if (!string.IsNullOrEmpty(timeSuffix)) { this.config.AdjustBySuffix(timeSuffix, ref hour, ref min, ref hasMin, ref hasAm, ref hasPm); } if (hour == 24) { hour = 0; } ret.Timex = "T" + hour.ToString("D2"); if (hasMin) { ret.Timex += ":" + min.ToString("D2"); } if (hasSec) { ret.Timex += ":" + second.ToString("D2"); } if (hour <= 12 && !hasPm && !hasAm) { ret.Comment = "ampm"; } ret.FutureValue = ret.PastValue = new DateObject(year, month, day, hour, min, second); ret.Success = true; return(ret); }
private DateTimeResolutionResult Match2Date(Match match, DateObject referenceDate) { var ret = new DateTimeResolutionResult(); var holidayStr = match.Groups["holiday"].Value; var year = referenceDate.Year; var hasYear = false; var yearNum = match.Groups["year"].Value; var yearCJK = match.Groups[Constants.YearCJKGroupName].Value; var yearRel = match.Groups["yearrel"].Value; if (!string.IsNullOrEmpty(yearNum)) { hasYear = true; yearNum = this.config.SanitizeYearToken(yearNum); year = int.Parse(yearNum, CultureInfo.InvariantCulture); } else if (!string.IsNullOrEmpty(yearCJK)) { hasYear = true; yearCJK = this.config.SanitizeYearToken(yearCJK); year = ConvertToInteger(yearCJK); } else if (!string.IsNullOrEmpty(yearRel)) { hasYear = true; int swift = this.config.GetSwiftYear(yearRel); if (swift >= -1) { year += swift; } } if (year < 100 && year >= 90) { year += Constants.BASE_YEAR_PAST_CENTURY; } else if (year < 20) { year += Constants.BASE_YEAR_CURRENT_CENTURY; } if (!string.IsNullOrEmpty(holidayStr)) { DateObject value; string timexStr; if (this.config.FixedHolidaysDict.ContainsKey(holidayStr)) { value = this.config.FixedHolidaysDict[holidayStr](year); timexStr = $"-{value.Month:D2}-{value.Day:D2}"; } else { if (this.config.HolidayFuncDict.ContainsKey(holidayStr)) { value = this.config.HolidayFuncDict[holidayStr](year); timexStr = this.config.NoFixedTimex[holidayStr]; } else { return(ret); } } if (hasYear) { ret.Timex = year.ToString("D4", CultureInfo.InvariantCulture) + timexStr; ret.FutureValue = ret.PastValue = DateObject.MinValue.SafeCreateFromValue(year, value.Month, value.Day); ret.Success = true; return(ret); } ret.Timex = "XXXX" + timexStr; ret.FutureValue = GetFutureValue(value, referenceDate, holidayStr); ret.PastValue = GetPastValue(value, referenceDate, holidayStr); ret.Success = true; return(ret); } return(ret); }
// merge the entity with its related contexts and then parse the combine text private static DateTimeResolutionResult GetResolution(ExtractResult er, DateTimeParseResult pr, DateTimeResolutionResult ret) { var parentText = (string)((Dictionary <string, object>)er.Data)[ExtendedModelResult.ParentTextKey]; var type = pr.Type; var singlePointResolution = string.Empty; var pastStartPointResolution = string.Empty; var pastEndPointResolution = string.Empty; var futureStartPointResolution = string.Empty; var futureEndPointResolution = string.Empty; var singlePointType = string.Empty; var startPointType = string.Empty; var endPointType = string.Empty; if (type is Constants.SYS_DATETIME_DATEPERIOD or Constants.SYS_DATETIME_TIMEPERIOD or Constants.SYS_DATETIME_DATETIMEPERIOD) { switch (type) { case Constants.SYS_DATETIME_DATEPERIOD: startPointType = TimeTypeConstants.START_DATE; endPointType = TimeTypeConstants.END_DATE; pastStartPointResolution = DateTimeFormatUtil.FormatDate(((Tuple <DateObject, DateObject>)ret.PastValue).Item1); pastEndPointResolution = DateTimeFormatUtil.FormatDate(((Tuple <DateObject, DateObject>)ret.PastValue).Item2); futureStartPointResolution = DateTimeFormatUtil.FormatDate(((Tuple <DateObject, DateObject>)ret.FutureValue).Item1); futureEndPointResolution = DateTimeFormatUtil.FormatDate(((Tuple <DateObject, DateObject>)ret.FutureValue).Item2); break; case Constants.SYS_DATETIME_DATETIMEPERIOD: startPointType = TimeTypeConstants.START_DATETIME; endPointType = TimeTypeConstants.END_DATETIME; if (ret.PastValue is Tuple <DateObject, DateObject> tuple) { pastStartPointResolution = DateTimeFormatUtil.FormatDateTime(tuple.Item1); pastEndPointResolution = DateTimeFormatUtil.FormatDateTime(tuple.Item2); futureStartPointResolution = DateTimeFormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)ret.FutureValue).Item1); futureEndPointResolution = DateTimeFormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)ret.FutureValue).Item2); } else if (ret.PastValue is DateObject datetime) { pastStartPointResolution = DateTimeFormatUtil.FormatDateTime(datetime); futureStartPointResolution = DateTimeFormatUtil.FormatDateTime((DateObject)ret.FutureValue); } break; case Constants.SYS_DATETIME_TIMEPERIOD: startPointType = TimeTypeConstants.START_TIME; endPointType = TimeTypeConstants.END_TIME; pastStartPointResolution = DateTimeFormatUtil.FormatTime(((Tuple <DateObject, DateObject>)ret.PastValue).Item1); pastEndPointResolution = DateTimeFormatUtil.FormatTime(((Tuple <DateObject, DateObject>)ret.PastValue).Item2); futureStartPointResolution = DateTimeFormatUtil.FormatTime(((Tuple <DateObject, DateObject>)ret.FutureValue).Item1); futureEndPointResolution = DateTimeFormatUtil.FormatTime(((Tuple <DateObject, DateObject>)ret.FutureValue).Item2); break; } }
// match several other cases // including '今天', '后天', '十三日' protected DateTimeResolutionResult ParseImplicitDate(string text, DateObject referenceDate) { var ret = new DateTimeResolutionResult(); // handle "十二日" "明年这个月三日" "本月十一日" var match = this.config.SpecialDate.MatchExact(text, trim: true); if (match.Success) { var yearStr = match.Groups["thisyear"].Value; var monthStr = match.Groups["thismonth"].Value; var dayStr = match.Groups["day"].Value; int month = referenceDate.Month, year = referenceDate.Year; var day = this.config.DayOfMonth[dayStr]; bool hasYear = false, hasMonth = false; if (!string.IsNullOrEmpty(monthStr)) { hasMonth = true; if (this.config.NextRe.Match(monthStr).Success) { month++; if (month == Constants.MaxMonth + 1) { month = Constants.MinMonth; year++; } } else if (this.config.LastRe.Match(monthStr).Success) { month--; if (month == Constants.MinMonth - 1) { month = Constants.MaxMonth; year--; } } if (!string.IsNullOrEmpty(yearStr)) { hasYear = true; if (this.config.NextRe.Match(yearStr).Success) { ++year; } else if (this.config.LastRe.Match(yearStr).Success) { --year; } } } ret.Timex = DateTimeFormatUtil.LuisDate(hasYear ? year : -1, hasMonth ? month : -1, day); DateObject futureDate, pastDate; if (day > GetMonthMaxDay(year, month)) { var futureMonth = month + 1; var pastMonth = month - 1; var futureYear = year; var pastYear = year; if (futureMonth == Constants.MaxMonth + 1) { futureMonth = Constants.MinMonth; futureYear = year++; } if (pastMonth == Constants.MinMonth - 1) { pastMonth = Constants.MaxMonth; pastYear = year--; } var isFutureValid = DateObjectExtension.IsValidDate(futureYear, futureMonth, day); var isPastValid = DateObjectExtension.IsValidDate(pastYear, pastMonth, day); if (isFutureValid && isPastValid) { futureDate = DateObject.MinValue.SafeCreateFromValue(futureYear, futureMonth, day); pastDate = DateObject.MinValue.SafeCreateFromValue(pastYear, pastMonth, day); } else if (isFutureValid && !isPastValid) { futureDate = pastDate = DateObject.MinValue.SafeCreateFromValue(futureYear, futureMonth, day); } else if (!isFutureValid && !isPastValid) { futureDate = pastDate = DateObject.MinValue.SafeCreateFromValue(pastYear, pastMonth, day); } else { // Fall back to normal cases, might lead to resolution failure // TODO: Ideally, this failure should be filtered out in extract phase futureDate = pastDate = DateObject.MinValue.SafeCreateFromValue(year, month, day); } } else { futureDate = DateObject.MinValue.SafeCreateFromValue(year, month, day); pastDate = DateObject.MinValue.SafeCreateFromValue(year, month, day); if (!hasMonth) { if (futureDate < referenceDate) { if (IsValidDate(year, month + 1, day)) { futureDate = futureDate.AddMonths(1); } } if (pastDate >= referenceDate) { if (IsValidDate(year, month - 1, day)) { pastDate = pastDate.AddMonths(-1); } else if (DateContext.IsFeb29th(year, month - 1, day)) { pastDate = pastDate.AddMonths(-2); } } } else if (!hasYear) { if (futureDate < referenceDate) { if (IsValidDate(year + 1, month, day)) { futureDate = futureDate.AddYears(1); } } if (pastDate >= referenceDate) { if (IsValidDate(year - 1, month, day)) { pastDate = pastDate.AddYears(-1); } } } } ret.FutureValue = futureDate; ret.PastValue = pastDate; ret.Success = true; return(ret); } // handle cases like "昨日", "明日", "大后天" match = this.config.SpecialDayRegex.MatchExact(text, trim: true); if (match.Success) { var value = referenceDate.AddDays(this.config.GetSwiftDay(match.Value)); ret.Timex = DateTimeFormatUtil.LuisDate(value); ret.FutureValue = ret.PastValue = DateObject.MinValue.SafeCreateFromValue(value.Year, value.Month, value.Day); ret.Success = true; return(ret); } if (!ret.Success) { ret = MatchThisWeekday(text, referenceDate); } if (!ret.Success) { ret = MatchNextWeekday(text, referenceDate); } if (!ret.Success) { ret = MatchLastWeekday(text, referenceDate); } if (!ret.Success) { ret = MatchWeekdayAlone(text, referenceDate); } return(ret); }
private DateTimeResolutionResult ParseMergedDuration(string text, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); var durationExtractor = this.config.DurationExtractor; // DurationExtractor without parameter will not extract merged duration var ers = durationExtractor.Extract(text, referenceTime); // only handle merged duration cases like "1 month 21 days" if (ers.Count <= 1) { ret.Success = false; return(ret); } var start = ers[0].Start ?? 0; if (start != 0) { var beforeStr = text.Substring(0, start - 1); if (!string.IsNullOrWhiteSpace(beforeStr)) { return(ret); } } var end = ers[ers.Count - 1].Start + ers[ers.Count - 1].Length ?? 0; if (end != text.Length) { var afterStr = text.Substring(end); if (!string.IsNullOrWhiteSpace(afterStr)) { return(ret); } } var prs = new List <DateTimeParseResult>(); var timexDict = new Dictionary <string, string>(); // insert timex into a dictionary foreach (var er in ers) { var unitRegex = this.config.DurationUnitRegex; var unitMatch = unitRegex.Match(er.Text); if (unitMatch.Success) { var pr = (DateTimeParseResult)Parse(er); if (pr.Value != null) { timexDict.Add(this.config.UnitMap[unitMatch.Groups["unit"].Value], pr.TimexStr); prs.Add(pr); } } } // sort the timex using the granularity of the duration, "P1M23D" for "1 month 23 days" and "23 days 1 month" if (prs.Count == ers.Count) { ret.Timex = TimexUtility.GenerateCompoundDurationTimex(timexDict, this.config.UnitValueMap); double value = 0; foreach (var pr in prs) { value += double.Parse(((DateTimeResolutionResult)pr.Value).FutureValue.ToString(), CultureInfo.InvariantCulture); } ret.FutureValue = ret.PastValue = value; } ret.Success = true; return(ret); }
protected virtual DateTimeResolutionResult ParseWeekdayOfMonth(string text, DateObject referenceDate) { var ret = new DateTimeResolutionResult(); var trimmedText = text.Trim(); var match = this.config.WeekDayOfMonthRegex.Match(trimmedText); if (!match.Success) { return(ret); } var cardinalStr = match.Groups["cardinal"].Value; var weekdayStr = match.Groups["weekday"].Value; var monthStr = match.Groups["month"].Value; var noYear = false; int year; int cardinal; if (cardinalStr.Equals(this.config.LastWeekDayToken, StringComparison.Ordinal)) { cardinal = 5; } else { cardinal = this.config.CardinalMap[cardinalStr]; } var weekday = this.config.DayOfWeek[weekdayStr]; int month; if (string.IsNullOrEmpty(monthStr)) { var swift = 0; if (trimmedText.StartsWith(this.config.NextMonthToken, StringComparison.Ordinal)) { swift = 1; } else if (trimmedText.StartsWith(this.config.LastMonthToken, StringComparison.Ordinal)) { swift = -1; } month = referenceDate.AddMonths(swift).Month; year = referenceDate.AddMonths(swift).Year; } else { month = this.config.MonthOfYear[monthStr]; year = referenceDate.Year; noYear = true; } var value = ComputeDate(cardinal, weekday, month, year); if (value.Month != month) { cardinal -= 1; value = value.AddDays(-7); } var futureDate = value; var pastDate = value; if (noYear && futureDate < referenceDate) { futureDate = ComputeDate(cardinal, weekday, month, year + 1); if (futureDate.Month != month) { futureDate = futureDate.AddDays(-7); } } if (noYear && pastDate >= referenceDate) { pastDate = ComputeDate(cardinal, weekday, month, year - 1); if (pastDate.Month != month) { pastDate = pastDate.AddDays(-7); } } // here is a very special case, timeX follows future date ret.Timex = $@"XXXX-{month:D2}-WXX-{weekday}-#{cardinal}"; ret.FutureValue = futureDate; ret.PastValue = pastDate; ret.Success = true; return(ret); }
// Parse specific TimeOfDay like "this night", "early morning", "late evening" protected virtual DateTimeResolutionResult ParseSpecificTimeOfDay(string text, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); var trimmedText = text.Trim().ToLowerInvariant(); var timeText = trimmedText; var match = this.Config.PeriodTimeOfDayWithDateRegex.Match(trimmedText); // Extract early/late prefix from text if any bool hasEarly = false, hasLate = false; if (match.Success) { timeText = match.Groups[Constants.TimeOfDayGroupName].Value; if (!string.IsNullOrEmpty(match.Groups["early"].Value)) { hasEarly = true; ret.Comment = Constants.Comment_Early; } if (!hasEarly && !string.IsNullOrEmpty(match.Groups["late"].Value)) { hasLate = true; ret.Comment = Constants.Comment_Late; } } else { match = this.Config.AmDescRegex.Match(trimmedText); if (!match.Success) { match = this.Config.PmDescRegex.Match(trimmedText); } if (match.Success) { timeText = match.Value; } } // Handle time of day // Late/early only works with time of day // Only standard time of day (morning, afternoon, evening and night) will not directly return if (!this.Config.GetMatchedTimeRange(timeText, out string timeStr, out int beginHour, out int endHour, out int endMin)) { return(ret); } // Modify time period if "early" or "late" exists // Since 'time of day' is defined as four hour periods // the first 2 hours represent early, the later 2 hours represent late if (hasEarly) { endHour = beginHour + 2; // Handling speical case: night ends with 23:59 if (endMin == 59) { endMin = 0; } } else if (hasLate) { beginHour = beginHour + 2; } if (Config.SpecificTimeOfDayRegex.IsExactMatch(trimmedText, trim: true)) { var swift = this.Config.GetSwiftPrefix(trimmedText); var date = referenceTime.AddDays(swift).Date; int day = date.Day, month = date.Month, year = date.Year; ret.Timex = DateTimeFormatUtil.FormatDate(date) + timeStr; ret.FutureValue = ret.PastValue = new Tuple <DateObject, DateObject>( DateObject.MinValue.SafeCreateFromValue(year, month, day, beginHour, 0, 0), DateObject.MinValue.SafeCreateFromValue(year, month, day, endHour, endMin, endMin)); ret.Success = true; return(ret); } // Handle Date followed by morning, afternoon and morning, afternoon followed by Date match = this.Config.PeriodTimeOfDayWithDateRegex.Match(trimmedText); if (!match.Success) { match = this.Config.AmDescRegex.Match(trimmedText); if (!match.Success) { match = this.Config.PmDescRegex.Match(trimmedText); } } if (match.Success) { var beforeStr = trimmedText.Substring(0, match.Index).Trim(); var afterStr = trimmedText.Substring(match.Index + match.Length).Trim(); // Eliminate time period, if any var timePeriodErs = this.Config.TimePeriodExtractor.Extract(beforeStr); if (timePeriodErs.Count > 0) { beforeStr = beforeStr.Remove(timePeriodErs[0].Start ?? 0, timePeriodErs[0].Length ?? 0).Trim(); } else { timePeriodErs = this.Config.TimePeriodExtractor.Extract(afterStr); if (timePeriodErs.Count > 0) { afterStr = afterStr.Remove(timePeriodErs[0].Start ?? 0, timePeriodErs[0].Length ?? 0).Trim(); } } var ers = this.Config.DateExtractor.Extract(beforeStr + ' ' + afterStr, referenceTime); if (ers.Count == 0 || ers[0].Length < beforeStr.Length) { var valid = false; if (ers.Count > 0 && ers[0].Start == 0) { var midStr = beforeStr.Substring(ers[0].Start + ers[0].Length ?? 0); if (string.IsNullOrWhiteSpace(midStr.Replace(',', ' '))) { valid = true; } } if (!valid) { ers = this.Config.DateExtractor.Extract(afterStr, referenceTime); if (ers.Count == 0 || ers[0].Length != afterStr.Length) { if (ers.Count > 0 && ers[0].Start + ers[0].Length == afterStr.Length) { var midStr = afterStr.Substring(0, ers[0].Start ?? 0); if (string.IsNullOrWhiteSpace(midStr.Replace(',', ' '))) { valid = true; } } } else { valid = true; } } if (!valid) { return(ret); } } var hasSpecificTimePeriod = false; if (timePeriodErs.Count > 0) { var timePr = this.Config.TimePeriodParser.Parse(timePeriodErs[0], referenceTime); if (timePr != null) { var periodFuture = (Tuple <DateObject, DateObject>)((DateTimeResolutionResult)timePr.Value).FutureValue; var periodPast = (Tuple <DateObject, DateObject>)((DateTimeResolutionResult)timePr.Value).PastValue; if (periodFuture == periodPast) { beginHour = periodFuture.Item1.Hour; endHour = periodFuture.Item2.Hour; } else { if (periodFuture.Item1.Hour >= beginHour || periodFuture.Item2.Hour <= endHour) { beginHour = periodFuture.Item1.Hour; endHour = periodFuture.Item2.Hour; } else { beginHour = periodPast.Item1.Hour; endHour = periodPast.Item2.Hour; } } hasSpecificTimePeriod = true; } } var pr = this.Config.DateParser.Parse(ers[0], referenceTime); var futureDate = (DateObject)((DateTimeResolutionResult)pr.Value).FutureValue; var pastDate = (DateObject)((DateTimeResolutionResult)pr.Value).PastValue; if (!hasSpecificTimePeriod) { ret.Timex = pr.TimexStr + timeStr; } else { ret.Timex = string.Format("({0}T{1},{0}T{2},PT{3}H)", pr.TimexStr, beginHour, endHour, endHour - beginHour); } ret.FutureValue = new Tuple <DateObject, DateObject>( DateObject.MinValue.SafeCreateFromValue(futureDate.Year, futureDate.Month, futureDate.Day, beginHour, 0, 0), DateObject.MinValue.SafeCreateFromValue(futureDate.Year, futureDate.Month, futureDate.Day, endHour, endMin, endMin)); ret.PastValue = new Tuple <DateObject, DateObject>( DateObject.MinValue.SafeCreateFromValue(pastDate.Year, pastDate.Month, pastDate.Day, beginHour, 0, 0), DateObject.MinValue.SafeCreateFromValue(pastDate.Year, pastDate.Month, pastDate.Day, endHour, endMin, endMin)); ret.Success = true; return(ret); } return(ret); }
// Handle cases like "三天前" private DateTimeResolutionResult ParserDurationWithAgoAndLater(string text, DateObject referenceDate) { var ret = new DateTimeResolutionResult(); var numStr = string.Empty; var unitStr = string.Empty; var durationRes = this.config.DurationExtractor.Extract(text, referenceDate); if (durationRes.Count > 0) { var match = this.config.UnitRegex.Match(text); if (match.Success) { var suffix = text.Substring((int)durationRes[0].Start + (int)durationRes[0].Length).Trim(); var srcUnit = match.Groups["unit"].Value; var numberStr = text.Substring((int)durationRes[0].Start, match.Index - (int)durationRes[0].Start).Trim(); var number = ConvertChineseToNum(numberStr); if (this.config.UnitMap.ContainsKey(srcUnit)) { unitStr = this.config.UnitMap[srcUnit]; var beforeMatch = this.config.BeforeRegex.Match(suffix); if (beforeMatch.Success && suffix.StartsWith(beforeMatch.Value, StringComparison.Ordinal)) { DateObject date; switch (unitStr) { case Constants.TimexDay: date = referenceDate.AddDays(-number); break; case Constants.TimexWeek: date = referenceDate.AddDays(-7 * number); break; case Constants.TimexMonthFull: date = referenceDate.AddMonths(-number); break; case Constants.TimexYear: date = referenceDate.AddYears(-number); break; default: return(ret); } ret.Timex = $"{DateTimeFormatUtil.LuisDate(date)}"; ret.FutureValue = ret.PastValue = date; ret.Success = true; return(ret); } var afterMatch = this.config.AfterRegex.Match(suffix); if (afterMatch.Success && suffix.StartsWith(afterMatch.Value, StringComparison.Ordinal)) { DateObject date; switch (unitStr) { case Constants.TimexDay: date = referenceDate.AddDays(number); break; case Constants.TimexWeek: date = referenceDate.AddDays(7 * number); break; case Constants.TimexMonthFull: date = referenceDate.AddMonths(number); break; case Constants.TimexYear: date = referenceDate.AddYears(number); break; default: return(ret); } ret.Timex = $"{DateTimeFormatUtil.LuisDate(date)}"; ret.FutureValue = ret.PastValue = date; ret.Success = true; return(ret); } } } } return(ret); }
// Handle cases like "Monday 7-9", where "7-9" can't be extracted by the TimePeriodExtractor private DateTimeResolutionResult ParsePureNumberCases(string text, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); var trimmedText = text.Trim().ToLower(); var match = this.Config.PureNumberFromToRegex.Match(trimmedText); if (!match.Success) { match = this.Config.PureNumberBetweenAndRegex.Match(trimmedText); } if (match.Success && (match.Index == 0 || match.Index + match.Length == trimmedText.Length)) { int beginHour, endHour; ret.Comment = ParseTimePeriod(match, out beginHour, out endHour); var dateStr = string.Empty; // Parse following date var dateExtractResult = this.Config.DateExtractor.Extract(trimmedText.Replace(match.Value, string.Empty), referenceTime); DateObject futureDate, pastDate; if (dateExtractResult.Count > 0) { var pr = this.Config.DateParser.Parse(dateExtractResult[0], referenceTime); if (pr.Value != null) { futureDate = (DateObject)((DateTimeResolutionResult)pr.Value).FutureValue; pastDate = (DateObject)((DateTimeResolutionResult)pr.Value).PastValue; dateStr = pr.TimexStr; } else { return(ret); } } else { return(ret); } var pastHours = endHour - beginHour; var beginTimex = TimexUtility.CombineDateAndTimeTimex(dateStr, DateTimeFormatUtil.ShortTime(beginHour)); var endTimex = TimexUtility.CombineDateAndTimeTimex(dateStr, DateTimeFormatUtil.ShortTime(endHour)); var durationTimex = TimexUtility.GenerateDurationTimex(endHour - beginHour, Constants.TimexHour, isLessThanDay: true); ret.Timex = TimexUtility.GenerateDateTimePeriodTimex(beginTimex, endTimex, durationTimex); ret.FutureValue = new Tuple <DateObject, DateObject>( DateObject.MinValue.SafeCreateFromValue(futureDate.Year, futureDate.Month, futureDate.Day, beginHour, 0, 0), DateObject.MinValue.SafeCreateFromValue(futureDate.Year, futureDate.Month, futureDate.Day, endHour, 0, 0)); ret.PastValue = new Tuple <DateObject, DateObject>( DateObject.MinValue.SafeCreateFromValue(pastDate.Year, pastDate.Month, pastDate.Day, beginHour, 0, 0), DateObject.MinValue.SafeCreateFromValue(pastDate.Year, pastDate.Month, pastDate.Day, endHour, 0, 0)); ret.Success = true; } return(ret); }
private DateTimeResolutionResult Match2Date(Match match, DateObject referenceDate) { var ret = new DateTimeResolutionResult(); var holidayStr = this.config.SanitizeHolidayToken(match.Groups["holiday"].Value.ToLowerInvariant()); // get year (if exist) var yearStr = match.Groups["year"].Value.ToLower(); var orderStr = match.Groups["order"].Value.ToLower(); int year; var hasYear = false; if (!string.IsNullOrEmpty(yearStr)) { year = int.Parse(yearStr); hasYear = true; } else if (!string.IsNullOrEmpty(orderStr)) { var swift = this.config.GetSwiftYear(orderStr); if (swift < -1) { return(ret); } year = referenceDate.Year + swift; hasYear = true; } else { year = referenceDate.Year; } string holidayKey = string.Empty; foreach (var holidayPair in this.config.HolidayNames) { if (holidayPair.Value.Contains(holidayStr)) { holidayKey = holidayPair.Key; break; } } var timexStr = string.Empty; if (!string.IsNullOrEmpty(holidayKey)) { var value = referenceDate; Func <int, DateObject> function; if (this.config.HolidayFuncDictionary.TryGetValue(holidayKey, out function)) { value = function(year); this.config.VariableHolidaysTimexDictionary.TryGetValue(holidayKey, out timexStr); if (string.IsNullOrEmpty(timexStr)) { timexStr = $"-{value.Month:D2}-{value.Day:D2}"; } } if (function == null) { return(ret); } if (hasYear) { ret.Timex = year.ToString("D4") + timexStr; ret.FutureValue = ret.PastValue = DateObject.MinValue.SafeCreateFromValue(year, value.Month, value.Day); ret.Success = true; return(ret); } ret.Timex = "XXXX" + timexStr; ret.FutureValue = GetFutureValue(value, referenceDate, holidayKey); ret.PastValue = GetPastValue(value, referenceDate, holidayKey); ret.Success = true; return(ret); } return(ret); }
// TODO: this can be abstracted with the similar method in BaseDatePeriodParser // Parse "in 20 minutes" private DateTimeResolutionResult ParseDuration(string text, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); // For the rest of datetime, it will be handled in next function if (Config.RestOfDateTimeRegex.IsMatch(text)) { return(ret); } var ers = Config.DurationExtractor.Extract(text, referenceTime); if (ers.Count == 1) { var pr = Config.DurationParser.Parse(ers[0]); var beforeStr = text.Substring(0, pr.Start ?? 0).Trim().ToLowerInvariant(); var afterStr = text.Substring((pr.Start ?? 0) + (pr.Length ?? 0)).Trim().ToLowerInvariant(); var numbersInSuffix = Config.CardinalExtractor.Extract(beforeStr); var numbersInDuration = Config.CardinalExtractor.Extract(ers[0].Text); // Handle cases like "2 upcoming days", "5 previous years" if (numbersInSuffix.Any() && !numbersInDuration.Any()) { var numberEr = numbersInSuffix.First(); var numberText = numberEr.Text; var durationText = ers[0].Text; var combinedText = $"{numberText} {durationText}"; var combinedDurationEr = Config.DurationExtractor.Extract(combinedText, referenceTime); if (combinedDurationEr.Any()) { pr = Config.DurationParser.Parse(combinedDurationEr.First()); var startIndex = numberEr.Start.Value + numberEr.Length.Value; beforeStr = beforeStr.Substring(startIndex).Trim(); } } if (pr.Value != null) { var swiftSeconds = 0; var mod = string.Empty; var durationResult = (DateTimeResolutionResult)pr.Value; if (durationResult.PastValue is double && durationResult.FutureValue is double) { swiftSeconds = (int)((double)durationResult.FutureValue); } DateObject beginTime; var endTime = beginTime = referenceTime; if (Config.PreviousPrefixRegex.IsExactMatch(beforeStr, trim: true)) { mod = Constants.BEFORE_MOD; beginTime = referenceTime.AddSeconds(-swiftSeconds); } // Handle the "within (the) (next) xx seconds/minutes/hours" case // Should also handle the multiple duration case like P1DT8H // Set the beginTime equal to reference time for now if (Config.WithinNextPrefixRegex.IsExactMatch(beforeStr, trim: true)) { endTime = beginTime.AddSeconds(swiftSeconds); } if (Config.FutureRegex.IsExactMatch(beforeStr, trim: true)) { mod = Constants.AFTER_MOD; endTime = beginTime.AddSeconds(swiftSeconds); } if (Config.PreviousPrefixRegex.IsExactMatch(afterStr, trim: true)) { mod = Constants.BEFORE_MOD; beginTime = referenceTime.AddSeconds(-swiftSeconds); } if (Config.FutureRegex.IsExactMatch(afterStr, trim: true)) { mod = Constants.AFTER_MOD; endTime = beginTime.AddSeconds(swiftSeconds); } if (Config.FutureSuffixRegex.IsExactMatch(afterStr, trim: true)) { mod = Constants.AFTER_MOD; endTime = beginTime.AddSeconds(swiftSeconds); } ret.Timex = $"({DateTimeFormatUtil.LuisDate(beginTime)}T{DateTimeFormatUtil.LuisTime(beginTime)}," + $"{DateTimeFormatUtil.LuisDate(endTime)}T{DateTimeFormatUtil.LuisTime(endTime)}," + $"{durationResult.Timex})"; ret.FutureValue = ret.PastValue = new Tuple <DateObject, DateObject>(beginTime, endTime); ret.Success = true; if (!string.IsNullOrEmpty(mod)) { ((DateTimeResolutionResult)pr.Value).Mod = mod; } ret.SubDateTimeEntities = new List <object> { pr }; return(ret); } } return(ret); }
private static DateTimeResolutionResult GetResolution(ExtractResult er, DateTimeParseResult pr, DateTimeResolutionResult ret) { var parentText = (string)((Dictionary <string, object>)er.Data)[ExtendedModelResult.ParentTextKey]; var type = pr.Type; var singlePointResolution = ""; var pastStartPointResolution = ""; var pastEndPointResolution = ""; var futureStartPointResolution = ""; var futureEndPointResolution = ""; var singlePointType = ""; var startPointType = ""; var endPointType = ""; if (type == Constants.SYS_DATETIME_DATEPERIOD || type == Constants.SYS_DATETIME_TIMEPERIOD || type == Constants.SYS_DATETIME_DATETIMEPERIOD) { switch (type) { case Constants.SYS_DATETIME_DATEPERIOD: startPointType = TimeTypeConstants.START_DATE; endPointType = TimeTypeConstants.END_DATE; pastStartPointResolution = FormatUtil.FormatDate(((Tuple <DateObject, DateObject>)ret.PastValue).Item1); pastEndPointResolution = FormatUtil.FormatDate(((Tuple <DateObject, DateObject>)ret.PastValue).Item2); futureStartPointResolution = FormatUtil.FormatDate(((Tuple <DateObject, DateObject>)ret.FutureValue).Item1); futureEndPointResolution = FormatUtil.FormatDate(((Tuple <DateObject, DateObject>)ret.FutureValue).Item2); break; case Constants.SYS_DATETIME_DATETIMEPERIOD: startPointType = TimeTypeConstants.START_DATETIME; endPointType = TimeTypeConstants.END_DATETIME; if (ret.PastValue is Tuple <DateObject, DateObject> tuple) { pastStartPointResolution = FormatUtil.FormatDateTime(tuple.Item1); pastEndPointResolution = FormatUtil.FormatDateTime(tuple.Item2); futureStartPointResolution = FormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)ret.FutureValue).Item1); futureEndPointResolution = FormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)ret.FutureValue).Item2); } else if (ret.PastValue is DateObject datetime) { pastStartPointResolution = FormatUtil.FormatDateTime(datetime); futureStartPointResolution = FormatUtil.FormatDateTime((DateObject)ret.FutureValue); } break; case Constants.SYS_DATETIME_TIMEPERIOD: startPointType = TimeTypeConstants.START_TIME; endPointType = TimeTypeConstants.END_TIME; pastStartPointResolution = FormatUtil.FormatTime(((Tuple <DateObject, DateObject>)ret.PastValue).Item1); pastEndPointResolution = FormatUtil.FormatTime(((Tuple <DateObject, DateObject>)ret.PastValue).Item2); futureStartPointResolution = FormatUtil.FormatTime(((Tuple <DateObject, DateObject>)ret.FutureValue).Item1); futureEndPointResolution = FormatUtil.FormatTime(((Tuple <DateObject, DateObject>)ret.FutureValue).Item2); break; } } else { switch (type) { case Constants.SYS_DATETIME_DATE: singlePointType = TimeTypeConstants.DATE; singlePointResolution = FormatUtil.FormatDate((DateObject)ret.FutureValue); break; case Constants.SYS_DATETIME_DATETIME: singlePointType = TimeTypeConstants.DATETIME; singlePointResolution = FormatUtil.FormatDateTime((DateObject)ret.FutureValue); break; case Constants.SYS_DATETIME_TIME: singlePointType = TimeTypeConstants.TIME; singlePointResolution = FormatUtil.FormatTime((DateObject)ret.FutureValue); break; } } ret.FutureResolution = new Dictionary <string, string>(); ret.PastResolution = new Dictionary <string, string>(); if (!string.IsNullOrEmpty(futureStartPointResolution)) { ret.FutureResolution.Add(startPointType, futureStartPointResolution); } if (!string.IsNullOrEmpty(futureEndPointResolution)) { ret.FutureResolution.Add(endPointType, futureEndPointResolution); } if (!string.IsNullOrEmpty(pastStartPointResolution)) { ret.PastResolution.Add(startPointType, pastStartPointResolution); } if (!string.IsNullOrEmpty(pastEndPointResolution)) { ret.PastResolution.Add(endPointType, pastEndPointResolution); } if (!string.IsNullOrEmpty(singlePointResolution)) { ret.FutureResolution.Add(singlePointType, singlePointResolution); ret.PastResolution.Add(singlePointType, singlePointResolution); } if (!string.IsNullOrEmpty(parentText)) { ret.FutureResolution.Add(ExtendedModelResult.ParentTextKey, parentText); ret.PastResolution.Add(ExtendedModelResult.ParentTextKey, parentText); } if (((DateTimeResolutionResult)pr.Value).Mod != null) { ret.Mod = ((DateTimeResolutionResult)pr.Value).Mod; } if (((DateTimeResolutionResult)pr.Value).TimeZoneResolution != null) { ret.TimeZoneResolution = ((DateTimeResolutionResult)pr.Value).TimeZoneResolution; } return(ret); }
// parse "morning", "afternoon", "night" private DateTimeResolutionResult ParseNight(string text, DateObject referenceTime) { int day = referenceTime.Day, month = referenceTime.Month, year = referenceTime.Year; var ret = new DateTimeResolutionResult(); // extract early/late prefix from text var match = this.config.TimeOfDayRegex.Match(text); bool hasEarly = false, hasLate = false; if (match.Success) { if (!string.IsNullOrEmpty(match.Groups["early"].Value)) { var early = match.Groups["early"].Value; text = text.Replace(early, ""); hasEarly = true; ret.Comment = Constants.Comment_Early; } if (!hasEarly && !string.IsNullOrEmpty(match.Groups["late"].Value)) { var late = match.Groups["late"].Value; text = text.Replace(late, ""); hasLate = true; ret.Comment = Constants.Comment_Late; } } if (!this.config.GetMatchedTimexRange(text, out string timex, out int beginHour, out int endHour, out int endMinSeg)) { return(new DateTimeResolutionResult()); } // modify time period if "early" or "late" is existed if (hasEarly) { endHour = beginHour + 2; // handling case: night end with 23:59 if (endMinSeg == 59) { endMinSeg = 0; } } else if (hasLate) { beginHour = beginHour + 2; } ret.Timex = timex; ret.FutureValue = ret.PastValue = new Tuple <DateObject, DateObject>( DateObject.MinValue.SafeCreateFromValue(year, month, day, beginHour, 0, 0), DateObject.MinValue.SafeCreateFromValue(year, month, day, endHour, endMinSeg, endMinSeg) ); ret.Success = true; return(ret); }
// merge the entity with its related contexts and then parse the combine text private DateTimeResolutionResult ParseDateTimeAndTimeAlt(ExtractResult er, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); // Original type of the extracted entity var subType = ((Dictionary <string, object>)er.Data)[Constants.SubType].ToString(); var dateTimeEr = new ExtractResult(); // e.g. {next week Mon} or {Tue}, formmer--"next week Mon" doesn't contain "context" key var hasContext = false; ExtractResult contextEr = null; if (((Dictionary <string, object>)er.Data).ContainsKey(Constants.Context)) { contextEr = (ExtractResult)((Dictionary <string, object>)er.Data)[Constants.Context]; if (contextEr.Type.Equals(Constants.ContextType_RelativeSuffix)) { dateTimeEr.Text = $"{er.Text} {contextEr.Text}"; } else { dateTimeEr.Text = $"{contextEr.Text} {er.Text}"; } hasContext = true; } else { dateTimeEr.Text = er.Text; } var dateTimePr = new DateTimeParseResult(); if (subType == Constants.SYS_DATETIME_DATE) { dateTimeEr.Type = Constants.SYS_DATETIME_DATE; dateTimePr = this.config.DateParser.Parse(dateTimeEr, referenceTime); } else if (subType == Constants.SYS_DATETIME_TIME) { if (!hasContext) { dateTimeEr.Type = Constants.SYS_DATETIME_TIME; dateTimePr = this.config.TimeParser.Parse(dateTimeEr, referenceTime); } else if (contextEr.Type == Constants.SYS_DATETIME_DATE || contextEr.Type == Constants.ContextType_RelativePrefix) { // For cases: // Monday 9 am or 11 am // next 9 am or 11 am dateTimeEr.Type = Constants.SYS_DATETIME_DATETIME; dateTimePr = this.config.DateTimeParser.Parse(dateTimeEr, referenceTime); } else if (contextEr.Type == Constants.ContextType_AmPm) { // For cases: in the afternoon 3 o'clock or 5 o'clock dateTimeEr.Type = Constants.SYS_DATETIME_TIME; dateTimePr = this.config.TimeParser.Parse(dateTimeEr, referenceTime); } } else if (subType == Constants.SYS_DATETIME_DATETIME) { // "next week Mon 9 am or Tue 1 pm" dateTimeEr.Type = Constants.SYS_DATETIME_DATETIME; dateTimePr = this.config.DateTimeParser.Parse(dateTimeEr, referenceTime); } else if (subType == Constants.SYS_DATETIME_TIMEPERIOD) { if (!hasContext) { dateTimeEr.Type = Constants.SYS_DATETIME_TIMEPERIOD; dateTimePr = this.config.TimePeriodParser.Parse(dateTimeEr, referenceTime); } else if (contextEr.Type == Constants.SYS_DATETIME_DATE || contextEr.Type == Constants.ContextType_RelativePrefix) { dateTimeEr.Type = Constants.SYS_DATETIME_DATETIMEPERIOD; dateTimePr = this.config.DateTimePeriodParser.Parse(dateTimeEr, referenceTime); } } else if (subType == Constants.SYS_DATETIME_DATETIMEPERIOD) { dateTimeEr.Type = Constants.SYS_DATETIME_DATETIMEPERIOD; dateTimePr = this.config.DateTimePeriodParser.Parse(dateTimeEr, referenceTime); } else if (subType == Constants.SYS_DATETIME_DATEPERIOD) { dateTimeEr.Type = Constants.SYS_DATETIME_DATEPERIOD; dateTimePr = this.config.DatePeriodParser.Parse(dateTimeEr, referenceTime); } if (dateTimePr.Value != null) { ret.FutureValue = ((DateTimeResolutionResult)dateTimePr.Value).FutureValue; ret.PastValue = ((DateTimeResolutionResult)dateTimePr.Value).PastValue; ret.Timex = dateTimePr.TimexStr; // Create resolution ret = GetResolution(er, dateTimePr, ret); ret.Success = true; } return(ret); }
// Match several other cases // Including 'today', 'the day after tomorrow', 'on 13' private DateTimeResolutionResult ParseImplicitDate(string text, DateObject referenceDate) { var trimmedText = text.Trim(); var ret = new DateTimeResolutionResult(); // Handle "on 12" var match = this.config.OnRegex.Match(this.config.DateTokenPrefix + trimmedText); if (match.Success && match.Index == 3 && match.Length == trimmedText.Length) { int month = referenceDate.Month, year = referenceDate.Year; var dayStr = match.Groups["day"].Value; var day = this.config.DayOfMonth[dayStr]; ret.Timex = DateTimeFormatUtil.LuisDate(-1, -1, day); DateObject futureDate, pastDate; var tryStr = DateTimeFormatUtil.LuisDate(year, month, day); if (DateObject.TryParse(tryStr, out DateObject _)) { futureDate = DateObject.MinValue.SafeCreateFromValue(year, month, day); pastDate = DateObject.MinValue.SafeCreateFromValue(year, month, day); if (futureDate < referenceDate) { futureDate = futureDate.AddMonths(+1); } if (pastDate >= referenceDate) { pastDate = pastDate.AddMonths(-1); } } else { futureDate = DateObject.MinValue.SafeCreateFromValue(year, month + 1, day); pastDate = DateObject.MinValue.SafeCreateFromValue(year, month - 1, day); } ret.FutureValue = futureDate; ret.PastValue = pastDate; ret.Success = true; return(ret); } // Handle "today", "the day before yesterday" var exactMatch = this.config.SpecialDayRegex.MatchExact(trimmedText, trim: true); if (exactMatch.Success) { var swift = GetSwiftDay(exactMatch.Value); var value = referenceDate.Date.AddDays(swift); ret.Timex = DateTimeFormatUtil.LuisDate(value); ret.FutureValue = ret.PastValue = value; ret.Success = true; return(ret); } // Handle "two days from tomorrow" exactMatch = this.config.SpecialDayWithNumRegex.MatchExact(trimmedText, trim: true); if (exactMatch.Success) { var swift = GetSwiftDay(exactMatch.Groups["day"].Value); var numErs = this.config.IntegerExtractor.Extract(trimmedText); var numOfDays = Convert.ToInt32((double)(this.config.NumberParser.Parse(numErs[0]).Value ?? 0)); var value = referenceDate.AddDays(numOfDays + swift); ret.Timex = DateTimeFormatUtil.LuisDate(value); ret.FutureValue = ret.PastValue = DateObject.MinValue.SafeCreateFromValue(value.Year, value.Month, value.Day); ret.Success = true; return(ret); } // Handle "two sundays from now" exactMatch = this.config.RelativeWeekDayRegex.MatchExact(trimmedText, trim: true); if (exactMatch.Success) { var numErs = this.config.IntegerExtractor.Extract(trimmedText); var num = Convert.ToInt32((double)(this.config.NumberParser.Parse(numErs[0]).Value ?? 0)); var weekdayStr = exactMatch.Groups["weekday"].Value; var value = referenceDate; // Check whether the determined day of this week has passed. if (value.DayOfWeek > (DayOfWeek)this.config.DayOfWeek[weekdayStr]) { num--; } while (num-- > 0) { value = value.Next((DayOfWeek)this.config.DayOfWeek[weekdayStr]); } ret.Timex = DateTimeFormatUtil.LuisDate(value); ret.FutureValue = ret.PastValue = DateObject.MinValue.SafeCreateFromValue(value.Year, value.Month, value.Day); ret.Success = true; return(ret); } // Handle "next Sunday", "upcoming Sunday" // We define "upcoming Sunday" as the nearest Sunday to come (not include today) // We define "next Sunday" as Sunday of next week exactMatch = this.config.NextRegex.MatchExact(trimmedText, trim: true); if (exactMatch.Success) { var weekdayStr = exactMatch.Groups["weekday"].Value; var value = referenceDate.Next((DayOfWeek)this.config.DayOfWeek[weekdayStr]); if (this.config.UpcomingPrefixRegex.MatchBegin(trimmedText, trim: true).Success) { value = referenceDate.Upcoming((DayOfWeek)this.config.DayOfWeek[weekdayStr]); } ret.Timex = DateTimeFormatUtil.LuisDate(value); ret.FutureValue = ret.PastValue = DateObject.MinValue.SafeCreateFromValue(value.Year, value.Month, value.Day); ret.Success = true; return(ret); } // Handle "this Friday" exactMatch = this.config.ThisRegex.MatchExact(trimmedText, trim: true); if (exactMatch.Success) { var weekdayStr = exactMatch.Groups["weekday"].Value; var value = referenceDate.This((DayOfWeek)this.config.DayOfWeek[weekdayStr]); ret.Timex = DateTimeFormatUtil.LuisDate(value); ret.FutureValue = ret.PastValue = DateObject.MinValue.SafeCreateFromValue(value.Year, value.Month, value.Day); ret.Success = true; return(ret); } // Handle "last Friday", "last mon" // We define "past Sunday" as the nearest Sunday that has already passed (not include today) // We define "previous Sunday" as Sunday of previous week exactMatch = this.config.LastRegex.MatchExact(trimmedText, trim: true); if (exactMatch.Success) { var weekdayStr = exactMatch.Groups["weekday"].Value; var value = referenceDate.Last((DayOfWeek)this.config.DayOfWeek[weekdayStr]); if (this.config.PastPrefixRegex.MatchBegin(trimmedText, trim: true).Success) { value = referenceDate.Past((DayOfWeek)this.config.DayOfWeek[weekdayStr]); } ret.Timex = DateTimeFormatUtil.LuisDate(value); ret.FutureValue = ret.PastValue = DateObject.MinValue.SafeCreateFromValue(value.Year, value.Month, value.Day); ret.Success = true; return(ret); } // Handle "Friday" exactMatch = this.config.WeekDayRegex.MatchExact(trimmedText, trim: true); if (exactMatch.Success) { var weekdayStr = exactMatch.Groups["weekday"].Value; var weekDay = this.config.DayOfWeek[weekdayStr]; var value = referenceDate.This((DayOfWeek)this.config.DayOfWeek[weekdayStr]); if (weekDay == 0) { weekDay = 7; } if (weekDay < (int)referenceDate.DayOfWeek) { value = referenceDate.Next((DayOfWeek)weekDay); } ret.Timex = "XXXX-WXX-" + weekDay; var futureDate = value; var pastDate = value; if (futureDate < referenceDate) { futureDate = futureDate.AddDays(7); } if (pastDate >= referenceDate) { pastDate = pastDate.AddDays(-7); } ret.FutureValue = DateObject.MinValue.SafeCreateFromValue(futureDate.Year, futureDate.Month, futureDate.Day); ret.PastValue = DateObject.MinValue.SafeCreateFromValue(pastDate.Year, pastDate.Month, pastDate.Day); ret.Success = true; return(ret); } // Handle "for the 27th." match = this.config.ForTheRegex.Match(text); if (match.Success) { int day = 0, month = referenceDate.Month, year = referenceDate.Year; var dayStr = match.Groups["DayOfMonth"].Value; // Create a extract result which content ordinal string of text ExtractResult er = new ExtractResult { Text = dayStr, Start = match.Groups["DayOfMonth"].Index, Length = match.Groups["DayOfMonth"].Length, }; day = Convert.ToInt32((double)(this.config.NumberParser.Parse(er).Value ?? 0)); ret.Timex = DateTimeFormatUtil.LuisDate(-1, -1, day); DateObject futureDate; var tryStr = DateTimeFormatUtil.LuisDate(year, month, day); if (DateObject.TryParse(tryStr, out DateObject _)) { futureDate = DateObject.MinValue.SafeCreateFromValue(year, month, day); } else { futureDate = DateObject.MinValue.SafeCreateFromValue(year, month + 1, day); } ret.FutureValue = futureDate; ret.PastValue = ret.FutureValue; ret.Success = true; return(ret); } // Handling cases like 'Thursday the 21st', which both 'Thursday' and '21st' refer to a same date match = this.config.WeekDayAndDayOfMothRegex.Match(text); if (match.Success) { int month = referenceDate.Month, year = referenceDate.Year; // create a extract result which content ordinal string of text ExtractResult extractResultTmp = new ExtractResult { Text = match.Groups["DayOfMonth"].Value, Start = match.Groups["DayOfMonth"].Index, Length = match.Groups["DayOfMonth"].Length, }; // parse the day in text into number var day = Convert.ToInt32((double)(this.config.NumberParser.Parse(extractResultTmp).Value ?? 0)); // The validity of the phrase is guaranteed in the Date Extractor ret.Timex = DateTimeFormatUtil.LuisDate(year, month, day); ret.FutureValue = new DateObject(year, month, day); ret.PastValue = new DateObject(year, month, day); ret.Success = true; return(ret); } // Handling cases like 'Monday 21', which both 'Monday' and '21' refer to the same date. // The year of expected date can be different to the year of referenceDate. match = this.config.WeekDayAndDayRegex.Match(text); if (match.Success) { int month = referenceDate.Month, year = referenceDate.Year; // Create a extract result which content ordinal string of text ExtractResult ertmp = new ExtractResult { Text = match.Groups["day"].Value, Start = match.Groups["day"].Index, Length = match.Groups["day"].Length, }; // Parse the day in text into number var day = Convert.ToInt32((double)(this.config.NumberParser.Parse(ertmp).Value ?? 0)); // Firstly, find a latest date with the "day" as pivotDate. // Secondly, if the pivotDate equals the referenced date, in other word, the day of the referenced date is exactly the "day". // In this way, check if the pivotDate is the weekday. If so, then the futureDate and the previousDate are the same date (referenced date). // Otherwise, increase the pivotDate month by month to find the latest futureDate and decrease the pivotDate month // by month to the latest previousDate. // Notice: if the "day" is larger than 28, some months should be ignored in the increase or decrease procedure. var pivotDate = new DateObject(year, month, 1); var daysInMonth = DateObject.DaysInMonth(year, month); if (daysInMonth >= day) { pivotDate = DateObject.MinValue.SafeCreateFromValue(year, month, day); } else { // Add 1 month is enough, since 1, 3, 5, 7, 8, 10, 12 months has 31 days pivotDate = pivotDate.AddMonths(1); pivotDate = DateObject.MinValue.SafeCreateFromValue(pivotDate.Year, pivotDate.Month, day); } var numWeekDayInt = (int)pivotDate.DayOfWeek; var extractedWeekDayStr = match.Groups["weekday"].Value; var weekDay = this.config.DayOfWeek[extractedWeekDayStr]; if (!pivotDate.Equals(DateObject.MinValue)) { if (day == referenceDate.Day && numWeekDayInt == weekDay) { // The referenceDate is the weekday and with the "day". ret.FutureValue = new DateObject(year, month, day); ret.PastValue = new DateObject(year, month, day); ret.Timex = DateTimeFormatUtil.LuisDate(year, month, day); } else { var futureDate = pivotDate; var pastDate = pivotDate; while ((int)futureDate.DayOfWeek != weekDay || futureDate.Day != day || futureDate < referenceDate) { // Increase the futureDate month by month to find the expected date (the "day" is the weekday) and // make sure the futureDate not less than the referenceDate. futureDate = futureDate.AddMonths(1); var tmp = DateObject.DaysInMonth(futureDate.Year, futureDate.Month); if (tmp >= day) { // For months like January 31, after add 1 month, February 31 won't be returned, so the day should be revised ASAP. futureDate = futureDate.SafeCreateFromValue(futureDate.Year, futureDate.Month, day); } } ret.FutureValue = futureDate; while ((int)pastDate.DayOfWeek != weekDay || pastDate.Day != day || pastDate > referenceDate) { // Decrease the pastDate month by month to find the expected date (the "day" is the weekday) and // make sure the pastDate not larger than the referenceDate. pastDate = pastDate.AddMonths(-1); var tmp = DateObject.DaysInMonth(pastDate.Year, pastDate.Month); if (tmp >= day) { // For months like March 31, after minus 1 month, February 31 won't be returned, so the day should be revised ASAP. pastDate = pastDate.SafeCreateFromValue(pastDate.Year, pastDate.Month, day); } } ret.PastValue = pastDate; if (weekDay == 0) { weekDay = 7; } ret.Timex = "XXXX-WXX-" + weekDay; } } ret.Success = true; return(ret); } return(ret); }
private DateTimeResolutionResult Match2Time(Match match, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); bool hasMin = false, hasSec = false, hasAm = false, hasPm = false, hasMid = false; int hour = 0, min = 0, second = 0, day = referenceTime.Day, month = referenceTime.Month, year = referenceTime.Year; var writtenTimeStr = match.Groups["writtentime"].Value; if (!string.IsNullOrEmpty(writtenTimeStr)) { // get hour var hourStr = match.Groups["hournum"].Value.ToLower(); hour = this.config.Numbers[hourStr]; // get minute var minStr = match.Groups["minnum"].Value; var tensStr = match.Groups["tens"].Value; if (!string.IsNullOrEmpty(minStr)) { min = this.config.Numbers[minStr]; if (!string.IsNullOrEmpty(tensStr)) { min += this.config.Numbers[tensStr]; } hasMin = true; } } else if (!string.IsNullOrEmpty(match.Groups["mid"].Value)) { hasMid = true; if (!string.IsNullOrEmpty(match.Groups["midnight"].Value)) { hour = 0; min = 0; second = 0; } else if (!string.IsNullOrEmpty(match.Groups["midmorning"].Value)) { hour = 10; min = 0; second = 0; } else if (!string.IsNullOrEmpty(match.Groups["midafternoon"].Value)) { hour = 14; min = 0; second = 0; } else if (!string.IsNullOrEmpty(match.Groups["midday"].Value)) { hour = Constants.HalfDayHourCount; min = 0; second = 0; } } else { // get hour var hourStr = match.Groups[Constants.HourGroupName].Value; if (string.IsNullOrEmpty(hourStr)) { hourStr = match.Groups["hournum"].Value.ToLower(); if (!this.config.Numbers.TryGetValue(hourStr, out hour)) { return(ret); } } else { if (!int.TryParse(hourStr, out hour)) { if (!this.config.Numbers.TryGetValue(hourStr.ToLower(), out hour)) { return(ret); } } } // get minute var minStr = match.Groups[Constants.MinuteGroupName].Value.ToLower(); if (string.IsNullOrEmpty(minStr)) { minStr = match.Groups["minnum"].Value; if (!string.IsNullOrEmpty(minStr)) { min = this.config.Numbers[minStr]; hasMin = true; } var tensStr = match.Groups["tens"].Value; if (!string.IsNullOrEmpty(tensStr)) { min += this.config.Numbers[tensStr]; hasMin = true; } } else { min = int.Parse(minStr); hasMin = true; } // get second var secStr = match.Groups[Constants.SecondGroupName].Value.ToLower(); if (!string.IsNullOrEmpty(secStr)) { second = int.Parse(secStr); hasSec = true; } } // Adjust by desc string var descStr = match.Groups[Constants.DescGroupName].Value.ToLower(); // ampm is a special case in which at 6ampm = at 6 if (config.UtilityConfiguration.AmDescRegex.Match(descStr).Success || config.UtilityConfiguration.AmPmDescRegex.Match(descStr).Success || match.Groups[Constants.ImplicitAmGroupName].Success) { if (hour >= Constants.HalfDayHourCount) { hour -= Constants.HalfDayHourCount; } if (!config.UtilityConfiguration.AmPmDescRegex.Match(descStr).Success) { hasAm = true; } } else if (config.UtilityConfiguration.PmDescRegex.Match(descStr).Success || match.Groups[Constants.ImplicitPmGroupName].Success) { if (hour < Constants.HalfDayHourCount) { hour += Constants.HalfDayHourCount; } hasPm = true; } // adjust min by prefix var timePrefix = match.Groups[Constants.PrefixGroupName].Value.ToLower(); if (!string.IsNullOrEmpty(timePrefix)) { this.config.AdjustByPrefix(timePrefix, ref hour, ref min, ref hasMin); } // adjust hour by suffix var timeSuffix = match.Groups[Constants.SuffixGroupName].Value.ToLower(); if (!string.IsNullOrEmpty(timeSuffix)) { this.config.AdjustBySuffix(timeSuffix, ref hour, ref min, ref hasMin, ref hasAm, ref hasPm); } if (hour == 24) { hour = 0; } ret.Timex = "T" + hour.ToString("D2"); if (hasMin) { ret.Timex += ":" + min.ToString("D2"); } if (hasSec) { ret.Timex += ":" + second.ToString("D2"); } if (hour <= Constants.HalfDayHourCount && !hasPm && !hasAm && !hasMid) { ret.Comment = Constants.Comment_AmPm; } ret.FutureValue = ret.PastValue = DateObject.MinValue.SafeCreateFromValue(year, month, day, hour, min, second); ret.Success = true; return(ret); }
// Parse a regex match which includes 'day', 'month' and 'year' (optional) group private DateTimeResolutionResult Match2Date(Match match, DateObject referenceDate, string relativeStr) { var ret = new DateTimeResolutionResult(); var monthStr = match.Groups["month"].Value; var dayStr = match.Groups["day"].Value; var yearStr = match.Groups["year"].Value; var weekdayStr = match.Groups["weekday"].Value; int month = 0, day = 0, year = 0; if (this.config.MonthOfYear.ContainsKey(monthStr) && this.config.DayOfMonth.ContainsKey(dayStr)) { month = this.config.MonthOfYear[monthStr]; day = this.config.DayOfMonth[dayStr]; if (!string.IsNullOrEmpty(yearStr)) { year = int.Parse(yearStr); if (year < 100 && year >= Constants.MinTwoDigitYearPastNum) { year += 1900; } else if (year >= 0 && year < Constants.MaxTwoDigitYearFutureNum) { year += 2000; } } } var noYear = false; if (year == 0) { year = referenceDate.Year; if (!string.IsNullOrEmpty(relativeStr)) { var swift = this.config.GetSwiftMonthOrYear(relativeStr); // @TODO Improve handling of next/last in particular cases "next friday 5/12" when the next friday is not 5/12. if (!string.IsNullOrEmpty(weekdayStr)) { swift = 0; } year += swift; } else { noYear = true; } ret.Timex = DateTimeFormatUtil.LuisDate(-1, month, day); } else { ret.Timex = DateTimeFormatUtil.LuisDate(year, month, day); } var futureDate = DateObject.MinValue.SafeCreateFromValue(year, month, day); var pastDate = DateObject.MinValue.SafeCreateFromValue(year, month, day); if (noYear && futureDate < referenceDate && !futureDate.IsDefaultValue()) { futureDate = futureDate.AddYears(+1); } if (noYear && pastDate >= referenceDate && !pastDate.IsDefaultValue()) { pastDate = pastDate.AddYears(-1); } ret.FutureValue = futureDate; ret.PastValue = pastDate; ret.Success = true; return(ret); }
// parse "in 20 minutes" private DateTimeResolutionResult ParseNumberWithUnit(string text, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); string unitStr; // if there are spaces between number and unit var ers = this.config.CardinalExtractor.Extract(text); if (ers.Count == 1) { var pr = this.config.CardinalParser.Parse(ers[0]); var srcUnit = text.Substring(ers[0].Start + ers[0].Length ?? 0).Trim(); if (srcUnit.StartsWith("个", StringComparison.Ordinal)) { srcUnit = srcUnit.Substring(1); } var beforeStr = text.Substring(0, ers[0].Start ?? 0); if (this.config.UnitMap.ContainsKey(srcUnit)) { var numStr = pr.ResolutionStr; unitStr = this.config.UnitMap[srcUnit]; var prefixMatch = this.config.PastRegex.MatchExact(beforeStr, trim: true); if (prefixMatch.Success) { DateObject beginDate, endDate; switch (unitStr) { case "H": beginDate = referenceTime.AddHours(-(double)pr.Value); endDate = referenceTime; break; case "M": beginDate = referenceTime.AddMinutes(-(double)pr.Value); endDate = referenceTime; break; case "S": beginDate = referenceTime.AddSeconds(-(double)pr.Value); endDate = referenceTime; break; default: return(ret); } ret.Timex = $"({DateTimeFormatUtil.LuisDate(beginDate)}T{DateTimeFormatUtil.LuisTime(beginDate)},{DateTimeFormatUtil.LuisDate(endDate)}T{DateTimeFormatUtil.LuisTime(endDate)},PT{numStr}{unitStr[0]})"; ret.FutureValue = ret.PastValue = new Tuple <DateObject, DateObject>(beginDate, endDate); ret.Success = true; return(ret); } prefixMatch = this.config.FutureRegex.MatchExact(beforeStr, trim: true); if (prefixMatch.Success) { DateObject beginDate, endDate; switch (unitStr) { case "H": beginDate = referenceTime; endDate = referenceTime.AddHours((double)pr.Value); break; case "M": beginDate = referenceTime; endDate = referenceTime.AddMinutes((double)pr.Value); break; case "S": beginDate = referenceTime; endDate = referenceTime.AddSeconds((double)pr.Value); break; default: return(ret); } ret.Timex = $"({DateTimeFormatUtil.LuisDate(beginDate)}T{DateTimeFormatUtil.LuisTime(beginDate)},{DateTimeFormatUtil.LuisDate(endDate)}T{DateTimeFormatUtil.LuisTime(endDate)},PT{numStr}{unitStr[0]})"; ret.FutureValue = ret.PastValue = new Tuple <DateObject, DateObject>(beginDate, endDate); ret.Success = true; return(ret); } } } // handle "last hour" var match = this.config.UnitRegex.Match(text); if (match.Success) { var srcUnit = match.Groups["unit"].Value; var beforeStr = text.Substring(0, match.Index).Trim(); if (this.config.UnitMap.ContainsKey(srcUnit)) { unitStr = this.config.UnitMap[srcUnit]; if (this.config.PastRegex.IsExactMatch(beforeStr, trim: true)) { DateObject beginDate, endDate; switch (unitStr) { case "H": beginDate = referenceTime.AddHours(-1); endDate = referenceTime; break; case "M": beginDate = referenceTime.AddMinutes(-1); endDate = referenceTime; break; case "S": beginDate = referenceTime.AddSeconds(-1); endDate = referenceTime; break; default: return(ret); } ret.Timex = $"({DateTimeFormatUtil.LuisDate(beginDate)}T{DateTimeFormatUtil.LuisTime(beginDate)},{DateTimeFormatUtil.LuisDate(endDate)}T{DateTimeFormatUtil.LuisTime(endDate)},PT1{unitStr[0]})"; ret.FutureValue = ret.PastValue = new Tuple <DateObject, DateObject>(beginDate, endDate); ret.Success = true; return(ret); } if (this.config.FutureRegex.IsExactMatch(beforeStr, trim: true)) { DateObject beginDate, endDate; switch (unitStr) { case "H": beginDate = referenceTime; endDate = referenceTime.AddHours(1); break; case "M": beginDate = referenceTime; endDate = referenceTime.AddMinutes(1); break; case "S": beginDate = referenceTime; endDate = referenceTime.AddSeconds(1); break; default: return(ret); } ret.Timex = $"({DateTimeFormatUtil.LuisDate(beginDate)}T{DateTimeFormatUtil.LuisTime(beginDate)},{DateTimeFormatUtil.LuisDate(endDate)}T{DateTimeFormatUtil.LuisTime(endDate)},PT1{unitStr[0]})"; ret.FutureValue = ret.PastValue = new Tuple <DateObject, DateObject>(beginDate, endDate); ret.Success = true; return(ret); } } } return(ret); }