1 /*
2 * Copyright (C) 2017 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 "util/calendar/calendar-icu.h"
18
19 #include <memory>
20
21 #include "util/base/macros.h"
22 #include "unicode/gregocal.h"
23 #include "unicode/timezone.h"
24 #include "unicode/ucal.h"
25
26 namespace libtextclassifier2 {
27 namespace {
MapToDayOfWeekOrDefault(int relation_type,int default_value)28 int MapToDayOfWeekOrDefault(int relation_type, int default_value) {
29 switch (relation_type) {
30 case DateParseData::MONDAY:
31 return UCalendarDaysOfWeek::UCAL_MONDAY;
32 case DateParseData::TUESDAY:
33 return UCalendarDaysOfWeek::UCAL_TUESDAY;
34 case DateParseData::WEDNESDAY:
35 return UCalendarDaysOfWeek::UCAL_WEDNESDAY;
36 case DateParseData::THURSDAY:
37 return UCalendarDaysOfWeek::UCAL_THURSDAY;
38 case DateParseData::FRIDAY:
39 return UCalendarDaysOfWeek::UCAL_FRIDAY;
40 case DateParseData::SATURDAY:
41 return UCalendarDaysOfWeek::UCAL_SATURDAY;
42 case DateParseData::SUNDAY:
43 return UCalendarDaysOfWeek::UCAL_SUNDAY;
44 default:
45 return default_value;
46 }
47 }
48
DispatchToRecedeOrToLastDayOfWeek(icu::Calendar * date,int relation_type,int distance)49 bool DispatchToRecedeOrToLastDayOfWeek(icu::Calendar* date, int relation_type,
50 int distance) {
51 UErrorCode status = U_ZERO_ERROR;
52 switch (relation_type) {
53 case DateParseData::MONDAY:
54 case DateParseData::TUESDAY:
55 case DateParseData::WEDNESDAY:
56 case DateParseData::THURSDAY:
57 case DateParseData::FRIDAY:
58 case DateParseData::SATURDAY:
59 case DateParseData::SUNDAY:
60 for (int i = 0; i < distance; i++) {
61 do {
62 if (U_FAILURE(status)) {
63 TC_LOG(ERROR) << "error day of week";
64 return false;
65 }
66 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1, status);
67 if (U_FAILURE(status)) {
68 TC_LOG(ERROR) << "error adding a day";
69 return false;
70 }
71 } while (date->get(UCalendarDateFields::UCAL_DAY_OF_WEEK, status) !=
72 MapToDayOfWeekOrDefault(relation_type, 1));
73 }
74 return true;
75 case DateParseData::DAY:
76 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, -1 * distance, status);
77 if (U_FAILURE(status)) {
78 TC_LOG(ERROR) << "error adding a day";
79 return false;
80 }
81
82 return true;
83 case DateParseData::WEEK:
84 date->set(UCalendarDateFields::UCAL_DAY_OF_WEEK, 1);
85 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, -7 * (distance - 1),
86 status);
87 if (U_FAILURE(status)) {
88 TC_LOG(ERROR) << "error adding a week";
89 return false;
90 }
91
92 return true;
93 case DateParseData::MONTH:
94 date->set(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1);
95 date->add(UCalendarDateFields::UCAL_MONTH, -1 * (distance - 1), status);
96 if (U_FAILURE(status)) {
97 TC_LOG(ERROR) << "error adding a month";
98 return false;
99 }
100 return true;
101 case DateParseData::YEAR:
102 date->set(UCalendarDateFields::UCAL_DAY_OF_YEAR, 1);
103 date->add(UCalendarDateFields::UCAL_YEAR, -1 * (distance - 1), status);
104 if (U_FAILURE(status)) {
105 TC_LOG(ERROR) << "error adding a year";
106
107 return true;
108 default:
109 return false;
110 }
111 return false;
112 }
113 }
114
DispatchToAdvancerOrToNextOrSameDayOfWeek(icu::Calendar * date,int relation_type)115 bool DispatchToAdvancerOrToNextOrSameDayOfWeek(icu::Calendar* date,
116 int relation_type) {
117 UErrorCode status = U_ZERO_ERROR;
118 switch (relation_type) {
119 case DateParseData::MONDAY:
120 case DateParseData::TUESDAY:
121 case DateParseData::WEDNESDAY:
122 case DateParseData::THURSDAY:
123 case DateParseData::FRIDAY:
124 case DateParseData::SATURDAY:
125 case DateParseData::SUNDAY:
126 while (date->get(UCalendarDateFields::UCAL_DAY_OF_WEEK, status) !=
127 MapToDayOfWeekOrDefault(relation_type, 1)) {
128 if (U_FAILURE(status)) {
129 TC_LOG(ERROR) << "error day of week";
130 return false;
131 }
132 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1, status);
133 if (U_FAILURE(status)) {
134 TC_LOG(ERROR) << "error adding a day";
135 return false;
136 }
137 }
138 return true;
139 case DateParseData::DAY:
140 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1, status);
141 if (U_FAILURE(status)) {
142 TC_LOG(ERROR) << "error adding a day";
143 return false;
144 }
145
146 return true;
147 case DateParseData::WEEK:
148 date->set(UCalendarDateFields::UCAL_DAY_OF_WEEK, 1);
149 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 7, status);
150 if (U_FAILURE(status)) {
151 TC_LOG(ERROR) << "error adding a week";
152 return false;
153 }
154
155 return true;
156 case DateParseData::MONTH:
157 date->set(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1);
158 date->add(UCalendarDateFields::UCAL_MONTH, 1, status);
159 if (U_FAILURE(status)) {
160 TC_LOG(ERROR) << "error adding a month";
161 return false;
162 }
163 return true;
164 case DateParseData::YEAR:
165 date->set(UCalendarDateFields::UCAL_DAY_OF_YEAR, 1);
166 date->add(UCalendarDateFields::UCAL_YEAR, 1, status);
167 if (U_FAILURE(status)) {
168 TC_LOG(ERROR) << "error adding a year";
169
170 return true;
171 default:
172 return false;
173 }
174 return false;
175 }
176 }
177
DispatchToAdvancerOrToNextDayOfWeek(icu::Calendar * date,int relation_type,int distance)178 bool DispatchToAdvancerOrToNextDayOfWeek(icu::Calendar* date, int relation_type,
179 int distance) {
180 UErrorCode status = U_ZERO_ERROR;
181 switch (relation_type) {
182 case DateParseData::MONDAY:
183 case DateParseData::TUESDAY:
184 case DateParseData::WEDNESDAY:
185 case DateParseData::THURSDAY:
186 case DateParseData::FRIDAY:
187 case DateParseData::SATURDAY:
188 case DateParseData::SUNDAY:
189 for (int i = 0; i < distance; i++) {
190 do {
191 if (U_FAILURE(status)) {
192 TC_LOG(ERROR) << "error day of week";
193 return false;
194 }
195 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1, status);
196 if (U_FAILURE(status)) {
197 TC_LOG(ERROR) << "error adding a day";
198 return false;
199 }
200 } while (date->get(UCalendarDateFields::UCAL_DAY_OF_WEEK, status) !=
201 MapToDayOfWeekOrDefault(relation_type, 1));
202 }
203 return true;
204 case DateParseData::DAY:
205 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, distance, status);
206 if (U_FAILURE(status)) {
207 TC_LOG(ERROR) << "error adding a day";
208 return false;
209 }
210
211 return true;
212 case DateParseData::WEEK:
213 date->set(UCalendarDateFields::UCAL_DAY_OF_WEEK, 1);
214 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 7 * distance, status);
215 if (U_FAILURE(status)) {
216 TC_LOG(ERROR) << "error adding a week";
217 return false;
218 }
219
220 return true;
221 case DateParseData::MONTH:
222 date->set(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1);
223 date->add(UCalendarDateFields::UCAL_MONTH, 1 * distance, status);
224 if (U_FAILURE(status)) {
225 TC_LOG(ERROR) << "error adding a month";
226 return false;
227 }
228 return true;
229 case DateParseData::YEAR:
230 date->set(UCalendarDateFields::UCAL_DAY_OF_YEAR, 1);
231 date->add(UCalendarDateFields::UCAL_YEAR, 1 * distance, status);
232 if (U_FAILURE(status)) {
233 TC_LOG(ERROR) << "error adding a year";
234
235 return true;
236 default:
237 return false;
238 }
239 return false;
240 }
241 }
242
RoundToGranularity(DatetimeGranularity granularity,icu::Calendar * calendar)243 bool RoundToGranularity(DatetimeGranularity granularity,
244 icu::Calendar* calendar) {
245 // Force recomputation before doing the rounding.
246 UErrorCode status = U_ZERO_ERROR;
247 calendar->get(UCalendarDateFields::UCAL_DAY_OF_WEEK, status);
248 if (U_FAILURE(status)) {
249 TC_LOG(ERROR) << "Can't interpret date.";
250 return false;
251 }
252
253 switch (granularity) {
254 case GRANULARITY_YEAR:
255 calendar->set(UCalendarDateFields::UCAL_MONTH, 0);
256 TC_FALLTHROUGH_INTENDED;
257 case GRANULARITY_MONTH:
258 calendar->set(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1);
259 TC_FALLTHROUGH_INTENDED;
260 case GRANULARITY_DAY:
261 calendar->set(UCalendarDateFields::UCAL_HOUR, 0);
262 TC_FALLTHROUGH_INTENDED;
263 case GRANULARITY_HOUR:
264 calendar->set(UCalendarDateFields::UCAL_MINUTE, 0);
265 TC_FALLTHROUGH_INTENDED;
266 case GRANULARITY_MINUTE:
267 calendar->set(UCalendarDateFields::UCAL_SECOND, 0);
268 break;
269
270 case GRANULARITY_WEEK:
271 calendar->set(UCalendarDateFields::UCAL_DAY_OF_WEEK,
272 calendar->getFirstDayOfWeek());
273 calendar->set(UCalendarDateFields::UCAL_HOUR, 0);
274 calendar->set(UCalendarDateFields::UCAL_MINUTE, 0);
275 calendar->set(UCalendarDateFields::UCAL_SECOND, 0);
276 break;
277
278 case GRANULARITY_UNKNOWN:
279 case GRANULARITY_SECOND:
280 break;
281 }
282
283 return true;
284 }
285
286 } // namespace
287
InterpretParseData(const DateParseData & parse_data,int64 reference_time_ms_utc,const std::string & reference_timezone,const std::string & reference_locale,DatetimeGranularity granularity,int64 * interpreted_time_ms_utc) const288 bool CalendarLib::InterpretParseData(const DateParseData& parse_data,
289 int64 reference_time_ms_utc,
290 const std::string& reference_timezone,
291 const std::string& reference_locale,
292 DatetimeGranularity granularity,
293 int64* interpreted_time_ms_utc) const {
294 UErrorCode status = U_ZERO_ERROR;
295
296 std::unique_ptr<icu::Calendar> date(icu::Calendar::createInstance(
297 icu::Locale::createFromName(reference_locale.c_str()), status));
298 if (U_FAILURE(status)) {
299 TC_LOG(ERROR) << "error getting calendar instance";
300 return false;
301 }
302
303 date->adoptTimeZone(icu::TimeZone::createTimeZone(
304 icu::UnicodeString::fromUTF8(reference_timezone)));
305 date->setTime(reference_time_ms_utc, status);
306
307 // By default, the parsed time is interpreted to be on the reference day. But
308 // a parsed date, should have time 0:00:00 unless specified.
309 date->set(UCalendarDateFields::UCAL_HOUR_OF_DAY, 0);
310 date->set(UCalendarDateFields::UCAL_MINUTE, 0);
311 date->set(UCalendarDateFields::UCAL_SECOND, 0);
312 date->set(UCalendarDateFields::UCAL_MILLISECOND, 0);
313
314 static const int64 kMillisInHour = 1000 * 60 * 60;
315 if (parse_data.field_set_mask & DateParseData::Fields::ZONE_OFFSET_FIELD) {
316 date->set(UCalendarDateFields::UCAL_ZONE_OFFSET,
317 parse_data.zone_offset * kMillisInHour);
318 }
319 if (parse_data.field_set_mask & DateParseData::Fields::DST_OFFSET_FIELD) {
320 // convert from hours to milliseconds
321 date->set(UCalendarDateFields::UCAL_DST_OFFSET,
322 parse_data.dst_offset * kMillisInHour);
323 }
324
325 if (parse_data.field_set_mask & DateParseData::Fields::RELATION_FIELD) {
326 switch (parse_data.relation) {
327 case DateParseData::Relation::NEXT:
328 if (parse_data.field_set_mask &
329 DateParseData::Fields::RELATION_TYPE_FIELD) {
330 if (!DispatchToAdvancerOrToNextDayOfWeek(
331 date.get(), parse_data.relation_type, 1)) {
332 return false;
333 }
334 }
335 break;
336 case DateParseData::Relation::NEXT_OR_SAME:
337 if (parse_data.field_set_mask &
338 DateParseData::Fields::RELATION_TYPE_FIELD) {
339 if (!DispatchToAdvancerOrToNextOrSameDayOfWeek(
340 date.get(), parse_data.relation_type)) {
341 return false;
342 }
343 }
344 break;
345 case DateParseData::Relation::LAST:
346 if (parse_data.field_set_mask &
347 DateParseData::Fields::RELATION_TYPE_FIELD) {
348 if (!DispatchToRecedeOrToLastDayOfWeek(date.get(),
349 parse_data.relation_type, 1)) {
350 return false;
351 }
352 }
353 break;
354 case DateParseData::Relation::NOW:
355 // NOOP
356 break;
357 case DateParseData::Relation::TOMORROW:
358 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1, status);
359 if (U_FAILURE(status)) {
360 TC_LOG(ERROR) << "error adding a day";
361 return false;
362 }
363 break;
364 case DateParseData::Relation::YESTERDAY:
365 date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, -1, status);
366 if (U_FAILURE(status)) {
367 TC_LOG(ERROR) << "error subtracting a day";
368 return false;
369 }
370 break;
371 case DateParseData::Relation::PAST:
372 if (parse_data.field_set_mask &
373 DateParseData::Fields::RELATION_TYPE_FIELD) {
374 if (parse_data.field_set_mask &
375 DateParseData::Fields::RELATION_DISTANCE_FIELD) {
376 if (!DispatchToRecedeOrToLastDayOfWeek(
377 date.get(), parse_data.relation_type,
378 parse_data.relation_distance)) {
379 return false;
380 }
381 }
382 }
383 break;
384 case DateParseData::Relation::FUTURE:
385 if (parse_data.field_set_mask &
386 DateParseData::Fields::RELATION_TYPE_FIELD) {
387 if (parse_data.field_set_mask &
388 DateParseData::Fields::RELATION_DISTANCE_FIELD) {
389 if (!DispatchToAdvancerOrToNextDayOfWeek(
390 date.get(), parse_data.relation_type,
391 parse_data.relation_distance)) {
392 return false;
393 }
394 }
395 }
396 break;
397 }
398 }
399 if (parse_data.field_set_mask & DateParseData::Fields::YEAR_FIELD) {
400 date->set(UCalendarDateFields::UCAL_YEAR, parse_data.year);
401 }
402 if (parse_data.field_set_mask & DateParseData::Fields::MONTH_FIELD) {
403 // NOTE: Java and ICU disagree on month formats
404 date->set(UCalendarDateFields::UCAL_MONTH, parse_data.month - 1);
405 }
406 if (parse_data.field_set_mask & DateParseData::Fields::DAY_FIELD) {
407 date->set(UCalendarDateFields::UCAL_DAY_OF_MONTH, parse_data.day_of_month);
408 }
409 if (parse_data.field_set_mask & DateParseData::Fields::HOUR_FIELD) {
410 if (parse_data.field_set_mask & DateParseData::Fields::AMPM_FIELD &&
411 parse_data.ampm == 1 && parse_data.hour < 12) {
412 date->set(UCalendarDateFields::UCAL_HOUR_OF_DAY, parse_data.hour + 12);
413 } else {
414 date->set(UCalendarDateFields::UCAL_HOUR_OF_DAY, parse_data.hour);
415 }
416 }
417 if (parse_data.field_set_mask & DateParseData::Fields::MINUTE_FIELD) {
418 date->set(UCalendarDateFields::UCAL_MINUTE, parse_data.minute);
419 }
420 if (parse_data.field_set_mask & DateParseData::Fields::SECOND_FIELD) {
421 date->set(UCalendarDateFields::UCAL_SECOND, parse_data.second);
422 }
423
424 if (!RoundToGranularity(granularity, date.get())) {
425 return false;
426 }
427
428 *interpreted_time_ms_utc = date->getTime(status);
429 if (U_FAILURE(status)) {
430 TC_LOG(ERROR) << "error getting time from instance";
431 return false;
432 }
433
434 return true;
435 }
436 } // namespace libtextclassifier2
437