chromium/chromeos/ash/components/policy/weekly_time/weekly_time.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/weekly_time.h"

#include "base/logging.h"
#include "base/ranges/algorithm.h"
#include "base/time/time.h"

namespace em = enterprise_management;

namespace policy {

namespace {

constexpr int64_t kMillisecondsPerWeek = base::Days(7).InMilliseconds();

}  // namespace

// static
const char WeeklyTime::kDayOfWeek[] = "day_of_week";
const char WeeklyTime::kTime[] = "time";
const char WeeklyTime::kTimezoneOffset[] = "timezone_offset";

const std::vector<std::string> WeeklyTime::kWeekDays = {
    "UNSPECIFIED", "MONDAY", "TUESDAY",  "WEDNESDAY",
    "THURSDAY",    "FRIDAY", "SATURDAY", "SUNDAY"};

WeeklyTime::WeeklyTime(int day_of_week,
                       int milliseconds,
                       std::optional<int> timezone_offset)
    : day_of_week_(day_of_week),
      milliseconds_(milliseconds),
      timezone_offset_(timezone_offset) {
  DCHECK_GT(day_of_week, 0);
  DCHECK_LE(day_of_week, 7);
  DCHECK_GE(milliseconds, 0);
  DCHECK_LT(milliseconds, base::Time::kMillisecondsPerDay);
}

WeeklyTime::WeeklyTime(const WeeklyTime& rhs) = default;

WeeklyTime& WeeklyTime::operator=(const WeeklyTime& rhs) = default;

base::Value WeeklyTime::ToValue() const {
  base::Value weekly_time(base::Value::Type::DICT);
  weekly_time.GetDict().Set(kDayOfWeek, day_of_week_);
  weekly_time.GetDict().Set(kTime, milliseconds_);
  if (timezone_offset_)
    weekly_time.GetDict().Set(kTimezoneOffset, timezone_offset_.value());
  return weekly_time;
}

base::TimeDelta WeeklyTime::GetDurationTo(const WeeklyTime& other) const {
  // Can't compare timezone agnostic intervals and non-timezone agnostic
  // intervals.
  DCHECK_EQ(timezone_offset_.has_value(), other.timezone_offset().has_value());
  WeeklyTime other_converted =
      timezone_offset_ ? other.ConvertToTimezone(timezone_offset_.value())
                       : other;
  int duration = base::Days(other_converted.day_of_week() - day_of_week_)
                     .InMilliseconds() +
                 other_converted.milliseconds() - milliseconds_;
  if (duration < 0) {
    duration += kMillisecondsPerWeek;
  }
  return base::Milliseconds(duration);
}

WeeklyTime WeeklyTime::AddMilliseconds(int milliseconds) const {
  milliseconds %= kMillisecondsPerWeek;
  // Make |milliseconds| positive number (add number of milliseconds per week)
  // for easier evaluation.
  milliseconds += kMillisecondsPerWeek;
  int shifted_milliseconds = milliseconds_ + milliseconds;
  // Get milliseconds from the start of the day.
  int result_milliseconds =
      shifted_milliseconds % base::Time::kMillisecondsPerDay;
  int day_offset = shifted_milliseconds / base::Time::kMillisecondsPerDay;
  // Convert day of week considering week is cyclic. +/- 1 is
  // because day of week is from 1 to 7.
  int result_day_of_week = (day_of_week_ + day_offset - 1) % 7 + 1;
  // AddMilliseconds should preserve the timezone.
  return WeeklyTime(result_day_of_week, result_milliseconds, timezone_offset_);
}

WeeklyTime WeeklyTime::ConvertToTimezone(int timezone_offset) const {
  DCHECK(timezone_offset_);
  return WeeklyTime(day_of_week_, milliseconds_, timezone_offset)
      .AddMilliseconds(timezone_offset - timezone_offset_.value());
}

// static
WeeklyTime WeeklyTime::GetGmtWeeklyTime(base::Time time) {
  base::Time::Exploded exploded;
  time.UTCExplode(&exploded);
  return GetWeeklyTimeFromExploded(exploded, 0);
}

// static
WeeklyTime WeeklyTime::GetLocalWeeklyTime(base::Time time) {
  base::Time::Exploded exploded;
  time.LocalExplode(&exploded);
  WeeklyTime result = GetWeeklyTimeFromExploded(exploded, std::nullopt);
  return result;
}

// static
std::unique_ptr<WeeklyTime> WeeklyTime::ExtractFromProto(
    const em::WeeklyTimeProto& container,
    std::optional<int> timezone_offset) {
  if (!container.has_day_of_week() ||
      container.day_of_week() == em::WeeklyTimeProto::DAY_OF_WEEK_UNSPECIFIED) {
    LOG(ERROR) << "Day of week is absent or unspecified.";
    return nullptr;
  }
  if (!container.has_time()) {
    LOG(ERROR) << "Time is absent.";
    return nullptr;
  }
  int time_of_day = container.time();
  if (!(time_of_day >= 0 && time_of_day < base::Time::kMillisecondsPerDay)) {
    LOG(ERROR) << "Invalid time value: " << time_of_day
               << ", the value should be in [0; "
               << base::Time::kMillisecondsPerDay << ").";
    return nullptr;
  }
  return std::make_unique<WeeklyTime>(container.day_of_week(), time_of_day,
                                      timezone_offset);
}

// static
std::unique_ptr<WeeklyTime> WeeklyTime::ExtractFromDict(
    const base::Value::Dict& dict,
    std::optional<int> timezone_offset) {
  auto* day_of_week = dict.FindString(kDayOfWeek);
  if (!day_of_week) {
    LOG(ERROR) << "day_of_week is absent.";
    return nullptr;
  }
  int day_of_week_value =
      base::ranges::find(kWeekDays, *day_of_week) - kWeekDays.begin();
  if (day_of_week_value <= 0 || day_of_week_value > 7) {
    LOG(ERROR) << "Invalid day_of_week: " << day_of_week;
    return nullptr;
  }
  auto time_of_day = dict.FindInt(kTime);
  if (!time_of_day.has_value()) {
    LOG(ERROR) << "Time is absent";
    return nullptr;
  }

  if (!(time_of_day.value() >= 0 &&
        time_of_day.value() < base::Time::kMillisecondsPerDay)) {
    LOG(ERROR) << "Invalid time value: " << time_of_day.value()
               << ", the value should be in [0; "
               << base::Time::kMillisecondsPerDay << ").";
    return nullptr;
  }
  return std::make_unique<WeeklyTime>(day_of_week_value, time_of_day.value(),
                                      timezone_offset);
}

WeeklyTime GetWeeklyTimeFromExploded(const base::Time::Exploded& exploded,
                                     const std::optional<int> timezone_offset) {
  int day_of_week = exploded.day_of_week == 0 ? 7 : exploded.day_of_week;
  int milliseconds = static_cast<int>(
      base::Hours(exploded.hour).InMilliseconds() +
      base::Minutes(exploded.minute).InMilliseconds() +
      base::Seconds(exploded.second).InMilliseconds() + exploded.millisecond);
  return WeeklyTime(day_of_week, milliseconds, timezone_offset);
}

}  // namespace policy