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

// Copyright 2018 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 <memory>
#include <utility>

#include "base/i18n/rtl.h"
#include "base/memory/ptr_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/icu_test_util.h"
#include "base/test/simple_test_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 "testing/gtest/include/gtest/gtest.h"

namespace em = enterprise_management;

namespace policy {
namespace weekly_time_utils {

namespace {

enum {
  kMonday = 1,
  kTuesday = 2,
  kWednesday = 3,
  kThursday = 4,
  kFriday = 5,
  kSaturday = 6,
  kSunday = 7,
};

constexpr base::Time::Exploded kDaylightSavingsTime = {.year = 2018,
                                                       .month = 8,
                                                       .day_of_week = 3,
                                                       .day_of_month = 8,
                                                       .hour = 15};
constexpr base::Time::Exploded kNonDaylightSavingsTime{.year = 2018,
                                                       .month = 1,
                                                       .day_of_month = 28};

base::Time TimeFromString(const char* text) {
  base::Time time;
  const bool success = base::Time::FromString(text, &time);
  DCHECK(success);
  return time;
}

}  // namespace

class TimeUtilsTimezoneFunctionsTest : public testing::Test {
 protected:
  void SetUp() override {
    // Daylight savings are off by default
    SetDaylightSavings(false);
    timezone_.reset(icu::TimeZone::createDefault());
    icu::TimeZone::adoptDefault(
        icu::TimeZone::createTimeZone("America/New_York"));
  }

  void TearDown() override { icu::TimeZone::adoptDefault(timezone_.release()); }

  void SetDaylightSavings(bool is_daylight_savings) {
    if (is_daylight_savings) {
      // Friday July 13th
      base::Time test_time;
      ASSERT_TRUE(
          base::Time::FromUTCExploded(kDaylightSavingsTime, &test_time));
      test_clock_.SetNow(test_time);
    } else {
      // Sunday January 28
      base::Time test_time;
      ASSERT_TRUE(
          base::Time::FromUTCExploded(kNonDaylightSavingsTime, &test_time));
      test_clock_.SetNow(test_time);
    }
  }

  base::SimpleTestClock test_clock_;

 private:
  std::unique_ptr<icu::TimeZone> timezone_;
};

TEST_F(TimeUtilsTimezoneFunctionsTest, GetOffsetFromTimezoneToGmt) {
  // GMT + 7
  auto zone = base::WrapUnique(icu::TimeZone::createTimeZone(
      icu::UnicodeString::fromUTF8("Asia/Jakarta")));
  int result;
  GetOffsetFromTimezoneToGmt(*zone, &test_clock_, &result);
  // Negative since it's a conversion from |timezone| to GMT.
  EXPECT_EQ(result, base::Hours(-7).InMilliseconds());
  // Passing in the string should also work and yield the same result.
  int result2;
  GetOffsetFromTimezoneToGmt("Asia/Jakarta", &test_clock_, &result2);
  EXPECT_EQ(result2, result);
}

TEST_F(TimeUtilsTimezoneFunctionsTest, GetOffsetFromTimezoneToGmtDaylight) {
  SetDaylightSavings(true);
  // GMT -7.
  auto zone = base::WrapUnique(icu::TimeZone::createTimeZone(
      icu::UnicodeString::fromUTF8("America/Los_Angeles")));
  int result;
  GetOffsetFromTimezoneToGmt(*zone, &test_clock_, &result);
  EXPECT_EQ(result, base::Hours(7).InMilliseconds());
  // Passing in the string should also work and yield the same result.
  int result2;
  GetOffsetFromTimezoneToGmt("America/Los_Angeles", &test_clock_, &result2);
  EXPECT_EQ(result2, result);
}

TEST_F(TimeUtilsTimezoneFunctionsTest, GetOffsetFromTimezoneToGmtNoDaylight) {
  SetDaylightSavings(false);
  // GMT + 8.
  auto zone = base::WrapUnique(icu::TimeZone::createTimeZone(
      icu::UnicodeString::fromUTF8("America/Los_Angeles")));
  int result;
  GetOffsetFromTimezoneToGmt(*zone, &test_clock_, &result);
  EXPECT_EQ(result, base::Hours(8).InMilliseconds());
  // Passing in the string should also work and yield the same result.
  int result2;
  GetOffsetFromTimezoneToGmt("America/Los_Angeles", &test_clock_, &result2);
  EXPECT_EQ(result2, result);
}

TEST(TimeUtilsEmptyIntervalVector, NeverContainsTime) {
  EXPECT_FALSE(Contains(base::Time{}, {}));
  EXPECT_FALSE(Contains(base::Time::Now(), {}));
}

TEST(TimeUtilsEmptyIntervalVector, HasNoNextEvent) {
  EXPECT_EQ(GetNextEventTime(base::Time{}, {}), std::nullopt);
  EXPECT_EQ(GetNextEventTime(base::Time::Now(), {}), std::nullopt);
}

TEST(TimeUtilsNonEmptyIntervalVector, SometimesContainsTime) {
  const std::vector<WeeklyTimeInterval> intervals = {
      // First
      {WeeklyTime{kSunday, 0, 0},
       WeeklyTime{kSunday, base::Hours(2).InMilliseconds(), 0}},
      // Second (overlaps with first)
      {WeeklyTime{kSunday, base::Hours(1).InMilliseconds(), 0},
       WeeklyTime{kSunday, base::Hours(3).InMilliseconds(), 0}},
      // Third, no overlap
      {WeeklyTime{kMonday, 0, 0},
       WeeklyTime{kMonday, base::Hours(2).InMilliseconds() + 17, 0}},
  };

  // First interval, before begin.
  EXPECT_FALSE(
      Contains(TimeFromString("Sat, 17 Apr 2021 23:59:59 GMT"), intervals));
  // First interval, at begin.
  EXPECT_TRUE(
      Contains(TimeFromString("Sun, 18 Apr 2021 00:00:00 GMT"), intervals));
  // First interval, within.
  EXPECT_TRUE(
      Contains(TimeFromString("Sun, 18 Apr 2021 00:42:00 GMT"), intervals));
  // First interval, right before the end.
  EXPECT_TRUE(
      Contains(TimeFromString("Sun, 18 Apr 2021 01:59:59 GMT"), intervals));
  // First interval, at the end (but still in second interval).
  EXPECT_TRUE(
      Contains(TimeFromString("Sun, 18 Apr 2021 02:00:00 GMT"), intervals));

  // Second interval, right before the end.
  EXPECT_TRUE(
      Contains(TimeFromString("Sun, 18 Apr 2021 02:59:59 GMT"), intervals));
  // Second interval, at the end.
  EXPECT_FALSE(
      Contains(TimeFromString("Sun, 18 Apr 2021 03:00:00 GMT"), intervals));

  // Third interval, before begin.
  EXPECT_FALSE(
      Contains(TimeFromString("Sun, 18 Apr 2021 23:59:59 GMT"), intervals));
  // Third interval, begin.
  EXPECT_TRUE(
      Contains(TimeFromString("Mon, 19 Apr 2021 00:00:00 GMT"), intervals));
  // Third interval, within.
  EXPECT_TRUE(
      Contains(TimeFromString("Mon, 19 Apr 2021 00:42:00 GMT"), intervals));
  // Third interval, right before the end.
  EXPECT_TRUE(
      Contains(TimeFromString("Mon, 19 Apr 2021 02:00:00.016 GMT"), intervals));
  // Third interval, at the end.
  EXPECT_FALSE(
      Contains(TimeFromString("Mon, 19 Apr 2021 02:00:00.017 GMT"), intervals));

  // Far outside.
  EXPECT_FALSE(
      Contains(TimeFromString("Tue, 20 Apr 2021 22:05:00 GMT"), intervals));
}

TEST(TimeUtilsNonEmptyIntervalVector, AlwaysHasNextEvent) {
  const std::vector<WeeklyTimeInterval> intervals = {
      // First
      {WeeklyTime{kSunday, 0, 0},
       WeeklyTime{kSunday, base::Hours(2).InMilliseconds(), 0}},
      // Second (overlaps with first)
      {WeeklyTime{kSunday, base::Hours(1).InMilliseconds(), 0},
       WeeklyTime{kSunday, base::Hours(3).InMilliseconds(), 0}},
      // Third, no overlap
      {WeeklyTime{kMonday, 0, 0},
       WeeklyTime{kMonday, base::Hours(2).InMilliseconds() + 17, 0}},
  };

  // Just to make sure: Time::FromString actually parses microseconds
  ASSERT_EQ(TimeFromString("Tue, 20 Apr 2021 22:05:00.123456 GMT") -
                TimeFromString("Tue, 20 Apr 2021 22:05:00 GMT"),
            base::Microseconds(123456));

  // First interval, before begin.
  EXPECT_EQ(GetNextEventTime(TimeFromString("Sat, 17 Apr 2021 23:59:59 GMT"),
                             intervals),
            TimeFromString("Sun, 18 Apr 2021 00:00:00 GMT"));
  // First interval, at begin.
  EXPECT_EQ(GetNextEventTime(TimeFromString("Sun, 18 Apr 2021 00:00:00 GMT"),
                             intervals),
            TimeFromString("Sun, 18 Apr 2021 01:00:00 GMT"));
  EXPECT_EQ(GetNextEventTime(TimeFromString("Sun, 18 Apr 2021 00:42:00 GMT"),
                             intervals),
            TimeFromString("Sun, 18 Apr 2021 01:00:00 GMT"));
  // First interval, right before the end.
  EXPECT_EQ(GetNextEventTime(TimeFromString("Sun, 18 Apr 2021 01:59:59 GMT"),
                             intervals),
            TimeFromString("Sun, 18 Apr 2021 02:00:00 GMT"));
  // First interval, at the end (but still in second interval).
  EXPECT_EQ(GetNextEventTime(TimeFromString("Sun, 18 Apr 2021 02:00:00 GMT"),
                             intervals),
            TimeFromString("Sun, 18 Apr 2021 03:00:00 GMT"));

  // Second interval, right before the end.
  EXPECT_EQ(GetNextEventTime(TimeFromString("Sun, 18 Apr 2021 02:59:59 GMT"),
                             intervals),
            TimeFromString("Sun, 18 Apr 2021 03:00:00 GMT"));
  // Second interval, at the end.
  EXPECT_EQ(GetNextEventTime(TimeFromString("Sun, 18 Apr 2021 03:00:00 GMT"),
                             intervals),
            TimeFromString("Mon, 19 Apr 2021 00:00:00 GMT"));

  // Third interval, before begin.
  EXPECT_EQ(GetNextEventTime(TimeFromString("Sun, 18 Apr 2021 23:59:59 GMT"),
                             intervals),
            TimeFromString("Mon, 19 Apr 2021 00:00:00 GMT"));
  // Third interval, begin.
  EXPECT_EQ(GetNextEventTime(TimeFromString("Mon, 19 Apr 2021 00:00:00 GMT"),
                             intervals),
            TimeFromString("Mon, 19 Apr 2021 02:00:00.017 GMT"));
  // Third interval, within.
  EXPECT_EQ(GetNextEventTime(TimeFromString("Mon, 19 Apr 2021 00:42:00 GMT"),
                             intervals),
            TimeFromString("Mon, 19 Apr 2021 02:00:00.017 GMT"));
  // Third interval, right before the end.
  EXPECT_EQ(GetNextEventTime(
                TimeFromString("Mon, 19 Apr 2021 02:00:00.016 GMT"), intervals),
            TimeFromString("Mon, 19 Apr 2021 02:00:00.017 GMT"));
  // Third interval, at the end.
  EXPECT_EQ(GetNextEventTime(
                TimeFromString("Mon, 19 Apr 2021 02:00:00.017 GMT"), intervals),
            TimeFromString("Sun, 25 Apr 2021 00:00:00. GMT"));

  // Far outside, with non-zero microseconds.
  EXPECT_EQ(
      GetNextEventTime(TimeFromString("Tue, 20 Apr 2021 22:05:00.111111 GMT"),
                       intervals),
      TimeFromString("Sun, 25 Apr 2021 00:00:00 GMT"));

  // Far outside, with non-zero microseconds.
  EXPECT_EQ(
      GetNextEventTime(TimeFromString("Tue, 20 Apr 2021 22:05:00.888888 GMT"),
                       intervals),
      TimeFromString("Sun, 25 Apr 2021 00:00:00 GMT"));
}

}  // namespace weekly_time_utils
}  // namespace policy