chromium/chromeos/ash/components/policy/weekly_time/time_utils.cc

// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chromeos/ash/components/policy/weekly_time/time_utils.h"

#include <algorithm>
#include <memory>

#include "base/i18n/time_formatting.h"
#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "base/time/clock.h"
#include "base/time/time.h"
#include "chromeos/ash/components/policy/weekly_time/weekly_time.h"
#include "chromeos/ash/components/policy/weekly_time/weekly_time_interval.h"
#include "third_party/icu/source/common/unicode/unistr.h"
#include "third_party/icu/source/common/unicode/utypes.h"
#include "third_party/icu/source/i18n/unicode/gregocal.h"

namespace policy {
namespace weekly_time_utils {

bool GetOffsetFromTimezoneToGmt(const std::string& timezone,
                                base::Clock* clock,
                                int* offset) {
  auto zone = base::WrapUnique(
      icu::TimeZone::createTimeZone(icu::UnicodeString::fromUTF8(timezone)));
  if (*zone == icu::TimeZone::getUnknown()) {
    LOG(ERROR) << "Unsupported timezone: " << timezone;
    return false;
  }

  return GetOffsetFromTimezoneToGmt(*zone, clock, offset);
}

bool GetOffsetFromTimezoneToGmt(const icu::TimeZone& timezone,
                                base::Clock* clock,
                                int* offset) {
  // Time in milliseconds which is added to GMT to get local time.
  int gmt_offset = timezone.getRawOffset();
  // Time in milliseconds which is added to local standard time to get local
  // wall clock time.
  int dst_offset = timezone.getDSTSavings();
  UErrorCode status = U_ZERO_ERROR;
  std::unique_ptr<icu::GregorianCalendar> gregorian_calendar =
      std::make_unique<icu::GregorianCalendar>(timezone, status);
  if (U_FAILURE(status)) {
    LOG(ERROR) << "Gregorian calendar error = " << u_errorName(status);
    return false;
  }
  UDate cur_date = static_cast<UDate>(clock->Now().InSecondsFSinceUnixEpoch() *
                                      base::Time::kMillisecondsPerSecond);
  status = U_ZERO_ERROR;
  gregorian_calendar->setTime(cur_date, status);
  if (U_FAILURE(status)) {
    LOG(ERROR) << "Gregorian calendar set time error = " << u_errorName(status);
    return false;
  }
  status = U_ZERO_ERROR;
  UBool day_light = gregorian_calendar->inDaylightTime(status);
  if (U_FAILURE(status)) {
    LOG(ERROR) << "Daylight time error = " << u_errorName(status);
    return false;
  }
  if (day_light)
    gmt_offset += dst_offset;
  // -|gmt_offset| is time which is added to local time to get GMT time.
  *offset = -gmt_offset;
  return true;
}

std::vector<WeeklyTimeInterval> ConvertIntervalsToGmt(
    const std::vector<WeeklyTimeInterval>& intervals) {
  std::vector<WeeklyTimeInterval> gmt_intervals;
  for (const auto& interval : intervals) {
    auto gmt_start = interval.start().ConvertToTimezone(0);
    auto gmt_end = interval.end().ConvertToTimezone(0);
    gmt_intervals.push_back(WeeklyTimeInterval(gmt_start, gmt_end));
  }
  return gmt_intervals;
}

bool Contains(const base::Time& time,
              const std::vector<WeeklyTimeInterval>& intervals) {
  WeeklyTime weekly_time = WeeklyTime::GetGmtWeeklyTime(time);
  for (const auto& interval : intervals) {
    DCHECK(interval.start().timezone_offset().has_value());
    if (interval.Contains(weekly_time))
      return true;
  }
  return false;
}

std::optional<base::Time> GetNextEventTime(
    const base::Time& current_time,
    const std::vector<WeeklyTimeInterval>& weekly_time_intervals) {
  if (weekly_time_intervals.empty())
    return std::nullopt;

  base::Time::Exploded exploded;
  current_time.UTCExplode(&exploded);
  const auto weekly_time = GetWeeklyTimeFromExploded(exploded, 0);

  // Weekly intervals repeat every week, therefore the maximum duration till
  // next weekly interval is one week.
  base::TimeDelta till_next_event = base::Days(7);
  for (const auto& interval : weekly_time_intervals) {
    if (weekly_time != interval.start())
      till_next_event = std::min(till_next_event,
                                 weekly_time.GetDurationTo(interval.start()));
    if (weekly_time != interval.end())
      till_next_event =
          std::min(till_next_event, weekly_time.GetDurationTo(interval.end()));
  }

  // base::Time has microseconds precision.
  // base::Time::Exploded and WeeklyTime have milliseconds precision.
  // By constructing |rounded_time| from |exploded|, we are adjusting the
  // precision to return the exact time.
  base::Time rounded_time;
  if (base::Time::FromUTCExploded(exploded, &rounded_time)) {
    return rounded_time + till_next_event;
  }

  // This is possible if FromUTCExploded fails during daylight saving time
  // switches, see base::Time::Midnight implementation.
  return current_time + till_next_event;
}

}  // namespace weekly_time_utils
}  // namespace policy