• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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