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
26namespace libtextclassifier2 {
27namespace {
28int 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
49bool 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
115bool 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
178bool 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
243bool 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
288bool 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