blob: 5c91e2267fe953d161f2dd5b640b5b91d9d7cc6b [file] [log] [blame]
/*
* 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_