public DateTimeParseResult Parse(ExtractResult er, DateObject refTime) { var referenceTime = refTime; object value = null; if (er.Type.Equals(ParserName, StringComparison.Ordinal)) { DateTimeResolutionResult innerResult; if (TimeZoneUtility.ShouldResolveTimeZone(er, config.Options)) { var metadata = er.Data as Dictionary <string, object>; var timezoneEr = metadata[Constants.SYS_DATETIME_TIMEZONE] as ExtractResult; var timezonePr = config.TimeZoneParser.Parse(timezoneEr); innerResult = InternalParse( er.Text.Substring(0, (int)(er.Length - timezoneEr.Length)), referenceTime); if (timezonePr != null && timezonePr.Value != null) { innerResult.TimeZoneResolution = ((DateTimeResolutionResult)timezonePr.Value).TimeZoneResolution; } } else { innerResult = InternalParse(er.Text, referenceTime); } if (innerResult.Success) { innerResult.FutureResolution = new Dictionary <string, string> { { TimeTypeConstants.START_TIME, DateTimeFormatUtil.FormatTime(((Tuple <DateObject, DateObject>)innerResult.FutureValue).Item1) }, { TimeTypeConstants.END_TIME, DateTimeFormatUtil.FormatTime(((Tuple <DateObject, DateObject>)innerResult.FutureValue).Item2) }, }; innerResult.PastResolution = new Dictionary <string, string> { { TimeTypeConstants.START_TIME, DateTimeFormatUtil.FormatTime(((Tuple <DateObject, DateObject>)innerResult.PastValue).Item1) }, { TimeTypeConstants.END_TIME, DateTimeFormatUtil.FormatTime(((Tuple <DateObject, DateObject>)innerResult.PastValue).Item2) }, }; value = innerResult; } } var ret = new DateTimeParseResult { Text = er.Text, Start = er.Start, Length = er.Length, Type = er.Type, Data = er.Data, Value = value, TimexStr = value == null ? string.Empty : ((DateTimeResolutionResult)value).Timex, ResolutionStr = string.Empty, }; return(ret); }
// 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 > DateObjectExtension.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); } // Handle "今から2日曜日" (2 Sundays from now) var exactMatch = this.config.SpecialDayWithNumRegex.MatchExact(text, trim: true); if (exactMatch.Success) { var numErs = this.config.IntegerExtractor.Extract(text); var weekdayStr = exactMatch.Groups["weekday"].Value; if (!string.IsNullOrEmpty(weekdayStr) && numErs.Count > 0) { var num = Convert.ToInt32((double)(this.config.NumberParser.Parse(numErs[0]).Value ?? 0)); 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 "明日から3週間" (3 weeks from tomorrow) var durationResult = this.config.DurationExtractor.Extract(text, referenceDate); var unitMatch = this.config.DurationRelativeDurationUnitRegex.Match(text); if (exactMatch.Success && unitMatch.Success && (durationResult.Count > 0) && string.IsNullOrEmpty(unitMatch.Groups["few"].Value)) { var pr = this.config.DurationParser.Parse(durationResult[0], referenceDate); var dayStr = unitMatch.Groups["later"].Value; var future = true; int swift = 0; if (pr != null) { if (!string.IsNullOrEmpty(dayStr)) { swift = this.config.GetSwiftDay(dayStr); } var resultDateTime = DurationParsingUtil.ShiftDateTime(pr.TimexStr, referenceDate.AddDays(swift), future); ret.Timex = $"{DateTimeFormatUtil.LuisDate(resultDateTime)}"; ret.FutureValue = ret.PastValue = resultDateTime; ret.Success = true; return(ret); } } if (!ret.Success) { ret = MatchWeekdayAndDay(text, referenceDate); } 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); }
// Handle cases like "三天前" "Three days ago" 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 unitMatch = this.config.DurationRelativeDurationUnitRegex.Match(text); // set the inexact number "数" (few) to 3 for now var number = numberStr.Equals(unitMatch.Groups["few"].Value, StringComparison.Ordinal) ? 3 : ConvertCJKToNum(numberStr); if (!numberStr.Equals(unitMatch.Groups["few"].Value, StringComparison.Ordinal)) { if (suffix.Equals(unitMatch.Value, StringComparison.Ordinal)) { var pr = this.config.DurationParser.Parse(durationRes[0], referenceDate); var future = suffix.Equals(unitMatch.Groups["later"].Value, StringComparison.Ordinal); int swift = 0; if (pr != null) { var resultDateTime = DurationParsingUtil.ShiftDateTime(pr.TimexStr, referenceDate.AddDays(swift), future); ret.Timex = $"{DateTimeFormatUtil.LuisDate(resultDateTime)}"; ret.FutureValue = ret.PastValue = resultDateTime; ret.Success = true; return(ret); } } } if (this.config.UnitMap.ContainsKey(srcUnit)) { unitStr = this.config.UnitMap[srcUnit]; ret.Timex = TimexUtility.GenerateDurationTimex(number, unitStr, DurationParsingUtil.IsLessThanDay(unitStr)); DateObject date = Constants.InvalidDate; var beforeMatch = this.config.BeforeRegex.Match(suffix); if (beforeMatch.Success && suffix.StartsWith(beforeMatch.Value, StringComparison.Ordinal)) { date = DurationParsingUtil.ShiftDateTime(ret.Timex, referenceDate, future: false); } var afterMatch = this.config.AfterRegex.Match(suffix); if (afterMatch.Success && suffix.StartsWith(afterMatch.Value, StringComparison.Ordinal)) { date = DurationParsingUtil.ShiftDateTime(ret.Timex, referenceDate, future: true); } if (date != Constants.InvalidDate) { ret.Timex = $"{DateTimeFormatUtil.LuisDate(date)}"; ret.FutureValue = ret.PastValue = date; 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 == 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 = 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; } } else { switch (type) { case Constants.SYS_DATETIME_DATE: singlePointType = TimeTypeConstants.DATE; singlePointResolution = DateTimeFormatUtil.FormatDate((DateObject)ret.FutureValue); break; case Constants.SYS_DATETIME_DATETIME: singlePointType = TimeTypeConstants.DATETIME; singlePointResolution = DateTimeFormatUtil.FormatDateTime((DateObject)ret.FutureValue); break; case Constants.SYS_DATETIME_TIME: singlePointType = TimeTypeConstants.TIME; singlePointResolution = DateTimeFormatUtil.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); }
// handle cases like "5分钟前", "1小时以后" private DateTimeResolutionResult ParserDurationWithAgoAndLater(string text, DateObject referenceDate) { var ret = new DateTimeResolutionResult(); var durationRes = this.config.DurationExtractor.Extract(text, referenceDate); if (durationRes.Count > 0) { var match = this.config.DateTimePeriodUnitRegex.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 = ConvertCJKToNum(numberStr); if (this.config.UnitMap.ContainsKey(srcUnit)) { var unitStr = this.config.UnitMap[srcUnit]; var beforeMatch = this.config.BeforeRegex.Match(suffix); if (beforeMatch.Success && suffix.StartsWith(beforeMatch.Value, StringComparison.InvariantCulture)) { DateObject date; switch (unitStr) { case Constants.TimexHour: date = referenceDate.AddHours(-number); break; case Constants.TimexMinute: date = referenceDate.AddMinutes(-number); break; case Constants.TimexSecond: date = referenceDate.AddSeconds(-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.TimexHour: date = referenceDate.AddHours(number); break; case Constants.TimexMinute: date = referenceDate.AddMinutes(number); break; case Constants.TimexSecond: date = referenceDate.AddSeconds(number); break; default: return(ret); } ret.Timex = $"{DateTimeFormatUtil.LuisDate(date)}"; ret.FutureValue = ret.PastValue = date; ret.Success = true; return(ret); } } } } 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]); } else if (config.GetSwiftMonthOrYear(trimmedText) == 2) { value = value.AddDays(7); } 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); }
// 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); // check if the extraction is empty or a relative ordinal (e.g. "next", "previous") if (er.Count == 0 || er[0].Metadata.IsOrdinalRelative) { 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); GetYearInAffix(suffix, ref year, ref ambiguous, out bool success); // Check also in prefix if (!success && this.config.CheckBothBeforeAfter) { var prefix = trimmedText.Substring(0, er[0].Start ?? 0); GetYearInAffix(prefix, ref year, ref ambiguous, out success); } } // 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); }
internal void ResolveAmpm(Dictionary <string, object> resolutionDic, string keyName) { if (resolutionDic.ContainsKey(keyName)) { var resolution = (Dictionary <string, string>)resolutionDic[keyName]; if (!resolutionDic.ContainsKey(DateTimeResolutionKey.Timex)) { return; } var timex = (string)resolutionDic[DateTimeResolutionKey.Timex]; resolutionDic.Remove(keyName); resolutionDic.Add(keyName + "Am", resolution); var resolutionPm = new Dictionary <string, string>(); switch ((string)resolutionDic[ResolutionKey.Type]) { case Constants.SYS_DATETIME_TIME: resolutionPm[ResolutionKey.Value] = DateTimeFormatUtil.ToPm(resolution[ResolutionKey.Value]); resolutionPm[DateTimeResolutionKey.Timex] = DateTimeFormatUtil.ToPm(timex); break; case Constants.SYS_DATETIME_DATETIME: var splited = resolution[ResolutionKey.Value].Split(' '); resolutionPm[ResolutionKey.Value] = splited[0] + " " + DateTimeFormatUtil.ToPm(splited[1]); resolutionPm[DateTimeResolutionKey.Timex] = DateTimeFormatUtil.AllStringToPm(timex); break; case Constants.SYS_DATETIME_TIMEPERIOD: if (resolution.ContainsKey(DateTimeResolutionKey.START)) { resolutionPm[DateTimeResolutionKey.START] = DateTimeFormatUtil.ToPm(resolution[DateTimeResolutionKey.START]); } if (resolution.ContainsKey(DateTimeResolutionKey.END)) { resolutionPm[DateTimeResolutionKey.END] = DateTimeFormatUtil.ToPm(resolution[DateTimeResolutionKey.END]); } resolutionPm[DateTimeResolutionKey.Timex] = DateTimeFormatUtil.AllStringToPm(timex); break; case Constants.SYS_DATETIME_DATETIMEPERIOD: splited = resolution[DateTimeResolutionKey.START].Split(' '); if (resolution.ContainsKey(DateTimeResolutionKey.START)) { resolutionPm[DateTimeResolutionKey.START] = splited[0] + " " + DateTimeFormatUtil.ToPm(splited[1]); } splited = resolution[DateTimeResolutionKey.END].Split(' '); if (resolution.ContainsKey(DateTimeResolutionKey.END)) { resolutionPm[DateTimeResolutionKey.END] = splited[0] + " " + DateTimeFormatUtil.ToPm(splited[1]); } resolutionPm[DateTimeResolutionKey.Timex] = DateTimeFormatUtil.AllStringToPm(timex); break; } resolutionDic.Add(keyName + "Pm", resolutionPm); } }
public DateTimeParseResult Parse(ExtractResult er, DateObject refTime) { var referenceTime = refTime; object value = null; if (er.Type.Equals(ParserName)) { var innerResult = MergeDateWithSingleTimePeriod(er.Text, referenceTime); if (!innerResult.Success) { innerResult = MergeTwoTimePoints(er.Text, referenceTime); } if (!innerResult.Success) { innerResult = ParseSpecificTimeOfDay(er.Text, referenceTime); } if (!innerResult.Success) { innerResult = ParseDuration(er.Text, referenceTime); } if (!innerResult.Success) { innerResult = ParseRelativeUnit(er.Text, referenceTime); } if (!innerResult.Success) { innerResult = ParseDateWithPeriodPrefix(er.Text, referenceTime); } if (!innerResult.Success) { // Cases like "today after 2:00pm", "1/1/2015 before 2:00 in the afternoon" innerResult = ParseDateWithTimePeriodSuffix(er.Text, referenceTime); } if (innerResult.Success) { if (!IsBeforeOrAfterMod(innerResult.Mod)) { innerResult.FutureResolution = new Dictionary <string, string> { { TimeTypeConstants.START_DATETIME, DateTimeFormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)innerResult.FutureValue).Item1) }, { TimeTypeConstants.END_DATETIME, DateTimeFormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)innerResult.FutureValue).Item2) }, }; innerResult.PastResolution = new Dictionary <string, string> { { TimeTypeConstants.START_DATETIME, DateTimeFormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)innerResult.PastValue).Item1) }, { TimeTypeConstants.END_DATETIME, DateTimeFormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)innerResult.PastValue).Item2) }, }; } else { if (innerResult.Mod == Constants.AFTER_MOD) { // Cases like "1/1/2015 after 2:00" there is no EndTime innerResult.FutureResolution = new Dictionary <string, string> { { TimeTypeConstants.START_DATETIME, DateTimeFormatUtil.FormatDateTime((DateObject)innerResult.FutureValue) }, }; innerResult.PastResolution = new Dictionary <string, string> { { TimeTypeConstants.START_DATETIME, DateTimeFormatUtil.FormatDateTime((DateObject)innerResult.PastValue) }, }; } else { // Cases like "1/1/2015 before 5:00 in the afternoon" there is no StartTime innerResult.FutureResolution = new Dictionary <string, string> { { TimeTypeConstants.END_DATETIME, DateTimeFormatUtil.FormatDateTime((DateObject)innerResult.FutureValue) }, }; innerResult.PastResolution = new Dictionary <string, string> { { TimeTypeConstants.END_DATETIME, DateTimeFormatUtil.FormatDateTime((DateObject)innerResult.PastValue) }, }; } } value = innerResult; } } var ret = new DateTimeParseResult { Text = er.Text, Start = er.Start, Length = er.Length, Type = er.Type, Data = er.Data, Value = value, TimexStr = value == null ? string.Empty : ((DateTimeResolutionResult)value).Timex, ResolutionStr = string.Empty, }; return(ret); }
private DateTimeResolutionResult ParseTimeOfToday(string text, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); var trimmedText = text.ToLowerInvariant().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.ToLower(); 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.ToLowerInvariant(); // 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)) { 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); }
// 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().ToLower(); 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.GetSwiftMonth(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); }
public static string GenerateYearTimex(int year, string specialYearPrefixes = null) { var yearTimex = DateTimeFormatUtil.LuisDate(year); return(specialYearPrefixes == null ? yearTimex : specialYearPrefixes + yearTimex); }
// 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.ToLower(); 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" match = this.config.SpecialDayRegex.Match(trimmedText); if (match.Success && match.Index == 0 && match.Length == trimmedText.Length) { var swift = this.config.GetSwiftDay(match.Value); var value = referenceDate.AddDays(swift); ret.Timex = DateTimeFormatUtil.LuisDate(value); ret.FutureValue = ret.PastValue = value; ret.Success = true; return(ret); } // handle "two days from tomorrow" match = this.config.SpecialDayWithNumRegex.Match(trimmedText); if (match.Success && match.Index == 0 && match.Length == trimmedText.Length) { var swift = this.config.GetSwiftDay(match.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 = value; ret.Success = true; return(ret); } // handle "two sundays from now" match = this.config.RelativeWeekDayRegex.Match(trimmedText); if (match.Success && match.Index == 0 && match.Length == trimmedText.Length) { var numErs = this.config.IntegerExtractor.Extract(trimmedText); var num = Convert.ToInt32((double)(this.config.NumberParser.Parse(numErs[0]).Value ?? 0)); var weekdayStr = match.Groups["weekday"].Value.ToLower(); 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 = value; ret.Success = true; return(ret); } // handle "next Sunday" match = this.config.NextRegex.Match(trimmedText); if (match.Success && match.Index == 0 && match.Length == trimmedText.Length) { var weekdayStr = match.Groups["weekday"].Value.ToLower(); var value = referenceDate.Next((DayOfWeek)this.config.DayOfWeek[weekdayStr]); ret.Timex = DateTimeFormatUtil.LuisDate(value); ret.FutureValue = ret.PastValue = value; ret.Success = true; return(ret); } // handle "this Friday" match = this.config.ThisRegex.Match(trimmedText); if (match.Success && match.Index == 0 && match.Length == trimmedText.Length) { var weekdayStr = match.Groups["weekday"].Value.ToLower(); var value = referenceDate.This((DayOfWeek)this.config.DayOfWeek[weekdayStr]); ret.Timex = DateTimeFormatUtil.LuisDate(value); ret.FutureValue = ret.PastValue = value; ret.Success = true; return(ret); } // handle "last Friday", "last mon" match = this.config.LastRegex.Match(trimmedText); if (match.Success && match.Index == 0 && match.Length == trimmedText.Length) { var weekdayStr = match.Groups["weekday"].Value.ToLower(); var value = referenceDate.Last((DayOfWeek)this.config.DayOfWeek[weekdayStr]); ret.Timex = DateTimeFormatUtil.LuisDate(value); ret.FutureValue = ret.PastValue = value; ret.Success = true; return(ret); } // handle "Friday" match = this.config.WeekDayRegex.Match(trimmedText); if (match.Success && match.Index == 0 && match.Length == trimmedText.Length) { var weekdayStr = match.Groups["weekday"].Value.ToLower(); 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 = futureDate; ret.PastValue = pastDate; 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.ToLower(); // 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 erTmp = 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(erTmp).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); } 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, CultureInfo.InvariantCulture); } hourStr = hourGroup.Captures[1].Value; if (config.Numbers.ContainsKey(hourStr)) { endHour = config.Numbers[hourStr]; } else { endHour = int.Parse(hourStr, CultureInfo.InvariantCulture); } 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, CultureInfo.InvariantCulture); } else if (minuteCapture.Index >= time2StartIndex && minuteCapture.Index + minuteCapture.Length <= time2EndIndex) { endMinute = int.Parse(minuteCapture.Value, CultureInfo.InvariantCulture); } } // 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, CultureInfo.InvariantCulture); } 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.StartsWith("a", StringComparison.Ordinal); var hasLeftPm = !string.IsNullOrEmpty(leftDesc) && leftDesc.StartsWith("p", StringComparison.Ordinal); var hasRightAm = !string.IsNullOrEmpty(rightDesc) && rightDesc.StartsWith("a", StringComparison.Ordinal); var hasRightPm = !string.IsNullOrEmpty(rightDesc) && rightDesc.StartsWith("p", StringComparison.Ordinal); 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); }
// Parse combined patterns Duration + Date, e.g. '3 days before Monday', '4 weeks after January 15th' private DateTimeResolutionResult ParseDurationWithDate(string text, DateObject referenceDate) { var ret = new DateTimeResolutionResult(); var durationRes = config.DurationExtractor.Extract(text, referenceDate); foreach (var duration in durationRes) { var matches = config.UnitRegex.Matches(duration.Text); if (matches.Count > 0) { var afterStr = text.Substring((int)duration.Start + (int)duration.Length); // Check if the Duration entity is followed by "before|from|after" var connector = config.BeforeAfterRegex.MatchBegin(afterStr, trim: true); if (connector.Success) { // Parse Duration var pr = config.DurationParser.Parse(duration, referenceDate); // Parse Date if (pr.Value != null) { var dateString = afterStr.Substring(connector.Index + connector.Length).Trim(); var innerResult = ParseBasicRegexMatch(dateString, referenceDate); if (!innerResult.Success) { innerResult = ParseImplicitDate(dateString, referenceDate); } if (!innerResult.Success) { innerResult = ParseWeekdayOfMonth(dateString, referenceDate); } if (!innerResult.Success) { innerResult = ParseNumberWithMonth(dateString, referenceDate); } if (!innerResult.Success) { innerResult = ParseSingleNumber(dateString, referenceDate); } if (!innerResult.Success) { var holidayEr = new ExtractResult { Start = 0, Length = dateString.Length, Text = dateString, Type = Constants.SYS_DATETIME_DATE, Data = null, Metadata = new Metadata { IsHoliday = true }, }; innerResult = (DateTimeResolutionResult)config.HolidayParser.Parse(holidayEr, referenceDate).Value; } // Combine parsed results Duration + Date if (innerResult.Success) { var isFuture = connector.Groups["after"].Success ? true : false; DateObject date = (DateObject)innerResult.FutureValue; var resultDateTime = DurationParsingUtil.ShiftDateTime(pr.TimexStr, date, future: isFuture); ret.Timex = $"{DateTimeFormatUtil.LuisDate(resultDateTime)}"; ret.FutureValue = ret.PastValue = resultDateTime; ret.SubDateTimeEntities = new List <object> { pr }; ret.Success = true; } } } } } 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; 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); }
// 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(); int month = 0, day = 0, year = 0; var monthStr = match.Groups["month"].Value; var dayStr = match.Groups["day"].Value; var weekdayStr = match.Groups["weekday"].Value; var yearStr = match.Groups["year"].Value; var writtenYear = match.Groups["fullyear"].Value; var ambiguousCentury = false; if (this.config.MonthOfYear.ContainsKey(monthStr) && this.config.DayOfMonth.ContainsKey(dayStr)) { month = this.config.MonthOfYear[monthStr]; day = this.config.DayOfMonth[dayStr]; if (!string.IsNullOrEmpty(writtenYear)) { year = this.config.DateExtractor.GetYearFromText(match); } else if (!string.IsNullOrEmpty(yearStr)) { year = int.Parse(yearStr, CultureInfo.InvariantCulture); if (year < 100 && year >= Constants.MinTwoDigitYearPastNum) { year += Constants.BASE_YEAR_PAST_CENTURY; } else if (year >= 0 && year < Constants.MaxTwoDigitYearFutureNum) { year += Constants.BASE_YEAR_CURRENT_CENTURY; } else if (year >= Constants.MaxTwoDigitYearFutureNum && year < Constants.MinTwoDigitYearPastNum) { // Two-digit years in the range [30, 40) are ambiguos ambiguousCentury = true; } } } 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 futurePastDates = DateContext.GenerateDates(noYear, referenceDate, year, month, day); ret.FutureValue = futurePastDates.future; ret.PastValue = futurePastDates.past; ret.Success = true; // Ambiguous two-digit years are assigned values in both centuries (e.g. 35 -> 1935, 2035) if (ambiguousCentury) { ret.PastValue = futurePastDates.past.AddYears(Constants.BASE_YEAR_PAST_CENTURY); ret.FutureValue = futurePastDates.future.AddYears(Constants.BASE_YEAR_CURRENT_CENTURY); ret.Timex = TimexUtility.ModifyAmbiguousCenturyTimex(ret.Timex); } 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(); 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 special case: night ends with 23:59 due to C# issues. 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.Equals(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); }
public DateTimeParseResult Parse(ExtractResult er, DateObject reference) { var referenceDate = reference; object value = null; if (er.Type.Equals(ParserName, StringComparison.Ordinal)) { var innerResult = ParseBasicRegexMatch(er.Text, referenceDate); if (!innerResult.Success) { innerResult = ParseImplicitDate(er.Text, referenceDate); } if (!innerResult.Success) { innerResult = ParseWeekdayOfMonth(er.Text, referenceDate); } if (!innerResult.Success) { innerResult = ParseDurationWithAgoAndLater(er.Text, referenceDate); } // NumberWithMonth must be the second last one, because it only need to find a number and a month to get a "success" if (!innerResult.Success) { innerResult = ParseNumberWithMonth(er.Text, referenceDate); } // SingleNumber last one if (!innerResult.Success) { innerResult = ParseSingleNumber(er.Text, referenceDate); } if (innerResult.Success) { innerResult.FutureResolution = new Dictionary <string, string> { { TimeTypeConstants.DATE, DateTimeFormatUtil.FormatDate((DateObject)innerResult.FutureValue) }, }; innerResult.PastResolution = new Dictionary <string, string> { { TimeTypeConstants.DATE, DateTimeFormatUtil.FormatDate((DateObject)innerResult.PastValue) }, }; value = innerResult; } } var ret = new DateTimeParseResult { Text = er.Text, Start = er.Start, Length = er.Length, Type = er.Type, Data = er.Data, Value = value, TimexStr = value == null ? string.Empty : ((DateTimeResolutionResult)value).Timex, ResolutionStr = string.Empty, }; return(ret); }
public DateTimeParseResult Parse(ExtractResult er, DateObject refTime) { var referenceTime = refTime; object value = null; if (er.Type.Equals(ParserName)) { var innerResult = InternalParse(er.Text, referenceTime); // Handling timeZone if (innerResult.Success && TimeZoneUtility.ShouldResolveTimeZone(er, this.Config.Options)) { var metadata = er.Data as Dictionary <string, object>; var timezoneEr = metadata[Constants.SYS_DATETIME_TIMEZONE] as ExtractResult; var timezonePr = this.Config.TimeZoneParser.Parse(timezoneEr); if (timezonePr != null && timezonePr.Value != null) { innerResult.TimeZoneResolution = ((DateTimeResolutionResult)timezonePr.Value).TimeZoneResolution; } } if (innerResult.Success) { if (!IsBeforeOrAfterMod(innerResult.Mod)) { innerResult.FutureResolution = new Dictionary <string, string> { { TimeTypeConstants.START_DATETIME, DateTimeFormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)innerResult.FutureValue).Item1) }, { TimeTypeConstants.END_DATETIME, DateTimeFormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)innerResult.FutureValue).Item2) }, }; innerResult.PastResolution = new Dictionary <string, string> { { TimeTypeConstants.START_DATETIME, DateTimeFormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)innerResult.PastValue).Item1) }, { TimeTypeConstants.END_DATETIME, DateTimeFormatUtil.FormatDateTime(((Tuple <DateObject, DateObject>)innerResult.PastValue).Item2) }, }; } else { if (innerResult.Mod == Constants.AFTER_MOD) { // Cases like "1/1/2015 after 2:00" there is no EndTime innerResult.FutureResolution = new Dictionary <string, string> { { TimeTypeConstants.START_DATETIME, DateTimeFormatUtil.FormatDateTime((DateObject)innerResult.FutureValue) }, }; innerResult.PastResolution = new Dictionary <string, string> { { TimeTypeConstants.START_DATETIME, DateTimeFormatUtil.FormatDateTime((DateObject)innerResult.PastValue) }, }; } else { // Cases like "1/1/2015 before 5:00 in the afternoon" there is no StartTime innerResult.FutureResolution = new Dictionary <string, string> { { TimeTypeConstants.END_DATETIME, DateTimeFormatUtil.FormatDateTime((DateObject)innerResult.FutureValue) }, }; innerResult.PastResolution = new Dictionary <string, string> { { TimeTypeConstants.END_DATETIME, DateTimeFormatUtil.FormatDateTime((DateObject)innerResult.PastValue) }, }; } } value = innerResult; } } var ret = new DateTimeParseResult { Text = er.Text, Start = er.Start, Length = er.Length, Type = er.Type, Data = er.Data, Value = value, TimexStr = value == null ? string.Empty : ((DateTimeResolutionResult)value).Timex, ResolutionStr = string.Empty, }; 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(); int month = 0, day = 0, year = 0; var monthStr = match.Groups["month"].Value; var dayStr = match.Groups["day"].Value; var weekdayStr = match.Groups["weekday"].Value; var yearStr = match.Groups["year"].Value; var writtenYear = match.Groups["fullyear"].Value; if (this.config.MonthOfYear.ContainsKey(monthStr) && this.config.DayOfMonth.ContainsKey(dayStr)) { month = this.config.MonthOfYear[monthStr]; day = this.config.DayOfMonth[dayStr]; if (!string.IsNullOrEmpty(writtenYear)) { year = this.config.DateExtractor.GetYearFromText(match); } else if (!string.IsNullOrEmpty(yearStr)) { year = int.Parse(yearStr, CultureInfo.InvariantCulture); 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); }
// 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(); 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); }
// 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 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); }
internal static void ResolveAmpm(Dictionary <string, object> resolutionDic, string keyName) { if (resolutionDic.ContainsKey(keyName)) { var resolution = (Dictionary <string, string>)resolutionDic[keyName]; var resolutionPm = new Dictionary <string, string>(); if (!resolutionDic.ContainsKey(DateTimeResolutionKey.Timex)) { return; } var timex = (string)resolutionDic[DateTimeResolutionKey.Timex]; resolutionDic.Remove(keyName); resolutionDic.Add(keyName + "Am", resolution); switch ((string)resolutionDic[ResolutionKey.Type]) { case Constants.SYS_DATETIME_TIME: resolutionPm[ResolutionKey.Value] = DateTimeFormatUtil.ToPm(resolution[ResolutionKey.Value]); resolutionPm[DateTimeResolutionKey.Timex] = DateTimeFormatUtil.ToPm(timex); break; case Constants.SYS_DATETIME_DATETIME: var split = resolution[ResolutionKey.Value].Split(' '); resolutionPm[ResolutionKey.Value] = split[0] + " " + DateTimeFormatUtil.ToPm(split[1]); resolutionPm[DateTimeResolutionKey.Timex] = DateTimeFormatUtil.AllStringToPm(timex); break; case Constants.SYS_DATETIME_TIMEPERIOD: if (resolution.ContainsKey(DateTimeResolutionKey.Start)) { resolutionPm[DateTimeResolutionKey.Start] = DateTimeFormatUtil.ToPm(resolution[DateTimeResolutionKey.Start]); } if (resolution.ContainsKey(DateTimeResolutionKey.End)) { resolutionPm[DateTimeResolutionKey.End] = DateTimeFormatUtil.ToPm(resolution[DateTimeResolutionKey.End]); } resolutionPm[DateTimeResolutionKey.Timex] = DateTimeFormatUtil.AllStringToPm(timex); break; case Constants.SYS_DATETIME_DATETIMEPERIOD: if (resolution.ContainsKey(DateTimeResolutionKey.Start)) { var start = Convert.ToDateTime(resolution[DateTimeResolutionKey.Start], CultureInfo.InvariantCulture); start = start.Hour == Constants.HalfDayHourCount ? start.AddHours(-Constants.HalfDayHourCount) : start.AddHours(Constants.HalfDayHourCount); resolutionPm[DateTimeResolutionKey.Start] = DateTimeFormatUtil.FormatDateTime(start); } if (resolution.ContainsKey(DateTimeResolutionKey.End)) { var end = Convert.ToDateTime(resolution[DateTimeResolutionKey.End], CultureInfo.InvariantCulture); end = end.Hour == Constants.HalfDayHourCount ? end.AddHours(-Constants.HalfDayHourCount) : end.AddHours(Constants.HalfDayHourCount); resolutionPm[DateTimeResolutionKey.End] = DateTimeFormatUtil.FormatDateTime(end); } resolutionPm[DateTimeResolutionKey.Timex] = DateTimeFormatUtil.AllStringToPm(timex); break; } resolutionDic.Add(keyName + "Pm", resolutionPm); } }
// 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(); var afterStr = text.Substring((pr.Start ?? 0) + (pr.Length ?? 0)).Trim(); 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); }
protected DateTimeResolutionResult MatchWeekdayAndDay(string text, DateObject reference) { var ret = new DateTimeResolutionResult(); // 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. var match = this.config.WeekDayAndDayRegex.Match(text); if (match.Success) { int month = reference.Month, year = reference.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 = ConvertCJKToNum(match.Groups["day"].Value); // 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 (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 < reference) { // 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 > reference) { // 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 = DateTimeFormatUtil.LuisDate(pastDate); } } ret.Success = true; return(ret); } return(ret); }
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; } int swift = 0; bool isMatch = false, isLater = false; string dayStr = null; // Item2 is a label identifying the regex defined in Item1 var agoLaterRegexTuples = new List <(Regex, string)> { (utilityConfiguration.AgoRegex, Constants.AGO_LABEL), (utilityConfiguration.LaterRegex, Constants.LATER_LABEL), }; // AgoRegex and LaterRegex cases foreach (var regex in agoLaterRegexTuples) { // Match in afterStr if (MatchingUtil.ContainsAgoLaterIndex(afterStr, regex.Item1, inSuffix: true)) { isMatch = true; isLater = regex.Item2 == Constants.LATER_LABEL ? true : false; var match = regex.Item1.Match(afterStr); dayStr = match.Groups["day"].Value; } if (utilityConfiguration.CheckBothBeforeAfter) { // Match split between beforeStr and afterStr if (string.IsNullOrEmpty(dayStr) && isMatch) { var match = regex.Item1.Match(beforeStr + " " + afterStr); dayStr = match.Groups["day"].Value; } // Match in beforeStr if (string.IsNullOrEmpty(dayStr) && MatchingUtil.ContainsAgoLaterIndex(beforeStr, regex.Item1, inSuffix: false)) { isMatch = true; isLater = regex.Item2 == Constants.LATER_LABEL ? true : false; var match = regex.Item1.Match(beforeStr); dayStr = match.Groups["day"].Value; } } if (isMatch) { break; } } // InConnectorRegex cases if (!isMatch) { if (MatchingUtil.ContainsTermIndex(beforeStr, utilityConfiguration.InConnectorRegex)) { // Match in afterStr isMatch = isLater = true; var match = utilityConfiguration.LaterRegex.Match(afterStr); dayStr = match.Groups["day"].Value; } else if (utilityConfiguration.CheckBothBeforeAfter && MatchingUtil.ContainsAgoLaterIndex(afterStr, utilityConfiguration.InConnectorRegex, inSuffix: true)) { // Match in beforeStr isMatch = isLater = true; var match = utilityConfiguration.LaterRegex.Match(beforeStr); dayStr = match.Groups["day"].Value; } } if (isMatch) { // Handle cases like "3 days before yesterday", "3 days after tomorrow" if (!string.IsNullOrEmpty(dayStr)) { swift = swiftDay(dayStr); } if (isLater) { 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); } } var isFuture = isLater ? true : false; resultDateTime = DurationParsingUtil.ShiftDateTime(timex, referenceTime.AddDays(swift), future: isFuture); ((DateTimeResolutionResult)durationParseResult.Value).Mod = isLater ? Constants.AFTER_MOD : Constants.BEFORE_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); }
public SortedDictionary <string, object> DateTimeResolution(DateTimeParseResult slot) { if (slot == null) { return(null); } var resolutions = new List <Dictionary <string, string> >(); var res = new Dictionary <string, object>(); var type = slot.Type; var timex = slot.TimexStr; var val = (DateTimeResolutionResult)slot.Value; if (val == null) { return(null); } var isLunar = val.IsLunar; var mod = val.Mod; string list = null; // Resolve dates list for date periods if (slot.Type.Equals(Constants.SYS_DATETIME_DATEPERIOD) && val.List != null) { list = string.Join(",", val.List.Select(o => DateTimeFormatUtil.LuisDate((DateObject)o)).ToArray()); } // With modifier, output Type might not be the same with type in resolution result // For example, if the resolution type is "date", with modifier the output type should be "daterange" var typeOutput = DetermineDateTimeType(slot.Type, hasMod: !string.IsNullOrEmpty(mod)); var comment = val.Comment; // The following should be added to res first, since ResolveAmPm requires these fields. AddResolutionFields(res, DateTimeResolutionKey.Timex, timex); AddResolutionFields(res, Constants.Comment, comment); AddResolutionFields(res, DateTimeResolutionKey.Mod, mod); AddResolutionFields(res, ResolutionKey.Type, typeOutput); AddResolutionFields(res, DateTimeResolutionKey.IsLunar, isLunar ? isLunar.ToString() : string.Empty); var hasTimeZone = false; // For standalone timezone entity recognition, we generate TimeZoneResolution for each entity we extracted. // We also merge time entity with timezone entity and add the information in TimeZoneResolution to every DateTime resolutions. if (val.TimeZoneResolution != null) { if (slot.Type.Equals(Constants.SYS_DATETIME_TIMEZONE)) { // single timezone AddResolutionFields(res, Constants.ResolveTimeZone, new Dictionary <string, string> { { ResolutionKey.Value, val.TimeZoneResolution.Value }, { Constants.UtcOffsetMinsKey, val.TimeZoneResolution.UtcOffsetMins.ToString() }, }); } else { // timezone as clarification of datetime hasTimeZone = true; AddResolutionFields(res, Constants.TimeZone, val.TimeZoneResolution.Value); AddResolutionFields(res, Constants.TimeZoneText, val.TimeZoneResolution.TimeZoneText); AddResolutionFields(res, Constants.UtcOffsetMinsKey, val.TimeZoneResolution.UtcOffsetMins.ToString()); } } var pastResolutionStr = ((DateTimeResolutionResult)slot.Value).PastResolution; var futureResolutionStr = ((DateTimeResolutionResult)slot.Value).FutureResolution; if (typeOutput == Constants.SYS_DATETIME_DATETIMEALT && pastResolutionStr.Count > 0) { typeOutput = DetermineResolutionDateTimeType(pastResolutionStr); } var resolutionPast = GenerateResolution(type, pastResolutionStr, mod); var resolutionFuture = GenerateResolution(type, futureResolutionStr, mod); // If past and future are same, keep only one if (resolutionFuture.OrderBy(t => t.Key).Select(t => t.Value) .SequenceEqual(resolutionPast.OrderBy(t => t.Key).Select(t => t.Value))) { if (resolutionPast.Count > 0) { AddResolutionFields(res, Constants.Resolve, resolutionPast); } } else { if (resolutionPast.Count > 0) { AddResolutionFields(res, Constants.ResolveToPast, resolutionPast); } if (resolutionFuture.Count > 0) { AddResolutionFields(res, Constants.ResolveToFuture, resolutionFuture); } } // If 'ampm', double our resolution accordingly if (!string.IsNullOrEmpty(comment) && comment.Equals(Constants.Comment_AmPm)) { if (res.ContainsKey(Constants.Resolve)) { ResolveAmpm(res, Constants.Resolve); } else { ResolveAmpm(res, Constants.ResolveToPast); ResolveAmpm(res, Constants.ResolveToFuture); } } // If WeekOf and in CalendarMode, modify the past part of our resolution if ((Config.Options & DateTimeOptions.CalendarMode) != 0 && !string.IsNullOrEmpty(comment) && comment.Equals(Constants.Comment_WeekOf)) { ResolveWeekOf(res, Constants.ResolveToPast); } foreach (var p in res) { if (p.Value is Dictionary <string, string> dictionary) { var value = new Dictionary <string, string>(); AddResolutionFields(value, DateTimeResolutionKey.Timex, timex); AddResolutionFields(value, DateTimeResolutionKey.Mod, mod); AddResolutionFields(value, ResolutionKey.Type, typeOutput); AddResolutionFields(value, DateTimeResolutionKey.IsLunar, isLunar ? isLunar.ToString() : string.Empty); AddResolutionFields(value, DateTimeResolutionKey.List, list); if (hasTimeZone) { AddResolutionFields(value, Constants.TimeZone, val.TimeZoneResolution.Value); AddResolutionFields(value, Constants.TimeZoneText, val.TimeZoneResolution.TimeZoneText); AddResolutionFields(value, Constants.UtcOffsetMinsKey, val.TimeZoneResolution.UtcOffsetMins.ToString()); } foreach (var q in dictionary) { if (value.ContainsKey(q.Key)) { value[q.Key] = q.Value; } else { value.Add(q.Key, q.Value); } } resolutions.Add(value); } } if (resolutionPast.Count == 0 && resolutionFuture.Count == 0 && val.TimeZoneResolution == null) { var notResolved = new Dictionary <string, string> { { DateTimeResolutionKey.Timex, timex }, { ResolutionKey.Type, typeOutput }, { ResolutionKey.Value, "not resolved" }, }; resolutions.Add(notResolved); } return(new SortedDictionary <string, object> { { ResolutionKey.ValueSet, resolutions } }); }
// Cases like "between 0730 to 0930", only this case is handled in this method private DateTimeResolutionResult ParsePureDigitNumCases(string text, DateObject referenceTime) { var ret = new DateTimeResolutionResult(); int year = referenceTime.Year, month = referenceTime.Month, day = referenceTime.Day; var trimmedText = text.Trim(); var match = this.config.PureNumberBetweenAndRegex.MatchBegin(trimmedText, trim: true); if (match.Success) { // get hours var hourGroup = match.Groups[Constants.HourGroupName]; var minuteGroup = match.Groups[Constants.MinuteGroupName]; if (hourGroup.Captures.Count == 2 && minuteGroup.Captures.Count == 2) { var beginHourEndIndex = hourGroup.Captures[0].Index + hourGroup.Captures[0].Length; var beginMinuteStartIndex = minuteGroup.Captures[0].Index; var endHourEndIndex = hourGroup.Captures[1].Index + hourGroup.Captures[1].Length; var endMinuteStartIndex = minuteGroup.Captures[1].Index; // falls into the case "between 0730 to 0930" if (beginHourEndIndex == beginMinuteStartIndex && endHourEndIndex == endMinuteStartIndex) { var startHourStr = hourGroup.Captures[0].Value; var startMinuteStr = minuteGroup.Captures[0].Value; var endHourStr = hourGroup.Captures[1].Value; var endMinuteStr = minuteGroup.Captures[1].Value; if (!this.config.Numbers.TryGetValue(startHourStr, out int beginHour)) { beginHour = int.Parse(startHourStr, CultureInfo.InvariantCulture); } if (!this.config.Numbers.TryGetValue(startMinuteStr, out int beginMinute)) { beginMinute = int.Parse(startMinuteStr, CultureInfo.InvariantCulture); } if (!this.config.Numbers.TryGetValue(endHourStr, out int endHour)) { endHour = int.Parse(endHourStr, CultureInfo.InvariantCulture); } if (!this.config.Numbers.TryGetValue(endMinuteStr, out int endMinute)) { endMinute = int.Parse(endMinuteStr, CultureInfo.InvariantCulture); } var beginDateTime = DateObject.MinValue.SafeCreateFromValue(year, month, day, beginHour, beginMinute, 0); var endDateTime = DateObject.MinValue.SafeCreateFromValue(year, month, day, endHour, endMinute, 0); 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); var endStr = DateTimeFormatUtil.ShortTime(endDateTime.Hour, endMinute); ret.Timex = $"({beginStr},{endStr},{DateTimeFormatUtil.LuisTimeSpan(endDateTime - beginDateTime)})"; ret.FutureValue = ret.PastValue = new Tuple <DateObject, DateObject>( beginDateTime, endDateTime); ret.Success = true; } } } return(ret); }