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