// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ash/policy/scheduled_task_handler/test/scheduled_task_test_util.h"
#include <algorithm>
#include <memory>
#include <string>
#include <utility>
#include "base/check.h"
#include "base/check_op.h"
#include "base/json/json_reader.h"
#include "base/notreached.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h"
#include "base/values.h"
#include "chrome/browser/ash/policy/scheduled_task_handler/scheduled_task_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/icu/source/i18n/unicode/gregocal.h"
#include "third_party/icu/source/i18n/unicode/ucal.h"
namespace policy {
namespace {
void DecodeJsonStringAndNormalize(const std::string& json_string,
base::Value* value) {
auto parsed_json = base::JSONReader::ReadAndReturnValueWithError(
json_string, base::JSON_ALLOW_TRAILING_COMMAS);
ASSERT_TRUE(parsed_json.has_value()) << parsed_json.error().message;
*value = std::move(*parsed_json);
}
// Creates a JSON policy for daily device scheduled tasks.
std::string CreateDailyScheduledTaskPolicyJson(
const std::string& task_time_field_name,
int hour,
int minute) {
return base::StringPrintf(
"{\"%s\": {\"hour\": %d, \"minute\": %d}, \"frequency\": "
"\"DAILY\"}",
task_time_field_name.c_str(), hour, minute);
}
// Creates a JSON policy for weekly device scheduled tasks.
std::string CreateWeeklyScheduledTaskPolicyJson(
const std::string& task_time_field_name,
int hour,
int minute,
const std::string& day_of_week) {
return base::StringPrintf(
"{\"%s\": {\"hour\": %d, \"minute\": %d}, \"frequency\": "
"\"WEEKLY\", \"day_of_week\": \"%s\"}",
task_time_field_name.c_str(), hour, minute, day_of_week.c_str());
}
// Creates a JSON policy for monthly device scheduled tasks.
std::string CreateMonthlyScheduledTaskPolicyJson(
const std::string& task_time_field_name,
int hour,
int minute,
int day_of_month) {
return base::StringPrintf(
"{\"%s\": {\"hour\": %d, \"minute\": %d}, \"frequency\": "
"\"MONTHLY\", \"day_of_month\": %d}",
task_time_field_name.c_str(), hour, minute, day_of_month);
}
// Converts day of week from UCalendarDaysOfWeek to string.
std::string IcuDayOfWeekToStringDayOfWeek(UCalendarDaysOfWeek day_of_week) {
switch (day_of_week) {
case UCAL_SUNDAY:
return "SUNDAY";
case UCAL_MONDAY:
return "MONDAY";
case UCAL_TUESDAY:
return "TUESDAY";
case UCAL_WEDNESDAY:
return "WEDNESDAY";
case UCAL_THURSDAY:
return "THURSDAY";
case UCAL_FRIDAY:
return "FRIDAY";
case UCAL_SATURDAY:
break;
}
DCHECK_EQ(day_of_week, UCAL_SATURDAY);
return "SATURDAY";
}
// Sets |output|'s time of day to |input|'s. Assume's |input| is valid.
void SetTimeOfDay(const icu::Calendar& input, icu::Calendar* output) {
// Getting each of these properties should succeed if |input| is valid.
UErrorCode status = U_ZERO_ERROR;
int32_t hour = input.get(UCAL_HOUR_OF_DAY, status);
ASSERT_TRUE(U_SUCCESS(status));
int32_t minute = input.get(UCAL_MINUTE, status);
ASSERT_TRUE(U_SUCCESS(status));
int32_t seconds = input.get(UCAL_SECOND, status);
ASSERT_TRUE(U_SUCCESS(status));
int32_t ms = input.get(UCAL_MILLISECOND, status);
ASSERT_TRUE(U_SUCCESS(status));
output->set(UCAL_HOUR_OF_DAY, hour);
output->set(UCAL_MINUTE, minute);
output->set(UCAL_SECOND, seconds);
output->set(UCAL_MILLISECOND, ms);
}
} // namespace
namespace scheduled_task_test_util {
base::TimeDelta CalculateTimerExpirationDelayInDailyPolicyForTimeZone(
base::Time cur_time,
base::TimeDelta delay,
const icu::TimeZone& old_tz,
const icu::TimeZone& new_tz) {
DCHECK(!delay.is_zero());
auto cur_time_utc_cal = scheduled_task_util::ConvertUtcToTzIcuTime(
cur_time, *icu::TimeZone::getGMT());
auto old_tz_timer_expiration_cal =
scheduled_task_util::ConvertUtcToTzIcuTime(cur_time + delay, old_tz);
auto new_tz_timer_expiration_cal =
scheduled_task_util::ConvertUtcToTzIcuTime(cur_time, new_tz);
SetTimeOfDay(*old_tz_timer_expiration_cal, new_tz_timer_expiration_cal.get());
base::TimeDelta result = scheduled_task_util::GetDiff(
*new_tz_timer_expiration_cal, *cur_time_utc_cal);
// If the scheduled task time in the new time zone has already passed then it
// will happen on the next day.
if (result <= base::TimeDelta())
result += base::Days(1);
return result;
}
int GetDaysInMonthInEpochYear(UCalendarMonths month) {
switch (month) {
case UCAL_JANUARY:
case UCAL_MARCH:
case UCAL_MAY:
case UCAL_JULY:
case UCAL_AUGUST:
case UCAL_OCTOBER:
case UCAL_DECEMBER:
return 31;
case UCAL_FEBRUARY:
return 28;
case UCAL_APRIL:
case UCAL_JUNE:
case UCAL_SEPTEMBER:
case UCAL_NOVEMBER:
return 30;
case UCAL_UNDECIMBER:
break;
}
NOTREACHED_IN_MIGRATION();
return -1;
}
bool AdvanceTimeAndSetDayOfMonth(int day_of_month, icu::Calendar* time) {
DCHECK(time);
UErrorCode status = U_ZERO_ERROR;
time->add(UCAL_DAY_OF_MONTH, 1, status);
if (U_FAILURE(status)) {
ADD_FAILURE() << "Failed to advance month";
return false;
}
// Cap day of month to a valid day in the incremented month.
int cur_max_days_in_month = time->getActualMaximum(UCAL_DAY_OF_MONTH, status);
if (U_FAILURE(status)) {
ADD_FAILURE() << "Failed to get max days in month";
return false;
}
time->set(UCAL_DAY_OF_MONTH, std::min(day_of_month, cur_max_days_in_month));
return true;
}
std::pair<base::Value, std::unique_ptr<icu::Calendar>> CreatePolicy(
const icu::TimeZone& time_zone,
base::Time current_time,
base::TimeDelta delay,
ScheduledTaskExecutor::Frequency frequency,
const std::string& task_time_field_name) {
// Calculate time from one hour from now and set the policy to
// happen daily at that time.
base::Time scheduled_task_time = current_time + delay;
auto scheduled_task_icu_time = scheduled_task_util::ConvertUtcToTzIcuTime(
scheduled_task_time, time_zone);
// Extracting fields from valid ICU time should always succeed.
UErrorCode status = U_ZERO_ERROR;
int32_t hour = scheduled_task_icu_time->get(UCAL_HOUR_OF_DAY, status);
DCHECK(U_SUCCESS(status));
int32_t minute = scheduled_task_icu_time->get(UCAL_MINUTE, status);
DCHECK(U_SUCCESS(status));
int32_t day_of_week = scheduled_task_icu_time->get(UCAL_DAY_OF_WEEK, status);
DCHECK(U_SUCCESS(status));
int32_t day_of_month =
scheduled_task_icu_time->get(UCAL_DAY_OF_MONTH, status);
DCHECK(U_SUCCESS(status));
base::Value scheduled_task_value;
switch (frequency) {
case ScheduledTaskExecutor::Frequency::kDaily: {
DecodeJsonStringAndNormalize(CreateDailyScheduledTaskPolicyJson(
task_time_field_name, hour, minute),
&scheduled_task_value);
break;
}
case ScheduledTaskExecutor::Frequency::kWeekly: {
DecodeJsonStringAndNormalize(
CreateWeeklyScheduledTaskPolicyJson(
task_time_field_name, hour, minute,
IcuDayOfWeekToStringDayOfWeek(
static_cast<UCalendarDaysOfWeek>(day_of_week))),
&scheduled_task_value);
break;
}
case ScheduledTaskExecutor::Frequency::kMonthly: {
DecodeJsonStringAndNormalize(
CreateMonthlyScheduledTaskPolicyJson(task_time_field_name, hour,
minute, day_of_month),
&scheduled_task_value);
break;
}
}
return std::make_pair(std::move(scheduled_task_value),
std::move(scheduled_task_icu_time));
}
base::Time IcuToBaseTime(const icu::Calendar& time) {
UErrorCode status = U_ZERO_ERROR;
UDate seconds_from_epoch = time.getTime(status) / 1000;
DCHECK(U_SUCCESS(status));
base::Time result = base::Time::FromTimeT(seconds_from_epoch);
if (result.is_null())
result = base::Time::UnixEpoch();
return result;
}
} // namespace scheduled_task_test_util
} // namespace policy