普通文本  |  437行  |  14.7 KB

/*
 * Copyright (C) 2017 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.
 */

#include "util/calendar/calendar-icu.h"

#include <memory>

#include "util/base/macros.h"
#include "unicode/gregocal.h"
#include "unicode/timezone.h"
#include "unicode/ucal.h"

namespace libtextclassifier2 {
namespace {
int MapToDayOfWeekOrDefault(int relation_type, int default_value) {
  switch (relation_type) {
    case DateParseData::MONDAY:
      return UCalendarDaysOfWeek::UCAL_MONDAY;
    case DateParseData::TUESDAY:
      return UCalendarDaysOfWeek::UCAL_TUESDAY;
    case DateParseData::WEDNESDAY:
      return UCalendarDaysOfWeek::UCAL_WEDNESDAY;
    case DateParseData::THURSDAY:
      return UCalendarDaysOfWeek::UCAL_THURSDAY;
    case DateParseData::FRIDAY:
      return UCalendarDaysOfWeek::UCAL_FRIDAY;
    case DateParseData::SATURDAY:
      return UCalendarDaysOfWeek::UCAL_SATURDAY;
    case DateParseData::SUNDAY:
      return UCalendarDaysOfWeek::UCAL_SUNDAY;
    default:
      return default_value;
  }
}

bool DispatchToRecedeOrToLastDayOfWeek(icu::Calendar* date, int relation_type,
                                       int distance) {
  UErrorCode status = U_ZERO_ERROR;
  switch (relation_type) {
    case DateParseData::MONDAY:
    case DateParseData::TUESDAY:
    case DateParseData::WEDNESDAY:
    case DateParseData::THURSDAY:
    case DateParseData::FRIDAY:
    case DateParseData::SATURDAY:
    case DateParseData::SUNDAY:
      for (int i = 0; i < distance; i++) {
        do {
          if (U_FAILURE(status)) {
            TC_LOG(ERROR) << "error day of week";
            return false;
          }
          date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1, status);
          if (U_FAILURE(status)) {
            TC_LOG(ERROR) << "error adding a day";
            return false;
          }
        } while (date->get(UCalendarDateFields::UCAL_DAY_OF_WEEK, status) !=
                 MapToDayOfWeekOrDefault(relation_type, 1));
      }
      return true;
    case DateParseData::DAY:
      date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, -1 * distance, status);
      if (U_FAILURE(status)) {
        TC_LOG(ERROR) << "error adding a day";
        return false;
      }

      return true;
    case DateParseData::WEEK:
      date->set(UCalendarDateFields::UCAL_DAY_OF_WEEK, 1);
      date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, -7 * (distance - 1),
                status);
      if (U_FAILURE(status)) {
        TC_LOG(ERROR) << "error adding a week";
        return false;
      }

      return true;
    case DateParseData::MONTH:
      date->set(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1);
      date->add(UCalendarDateFields::UCAL_MONTH, -1 * (distance - 1), status);
      if (U_FAILURE(status)) {
        TC_LOG(ERROR) << "error adding a month";
        return false;
      }
      return true;
    case DateParseData::YEAR:
      date->set(UCalendarDateFields::UCAL_DAY_OF_YEAR, 1);
      date->add(UCalendarDateFields::UCAL_YEAR, -1 * (distance - 1), status);
      if (U_FAILURE(status)) {
        TC_LOG(ERROR) << "error adding a year";

        return true;
        default:
          return false;
      }
      return false;
  }
}

bool DispatchToAdvancerOrToNextOrSameDayOfWeek(icu::Calendar* date,
                                               int relation_type) {
  UErrorCode status = U_ZERO_ERROR;
  switch (relation_type) {
    case DateParseData::MONDAY:
    case DateParseData::TUESDAY:
    case DateParseData::WEDNESDAY:
    case DateParseData::THURSDAY:
    case DateParseData::FRIDAY:
    case DateParseData::SATURDAY:
    case DateParseData::SUNDAY:
      while (date->get(UCalendarDateFields::UCAL_DAY_OF_WEEK, status) !=
             MapToDayOfWeekOrDefault(relation_type, 1)) {
        if (U_FAILURE(status)) {
          TC_LOG(ERROR) << "error day of week";
          return false;
        }
        date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1, status);
        if (U_FAILURE(status)) {
          TC_LOG(ERROR) << "error adding a day";
          return false;
        }
      }
      return true;
    case DateParseData::DAY:
      date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1, status);
      if (U_FAILURE(status)) {
        TC_LOG(ERROR) << "error adding a day";
        return false;
      }

      return true;
    case DateParseData::WEEK:
      date->set(UCalendarDateFields::UCAL_DAY_OF_WEEK, 1);
      date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 7, status);
      if (U_FAILURE(status)) {
        TC_LOG(ERROR) << "error adding a week";
        return false;
      }

      return true;
    case DateParseData::MONTH:
      date->set(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1);
      date->add(UCalendarDateFields::UCAL_MONTH, 1, status);
      if (U_FAILURE(status)) {
        TC_LOG(ERROR) << "error adding a month";
        return false;
      }
      return true;
    case DateParseData::YEAR:
      date->set(UCalendarDateFields::UCAL_DAY_OF_YEAR, 1);
      date->add(UCalendarDateFields::UCAL_YEAR, 1, status);
      if (U_FAILURE(status)) {
        TC_LOG(ERROR) << "error adding a year";

        return true;
        default:
          return false;
      }
      return false;
  }
}

bool DispatchToAdvancerOrToNextDayOfWeek(icu::Calendar* date, int relation_type,
                                         int distance) {
  UErrorCode status = U_ZERO_ERROR;
  switch (relation_type) {
    case DateParseData::MONDAY:
    case DateParseData::TUESDAY:
    case DateParseData::WEDNESDAY:
    case DateParseData::THURSDAY:
    case DateParseData::FRIDAY:
    case DateParseData::SATURDAY:
    case DateParseData::SUNDAY:
      for (int i = 0; i < distance; i++) {
        do {
          if (U_FAILURE(status)) {
            TC_LOG(ERROR) << "error day of week";
            return false;
          }
          date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1, status);
          if (U_FAILURE(status)) {
            TC_LOG(ERROR) << "error adding a day";
            return false;
          }
        } while (date->get(UCalendarDateFields::UCAL_DAY_OF_WEEK, status) !=
                 MapToDayOfWeekOrDefault(relation_type, 1));
      }
      return true;
    case DateParseData::DAY:
      date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, distance, status);
      if (U_FAILURE(status)) {
        TC_LOG(ERROR) << "error adding a day";
        return false;
      }

      return true;
    case DateParseData::WEEK:
      date->set(UCalendarDateFields::UCAL_DAY_OF_WEEK, 1);
      date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 7 * distance, status);
      if (U_FAILURE(status)) {
        TC_LOG(ERROR) << "error adding a week";
        return false;
      }

      return true;
    case DateParseData::MONTH:
      date->set(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1);
      date->add(UCalendarDateFields::UCAL_MONTH, 1 * distance, status);
      if (U_FAILURE(status)) {
        TC_LOG(ERROR) << "error adding a month";
        return false;
      }
      return true;
    case DateParseData::YEAR:
      date->set(UCalendarDateFields::UCAL_DAY_OF_YEAR, 1);
      date->add(UCalendarDateFields::UCAL_YEAR, 1 * distance, status);
      if (U_FAILURE(status)) {
        TC_LOG(ERROR) << "error adding a year";

        return true;
        default:
          return false;
      }
      return false;
  }
}

bool RoundToGranularity(DatetimeGranularity granularity,
                        icu::Calendar* calendar) {
  // Force recomputation before doing the rounding.
  UErrorCode status = U_ZERO_ERROR;
  calendar->get(UCalendarDateFields::UCAL_DAY_OF_WEEK, status);
  if (U_FAILURE(status)) {
    TC_LOG(ERROR) << "Can't interpret date.";
    return false;
  }

  switch (granularity) {
    case GRANULARITY_YEAR:
      calendar->set(UCalendarDateFields::UCAL_MONTH, 0);
      TC_FALLTHROUGH_INTENDED;
    case GRANULARITY_MONTH:
      calendar->set(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1);
      TC_FALLTHROUGH_INTENDED;
    case GRANULARITY_DAY:
      calendar->set(UCalendarDateFields::UCAL_HOUR, 0);
      TC_FALLTHROUGH_INTENDED;
    case GRANULARITY_HOUR:
      calendar->set(UCalendarDateFields::UCAL_MINUTE, 0);
      TC_FALLTHROUGH_INTENDED;
    case GRANULARITY_MINUTE:
      calendar->set(UCalendarDateFields::UCAL_SECOND, 0);
      break;

    case GRANULARITY_WEEK:
      calendar->set(UCalendarDateFields::UCAL_DAY_OF_WEEK,
                    calendar->getFirstDayOfWeek());
      calendar->set(UCalendarDateFields::UCAL_HOUR, 0);
      calendar->set(UCalendarDateFields::UCAL_MINUTE, 0);
      calendar->set(UCalendarDateFields::UCAL_SECOND, 0);
      break;

    case GRANULARITY_UNKNOWN:
    case GRANULARITY_SECOND:
      break;
  }

  return true;
}

}  // namespace

bool CalendarLib::InterpretParseData(const DateParseData& parse_data,
                                     int64 reference_time_ms_utc,
                                     const std::string& reference_timezone,
                                     const std::string& reference_locale,
                                     DatetimeGranularity granularity,
                                     int64* interpreted_time_ms_utc) const {
  UErrorCode status = U_ZERO_ERROR;

  std::unique_ptr<icu::Calendar> date(icu::Calendar::createInstance(
      icu::Locale::createFromName(reference_locale.c_str()), status));
  if (U_FAILURE(status)) {
    TC_LOG(ERROR) << "error getting calendar instance";
    return false;
  }

  date->adoptTimeZone(icu::TimeZone::createTimeZone(
      icu::UnicodeString::fromUTF8(reference_timezone)));
  date->setTime(reference_time_ms_utc, status);

  // 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.
  date->set(UCalendarDateFields::UCAL_HOUR_OF_DAY, 0);
  date->set(UCalendarDateFields::UCAL_MINUTE, 0);
  date->set(UCalendarDateFields::UCAL_SECOND, 0);
  date->set(UCalendarDateFields::UCAL_MILLISECOND, 0);

  static const int64 kMillisInHour = 1000 * 60 * 60;
  if (parse_data.field_set_mask & DateParseData::Fields::ZONE_OFFSET_FIELD) {
    date->set(UCalendarDateFields::UCAL_ZONE_OFFSET,
              parse_data.zone_offset * kMillisInHour);
  }
  if (parse_data.field_set_mask & DateParseData::Fields::DST_OFFSET_FIELD) {
    // convert from hours to milliseconds
    date->set(UCalendarDateFields::UCAL_DST_OFFSET,
              parse_data.dst_offset * kMillisInHour);
  }

  if (parse_data.field_set_mask & DateParseData::Fields::RELATION_FIELD) {
    switch (parse_data.relation) {
      case DateParseData::Relation::NEXT:
        if (parse_data.field_set_mask &
            DateParseData::Fields::RELATION_TYPE_FIELD) {
          if (!DispatchToAdvancerOrToNextDayOfWeek(
                  date.get(), parse_data.relation_type, 1)) {
            return false;
          }
        }
        break;
      case DateParseData::Relation::NEXT_OR_SAME:
        if (parse_data.field_set_mask &
            DateParseData::Fields::RELATION_TYPE_FIELD) {
          if (!DispatchToAdvancerOrToNextOrSameDayOfWeek(
                  date.get(), parse_data.relation_type)) {
            return false;
          }
        }
        break;
      case DateParseData::Relation::LAST:
        if (parse_data.field_set_mask &
            DateParseData::Fields::RELATION_TYPE_FIELD) {
          if (!DispatchToRecedeOrToLastDayOfWeek(date.get(),
                                                 parse_data.relation_type, 1)) {
            return false;
          }
        }
        break;
      case DateParseData::Relation::NOW:
        // NOOP
        break;
      case DateParseData::Relation::TOMORROW:
        date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, 1, status);
        if (U_FAILURE(status)) {
          TC_LOG(ERROR) << "error adding a day";
          return false;
        }
        break;
      case DateParseData::Relation::YESTERDAY:
        date->add(UCalendarDateFields::UCAL_DAY_OF_MONTH, -1, status);
        if (U_FAILURE(status)) {
          TC_LOG(ERROR) << "error subtracting a day";
          return false;
        }
        break;
      case DateParseData::Relation::PAST:
        if (parse_data.field_set_mask &
            DateParseData::Fields::RELATION_TYPE_FIELD) {
          if (parse_data.field_set_mask &
              DateParseData::Fields::RELATION_DISTANCE_FIELD) {
            if (!DispatchToRecedeOrToLastDayOfWeek(
                    date.get(), parse_data.relation_type,
                    parse_data.relation_distance)) {
              return false;
            }
          }
        }
        break;
      case DateParseData::Relation::FUTURE:
        if (parse_data.field_set_mask &
            DateParseData::Fields::RELATION_TYPE_FIELD) {
          if (parse_data.field_set_mask &
              DateParseData::Fields::RELATION_DISTANCE_FIELD) {
            if (!DispatchToAdvancerOrToNextDayOfWeek(
                    date.get(), parse_data.relation_type,
                    parse_data.relation_distance)) {
              return false;
            }
          }
        }
        break;
    }
  }
  if (parse_data.field_set_mask & DateParseData::Fields::YEAR_FIELD) {
    date->set(UCalendarDateFields::UCAL_YEAR, parse_data.year);
  }
  if (parse_data.field_set_mask & DateParseData::Fields::MONTH_FIELD) {
    // NOTE: Java and ICU disagree on month formats
    date->set(UCalendarDateFields::UCAL_MONTH, parse_data.month - 1);
  }
  if (parse_data.field_set_mask & DateParseData::Fields::DAY_FIELD) {
    date->set(UCalendarDateFields::UCAL_DAY_OF_MONTH, 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 == 1 && parse_data.hour < 12) {
      date->set(UCalendarDateFields::UCAL_HOUR_OF_DAY, parse_data.hour + 12);
    } else {
      date->set(UCalendarDateFields::UCAL_HOUR_OF_DAY, parse_data.hour);
    }
  }
  if (parse_data.field_set_mask & DateParseData::Fields::MINUTE_FIELD) {
    date->set(UCalendarDateFields::UCAL_MINUTE, parse_data.minute);
  }
  if (parse_data.field_set_mask & DateParseData::Fields::SECOND_FIELD) {
    date->set(UCalendarDateFields::UCAL_SECOND, parse_data.second);
  }

  if (!RoundToGranularity(granularity, date.get())) {
    return false;
  }

  *interpreted_time_ms_utc = date->getTime(status);
  if (U_FAILURE(status)) {
    TC_LOG(ERROR) << "error getting time from instance";
    return false;
  }

  return true;
}
}  // namespace libtextclassifier2