| /* |
| * 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 DatetimeParsedData& 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 DatetimeParsedData& data) const; |
| |
| private: |
| // Adjusts the calendar's time instant according to a relative date reference |
| // in the parsed data. |
| bool ApplyRelationField(const DatetimeParsedData& 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(DatetimeComponent date_time_component, int distance, |
| bool allow_today, TCalendar* calendar) const; |
| }; |
| |
| template <class TCalendar> |
| bool CalendarLibTempl<TCalendar>::InterpretParseData( |
| const DatetimeParsedData& 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.HasFieldType(DatetimeComponent::ComponentType::ZONE_OFFSET)) { |
| int zone_offset; |
| parse_data.GetFieldValue(DatetimeComponent::ComponentType::ZONE_OFFSET, |
| &zone_offset); |
| TC3_CALENDAR_CHECK(calendar->SetZoneOffset(zone_offset * kMillisInHour)) |
| } |
| |
| if (parse_data.HasFieldType(DatetimeComponent::ComponentType::DST_OFFSET)) { |
| int dst_offset; |
| if (parse_data.GetFieldValue(DatetimeComponent::ComponentType::DST_OFFSET, |
| &dst_offset)) { |
| TC3_CALENDAR_CHECK(calendar->SetDstOffset(dst_offset * kMillisInHour)) |
| } |
| } |
| std::vector<DatetimeComponent> relative_components; |
| parse_data.GetRelativeDatetimeComponents(&relative_components); |
| if (!relative_components.empty()) { |
| TC3_CALENDAR_CHECK(ApplyRelationField(parse_data, calendar)); |
| const DatetimeComponent& relative_component = relative_components.back(); |
| should_round_to_granularity = relative_component.ShouldRoundToGranularity(); |
| } 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.HasAbsoluteValue(DatetimeComponent::ComponentType::YEAR)) { |
| int year; |
| parse_data.GetFieldValue(DatetimeComponent::ComponentType::YEAR, &year); |
| TC3_CALENDAR_CHECK(calendar->SetYear(year)) |
| } |
| if (parse_data.HasAbsoluteValue(DatetimeComponent::ComponentType::MONTH)) { |
| int month; |
| parse_data.GetFieldValue(DatetimeComponent::ComponentType::MONTH, &month); |
| // ICU has months starting at 0, Java and Datetime parser at 1, so we |
| // need to subtract 1. |
| TC3_CALENDAR_CHECK(calendar->SetMonth(month - 1)) |
| } |
| |
| if (parse_data.HasAbsoluteValue( |
| DatetimeComponent::ComponentType::DAY_OF_MONTH)) { |
| int day_of_month; |
| parse_data.GetFieldValue(DatetimeComponent::ComponentType::DAY_OF_MONTH, |
| &day_of_month); |
| TC3_CALENDAR_CHECK(calendar->SetDayOfMonth(day_of_month)) |
| } |
| if (parse_data.HasAbsoluteValue(DatetimeComponent::ComponentType::HOUR)) { |
| int hour; |
| parse_data.GetFieldValue(DatetimeComponent::ComponentType::HOUR, &hour); |
| if (parse_data.HasFieldType(DatetimeComponent::ComponentType::MERIDIEM)) { |
| int merdiem; |
| parse_data.GetFieldValue(DatetimeComponent::ComponentType::MERIDIEM, |
| &merdiem); |
| if (merdiem == 1 && hour < 12) { |
| TC3_CALENDAR_CHECK(calendar->SetHourOfDay(hour + 12)) |
| } else if (merdiem == 0 && hour == 12) { |
| // Set hour of the day's value to zero (12am == 0:00 in 24 hour format). |
| // Please see issue b/139923083. |
| TC3_CALENDAR_CHECK(calendar->SetHourOfDay(0)); |
| } else { |
| TC3_CALENDAR_CHECK(calendar->SetHourOfDay(hour)) |
| } |
| } else { |
| TC3_CALENDAR_CHECK(calendar->SetHourOfDay(hour)) |
| } |
| } |
| if (parse_data.HasAbsoluteValue(DatetimeComponent::ComponentType::MINUTE)) { |
| int minute; |
| parse_data.GetFieldValue(DatetimeComponent::ComponentType::MINUTE, &minute); |
| TC3_CALENDAR_CHECK(calendar->SetMinute(minute)) |
| } |
| if (parse_data.HasAbsoluteValue(DatetimeComponent::ComponentType::SECOND)) { |
| int second; |
| parse_data.GetFieldValue(DatetimeComponent::ComponentType::SECOND, &second); |
| TC3_CALENDAR_CHECK(calendar->SetSecond(second)) |
| } |
| if (should_round_to_granularity) { |
| TC3_CALENDAR_CHECK(RoundToGranularity(*granularity, calendar)) |
| } |
| return true; |
| } |
| |
| template <class TCalendar> |
| bool CalendarLibTempl<TCalendar>::ApplyRelationField( |
| const DatetimeParsedData& parse_data, TCalendar* calendar) const { |
| std::vector<DatetimeComponent> relative_date_time_components; |
| parse_data.GetRelativeDatetimeComponents(&relative_date_time_components); |
| if (relative_date_time_components.empty()) { |
| // There is no relative field set in the parsed data. |
| return false; |
| } |
| // Current only one relative date time component is possible. |
| DatetimeComponent relative_date_time_component = |
| relative_date_time_components.back(); |
| |
| switch (relative_date_time_component.relative_qualifier) { |
| case DatetimeComponent::RelativeQualifier::UNSPECIFIED: |
| TC3_LOG(ERROR) << "UNSPECIFIED RelationType."; |
| return false; |
| case DatetimeComponent::RelativeQualifier::NEXT: |
| TC3_CALENDAR_CHECK(AdjustByRelation(relative_date_time_component, |
| /*distance=*/1, |
| /*allow_today=*/false, calendar)); |
| return true; |
| case DatetimeComponent::RelativeQualifier::THIS: |
| TC3_CALENDAR_CHECK(AdjustByRelation(relative_date_time_component, |
| /*distance=*/1, |
| /*allow_today=*/true, calendar)) |
| return true; |
| case DatetimeComponent::RelativeQualifier::LAST: |
| TC3_CALENDAR_CHECK(AdjustByRelation(relative_date_time_component, |
| /*distance=*/-1, |
| /*allow_today=*/false, calendar)) |
| return true; |
| case DatetimeComponent::RelativeQualifier::NOW: |
| return true; // NOOP |
| case DatetimeComponent::RelativeQualifier::TOMORROW: |
| TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(1)); |
| return true; |
| case DatetimeComponent::RelativeQualifier::YESTERDAY: |
| TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(-1)); |
| return true; |
| case DatetimeComponent::RelativeQualifier::PAST: |
| TC3_CALENDAR_CHECK( |
| AdjustByRelation(relative_date_time_component, |
| -relative_date_time_component.relative_count, |
| /*allow_today=*/false, calendar)) |
| return true; |
| case DatetimeComponent::RelativeQualifier::FUTURE: |
| TC3_CALENDAR_CHECK( |
| AdjustByRelation(relative_date_time_component, |
| relative_date_time_component.relative_count, |
| /*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( |
| DatetimeComponent date_time_component, int distance, bool allow_today, |
| TCalendar* calendar) const { |
| const int distance_sign = distance < 0 ? -1 : 1; |
| switch (date_time_component.component_type) { |
| case DatetimeComponent::ComponentType::DAY_OF_WEEK: |
| 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 == (date_time_component.value)) { |
| distance += -distance_sign; |
| if (distance == 0) break; |
| } |
| TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(distance_sign)) |
| } |
| return true; |
| case DatetimeComponent::ComponentType::SECOND: |
| TC3_CALENDAR_CHECK(calendar->AddSecond(distance)); |
| return true; |
| case DatetimeComponent::ComponentType::MINUTE: |
| TC3_CALENDAR_CHECK(calendar->AddMinute(distance)); |
| return true; |
| case DatetimeComponent::ComponentType::HOUR: |
| TC3_CALENDAR_CHECK(calendar->AddHourOfDay(distance)); |
| return true; |
| case DatetimeComponent::ComponentType::DAY_OF_MONTH: |
| TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(distance)); |
| return true; |
| case DatetimeComponent::ComponentType::WEEK: |
| TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(7 * distance)) |
| TC3_CALENDAR_CHECK(calendar->SetDayOfWeek(1)) |
| return true; |
| case DatetimeComponent::ComponentType::MONTH: |
| TC3_CALENDAR_CHECK(calendar->AddMonth(distance)) |
| TC3_CALENDAR_CHECK(calendar->SetDayOfMonth(1)) |
| return true; |
| case DatetimeComponent::ComponentType::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>(date_time_component.component_type); |
| return false; |
| } |
| return false; |
| } |
| |
| template <class TCalendar> |
| DatetimeGranularity CalendarLibTempl<TCalendar>::GetGranularity( |
| const DatetimeParsedData& data) const { |
| return data.GetFinestGranularity(); |
| } |
| |
| }; // namespace calendar |
| |
| #undef TC3_CALENDAR_CHECK |
| |
| } // namespace libtextclassifier3 |
| |
| #endif // LIBTEXTCLASSIFIER_UTILS_CALENDAR_CALENDAR_COMMON_H_ |