/* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef LIBTEXTCLASSIFIER_UTILS_CALENDAR_CALENDAR_COMMON_H_ #define LIBTEXTCLASSIFIER_UTILS_CALENDAR_CALENDAR_COMMON_H_ #include "annotator/types.h" #include "utils/base/integral_types.h" #include "utils/base/logging.h" #include "utils/base/macros.h" namespace libtextclassifier3 { namespace calendar { // Macro to reduce the amount of boilerplate needed for propagating errors. #define TC3_CALENDAR_CHECK(EXPR) \ if (!(EXPR)) { \ return false; \ } // An implementation of CalendarLib that is independent of the particular // calendar implementation used (implementation type is passed as template // argument). template <class TCalendar> class CalendarLibTempl { public: bool InterpretParseData(const DateParseData& parse_data, int64 reference_time_ms_utc, const std::string& reference_timezone, const std::string& reference_locale, TCalendar* calendar, DatetimeGranularity* granularity) const; DatetimeGranularity GetGranularity(const DateParseData& data) const; private: // Adjusts the calendar's time instant according to a relative date reference // in the parsed data. bool ApplyRelationField(const DateParseData& parse_data, TCalendar* calendar) const; // Round the time instant's precision down to the given granularity. bool RoundToGranularity(DatetimeGranularity granularity, TCalendar* calendar) const; // Adjusts time in steps of relation_type, by distance steps. // For example: // - Adjusting by -2 MONTHS will return the beginning of the 1st // two weeks ago. // - Adjusting by +4 Wednesdays will return the beginning of the next // Wednesday at least 4 weeks from now. // If allow_today is true, the same day of the week may be kept // if it already matches the relation type. bool AdjustByRelation(DateParseData::RelationType relation_type, int distance, bool allow_today, TCalendar* calendar) const; }; template <class TCalendar> bool CalendarLibTempl<TCalendar>::InterpretParseData( const DateParseData& parse_data, int64 reference_time_ms_utc, const std::string& reference_timezone, const std::string& reference_locale, TCalendar* calendar, DatetimeGranularity* granularity) const { TC3_CALENDAR_CHECK(calendar->Initialize(reference_timezone, reference_locale, reference_time_ms_utc)) bool should_round_to_granularity = true; *granularity = GetGranularity(parse_data); // Apply each of the parsed fields in order of increasing granularity. static const int64 kMillisInHour = 1000 * 60 * 60; if (parse_data.field_set_mask & DateParseData::Fields::ZONE_OFFSET_FIELD) { TC3_CALENDAR_CHECK( calendar->SetZoneOffset(parse_data.zone_offset * kMillisInHour)) } if (parse_data.field_set_mask & DateParseData::Fields::DST_OFFSET_FIELD) { TC3_CALENDAR_CHECK( calendar->SetDstOffset(parse_data.dst_offset * kMillisInHour)) } if (parse_data.field_set_mask & DateParseData::Fields::RELATION_FIELD) { TC3_CALENDAR_CHECK(ApplyRelationField(parse_data, calendar)); // Don't round to the granularity for relative expressions that specify the // distance. So that, e.g. "in 2 hours" when it's 8:35:03 will result in // 10:35:03. if (parse_data.field_set_mask & DateParseData::Fields::RELATION_DISTANCE_FIELD) { should_round_to_granularity = false; } } else { // By default, the parsed time is interpreted to be on the reference day. // But a parsed date should have time 0:00:00 unless specified. TC3_CALENDAR_CHECK(calendar->SetHourOfDay(0)) TC3_CALENDAR_CHECK(calendar->SetMinute(0)) TC3_CALENDAR_CHECK(calendar->SetSecond(0)) TC3_CALENDAR_CHECK(calendar->SetMillisecond(0)) } if (parse_data.field_set_mask & DateParseData::Fields::YEAR_FIELD) { TC3_CALENDAR_CHECK(calendar->SetYear(parse_data.year)) } if (parse_data.field_set_mask & DateParseData::Fields::MONTH_FIELD) { // ICU has months starting at 0, Java and Datetime parser at 1, so we // need to subtract 1. TC3_CALENDAR_CHECK(calendar->SetMonth(parse_data.month - 1)) } if (parse_data.field_set_mask & DateParseData::Fields::DAY_FIELD) { TC3_CALENDAR_CHECK(calendar->SetDayOfMonth(parse_data.day_of_month)) } if (parse_data.field_set_mask & DateParseData::Fields::HOUR_FIELD) { if (parse_data.field_set_mask & DateParseData::Fields::AMPM_FIELD && parse_data.ampm == DateParseData::AMPM::PM && parse_data.hour < 12) { TC3_CALENDAR_CHECK(calendar->SetHourOfDay(parse_data.hour + 12)) } else if (parse_data.ampm == DateParseData::AMPM::AM && parse_data.hour == 12) { // Do nothing. 12am == 0. } else { TC3_CALENDAR_CHECK(calendar->SetHourOfDay(parse_data.hour)) } } if (parse_data.field_set_mask & DateParseData::Fields::MINUTE_FIELD) { TC3_CALENDAR_CHECK(calendar->SetMinute(parse_data.minute)) } if (parse_data.field_set_mask & DateParseData::Fields::SECOND_FIELD) { TC3_CALENDAR_CHECK(calendar->SetSecond(parse_data.second)) } if (should_round_to_granularity) { TC3_CALENDAR_CHECK(RoundToGranularity(*granularity, calendar)) } return true; } template <class TCalendar> bool CalendarLibTempl<TCalendar>::ApplyRelationField( const DateParseData& parse_data, TCalendar* calendar) const { constexpr int relation_type_mask = DateParseData::Fields::RELATION_TYPE_FIELD; constexpr int relation_distance_mask = DateParseData::Fields::RELATION_DISTANCE_FIELD; switch (parse_data.relation) { case DateParseData::Relation::UNSPECIFIED: TC3_LOG(ERROR) << "UNSPECIFIED RelationType."; return false; case DateParseData::Relation::NEXT: if (parse_data.field_set_mask & relation_type_mask) { TC3_CALENDAR_CHECK(AdjustByRelation(parse_data.relation_type, /*distance=*/1, /*allow_today=*/false, calendar)); } return true; case DateParseData::Relation::NEXT_OR_SAME: if (parse_data.field_set_mask & relation_type_mask) { TC3_CALENDAR_CHECK(AdjustByRelation(parse_data.relation_type, /*distance=*/1, /*allow_today=*/true, calendar)) } return true; case DateParseData::Relation::LAST: if (parse_data.field_set_mask & relation_type_mask) { TC3_CALENDAR_CHECK(AdjustByRelation(parse_data.relation_type, /*distance=*/-1, /*allow_today=*/false, calendar)) } return true; case DateParseData::Relation::NOW: return true; // NOOP case DateParseData::Relation::TOMORROW: TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(1)); return true; case DateParseData::Relation::YESTERDAY: TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(-1)); return true; case DateParseData::Relation::PAST: if ((parse_data.field_set_mask & relation_type_mask) && (parse_data.field_set_mask & relation_distance_mask)) { TC3_CALENDAR_CHECK(AdjustByRelation(parse_data.relation_type, -parse_data.relation_distance, /*allow_today=*/false, calendar)) } return true; case DateParseData::Relation::FUTURE: if ((parse_data.field_set_mask & relation_type_mask) && (parse_data.field_set_mask & relation_distance_mask)) { TC3_CALENDAR_CHECK(AdjustByRelation(parse_data.relation_type, parse_data.relation_distance, /*allow_today=*/false, calendar)) } return true; } return false; } template <class TCalendar> bool CalendarLibTempl<TCalendar>::RoundToGranularity( DatetimeGranularity granularity, TCalendar* calendar) const { // Force recomputation before doing the rounding. int unused; TC3_CALENDAR_CHECK(calendar->GetDayOfWeek(&unused)); switch (granularity) { case GRANULARITY_YEAR: TC3_CALENDAR_CHECK(calendar->SetMonth(0)); TC3_FALLTHROUGH_INTENDED; case GRANULARITY_MONTH: TC3_CALENDAR_CHECK(calendar->SetDayOfMonth(1)); TC3_FALLTHROUGH_INTENDED; case GRANULARITY_DAY: TC3_CALENDAR_CHECK(calendar->SetHourOfDay(0)); TC3_FALLTHROUGH_INTENDED; case GRANULARITY_HOUR: TC3_CALENDAR_CHECK(calendar->SetMinute(0)); TC3_FALLTHROUGH_INTENDED; case GRANULARITY_MINUTE: TC3_CALENDAR_CHECK(calendar->SetSecond(0)); break; case GRANULARITY_WEEK: int first_day_of_week; TC3_CALENDAR_CHECK(calendar->GetFirstDayOfWeek(&first_day_of_week)); TC3_CALENDAR_CHECK(calendar->SetDayOfWeek(first_day_of_week)); TC3_CALENDAR_CHECK(calendar->SetHourOfDay(0)); TC3_CALENDAR_CHECK(calendar->SetMinute(0)); TC3_CALENDAR_CHECK(calendar->SetSecond(0)); break; case GRANULARITY_UNKNOWN: case GRANULARITY_SECOND: break; } return true; } template <class TCalendar> bool CalendarLibTempl<TCalendar>::AdjustByRelation( DateParseData::RelationType relation_type, int distance, bool allow_today, TCalendar* calendar) const { const int distance_sign = distance < 0 ? -1 : 1; switch (relation_type) { case DateParseData::RelationType::MONDAY: case DateParseData::RelationType::TUESDAY: case DateParseData::RelationType::WEDNESDAY: case DateParseData::RelationType::THURSDAY: case DateParseData::RelationType::FRIDAY: case DateParseData::RelationType::SATURDAY: case DateParseData::RelationType::SUNDAY: if (!allow_today) { // If we're not including the same day as the reference, skip it. TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(distance_sign)) } // Keep walking back until we hit the desired day of the week. while (distance != 0) { int day_of_week; TC3_CALENDAR_CHECK(calendar->GetDayOfWeek(&day_of_week)) if (day_of_week == static_cast<int>(relation_type)) { distance += -distance_sign; if (distance == 0) break; } TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(distance_sign)) } return true; case DateParseData::RelationType::SECOND: TC3_CALENDAR_CHECK(calendar->AddSecond(distance)); return true; case DateParseData::RelationType::MINUTE: TC3_CALENDAR_CHECK(calendar->AddMinute(distance)); return true; case DateParseData::RelationType::HOUR: TC3_CALENDAR_CHECK(calendar->AddHourOfDay(distance)); return true; case DateParseData::RelationType::DAY: TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(distance)); return true; case DateParseData::RelationType::WEEK: TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(7 * distance)) TC3_CALENDAR_CHECK(calendar->SetDayOfWeek(1)) return true; case DateParseData::RelationType::MONTH: TC3_CALENDAR_CHECK(calendar->AddMonth(distance)) TC3_CALENDAR_CHECK(calendar->SetDayOfMonth(1)) return true; case DateParseData::RelationType::YEAR: TC3_CALENDAR_CHECK(calendar->AddYear(distance)) TC3_CALENDAR_CHECK(calendar->SetDayOfYear(1)) return true; default: TC3_LOG(ERROR) << "Unknown relation type: " << static_cast<int>(relation_type); return false; } return false; } template <class TCalendar> DatetimeGranularity CalendarLibTempl<TCalendar>::GetGranularity( const DateParseData& data) const { DatetimeGranularity granularity = DatetimeGranularity::GRANULARITY_YEAR; if ((data.field_set_mask & DateParseData::YEAR_FIELD) || (data.field_set_mask & DateParseData::RELATION_TYPE_FIELD && (data.relation_type == DateParseData::RelationType::YEAR))) { granularity = DatetimeGranularity::GRANULARITY_YEAR; } if ((data.field_set_mask & DateParseData::MONTH_FIELD) || (data.field_set_mask & DateParseData::RELATION_TYPE_FIELD && (data.relation_type == DateParseData::RelationType::MONTH))) { granularity = DatetimeGranularity::GRANULARITY_MONTH; } if (data.field_set_mask & DateParseData::RELATION_TYPE_FIELD && (data.relation_type == DateParseData::RelationType::WEEK)) { granularity = DatetimeGranularity::GRANULARITY_WEEK; } if (data.field_set_mask & DateParseData::DAY_FIELD || (data.field_set_mask & DateParseData::RELATION_FIELD && (data.relation == DateParseData::Relation::NOW || data.relation == DateParseData::Relation::TOMORROW || data.relation == DateParseData::Relation::YESTERDAY)) || (data.field_set_mask & DateParseData::RELATION_TYPE_FIELD && (data.relation_type == DateParseData::RelationType::MONDAY || data.relation_type == DateParseData::RelationType::TUESDAY || data.relation_type == DateParseData::RelationType::WEDNESDAY || data.relation_type == DateParseData::RelationType::THURSDAY || data.relation_type == DateParseData::RelationType::FRIDAY || data.relation_type == DateParseData::RelationType::SATURDAY || data.relation_type == DateParseData::RelationType::SUNDAY || data.relation_type == DateParseData::RelationType::DAY))) { granularity = DatetimeGranularity::GRANULARITY_DAY; } if (data.field_set_mask & DateParseData::HOUR_FIELD || (data.field_set_mask & DateParseData::RELATION_TYPE_FIELD && (data.relation_type == DateParseData::RelationType::HOUR))) { granularity = DatetimeGranularity::GRANULARITY_HOUR; } if (data.field_set_mask & DateParseData::MINUTE_FIELD || (data.field_set_mask & DateParseData::RELATION_TYPE_FIELD && data.relation_type == DateParseData::RelationType::MINUTE)) { granularity = DatetimeGranularity::GRANULARITY_MINUTE; } if (data.field_set_mask & DateParseData::SECOND_FIELD || (data.field_set_mask & DateParseData::RELATION_TYPE_FIELD && (data.relation_type == DateParseData::RelationType::SECOND))) { granularity = DatetimeGranularity::GRANULARITY_SECOND; } return granularity; } }; // namespace calendar #undef TC3_CALENDAR_CHECK } // namespace libtextclassifier3 #endif // LIBTEXTCLASSIFIER_UTILS_CALENDAR_CALENDAR_COMMON_H_