EntityToType.cs
1 //
2 // Copyright (c) Microsoft. All rights reserved.
3 // Licensed under the MIT license.
4 //
5 // Microsoft Bot Framework: http://botframework.com
6 //
7 // Bot Builder SDK GitHub:
8 // https://github.com/Microsoft/BotBuilder
9 //
10 // Copyright (c) Microsoft Corporation
11 // All rights reserved.
12 //
13 // MIT License:
14 // Permission is hereby granted, free of charge, to any person obtaining
15 // a copy of this software and associated documentation files (the
16 // "Software"), to deal in the Software without restriction, including
17 // without limitation the rights to use, copy, modify, merge, publish,
18 // distribute, sublicense, and/or sell copies of the Software, and to
19 // permit persons to whom the Software is furnished to do so, subject to
20 // the following conditions:
21 //
22 // The above copyright notice and this permission notice shall be
23 // included in all copies or substantial portions of the Software.
24 //
25 // THE SOFTWARE IS PROVIDED ""AS IS"", WITHOUT WARRANTY OF ANY KIND,
26 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
28 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
29 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
30 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
31 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32 //
33 
36 using System;
37 using System.Collections.Generic;
38 using System.Globalization;
39 using System.Linq;
40 using System.Text;
41 using System.Threading.Tasks;
43 
44 namespace Microsoft.Bot.Builder.Luis
45 {
49  public interface IEntityToType
50  {
58  bool TryMapToTimeSpan(DateTime now, IEnumerable<EntityRecommendation> entities, out TimeSpan span);
59 
67  bool TryMapToDateRanges(DateTime now, IEnumerable<EntityRecommendation> entities, out IEnumerable<Range<DateTime>> ranges);
68  }
69 
70  public sealed class StrictEntityToType : IEntityToType
71  {
72  private readonly IResolutionParser parser;
73  private readonly ICalendarPlus calendar;
74 
76  {
77  SetField.NotNull(out this.parser, nameof(parser), parser);
78  SetField.NotNull(out this.calendar, nameof(calendar), calendar);
79  }
80 
81  bool IEntityToType.TryMapToDateRanges(DateTime now, IEnumerable<EntityRecommendation> entities, out IEnumerable<Range<DateTime>> ranges)
82  {
83  var resolutions = this.parser.ParseResolutions(entities);
84  var dateTimes = resolutions.OfType<DateTimeResolution>().ToArray();
85 
86  if (dateTimes.Length > 0)
87  {
88  // possibly infinite
89  var merged = dateTimes
90  .Select(r => Interpret(r, now, this.calendar.Calendar, this.calendar.WeekRule, this.calendar.FirstDayOfWeek, this.calendar.HourFor))
91  .Aggregate((l, r) => l.SortedMerge(r));
92 
93  ranges = merged;
94  return true;
95  }
96  else
97  {
98  ranges = null;
99  return false;
100  }
101  }
102 
103  bool IEntityToType.TryMapToTimeSpan(DateTime now, IEnumerable<EntityRecommendation> entities, out TimeSpan span)
104  {
105  span = default(TimeSpan);
106  return false;
107  }
108 
119  public static IEnumerable<Range<DateTime>> Interpret(DateTimeResolution resolution, DateTime now, Calendar calendar, CalendarWeekRule rule, DayOfWeek firstDayOfWeek, Func<DayPart, int> HourFor)
120  {
121  // remove any millisecond components
122  now = new DateTime(now.Year, now.Month, now.Day, now.Hour, now.Minute, now.Second, now.Kind);
123 
124  switch (resolution.Reference)
125  {
126  case Reference.PAST_REF:
127  yield return Range.From(DateTime.MinValue, now);
128  yield break;
129  case Reference.PRESENT_REF:
130  yield return Range.From(now, now);
131  yield break;
132  case Reference.FUTURE_REF:
133  yield return Range.From(now, DateTime.MaxValue);
134  yield break;
135  case null:
136  break;
137  default:
138  throw new NotImplementedException();
139  }
140 
141  var start = now;
142 
143  // TODO: maybe clamp to prevent divergence
144  while (start < DateTime.MaxValue)
145  {
146  var after = start;
147 
148  while (true)
149  {
150  // for each date component in decreasing order of significance:
151  // if it's not a variable (-1) or missing (null) component, then
152  // add a unit of that component to "start"
153  // round down to the component's granularity
154  // calculate the "after" based on the size of that component
155 
156  if (resolution.Year >= 0 && start.Year != resolution.Year)
157  {
158  if (start.Year < resolution.Year)
159  {
160  start = start.AddYears(1);
161  start = new DateTime(start.Year, 1, 1, 0, 0, 0, 0, start.Kind);
162  after = start.AddYears(1);
163  continue;
164  }
165  else
166  {
167  yield break;
168  }
169  }
170 
171  if (resolution.Month >= 0 && start.Month != resolution.Month)
172  {
173  start = start.AddMonths(1);
174  start = new DateTime(start.Year, start.Month, 1, 0, 0, 0, 0, start.Kind);
175  after = start.AddMonths(1);
176  continue;
177  }
178 
179  var week = calendar.GetWeekOfYear(start, rule, firstDayOfWeek);
180  if (resolution.Week >= 0 && week != resolution.Week)
181  {
182  start = start.AddDays(7);
183  start = new DateTime(start.Year, start.Month, start.Day, 0, 0, 0, 0, start.Kind);
184 
185  while (start.DayOfWeek != firstDayOfWeek)
186  {
187  start = start.AddDays(-1);
188  }
189 
190  after = start.AddDays(7);
191  continue;
192  }
193 
194  if (resolution.DayOfWeek != null && start.DayOfWeek != resolution.DayOfWeek)
195  {
196  start = start.AddDays(1);
197  start = new DateTime(start.Year, start.Month, start.Day, 0, 0, 0, 0, start.Kind);
198  after = start.AddDays(1);
199  continue;
200  }
201 
202  if (resolution.Day >= 0 && start.Day != resolution.Day)
203  {
204  start = start.AddDays(1);
205  start = new DateTime(start.Year, start.Month, start.Day, 0, 0, 0, 0, start.Kind);
206  after = start.AddDays(1);
207  continue;
208  }
209 
210  if (resolution.DayPart != null && start.Hour != HourFor(resolution.DayPart.Value))
211  {
212  var hourStart = HourFor(resolution.DayPart.Value);
213  var hourAfter = HourFor(resolution.DayPart.Value.Next());
214  var hourDelta = hourAfter - hourStart;
215  if (hourDelta < 0)
216  {
217  hourDelta += 24;
218  }
219 
220  start = start.AddHours(1);
221  start = new DateTime(start.Year, start.Month, start.Day, start.Hour, 0, 0, 0, start.Kind);
222  after = start.AddHours(hourDelta);
223  continue;
224  }
225 
226  if (resolution.Hour >= 0 && start.Hour != resolution.Hour)
227  {
228  start = start.AddHours(1);
229  start = new DateTime(start.Year, start.Month, start.Day, start.Hour, 0, 0, 0, start.Kind);
230  after = start.AddHours(1);
231  continue;
232  }
233 
234  if (resolution.Minute >= 0 && start.Minute != resolution.Minute)
235  {
236  start = start.AddMinutes(1);
237  start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, 0, 0, start.Kind);
238  after = start.AddMinutes(1);
239  continue;
240  }
241 
242  if (resolution.Second >= 0 && start.Second != resolution.Second)
243  {
244  start = start.AddSeconds(1);
245  start = new DateTime(start.Year, start.Month, start.Day, start.Hour, start.Minute, start.Second, 0, start.Kind);
246  after = start.AddSeconds(1);
247  continue;
248  }
249 
250  // if all of the components were variable or missing,
251  // then in order of increasing component granularity,
252  // if the component is variable rather than missing, then increment by that granularity
253  if (start == after)
254  {
255  if (resolution.Second < 0)
256  {
257  after = start.AddSeconds(1);
258  }
259  else if (resolution.Minute < 0)
260  {
261  after = start.AddMinutes(1);
262  }
263  else if (resolution.Hour < 0)
264  {
265  after = start.AddHours(1);
266  }
267  else if (resolution.Day < 0)
268  {
269  after = start.AddDays(1);
270  }
271  else if (resolution.Week < 0)
272  {
273  after = start.AddDays(7);
274  }
275  else if (resolution.Month < 0)
276  {
277  after = start.AddMonths(1);
278  }
279  else if (resolution.Year < 0)
280  {
281  after = start.AddYears(1);
282  }
283  else
284  {
285  // a second is our minimum granularity
286  after = start.AddSeconds(1);
287  }
288  }
289 
290  if (start >= now)
291  {
292  yield return new Range<DateTime>(start, after);
293  }
294 
295  start = after;
296  }
297  }
298  }
299  }
300 }
static IEnumerable< Range< DateTime > > Interpret(DateTimeResolution resolution, DateTime now, Calendar calendar, CalendarWeekRule rule, DayOfWeek firstDayOfWeek, Func< DayPart, int > HourFor)
Interpret a parsed DateTimeResolution to provide a series of DateTime ranges
An abtraction to map from a LUIS EntityRecommendation to specific CLR types.
Definition: EntityToType.cs:49
Namespace for models generated from the http://luis.ai REST API.
Definition: FormDialog.cs:837
Policy for interpreting LUIS resolutions.
Definition: CalendarPlus.cs:47
Namespace for internal machinery that is not useful for most developers and may change in the future...
bool TryMapToTimeSpan(DateTime now, IEnumerable< EntityRecommendation > entities, out TimeSpan span)
Try to map LUIS EntityRecommendation instances to a TimeSpan, relative to now.
StrictEntityToType(IResolutionParser parser, ICalendarPlus calendar)
Definition: EntityToType.cs:75
Namespace for the internal fibers machinery that is not useful for most developers and may change in ...
Namespace for the machinery needed to talk to http://luis.ai.
bool TryMapToDateRanges(DateTime now, IEnumerable< EntityRecommendation > entities, out IEnumerable< Range< DateTime >> ranges)
Try to map LUIS EntityRecommendation instances to a list of DateTime ranges, relative to now...
Root namespace for the Microsoft Bot Builder SDK.