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 DatetimeParsedData& parse_data,
41 int64 reference_time_ms_utc,
42 const std::string& reference_timezone,
43 const std::string& reference_locale,
44 bool prefer_future_for_unspecified_date,
45 TCalendar* calendar,
46 DatetimeGranularity* granularity) const;
47
48 DatetimeGranularity GetGranularity(const DatetimeParsedData& data) const;
49
50 private:
51 // Adjusts the calendar's time instant according to a relative date reference
52 // in the parsed data.
53 bool ApplyRelationField(const DatetimeComponent& relative_date_time_component,
54 TCalendar* calendar) const;
55
56 // Round the time instant's precision down to the given granularity.
57 bool RoundToGranularity(DatetimeGranularity granularity,
58 TCalendar* calendar) const;
59
60 // Adjusts time in steps of relation_type, by distance steps.
61 // For example:
62 // - Adjusting by -2 MONTHS will return the beginning of the 1st
63 // two weeks ago.
64 // - Adjusting by +4 Wednesdays will return the beginning of the next
65 // Wednesday at least 4 weeks from now.
66 // If allow_today is true, the same day of the week may be kept
67 // if it already matches the relation type.
68 bool AdjustByRelation(DatetimeComponent date_time_component, int distance,
69 bool allow_today, TCalendar* calendar) const;
70 };
71
HasOnlyTimeComponents(const DatetimeParsedData & parse_data)72 inline bool HasOnlyTimeComponents(const DatetimeParsedData& parse_data) {
73 std::vector<DatetimeComponent> components;
74 parse_data.GetDatetimeComponents(&components);
75
76 for (const DatetimeComponent& component : components) {
77 if (!(component.component_type == DatetimeComponent::ComponentType::HOUR ||
78 component.component_type ==
79 DatetimeComponent::ComponentType::MINUTE ||
80 component.component_type ==
81 DatetimeComponent::ComponentType::SECOND ||
82 component.component_type ==
83 DatetimeComponent::ComponentType::MERIDIEM)) {
84 return false;
85 }
86 }
87 return true;
88 }
89
90 template <class TCalendar>
InterpretParseData(const DatetimeParsedData & parse_data,int64 reference_time_ms_utc,const std::string & reference_timezone,const std::string & reference_locale,bool prefer_future_for_unspecified_date,TCalendar * calendar,DatetimeGranularity * granularity)91 bool CalendarLibTempl<TCalendar>::InterpretParseData(
92 const DatetimeParsedData& parse_data, int64 reference_time_ms_utc,
93 const std::string& reference_timezone, const std::string& reference_locale,
94 bool prefer_future_for_unspecified_date, TCalendar* calendar,
95 DatetimeGranularity* granularity) const {
96 TC3_CALENDAR_CHECK(calendar->Initialize(reference_timezone, reference_locale,
97 reference_time_ms_utc))
98
99 bool should_round_to_granularity = true;
100 *granularity = GetGranularity(parse_data);
101
102 // Apply each of the parsed fields in order of increasing granularity.
103 static const int64 kMillisInMinute = 1000 * 60;
104 if (parse_data.HasFieldType(DatetimeComponent::ComponentType::ZONE_OFFSET)) {
105 int zone_offset;
106 parse_data.GetFieldValue(DatetimeComponent::ComponentType::ZONE_OFFSET,
107 &zone_offset);
108 TC3_CALENDAR_CHECK(calendar->SetZoneOffset(zone_offset * kMillisInMinute))
109 }
110 static const int64 kMillisInHour = 1000 * 60 * 60;
111 if (parse_data.HasFieldType(DatetimeComponent::ComponentType::DST_OFFSET)) {
112 int dst_offset;
113 if (parse_data.GetFieldValue(DatetimeComponent::ComponentType::DST_OFFSET,
114 &dst_offset)) {
115 TC3_CALENDAR_CHECK(calendar->SetDstOffset(dst_offset * kMillisInHour))
116 }
117 }
118 std::vector<DatetimeComponent> relative_components;
119 parse_data.GetRelativeDatetimeComponents(&relative_components);
120 if (!relative_components.empty()) {
121 // Currently only one relative date time component is possible.
122 const DatetimeComponent& relative_component = relative_components.back();
123 TC3_CALENDAR_CHECK(ApplyRelationField(relative_component, calendar));
124 should_round_to_granularity = relative_component.ShouldRoundToGranularity();
125 } else {
126 // By default, the parsed time is interpreted to be on the reference day.
127 // But a parsed date should have time 0:00:00 unless specified.
128 TC3_CALENDAR_CHECK(calendar->SetHourOfDay(0))
129 TC3_CALENDAR_CHECK(calendar->SetMinute(0))
130 TC3_CALENDAR_CHECK(calendar->SetSecond(0))
131 TC3_CALENDAR_CHECK(calendar->SetMillisecond(0))
132 }
133 if (parse_data.HasAbsoluteValue(DatetimeComponent::ComponentType::YEAR)) {
134 int year;
135 parse_data.GetFieldValue(DatetimeComponent::ComponentType::YEAR, &year);
136 TC3_CALENDAR_CHECK(calendar->SetYear(year))
137 }
138 if (parse_data.HasAbsoluteValue(DatetimeComponent::ComponentType::MONTH)) {
139 int month;
140 parse_data.GetFieldValue(DatetimeComponent::ComponentType::MONTH, &month);
141 // ICU has months starting at 0, Java and Datetime parser at 1, so we
142 // need to subtract 1.
143 TC3_CALENDAR_CHECK(calendar->SetMonth(month - 1))
144 }
145
146 if (parse_data.HasAbsoluteValue(
147 DatetimeComponent::ComponentType::DAY_OF_MONTH)) {
148 int day_of_month;
149 parse_data.GetFieldValue(DatetimeComponent::ComponentType::DAY_OF_MONTH,
150 &day_of_month);
151 TC3_CALENDAR_CHECK(calendar->SetDayOfMonth(day_of_month))
152 }
153 if (parse_data.HasAbsoluteValue(DatetimeComponent::ComponentType::HOUR)) {
154 int hour;
155 parse_data.GetFieldValue(DatetimeComponent::ComponentType::HOUR, &hour);
156 if (parse_data.HasFieldType(DatetimeComponent::ComponentType::MERIDIEM)) {
157 int merdiem;
158 parse_data.GetFieldValue(DatetimeComponent::ComponentType::MERIDIEM,
159 &merdiem);
160 if (merdiem == 1 && hour < 12) {
161 TC3_CALENDAR_CHECK(calendar->SetHourOfDay(hour + 12))
162 } else if (merdiem == 0 && hour == 12) {
163 // Set hour of the day's value to zero (12am == 0:00 in 24 hour format).
164 // Please see issue b/139923083.
165 TC3_CALENDAR_CHECK(calendar->SetHourOfDay(0));
166 } else {
167 TC3_CALENDAR_CHECK(calendar->SetHourOfDay(hour))
168 }
169 } else {
170 TC3_CALENDAR_CHECK(calendar->SetHourOfDay(hour))
171 }
172 }
173 if (parse_data.HasAbsoluteValue(DatetimeComponent::ComponentType::MINUTE)) {
174 int minute;
175 parse_data.GetFieldValue(DatetimeComponent::ComponentType::MINUTE, &minute);
176 TC3_CALENDAR_CHECK(calendar->SetMinute(minute))
177 }
178 if (parse_data.HasAbsoluteValue(DatetimeComponent::ComponentType::SECOND)) {
179 int second;
180 parse_data.GetFieldValue(DatetimeComponent::ComponentType::SECOND, &second);
181 TC3_CALENDAR_CHECK(calendar->SetSecond(second))
182 }
183 if (should_round_to_granularity) {
184 TC3_CALENDAR_CHECK(RoundToGranularity(*granularity, calendar))
185 }
186
187 int64 calendar_millis;
188 TC3_CALENDAR_CHECK(calendar->GetTimeInMillis(&calendar_millis))
189 if (prefer_future_for_unspecified_date &&
190 calendar_millis < reference_time_ms_utc &&
191 HasOnlyTimeComponents(parse_data)) {
192 calendar->AddDayOfMonth(1);
193 }
194
195 return true;
196 }
197
198 template <class TCalendar>
ApplyRelationField(const DatetimeComponent & relative_date_time_component,TCalendar * calendar)199 bool CalendarLibTempl<TCalendar>::ApplyRelationField(
200 const DatetimeComponent& relative_date_time_component,
201 TCalendar* calendar) const {
202 switch (relative_date_time_component.relative_qualifier) {
203 case DatetimeComponent::RelativeQualifier::UNSPECIFIED:
204 TC3_LOG(ERROR) << "UNSPECIFIED RelationType.";
205 return false;
206 case DatetimeComponent::RelativeQualifier::NEXT:
207 TC3_CALENDAR_CHECK(AdjustByRelation(relative_date_time_component,
208 /*distance=*/1,
209 /*allow_today=*/false, calendar));
210 return true;
211 case DatetimeComponent::RelativeQualifier::THIS:
212 TC3_CALENDAR_CHECK(AdjustByRelation(relative_date_time_component,
213 /*distance=*/1,
214 /*allow_today=*/true, calendar))
215 return true;
216 case DatetimeComponent::RelativeQualifier::LAST:
217 TC3_CALENDAR_CHECK(AdjustByRelation(relative_date_time_component,
218 /*distance=*/-1,
219 /*allow_today=*/false, calendar))
220 return true;
221 case DatetimeComponent::RelativeQualifier::NOW:
222 return true; // NOOP
223 case DatetimeComponent::RelativeQualifier::TOMORROW:
224 TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(1));
225 return true;
226 case DatetimeComponent::RelativeQualifier::YESTERDAY:
227 TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(-1));
228 return true;
229 case DatetimeComponent::RelativeQualifier::PAST:
230 TC3_CALENDAR_CHECK(
231 AdjustByRelation(relative_date_time_component,
232 relative_date_time_component.relative_count,
233 /*allow_today=*/false, calendar))
234 return true;
235 case DatetimeComponent::RelativeQualifier::FUTURE:
236 TC3_CALENDAR_CHECK(
237 AdjustByRelation(relative_date_time_component,
238 relative_date_time_component.relative_count,
239 /*allow_today=*/false, calendar))
240 return true;
241 }
242 return false;
243 }
244
245 template <class TCalendar>
RoundToGranularity(DatetimeGranularity granularity,TCalendar * calendar)246 bool CalendarLibTempl<TCalendar>::RoundToGranularity(
247 DatetimeGranularity granularity, TCalendar* calendar) const {
248 // Force recomputation before doing the rounding.
249 int unused;
250 TC3_CALENDAR_CHECK(calendar->GetDayOfWeek(&unused));
251
252 switch (granularity) {
253 case GRANULARITY_YEAR:
254 TC3_CALENDAR_CHECK(calendar->SetMonth(0));
255 TC3_FALLTHROUGH_INTENDED;
256 case GRANULARITY_MONTH:
257 TC3_CALENDAR_CHECK(calendar->SetDayOfMonth(1));
258 TC3_FALLTHROUGH_INTENDED;
259 case GRANULARITY_DAY:
260 TC3_CALENDAR_CHECK(calendar->SetHourOfDay(0));
261 TC3_FALLTHROUGH_INTENDED;
262 case GRANULARITY_HOUR:
263 TC3_CALENDAR_CHECK(calendar->SetMinute(0));
264 TC3_FALLTHROUGH_INTENDED;
265 case GRANULARITY_MINUTE:
266 TC3_CALENDAR_CHECK(calendar->SetSecond(0));
267 break;
268
269 case GRANULARITY_WEEK:
270 int first_day_of_week;
271 TC3_CALENDAR_CHECK(calendar->GetFirstDayOfWeek(&first_day_of_week));
272 TC3_CALENDAR_CHECK(calendar->SetDayOfWeek(first_day_of_week));
273 TC3_CALENDAR_CHECK(calendar->SetHourOfDay(0));
274 TC3_CALENDAR_CHECK(calendar->SetMinute(0));
275 TC3_CALENDAR_CHECK(calendar->SetSecond(0));
276 break;
277
278 case GRANULARITY_UNKNOWN:
279 case GRANULARITY_SECOND:
280 break;
281 }
282 return true;
283 }
284
285 template <class TCalendar>
AdjustByRelation(DatetimeComponent date_time_component,int distance,bool allow_today,TCalendar * calendar)286 bool CalendarLibTempl<TCalendar>::AdjustByRelation(
287 DatetimeComponent date_time_component, int distance, bool allow_today,
288 TCalendar* calendar) const {
289 const int distance_sign = distance < 0 ? -1 : 1;
290 switch (date_time_component.component_type) {
291 case DatetimeComponent::ComponentType::DAY_OF_WEEK:
292 if (!allow_today) {
293 // If we're not including the same day as the reference, skip it.
294 TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(distance_sign))
295 }
296 // Keep walking back until we hit the desired day of the week.
297 while (distance != 0) {
298 int day_of_week;
299 TC3_CALENDAR_CHECK(calendar->GetDayOfWeek(&day_of_week))
300 if (day_of_week == (date_time_component.value)) {
301 distance += -distance_sign;
302 if (distance == 0) break;
303 }
304 TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(distance_sign))
305 }
306 return true;
307 case DatetimeComponent::ComponentType::SECOND:
308 TC3_CALENDAR_CHECK(calendar->AddSecond(distance));
309 return true;
310 case DatetimeComponent::ComponentType::MINUTE:
311 TC3_CALENDAR_CHECK(calendar->AddMinute(distance));
312 return true;
313 case DatetimeComponent::ComponentType::HOUR:
314 TC3_CALENDAR_CHECK(calendar->AddHourOfDay(distance));
315 return true;
316 case DatetimeComponent::ComponentType::DAY_OF_MONTH:
317 TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(distance));
318 return true;
319 case DatetimeComponent::ComponentType::WEEK:
320 TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(7 * distance))
321 TC3_CALENDAR_CHECK(calendar->SetDayOfWeek(1))
322 return true;
323 case DatetimeComponent::ComponentType::MONTH:
324 TC3_CALENDAR_CHECK(calendar->AddMonth(distance))
325 TC3_CALENDAR_CHECK(calendar->SetDayOfMonth(1))
326 return true;
327 case DatetimeComponent::ComponentType::YEAR:
328 TC3_CALENDAR_CHECK(calendar->AddYear(distance))
329 TC3_CALENDAR_CHECK(calendar->SetDayOfYear(1))
330 return true;
331 default:
332 TC3_LOG(ERROR) << "Unknown relation type: "
333 << static_cast<int>(date_time_component.component_type);
334 return false;
335 }
336 return false;
337 }
338
339 template <class TCalendar>
GetGranularity(const DatetimeParsedData & data)340 DatetimeGranularity CalendarLibTempl<TCalendar>::GetGranularity(
341 const DatetimeParsedData& data) const {
342 return data.GetFinestGranularity();
343 }
344
345 }; // namespace calendar
346
347 #undef TC3_CALENDAR_CHECK
348
349 } // namespace libtextclassifier3
350
351 #endif // LIBTEXTCLASSIFIER_UTILS_CALENDAR_CALENDAR_COMMON_H_
352