1 /*
2  * Copyright (C) 2018 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 #include "annotator/datetime/datetime-grounder.h"
18 
19 #include <limits>
20 #include <unordered_map>
21 #include <vector>
22 
23 #include "annotator/datetime/datetime_generated.h"
24 #include "annotator/datetime/utils.h"
25 #include "annotator/types.h"
26 #include "utils/base/integral_types.h"
27 #include "utils/base/status.h"
28 #include "utils/base/status_macros.h"
29 
30 using ::libtextclassifier3::grammar::datetime::AbsoluteDateTime;
31 using ::libtextclassifier3::grammar::datetime::ComponentType;
32 using ::libtextclassifier3::grammar::datetime::Meridiem;
33 using ::libtextclassifier3::grammar::datetime::RelativeDateTime;
34 using ::libtextclassifier3::grammar::datetime::RelativeDatetimeComponent;
35 using ::libtextclassifier3::grammar::datetime::UngroundedDatetime;
36 using ::libtextclassifier3::grammar::datetime::RelativeDatetimeComponent_::
37     Modifier;
38 
39 namespace libtextclassifier3 {
40 
41 namespace {
42 
43 const std::unordered_map<int, int> kMonthDefaultLastDayMap(
44     {{/*no_month*/ 0, 31},
45      {/*January*/ 1, 31},
46      {/*Febuary*/ 2, 29},
47      {/*March*/ 3, 31},
48      {/*April*/ 4, 30},
49      {/*May*/ 5, 31},
50      {/*June*/ 6, 30},
51      {/*July*/ 7, 31},
52      {/*August*/ 8, 31},
53      {/*September*/ 9, 30},
54      {/*October*/ 10, 31},
55      {/*November*/ 11, 30},
56      {/*December*/ 12, 31}});
57 
IsValidDatetime(const AbsoluteDateTime * absolute_datetime)58 bool IsValidDatetime(const AbsoluteDateTime* absolute_datetime) {
59   // Sanity Checks.
60   if (absolute_datetime->minute() > 59 || absolute_datetime->second() > 59 ||
61       absolute_datetime->hour() > 23 || absolute_datetime->month() > 12 ||
62       absolute_datetime->month() == 0) {
63     return false;
64   }
65   if (absolute_datetime->day() >= 0) {
66     int min_day_value = 1;
67     int max_day_value = 31;
68     if (absolute_datetime->month() >= 0 && absolute_datetime->month() <= 12) {
69       max_day_value = kMonthDefaultLastDayMap.at(absolute_datetime->month());
70       if (absolute_datetime->day() < min_day_value ||
71           absolute_datetime->day() > max_day_value) {
72         return false;
73       }
74     }
75   }
76   return true;
77 }
78 
IsValidDatetime(const RelativeDateTime * relative_datetime)79 bool IsValidDatetime(const RelativeDateTime* relative_datetime) {
80   if (relative_datetime->base()) {
81     return IsValidDatetime(relative_datetime->base());
82   }
83   return true;
84 }
85 
ToRelativeQualifier(const Modifier & modifier)86 StatusOr<DatetimeComponent::RelativeQualifier> ToRelativeQualifier(
87     const Modifier& modifier) {
88   switch (modifier) {
89     case Modifier::Modifier_THIS:
90       return DatetimeComponent::RelativeQualifier::THIS;
91     case Modifier::Modifier_LAST:
92       return DatetimeComponent::RelativeQualifier::LAST;
93     case Modifier::Modifier_NEXT:
94       return DatetimeComponent::RelativeQualifier::NEXT;
95     case Modifier::Modifier_NOW:
96       return DatetimeComponent::RelativeQualifier::NOW;
97     case Modifier::Modifier_TOMORROW:
98       return DatetimeComponent::RelativeQualifier::TOMORROW;
99     case Modifier::Modifier_YESTERDAY:
100       return DatetimeComponent::RelativeQualifier::YESTERDAY;
101     case Modifier::Modifier_PAST:
102       return DatetimeComponent::RelativeQualifier::PAST;
103     case Modifier::Modifier_FUTURE:
104       return DatetimeComponent::RelativeQualifier::FUTURE;
105     case Modifier::Modifier_UNSPECIFIED:
106       return DatetimeComponent::RelativeQualifier::UNSPECIFIED;
107     default:
108       return Status(StatusCode::INTERNAL,
109                     "Couldn't parse the Modifier to RelativeQualifier.");
110   }
111 }
112 
ToComponentType(const grammar::datetime::ComponentType component_type)113 StatusOr<DatetimeComponent::ComponentType> ToComponentType(
114     const grammar::datetime::ComponentType component_type) {
115   switch (component_type) {
116     case grammar::datetime::ComponentType_YEAR:
117       return DatetimeComponent::ComponentType::YEAR;
118     case grammar::datetime::ComponentType_MONTH:
119       return DatetimeComponent::ComponentType::MONTH;
120     case grammar::datetime::ComponentType_WEEK:
121       return DatetimeComponent::ComponentType::WEEK;
122     case grammar::datetime::ComponentType_DAY_OF_WEEK:
123       return DatetimeComponent::ComponentType::DAY_OF_WEEK;
124     case grammar::datetime::ComponentType_DAY_OF_MONTH:
125       return DatetimeComponent::ComponentType::DAY_OF_MONTH;
126     case grammar::datetime::ComponentType_HOUR:
127       return DatetimeComponent::ComponentType::HOUR;
128     case grammar::datetime::ComponentType_MINUTE:
129       return DatetimeComponent::ComponentType::MINUTE;
130     case grammar::datetime::ComponentType_SECOND:
131       return DatetimeComponent::ComponentType::SECOND;
132     case grammar::datetime::ComponentType_MERIDIEM:
133       return DatetimeComponent::ComponentType::MERIDIEM;
134     case grammar::datetime::ComponentType_UNSPECIFIED:
135       return DatetimeComponent::ComponentType::UNSPECIFIED;
136     default:
137       return Status(StatusCode::INTERNAL,
138                     "Couldn't parse the DatetimeComponent's ComponentType from "
139                     "grammar's datetime ComponentType.");
140   }
141 }
142 
FillAbsoluteDateTimeComponents(const grammar::datetime::AbsoluteDateTime * absolute_datetime,DatetimeParsedData * datetime_parsed_data)143 void FillAbsoluteDateTimeComponents(
144     const grammar::datetime::AbsoluteDateTime* absolute_datetime,
145     DatetimeParsedData* datetime_parsed_data) {
146   if (absolute_datetime->year() >= 0) {
147     datetime_parsed_data->SetAbsoluteValue(
148         DatetimeComponent::ComponentType::YEAR,
149         GetAdjustedYear(absolute_datetime->year()));
150   }
151   if (absolute_datetime->month() >= 0) {
152     datetime_parsed_data->SetAbsoluteValue(
153         DatetimeComponent::ComponentType::MONTH, absolute_datetime->month());
154   }
155   if (absolute_datetime->day() >= 0) {
156     datetime_parsed_data->SetAbsoluteValue(
157         DatetimeComponent::ComponentType::DAY_OF_MONTH,
158         absolute_datetime->day());
159   }
160   if (absolute_datetime->week_day() >= 0) {
161     datetime_parsed_data->SetAbsoluteValue(
162         DatetimeComponent::ComponentType::DAY_OF_WEEK,
163         absolute_datetime->week_day());
164   }
165   if (absolute_datetime->hour() >= 0) {
166     datetime_parsed_data->SetAbsoluteValue(
167         DatetimeComponent::ComponentType::HOUR, absolute_datetime->hour());
168   }
169   if (absolute_datetime->minute() >= 0) {
170     datetime_parsed_data->SetAbsoluteValue(
171         DatetimeComponent::ComponentType::MINUTE, absolute_datetime->minute());
172   }
173   if (absolute_datetime->second() >= 0) {
174     datetime_parsed_data->SetAbsoluteValue(
175         DatetimeComponent::ComponentType::SECOND, absolute_datetime->second());
176   }
177   if (absolute_datetime->meridiem() != grammar::datetime::Meridiem_UNKNOWN) {
178     datetime_parsed_data->SetAbsoluteValue(
179         DatetimeComponent::ComponentType::MERIDIEM,
180         absolute_datetime->meridiem() == grammar::datetime::Meridiem_AM ? 0
181                                                                         : 1);
182   }
183   if (absolute_datetime->time_zone()) {
184     datetime_parsed_data->SetAbsoluteValue(
185         DatetimeComponent::ComponentType::ZONE_OFFSET,
186         absolute_datetime->time_zone()->utc_offset_mins());
187   }
188 }
189 
FillRelativeDateTimeComponents(const grammar::datetime::RelativeDateTime * relative_datetime)190 StatusOr<DatetimeParsedData> FillRelativeDateTimeComponents(
191     const grammar::datetime::RelativeDateTime* relative_datetime) {
192   DatetimeParsedData datetime_parsed_data;
193   for (const RelativeDatetimeComponent* relative_component :
194        *relative_datetime->relative_datetime_component()) {
195     TC3_ASSIGN_OR_RETURN(const DatetimeComponent::ComponentType component_type,
196                          ToComponentType(relative_component->component_type()));
197     datetime_parsed_data.SetRelativeCount(component_type,
198                                           relative_component->value());
199     TC3_ASSIGN_OR_RETURN(
200         const DatetimeComponent::RelativeQualifier relative_qualifier,
201         ToRelativeQualifier(relative_component->modifier()));
202     datetime_parsed_data.SetRelativeValue(component_type, relative_qualifier);
203   }
204   if (relative_datetime->base()) {
205     FillAbsoluteDateTimeComponents(relative_datetime->base(),
206                                    &datetime_parsed_data);
207   }
208   return datetime_parsed_data;
209 }
210 
211 }  // namespace
212 
DatetimeGrounder(const CalendarLib * calendarlib)213 DatetimeGrounder::DatetimeGrounder(const CalendarLib* calendarlib)
214     : calendarlib_(*calendarlib) {}
215 
Ground(const int64 reference_time_ms_utc,const std::string & reference_timezone,const std::string & reference_locale,const grammar::datetime::UngroundedDatetime * ungrounded_datetime) const216 StatusOr<std::vector<DatetimeParseResult>> DatetimeGrounder::Ground(
217     const int64 reference_time_ms_utc, const std::string& reference_timezone,
218     const std::string& reference_locale,
219     const grammar::datetime::UngroundedDatetime* ungrounded_datetime) const {
220   DatetimeParsedData datetime_parsed_data;
221   if (ungrounded_datetime->absolute_datetime()) {
222     FillAbsoluteDateTimeComponents(ungrounded_datetime->absolute_datetime(),
223                                    &datetime_parsed_data);
224   } else if (ungrounded_datetime->relative_datetime()) {
225     TC3_ASSIGN_OR_RETURN(datetime_parsed_data,
226                          FillRelativeDateTimeComponents(
227                              ungrounded_datetime->relative_datetime()));
228   }
229   std::vector<DatetimeParsedData> interpretations;
230   FillInterpretations(datetime_parsed_data,
231                       calendarlib_.GetGranularity(datetime_parsed_data),
232                       &interpretations);
233   std::vector<DatetimeParseResult> datetime_parse_result;
234 
235   for (const DatetimeParsedData& interpretation : interpretations) {
236     std::vector<DatetimeComponent> date_components;
237     interpretation.GetDatetimeComponents(&date_components);
238     DatetimeParseResult result;
239     // Text classifier only provides ambiguity limited to “AM/PM” which is
240     // encoded in the pair of DatetimeParseResult; both corresponding to the
241     // same date, but one corresponding to “AM” and the other one corresponding
242     //  to “PM”.
243     if (!calendarlib_.InterpretParseData(
244             interpretation, reference_time_ms_utc, reference_timezone,
245             reference_locale, /*prefer_future_for_unspecified_date=*/true,
246             &(result.time_ms_utc), &(result.granularity))) {
247       return Status(
248           StatusCode::INTERNAL,
249           "Couldn't parse the UngroundedDatetime to DatetimeParseResult.");
250     }
251 
252     // Sort the date time units by component type.
253     std::sort(date_components.begin(), date_components.end(),
254               [](DatetimeComponent a, DatetimeComponent b) {
255                 return a.component_type > b.component_type;
256               });
257     result.datetime_components.swap(date_components);
258     datetime_parse_result.push_back(result);
259   }
260   return datetime_parse_result;
261 }
262 
IsValidUngroundedDatetime(const UngroundedDatetime * ungrounded_datetime) const263 bool DatetimeGrounder::IsValidUngroundedDatetime(
264     const UngroundedDatetime* ungrounded_datetime) const {
265   if (ungrounded_datetime->absolute_datetime()) {
266     return IsValidDatetime(ungrounded_datetime->absolute_datetime());
267   } else if (ungrounded_datetime->relative_datetime()) {
268     return IsValidDatetime(ungrounded_datetime->relative_datetime());
269   }
270   return false;
271 }
272 
273 }  // namespace libtextclassifier3
274