chromium/ash/system/time/calendar_utils_unittest.cc

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

#include "ash/system/time/calendar_utils.h"

#include "ash/system/time/calendar_unittest_utils.h"
#include "ash/system/time/date_helper.h"
#include "ash/test/ash_test_base.h"
#include "base/i18n/rtl.h"
#include "base/ranges/algorithm.h"
#include "base/time/time.h"
#include "chromeos/ash/components/settings/scoped_timezone_settings.h"
#include "third_party/abseil-cpp/absl/strings/ascii.h"

namespace ash {

namespace {

void SetDefaultLocale(const std::string& lang) {
  base::i18n::SetICUDefaultLocale(lang);
  ash::DateHelper::GetInstance()->ResetForTesting();
}

std::unique_ptr<google_apis::calendar::CalendarEvent> CreateEvent(
    const char* start_time,
    const char* end_time,
    bool all_day_event = false) {
  return calendar_test_utils::CreateEvent(
      "id_7", "summary_7", start_time, end_time,
      google_apis::calendar::CalendarEvent::EventStatus::kConfirmed,
      google_apis::calendar::CalendarEvent::ResponseStatus::kAccepted,
      all_day_event);
}

}  // namespace

using CalendarUtilsUnitTest = AshTestBase;

// Tests the time difference calculation with different timezones and
// considering daylight savings.
TEST_F(CalendarUtilsUnitTest, GetTimeDifference) {
  // Create a date: Aug,1st 2021.
  base::Time date;
  ASSERT_TRUE(base::Time::FromString("1 Aug 2021 10:00 GMT", &date));

  // Sets the timezone to "America/Los_Angeles";
  ash::system::ScopedTimezoneSettings timezone_settings(u"PST");

  // Before daylight saving the time difference is 7 hours.
  EXPECT_EQ(base::Minutes(-420), calendar_utils::GetTimeDifference(date));

  // Create a date after daylight saving: Dec,1st 2021.
  base::Time date2;
  ASSERT_TRUE(base::Time::FromString("1 Dec 2021 10:00 GMT", &date2));

  // After daylight saving the time difference is 8 hours.
  EXPECT_EQ(base::Minutes(-480), calendar_utils::GetTimeDifference(date2));

  // Set the timezone to GMT.
  timezone_settings.SetTimezoneFromID(u"GMT");

  EXPECT_EQ(base::Minutes(0), calendar_utils::GetTimeDifference(date));
  EXPECT_EQ(base::Minutes(0), calendar_utils::GetTimeDifference(date2));
}

TEST_F(CalendarUtilsUnitTest, DateFormatter) {
  // Create a date: Aug 1, 2021.
  base::Time date;
  ASSERT_TRUE(base::Time::FromString("1 Aug 2021 10:00 GMT", &date));
  ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");

  // Test DateFormatter to return date in "MMMMdyyyy" format.
  EXPECT_EQ(u"August 1, 2021", calendar_utils::GetMonthDayYear(date));

  // Test DateFormatter to return month name.
  EXPECT_EQ(u"August", calendar_utils::GetMonthName(date));

  // Test DateFormatter to return day of month.
  EXPECT_EQ(u"1", calendar_utils::GetDayOfMonth(date));

  // Test DateFormatter to return month name and day of month.
  EXPECT_EQ(u"August 1", calendar_utils::GetMonthNameAndDayOfMonth(date));

  // Test DateFormatter to return the time zone.
  EXPECT_EQ(u"Greenwich Mean Time", calendar_utils::GetTimeZone(date));

  // Test DateFormatter to return year.
  EXPECT_EQ(u"2021", calendar_utils::GetYear(date));

  // Test DateFormatter to return month name and year.
  EXPECT_EQ(u"August 2021", calendar_utils::GetMonthNameAndYear(date));
}

TEST_F(CalendarUtilsUnitTest, DateFormatterClockTimes) {
  ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");

  // Using "en" locale as other languages format their hours differently.
  SetDefaultLocale("en");

  // Create AM time: 9:05 GMT.
  base::Time am_time;
  ASSERT_TRUE(base::Time::FromString("1 Aug 2021 9:05 GMT", &am_time));

  // Create PM time: 23:30 GMT.
  base::Time pm_time;
  ASSERT_TRUE(base::Time::FromString("1 Aug 2021 23:30 GMT", &pm_time));

  // Create midnight: 00:00 GMT.
  base::Time midnight;
  ASSERT_TRUE(base::Time::FromString("1 Aug 2021 00:00 GMT", &midnight));

  // Return time in twelve hour clock format. (no '0' padding)
  EXPECT_EQ(u"9:05\u202fAM", calendar_utils::GetTwelveHourClockTime(am_time));
  EXPECT_EQ(u"11:30\u202fPM", calendar_utils::GetTwelveHourClockTime(pm_time));
  EXPECT_EQ(u"12:00\u202fAM", calendar_utils::GetTwelveHourClockTime(midnight));

  // Return time in twenty four hour clock format. (has '0' padding)
  EXPECT_EQ(u"09:05", calendar_utils::GetTwentyFourHourClockTime(am_time));
  EXPECT_EQ(u"23:30", calendar_utils::GetTwentyFourHourClockTime(pm_time));
  EXPECT_EQ(u"00:00", calendar_utils::GetTwentyFourHourClockTime(midnight));

  // Return single hours in twelve hour format. (no '0' padding)
  EXPECT_EQ(u"9", calendar_utils::GetTwelveHourClockHours(am_time));
  EXPECT_EQ(u"11", calendar_utils::GetTwelveHourClockHours(pm_time));
  EXPECT_EQ(u"12", calendar_utils::GetTwelveHourClockHours(midnight));

  // Return single hours in twenty four hour format. (has '0' padding)
  EXPECT_EQ(u"09", calendar_utils::GetTwentyFourHourClockHours(am_time));
  EXPECT_EQ(u"23", calendar_utils::GetTwentyFourHourClockHours(pm_time));
  EXPECT_EQ(u"00", calendar_utils::GetTwentyFourHourClockHours(midnight));

  // Return minutes. (has '0' padding)
  EXPECT_EQ(u"05", calendar_utils::GetMinutes(am_time));
  EXPECT_EQ(u"30", calendar_utils::GetMinutes(pm_time));
  EXPECT_EQ(u"00", calendar_utils::GetMinutes(midnight));
}

TEST_F(CalendarUtilsUnitTest, HoursAndMinutesInDifferentLocales) {
  ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");

  // Create AM time: 9:05 GMT.
  base::Time am_time;
  ASSERT_TRUE(base::Time::FromString("1 Aug 2021 9:05 GMT", &am_time));

  // Create PM time: 23:30 GMT.
  base::Time pm_time;
  ASSERT_TRUE(base::Time::FromString("1 Aug 2021 23:30 GMT", &pm_time));

  // Create midnight: 00:00 GMT.
  base::Time midnight;
  ASSERT_TRUE(base::Time::FromString("1 Aug 2021 00:00 GMT", &midnight));

  for (auto* locale : kLocales) {
    // Skip locales that are tested in "LocalesWithUniqueNumerals".
    if (kLocalesWithUniqueNumerals.count(locale)) {
      continue;
    }

    SetDefaultLocale(locale);

    // If the length of the hour string is more than 1 in a single digit hour
    // (9AM) then it is zero-padded.
    bool zero_padded_12H =
        calendar_utils::GetTwelveHourClockHours(am_time).length() > 1;
    bool zero_padded_24H =
        calendar_utils::GetTwentyFourHourClockHours(am_time).length() > 1;

    // Return hours in twelve hour format.
    EXPECT_EQ(zero_padded_12H ? u"09" : u"9",
              calendar_utils::GetTwelveHourClockHours(am_time));
    EXPECT_EQ(u"11", calendar_utils::GetTwelveHourClockHours(pm_time));
    // Locale 'ja'uses  'K' format (0~11) for its 12-hour clock.
    EXPECT_EQ((strcmp(locale, "ja") == 0) ? u"0" : u"12",
              calendar_utils::GetTwelveHourClockHours(midnight));

    // Return hours in twenty four hour format.
    EXPECT_EQ(zero_padded_24H ? u"09" : u"9",
              calendar_utils::GetTwentyFourHourClockHours(am_time));
    EXPECT_EQ(u"23", calendar_utils::GetTwentyFourHourClockHours(pm_time));
    EXPECT_EQ(zero_padded_24H ? u"00" : u"0",
              calendar_utils::GetTwentyFourHourClockHours(midnight));

    // Return minutes. (always zero-padded)
    EXPECT_EQ(u"05", calendar_utils::GetMinutes(am_time));
    EXPECT_EQ(u"30", calendar_utils::GetMinutes(pm_time));
    EXPECT_EQ(u"00", calendar_utils::GetMinutes(midnight));
  }

  // Reset locale to English for subsequent tests.
  SetDefaultLocale("en");
}

TEST_F(CalendarUtilsUnitTest, LocalesWithUniqueNumerals) {
  ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");

  // Create time: 23:03 GMT.
  base::Time time;
  ASSERT_TRUE(base::Time::FromString("1 Aug 2021 23:03 GMT", &time));

  for (auto locale : kLocalesWithUniqueNumerals) {
    SetDefaultLocale(locale);

    if (locale == "bn") {
      EXPECT_EQ(u"২৩", calendar_utils::GetTwentyFourHourClockHours(time));
      EXPECT_EQ(u"০৩", calendar_utils::GetMinutes(time));
    } else if (locale == "fa" || locale == "pa-pk") {
      EXPECT_EQ(u"۲۳", calendar_utils::GetTwentyFourHourClockHours(time));
      EXPECT_EQ(u"۰۳", calendar_utils::GetMinutes(time));
    } else if (locale == "mr") {
      EXPECT_EQ(u"२३", calendar_utils::GetTwentyFourHourClockHours(time));
      EXPECT_EQ(u"०३", calendar_utils::GetMinutes(time));
    } else {
      EXPECT_TRUE(false) << "Locale '" << locale << "' needs a test case.";
    }
  }

  // Reset locale to English for subsequent tests.
  SetDefaultLocale("en");
}

TEST_F(CalendarUtilsUnitTest, IntervalFormatter) {
  base::Time date1;
  base::Time date2;
  base::Time date3;
  ASSERT_TRUE(base::Time::FromString("1 Aug 2021 10:00 GMT", &date1));
  ASSERT_TRUE(base::Time::FromString("1 Aug 2021 11:30 GMT", &date2));
  ASSERT_TRUE(base::Time::FromString("1 Aug 2021 15:49 GMT", &date3));

  ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");

  EXPECT_EQ(u"10:00\u2009–\u200911:30\u202fAM",
            calendar_utils::FormatTwelveHourClockTimeInterval(date1, date2));

  EXPECT_EQ(u"10:00\u202fAM\u2009–\u20093:49\u202fPM",
            calendar_utils::FormatTwelveHourClockTimeInterval(date1, date3));

  EXPECT_EQ(
      u"10:00\u2009–\u200911:30",
      calendar_utils::FormatTwentyFourHourClockTimeInterval(date1, date2));

  EXPECT_EQ(
      u"10:00\u2009–\u200915:49",
      calendar_utils::FormatTwentyFourHourClockTimeInterval(date1, date3));
}

TEST_F(CalendarUtilsUnitTest, TimezoneChanged) {
  // Create a date: Aug,1st 2021.
  base::Time date;
  ASSERT_TRUE(base::Time::FromString("1 Aug 2021 3:00 GMT", &date));
  ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");

  // Test DateFormatter to return the time zone.
  EXPECT_EQ(u"Greenwich Mean Time", calendar_utils::GetTimeZone(date));

  // Test DateFormatter to return date in "MMMMdyyyy" format.
  EXPECT_EQ(u"August 1, 2021", calendar_utils::GetMonthDayYear(date));

  // Set timezone to Pacific Daylight Time (date changes to previous day).
  timezone_settings.SetTimezoneFromID(u"PST");

  // Test DateFormatter to return the time zone.
  EXPECT_EQ(u"Pacific Daylight Time", calendar_utils::GetTimeZone(date));

  // Test DateFormatter to return date in "MMMMdyyyy" format.
  EXPECT_EQ(u"July 31, 2021", calendar_utils::GetMonthDayYear(date));
}

TEST_F(CalendarUtilsUnitTest, GetMonthsBetween) {
  base::Time start_date, end_date;

  ASSERT_TRUE(base::Time::FromString("23 Oct 2009 11:00 GMT", &start_date));
  ASSERT_TRUE(base::Time::FromString("23 Oct 2009 11:00 GMT", &end_date));
  EXPECT_EQ(calendar_utils::GetMonthsBetween(start_date, end_date), 0);

  ASSERT_TRUE(base::Time::FromString("23 Oct 2009 11:00 GMT", &start_date));
  ASSERT_TRUE(base::Time::FromString("23 Nov 2009 11:00 GMT", &end_date));
  EXPECT_EQ(calendar_utils::GetMonthsBetween(start_date, end_date), 1);

  ASSERT_TRUE(base::Time::FromString("23 Oct 2009 11:00 GMT", &start_date));
  ASSERT_TRUE(base::Time::FromString("23 Sep 2009 11:00 GMT", &end_date));
  EXPECT_EQ(calendar_utils::GetMonthsBetween(start_date, end_date), -1);

  ASSERT_TRUE(base::Time::FromString("23 Oct 2009 11:00 GMT", &start_date));
  ASSERT_TRUE(base::Time::FromString("23 Oct 2010 11:00 GMT", &end_date));
  EXPECT_EQ(calendar_utils::GetMonthsBetween(start_date, end_date), 12);

  ASSERT_TRUE(base::Time::FromString("23 Oct 2009 11:00 GMT", &start_date));
  ASSERT_TRUE(base::Time::FromString("23 Oct 2008 11:00 GMT", &end_date));
  EXPECT_EQ(calendar_utils::GetMonthsBetween(start_date, end_date), -12);

  ASSERT_TRUE(base::Time::FromString("23 Oct 2009 11:00 GMT", &start_date));
  ASSERT_TRUE(base::Time::FromString("23 Apr 2010 11:00 GMT", &end_date));
  EXPECT_EQ(calendar_utils::GetMonthsBetween(start_date, end_date), 6);

  ASSERT_TRUE(base::Time::FromString("23 Oct 2009 11:00 GMT", &start_date));
  ASSERT_TRUE(base::Time::FromString("23 Apr 2009 11:00 GMT", &end_date));
  EXPECT_EQ(calendar_utils::GetMonthsBetween(start_date, end_date), -6);

  ASSERT_TRUE(base::Time::FromString("01 Oct 2009 11:00 GMT", &start_date));
  ASSERT_TRUE(base::Time::FromString("02 Oct 2009 11:00 GMT", &end_date));
  EXPECT_EQ(calendar_utils::GetMonthsBetween(start_date, end_date), 0);

  ASSERT_TRUE(base::Time::FromString("01 Oct 2009 11:00 GMT", &start_date));
  ASSERT_TRUE(base::Time::FromString("31 Oct 2009 11:00 GMT", &end_date));
  EXPECT_EQ(calendar_utils::GetMonthsBetween(start_date, end_date), 0);

  ASSERT_TRUE(base::Time::FromString("31 Oct 2009 11:00 GMT", &start_date));
  ASSERT_TRUE(base::Time::FromString("01 Nov 2009 11:00 GMT", &end_date));
  EXPECT_EQ(calendar_utils::GetMonthsBetween(start_date, end_date), 1);

  ASSERT_TRUE(base::Time::FromString("01 Oct 2009 11:00 GMT", &start_date));
  ASSERT_TRUE(base::Time::FromString("30 Sep 2009 11:00 GMT", &end_date));
  EXPECT_EQ(calendar_utils::GetMonthsBetween(start_date, end_date), -1);

  ASSERT_TRUE(base::Time::FromString("23 Oct 2009 11:00 GMT", &start_date));
  ASSERT_TRUE(base::Time::FromString("23 Oct 2022 11:00 GMT", &end_date));
  EXPECT_EQ(calendar_utils::GetMonthsBetween(start_date, end_date), 13 * 12);

  ASSERT_TRUE(base::Time::FromString("23 Oct 2009 11:00 GMT", &start_date));
  ASSERT_TRUE(base::Time::FromString("23 Oct 1996 11:00 GMT", &end_date));
  EXPECT_EQ(calendar_utils::GetMonthsBetween(start_date, end_date), -13 * 12);

  ASSERT_TRUE(base::Time::FromString("23 Oct 2009 11:00 GMT", &start_date));
  ASSERT_TRUE(base::Time::FromString("23 Dec 2022 11:00 GMT", &end_date));
  EXPECT_EQ(calendar_utils::GetMonthsBetween(start_date, end_date),
            (13 * 12) + 2);
  ASSERT_TRUE(base::Time::FromString("23 Oct 2009 11:00 GMT", &start_date));
  ASSERT_TRUE(base::Time::FromString("23 Dec 1996 11:00 GMT", &end_date));
  EXPECT_EQ(calendar_utils::GetMonthsBetween(start_date, end_date),
            (-13 * 12) + 2);
  ASSERT_TRUE(base::Time::FromString("31 Dec 2009 11:00 GMT", &start_date));
  ASSERT_TRUE(base::Time::FromString("01 Jan 2010 11:00 GMT", &end_date));
  EXPECT_EQ(calendar_utils::GetMonthsBetween(start_date, end_date), 1);
  ASSERT_TRUE(base::Time::FromString("01 Jan 2010 11:00 GMT", &start_date));
  ASSERT_TRUE(base::Time::FromString("31 Dec 2009 11:00 GMT", &end_date));
  EXPECT_EQ(calendar_utils::GetMonthsBetween(start_date, end_date), -1);
}

TEST_F(CalendarUtilsUnitTest, GetFetchStartEndTimes) {
  base::Time date, expected_start, expected_end;
  std::pair<base::Time, base::Time> fetch;

  // Event and timezone are both GMT, no difference.
  ash::system::ScopedTimezoneSettings timezone_settings(u"GMT");
  ASSERT_TRUE(base::Time::FromString("01 Apr 2022 00:00 GMT", &date));
  ASSERT_TRUE(base::Time::FromString("01 Apr 2022 00:00 GMT", &expected_start));
  ASSERT_TRUE(base::Time::FromString("01 May 2022 00:00 GMT", &expected_end));
  fetch = calendar_utils::GetFetchStartEndTimes(date);
  EXPECT_EQ(fetch.first, expected_start);
  EXPECT_EQ(fetch.second, expected_end);

  // Timezone "America/Los_Angeles" is GMT - 7h.
  timezone_settings.SetTimezoneFromID(u"America/Los_Angeles");
  ASSERT_TRUE(base::Time::FromString("01 Apr 2022 00:00 GMT", &date));
  ASSERT_TRUE(base::Time::FromString("01 Apr 2022 07:00 GMT", &expected_start));
  ASSERT_TRUE(base::Time::FromString("01 May 2022 07:00 GMT", &expected_end));
  fetch = calendar_utils::GetFetchStartEndTimes(date);
  EXPECT_EQ(fetch.first, expected_start);
  EXPECT_EQ(fetch.second, expected_end);

  // The month is with DST ended date. The time difference is changed from -7h
  // to -8h.
  ASSERT_TRUE(base::Time::FromString("01 Nov 2022 00:00 GMT", &date));
  ASSERT_TRUE(base::Time::FromString("01 Nov 2022 07:00 GMT", &expected_start));
  ASSERT_TRUE(base::Time::FromString("01 Dec 2022 08:00 GMT", &expected_end));
  fetch = calendar_utils::GetFetchStartEndTimes(date);
  EXPECT_EQ(fetch.first, expected_start);
  EXPECT_EQ(fetch.second, expected_end);

  // Timezone "Asia/Bangkok" is GMT + 7h.
  timezone_settings.SetTimezoneFromID(u"Asia/Bangkok");
  ASSERT_TRUE(base::Time::FromString("01 Apr 2022 00:00 GMT", &date));
  ASSERT_TRUE(base::Time::FromString("31 Mar 2022 17:00 GMT", &expected_start));
  ASSERT_TRUE(base::Time::FromString("30 Apr 2022 17:00 GMT", &expected_end));
  fetch = calendar_utils::GetFetchStartEndTimes(date);
  EXPECT_EQ(fetch.first, expected_start);
  EXPECT_EQ(fetch.second, expected_end);
}

TEST_F(CalendarUtilsUnitTest, MinMaxTime) {
  base::Time date_1;
  base::Time date_2;
  base::Time date_3;
  base::Time date_4;
  ASSERT_TRUE(base::Time::FromString("01 Apr 2022 00:00 GMT", &date_1));
  ASSERT_TRUE(base::Time::FromString("01 Apr 2023 00:00 GMT", &date_2));
  ASSERT_TRUE(base::Time::FromString("01 Apr 2022 20:00 GMT", &date_3));
  ASSERT_TRUE(base::Time::FromString("31 Mar 2022 00:00 GMT", &date_4));

  EXPECT_EQ(date_2, calendar_utils::GetMaxTime(date_1, date_2));
  EXPECT_EQ(date_3, calendar_utils::GetMaxTime(date_1, date_3));
  EXPECT_EQ(date_4, calendar_utils::GetMinTime(date_1, date_4));
}

TEST_F(
    CalendarUtilsUnitTest,
    GivenAnEventWithAStartAndEndTime_WhenGetStartAndEndTimesIsCalled_ThenReturnDatesAdjustedForLocalTimezone) {
  const char* start_time_string = "22 Nov 2021 23:30 GMT";
  const char* end_time_string = "23 Nov 2021 0:30 GMT";
  const auto event = CreateEvent(start_time_string, end_time_string);
  base::Time expected_start, expected_end;
  ash::system::ScopedTimezoneSettings timezone_settings(u"PST");

  EXPECT_TRUE(base::Time::FromString(start_time_string, &expected_start));
  EXPECT_TRUE(base::Time::FromString(end_time_string, &expected_end));

  const auto [actual_start, actual_end] = calendar_utils::GetStartAndEndTime(
      event.get(), expected_start, expected_start.UTCMidnight(),
      expected_start.LocalMidnight());

  EXPECT_EQ(actual_start, expected_start);
  EXPECT_EQ(actual_end, expected_end);
}

TEST_F(CalendarUtilsUnitTest,
       ShouldReturnDatesAdjustedForLocalMidnight_GivenAnAllDayEvent) {
  const char* start_time_string = "22 Nov 2021 00:00 UTC";
  const char* end_time_string = "23 Nov 2021 00:00 UTC";
  // After getting the date, it should have been adjusted to 23:59 local time,
  // so 07:59 UTC with PST timezone.
  const char* expected_end_string = "23 Nov 2021 07:59 UTC";
  const auto event = CreateEvent(start_time_string, end_time_string, true);
  base::Time expected_start, expected_end;
  ash::system::ScopedTimezoneSettings timezone_settings(u"PST");
  calendar_test_utils::ScopedLibcTimeZone scoped_libc_timezone("PST");
  ASSERT_TRUE(scoped_libc_timezone.is_success());

  EXPECT_TRUE(base::Time::FromString(start_time_string, &expected_start));
  EXPECT_TRUE(base::Time::FromUTCString(expected_end_string, &expected_end));

  const auto [actual_start, actual_end] = calendar_utils::GetStartAndEndTime(
      event.get(), expected_start, expected_start.UTCMidnight(),
      expected_start.LocalMidnight());

  EXPECT_EQ(actual_start, expected_start);
  EXPECT_EQ(actual_end, expected_end);
}

// Regression test for b/263270426.
TEST_F(CalendarUtilsUnitTest, GetYearOfDay) {
  SetDefaultLocale("es");

  // Create time: Jan 2023 23:03 GMT. Which is on the week year of 2022 with
  // Spanish locale.
  base::Time time;
  ASSERT_TRUE(base::Time::FromString("1 Jan 2023 23:03 GMT", &time));

  EXPECT_EQ(u"2023", calendar_utils::GetYear(time));

  // Reset locale to English for other tests.
  SetDefaultLocale("en");
}

TEST_F(CalendarUtilsUnitTest, ChildLoggedIn) {
  SimulateUserLogin("[email protected]", user_manager::UserType::kChild);
  EXPECT_TRUE(calendar_utils::IsActiveUser());
}

TEST_F(CalendarUtilsUnitTest, InactiveUser) {
  SimulateUserLogin("[email protected]", user_manager::UserType::kGuest);
  EXPECT_FALSE(calendar_utils::IsActiveUser());
}

struct TimezoneTestParams {
  const char* midnight_string;
  const char* midnight_utc_string;
  const std::u16string timezone_id;
  const std::string timezone;
};

class CalendarUtilsMidnightTest
    : public CalendarUtilsUnitTest,
      public testing::WithParamInterface<TimezoneTestParams> {
 public:
  const char* GetMidnightString() { return GetParam().midnight_string; }
  const char* GetMidnightUTCString() { return GetParam().midnight_utc_string; }
  const std::u16string GetTimezoneId() { return GetParam().timezone_id; }
  const std::string GetTimezone() { return GetParam().timezone; }

  // testing::Test:
  void SetUp() override { CalendarUtilsUnitTest::SetUp(); }

  void TearDown() override { CalendarUtilsUnitTest::TearDown(); }
};

INSTANTIATE_TEST_SUITE_P(
    All,
    CalendarUtilsMidnightTest,
    testing::Values(
        // GMT-8 Timezone.
        TimezoneTestParams{"19 Jan 2023 00:00 UTC", "19 Jan 2023 08:00 UTC",
                           u"America/Los_Angeles", "America/Los_Angeles"},
        // GMT Timezone.
        TimezoneTestParams{"19 Jan 2023 00:00 UTC", "19 Jan 2023 00:00 UTC",
                           u"Europe/London", "Europe/London"},
        // GMT+13 Timezone. Based on the `selected_date_string`, midnight will
        // be the following day in this timezone.
        TimezoneTestParams{"20 Jan 2023 00:00 UTC", "19 Jan 2023 11:00 UTC",
                           u"Pacific/Auckland", "Pacific/Auckland"}),
    [](const testing::TestParamInfo<CalendarUtilsMidnightTest::ParamType>&
           info) {
      std::string name = info.param.timezone;
      base::ranges::replace_if(
          name, [](unsigned char c) { return !absl::ascii_isalnum(c); }, '_');
      return name;
    });

TEST_P(CalendarUtilsMidnightTest,
       GetCorrectLocalAndUtcMidnightAcrossTimezones) {
  // 12:00 UTC will fall into a different day in far ahead timezones so we
  // anchor here.
  const char* now_string = "19 Jan 2023 12:00 UTC";
  const char* expected_midnight_string = GetMidnightString();
  const char* expected_midnight_utc_string = GetMidnightUTCString();

  // Set timezone to GMT+13.
  ash::system::ScopedTimezoneSettings timezone_settings(GetTimezoneId());
  calendar_test_utils::ScopedLibcTimeZone scoped_libc_timezone(GetTimezone());
  ASSERT_TRUE(scoped_libc_timezone.is_success());

  // Convert expected string values to base::Time.
  base::Time now, expected_midnight, expected_midnight_utc;
  EXPECT_TRUE(base::Time::FromUTCString(now_string, &now));
  EXPECT_TRUE(
      base::Time::FromUTCString(expected_midnight_string, &expected_midnight));
  EXPECT_TRUE(base::Time::FromUTCString(expected_midnight_utc_string,
                                        &expected_midnight_utc));

  const auto [actual_midnight, actual_midnight_utc] =
      calendar_utils::GetMidnight(now);

  EXPECT_EQ(expected_midnight, actual_midnight);
  EXPECT_EQ(expected_midnight_utc, actual_midnight_utc);
}

}  // namespace ash