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 #ifndef LIBTEXTCLASSIFIER_UTILS_CALENDAR_CALENDAR_COMMON_H_
18 #define LIBTEXTCLASSIFIER_UTILS_CALENDAR_CALENDAR_COMMON_H_
19
20 #include "annotator/types.h"
21 #include "utils/base/integral_types.h"
22 #include "utils/base/logging.h"
23 #include "utils/base/macros.h"
24
25 namespace libtextclassifier3 {
26 namespace calendar {
27
28 // Macro to reduce the amount of boilerplate needed for propagating errors.
29 #define TC3_CALENDAR_CHECK(EXPR) \
30 if (!(EXPR)) { \
31 return false; \
32 }
33
34 // An implementation of CalendarLib that is independent of the particular
35 // calendar implementation used (implementation type is passed as template
36 // argument).
37 template <class TCalendar>
38 class CalendarLibTempl {
39 public:
40 bool InterpretParseData(const DateParseData& parse_data,
41 int64 reference_time_ms_utc,
42 const std::string& reference_timezone,
43 const std::string& reference_locale,
44 TCalendar* calendar,
45 DatetimeGranularity* granularity) const;
46
47 DatetimeGranularity GetGranularity(const DateParseData& data) const;
48
49 private:
50 // Adjusts the calendar's time instant according to a relative date reference
51 // in the parsed data.
52 bool ApplyRelationField(const DateParseData& parse_data,
53 TCalendar* calendar) const;
54
55 // Round the time instant's precision down to the given granularity.
56 bool RoundToGranularity(DatetimeGranularity granularity,
57 TCalendar* calendar) const;
58
59 // Adjusts time in steps of relation_type, by distance steps.
60 // For example:
61 // - Adjusting by -2 MONTHS will return the beginning of the 1st
62 // two weeks ago.
63 // - Adjusting by +4 Wednesdays will return the beginning of the next
64 // Wednesday at least 4 weeks from now.
65 // If allow_today is true, the same day of the week may be kept
66 // if it already matches the relation type.
67 bool AdjustByRelation(DateParseData::RelationType relation_type, int distance,
68 bool allow_today, TCalendar* calendar) const;
69 };
70
71 template <class TCalendar>
InterpretParseData(const DateParseData & parse_data,int64 reference_time_ms_utc,const std::string & reference_timezone,const std::string & reference_locale,TCalendar * calendar,DatetimeGranularity * granularity)72 bool CalendarLibTempl<TCalendar>::InterpretParseData(
73 const DateParseData& parse_data, int64 reference_time_ms_utc,
74 const std::string& reference_timezone, const std::string& reference_locale,
75 TCalendar* calendar, DatetimeGranularity* granularity) const {
76 TC3_CALENDAR_CHECK(calendar->Initialize(reference_timezone, reference_locale,
77 reference_time_ms_utc))
78
79 bool should_round_to_granularity = true;
80 *granularity = GetGranularity(parse_data);
81
82 // Apply each of the parsed fields in order of increasing granularity.
83 static const int64 kMillisInHour = 1000 * 60 * 60;
84 if (parse_data.field_set_mask & DateParseData::Fields::ZONE_OFFSET_FIELD) {
85 TC3_CALENDAR_CHECK(
86 calendar->SetZoneOffset(parse_data.zone_offset * kMillisInHour))
87 }
88 if (parse_data.field_set_mask & DateParseData::Fields::DST_OFFSET_FIELD) {
89 TC3_CALENDAR_CHECK(
90 calendar->SetDstOffset(parse_data.dst_offset * kMillisInHour))
91 }
92 if (parse_data.field_set_mask & DateParseData::Fields::RELATION_FIELD) {
93 TC3_CALENDAR_CHECK(ApplyRelationField(parse_data, calendar));
94 // Don't round to the granularity for relative expressions that specify the
95 // distance. So that, e.g. "in 2 hours" when it's 8:35:03 will result in
96 // 10:35:03.
97 if (parse_data.field_set_mask &
98 DateParseData::Fields::RELATION_DISTANCE_FIELD) {
99 should_round_to_granularity = false;
100 }
101 } else {
102 // By default, the parsed time is interpreted to be on the reference day.
103 // But a parsed date should have time 0:00:00 unless specified.
104 TC3_CALENDAR_CHECK(calendar->SetHourOfDay(0))
105 TC3_CALENDAR_CHECK(calendar->SetMinute(0))
106 TC3_CALENDAR_CHECK(calendar->SetSecond(0))
107 TC3_CALENDAR_CHECK(calendar->SetMillisecond(0))
108 }
109 if (parse_data.field_set_mask & DateParseData::Fields::YEAR_FIELD) {
110 TC3_CALENDAR_CHECK(calendar->SetYear(parse_data.year))
111 }
112 if (parse_data.field_set_mask & DateParseData::Fields::MONTH_FIELD) {
113 // ICU has months starting at 0, Java and Datetime parser at 1, so we
114 // need to subtract 1.
115 TC3_CALENDAR_CHECK(calendar->SetMonth(parse_data.month - 1))
116 }
117 if (parse_data.field_set_mask & DateParseData::Fields::DAY_FIELD) {
118 TC3_CALENDAR_CHECK(calendar->SetDayOfMonth(parse_data.day_of_month))
119 }
120 if (parse_data.field_set_mask & DateParseData::Fields::HOUR_FIELD) {
121 if (parse_data.field_set_mask & DateParseData::Fields::AMPM_FIELD &&
122 parse_data.ampm == DateParseData::AMPM::PM && parse_data.hour < 12) {
123 TC3_CALENDAR_CHECK(calendar->SetHourOfDay(parse_data.hour + 12))
124 } else if (parse_data.ampm == DateParseData::AMPM::AM &&
125 parse_data.hour == 12) {
126 // Do nothing. 12am == 0.
127 } else {
128 TC3_CALENDAR_CHECK(calendar->SetHourOfDay(parse_data.hour))
129 }
130 }
131 if (parse_data.field_set_mask & DateParseData::Fields::MINUTE_FIELD) {
132 TC3_CALENDAR_CHECK(calendar->SetMinute(parse_data.minute))
133 }
134 if (parse_data.field_set_mask & DateParseData::Fields::SECOND_FIELD) {
135 TC3_CALENDAR_CHECK(calendar->SetSecond(parse_data.second))
136 }
137
138 if (should_round_to_granularity) {
139 TC3_CALENDAR_CHECK(RoundToGranularity(*granularity, calendar))
140 }
141 return true;
142 }
143
144 template <class TCalendar>
ApplyRelationField(const DateParseData & parse_data,TCalendar * calendar)145 bool CalendarLibTempl<TCalendar>::ApplyRelationField(
146 const DateParseData& parse_data, TCalendar* calendar) const {
147 constexpr int relation_type_mask = DateParseData::Fields::RELATION_TYPE_FIELD;
148 constexpr int relation_distance_mask =
149 DateParseData::Fields::RELATION_DISTANCE_FIELD;
150 switch (parse_data.relation) {
151 case DateParseData::Relation::UNSPECIFIED:
152 TC3_LOG(ERROR) << "UNSPECIFIED RelationType.";
153 return false;
154 case DateParseData::Relation::NEXT:
155 if (parse_data.field_set_mask & relation_type_mask) {
156 TC3_CALENDAR_CHECK(AdjustByRelation(parse_data.relation_type,
157 /*distance=*/1,
158 /*allow_today=*/false, calendar));
159 }
160 return true;
161 case DateParseData::Relation::NEXT_OR_SAME:
162 if (parse_data.field_set_mask & relation_type_mask) {
163 TC3_CALENDAR_CHECK(AdjustByRelation(parse_data.relation_type,
164 /*distance=*/1,
165 /*allow_today=*/true, calendar))
166 }
167 return true;
168 case DateParseData::Relation::LAST:
169 if (parse_data.field_set_mask & relation_type_mask) {
170 TC3_CALENDAR_CHECK(AdjustByRelation(parse_data.relation_type,
171 /*distance=*/-1,
172 /*allow_today=*/false, calendar))
173 }
174 return true;
175 case DateParseData::Relation::NOW:
176 return true; // NOOP
177 case DateParseData::Relation::TOMORROW:
178 TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(1));
179 return true;
180 case DateParseData::Relation::YESTERDAY:
181 TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(-1));
182 return true;
183 case DateParseData::Relation::PAST:
184 if ((parse_data.field_set_mask & relation_type_mask) &&
185 (parse_data.field_set_mask & relation_distance_mask)) {
186 TC3_CALENDAR_CHECK(AdjustByRelation(parse_data.relation_type,
187 -parse_data.relation_distance,
188 /*allow_today=*/false, calendar))
189 }
190 return true;
191 case DateParseData::Relation::FUTURE:
192 if ((parse_data.field_set_mask & relation_type_mask) &&
193 (parse_data.field_set_mask & relation_distance_mask)) {
194 TC3_CALENDAR_CHECK(AdjustByRelation(parse_data.relation_type,
195 parse_data.relation_distance,
196 /*allow_today=*/false, calendar))
197 }
198 return true;
199 }
200 return false;
201 }
202
203 template <class TCalendar>
RoundToGranularity(DatetimeGranularity granularity,TCalendar * calendar)204 bool CalendarLibTempl<TCalendar>::RoundToGranularity(
205 DatetimeGranularity granularity, TCalendar* calendar) const {
206 // Force recomputation before doing the rounding.
207 int unused;
208 TC3_CALENDAR_CHECK(calendar->GetDayOfWeek(&unused));
209
210 switch (granularity) {
211 case GRANULARITY_YEAR:
212 TC3_CALENDAR_CHECK(calendar->SetMonth(0));
213 TC3_FALLTHROUGH_INTENDED;
214 case GRANULARITY_MONTH:
215 TC3_CALENDAR_CHECK(calendar->SetDayOfMonth(1));
216 TC3_FALLTHROUGH_INTENDED;
217 case GRANULARITY_DAY:
218 TC3_CALENDAR_CHECK(calendar->SetHourOfDay(0));
219 TC3_FALLTHROUGH_INTENDED;
220 case GRANULARITY_HOUR:
221 TC3_CALENDAR_CHECK(calendar->SetMinute(0));
222 TC3_FALLTHROUGH_INTENDED;
223 case GRANULARITY_MINUTE:
224 TC3_CALENDAR_CHECK(calendar->SetSecond(0));
225 break;
226
227 case GRANULARITY_WEEK:
228 int first_day_of_week;
229 TC3_CALENDAR_CHECK(calendar->GetFirstDayOfWeek(&first_day_of_week));
230 TC3_CALENDAR_CHECK(calendar->SetDayOfWeek(first_day_of_week));
231 TC3_CALENDAR_CHECK(calendar->SetHourOfDay(0));
232 TC3_CALENDAR_CHECK(calendar->SetMinute(0));
233 TC3_CALENDAR_CHECK(calendar->SetSecond(0));
234 break;
235
236 case GRANULARITY_UNKNOWN:
237 case GRANULARITY_SECOND:
238 break;
239 }
240 return true;
241 }
242
243 template <class TCalendar>
AdjustByRelation(DateParseData::RelationType relation_type,int distance,bool allow_today,TCalendar * calendar)244 bool CalendarLibTempl<TCalendar>::AdjustByRelation(
245 DateParseData::RelationType relation_type, int distance, bool allow_today,
246 TCalendar* calendar) const {
247 const int distance_sign = distance < 0 ? -1 : 1;
248 switch (relation_type) {
249 case DateParseData::RelationType::MONDAY:
250 case DateParseData::RelationType::TUESDAY:
251 case DateParseData::RelationType::WEDNESDAY:
252 case DateParseData::RelationType::THURSDAY:
253 case DateParseData::RelationType::FRIDAY:
254 case DateParseData::RelationType::SATURDAY:
255 case DateParseData::RelationType::SUNDAY:
256 if (!allow_today) {
257 // If we're not including the same day as the reference, skip it.
258 TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(distance_sign))
259 }
260 // Keep walking back until we hit the desired day of the week.
261 while (distance != 0) {
262 int day_of_week;
263 TC3_CALENDAR_CHECK(calendar->GetDayOfWeek(&day_of_week))
264 if (day_of_week == static_cast<int>(relation_type)) {
265 distance += -distance_sign;
266 if (distance == 0) break;
267 }
268 TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(distance_sign))
269 }
270 return true;
271 case DateParseData::RelationType::SECOND:
272 TC3_CALENDAR_CHECK(calendar->AddSecond(distance));
273 return true;
274 case DateParseData::RelationType::MINUTE:
275 TC3_CALENDAR_CHECK(calendar->AddMinute(distance));
276 return true;
277 case DateParseData::RelationType::HOUR:
278 TC3_CALENDAR_CHECK(calendar->AddHourOfDay(distance));
279 return true;
280 case DateParseData::RelationType::DAY:
281 TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(distance));
282 return true;
283 case DateParseData::RelationType::WEEK:
284 TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(7 * distance))
285 TC3_CALENDAR_CHECK(calendar->SetDayOfWeek(1))
286 return true;
287 case DateParseData::RelationType::MONTH:
288 TC3_CALENDAR_CHECK(calendar->AddMonth(distance))
289 TC3_CALENDAR_CHECK(calendar->SetDayOfMonth(1))
290 return true;
291 case DateParseData::RelationType::YEAR:
292 TC3_CALENDAR_CHECK(calendar->AddYear(distance))
293 TC3_CALENDAR_CHECK(calendar->SetDayOfYear(1))
294 return true;
295 default:
296 TC3_LOG(ERROR) << "Unknown relation type: "
297 << static_cast<int>(relation_type);
298 return false;
299 }
300 return false;
301 }
302
303 template <class TCalendar>
GetGranularity(const DateParseData & data)304 DatetimeGranularity CalendarLibTempl<TCalendar>::GetGranularity(
305 const DateParseData& data) const {
306 DatetimeGranularity granularity = DatetimeGranularity::GRANULARITY_YEAR;
307 if ((data.field_set_mask & DateParseData::YEAR_FIELD) ||
308 (data.field_set_mask & DateParseData::RELATION_TYPE_FIELD &&
309 (data.relation_type == DateParseData::RelationType::YEAR))) {
310 granularity = DatetimeGranularity::GRANULARITY_YEAR;
311 }
312 if ((data.field_set_mask & DateParseData::MONTH_FIELD) ||
313 (data.field_set_mask & DateParseData::RELATION_TYPE_FIELD &&
314 (data.relation_type == DateParseData::RelationType::MONTH))) {
315 granularity = DatetimeGranularity::GRANULARITY_MONTH;
316 }
317 if (data.field_set_mask & DateParseData::RELATION_TYPE_FIELD &&
318 (data.relation_type == DateParseData::RelationType::WEEK)) {
319 granularity = DatetimeGranularity::GRANULARITY_WEEK;
320 }
321 if (data.field_set_mask & DateParseData::DAY_FIELD ||
322 (data.field_set_mask & DateParseData::RELATION_FIELD &&
323 (data.relation == DateParseData::Relation::NOW ||
324 data.relation == DateParseData::Relation::TOMORROW ||
325 data.relation == DateParseData::Relation::YESTERDAY)) ||
326 (data.field_set_mask & DateParseData::RELATION_TYPE_FIELD &&
327 (data.relation_type == DateParseData::RelationType::MONDAY ||
328 data.relation_type == DateParseData::RelationType::TUESDAY ||
329 data.relation_type == DateParseData::RelationType::WEDNESDAY ||
330 data.relation_type == DateParseData::RelationType::THURSDAY ||
331 data.relation_type == DateParseData::RelationType::FRIDAY ||
332 data.relation_type == DateParseData::RelationType::SATURDAY ||
333 data.relation_type == DateParseData::RelationType::SUNDAY ||
334 data.relation_type == DateParseData::RelationType::DAY))) {
335 granularity = DatetimeGranularity::GRANULARITY_DAY;
336 }
337 if (data.field_set_mask & DateParseData::HOUR_FIELD ||
338 (data.field_set_mask & DateParseData::RELATION_TYPE_FIELD &&
339 (data.relation_type == DateParseData::RelationType::HOUR))) {
340 granularity = DatetimeGranularity::GRANULARITY_HOUR;
341 }
342 if (data.field_set_mask & DateParseData::MINUTE_FIELD ||
343 (data.field_set_mask & DateParseData::RELATION_TYPE_FIELD &&
344 data.relation_type == DateParseData::RelationType::MINUTE)) {
345 granularity = DatetimeGranularity::GRANULARITY_MINUTE;
346 }
347 if (data.field_set_mask & DateParseData::SECOND_FIELD ||
348 (data.field_set_mask & DateParseData::RELATION_TYPE_FIELD &&
349 (data.relation_type == DateParseData::RelationType::SECOND))) {
350 granularity = DatetimeGranularity::GRANULARITY_SECOND;
351 }
352
353 return granularity;
354 }
355
356 }; // namespace calendar
357
358 #undef TC3_CALENDAR_CHECK
359
360 } // namespace libtextclassifier3
361
362 #endif // LIBTEXTCLASSIFIER_UTILS_CALENDAR_CALENDAR_COMMON_H_
363