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