blob: 7e606de329a98e7c529b7071d9bf87ff58b068d0 [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,
DatetimeGranularity granularity,
TCalendar* calendar) 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,
DatetimeGranularity granularity, TCalendar* calendar) const {
TC3_CALENDAR_CHECK(calendar->Initialize(reference_timezone, reference_locale,
reference_time_ms_utc))
// 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))
// 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));
}
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 {
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))
}
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::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::MONDAY:
case DateParseData::TUESDAY:
case DateParseData::WEDNESDAY:
case DateParseData::THURSDAY:
case DateParseData::FRIDAY:
case DateParseData::SATURDAY:
case DateParseData::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 == relation_type) {
distance += -distance_sign;
if (distance == 0) break;
}
TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(distance_sign))
}
return true;
case DateParseData::DAY:
TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(distance));
return true;
case DateParseData::WEEK:
TC3_CALENDAR_CHECK(calendar->AddDayOfMonth(7 * distance))
TC3_CALENDAR_CHECK(calendar->SetDayOfWeek(1))
return true;
case DateParseData::MONTH:
TC3_CALENDAR_CHECK(calendar->AddMonth(distance))
TC3_CALENDAR_CHECK(calendar->SetDayOfMonth(1))
return true;
case DateParseData::YEAR:
TC3_CALENDAR_CHECK(calendar->AddYear(distance))
TC3_CALENDAR_CHECK(calendar->SetDayOfYear(1))
return true;
default:
return false;
}
return false;
}
}; // namespace calendar
#undef TC3_CALENDAR_CHECK
} // namespace libtextclassifier3
#endif // LIBTEXTCLASSIFIER_UTILS_CALENDAR_CALENDAR_COMMON_H_