| // Copyright 2016 The Fuchsia Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "src/lib/util/datetime_util.h" |
| |
| #include <ctime> |
| |
| #include <gtest/gtest.h> |
| |
| #include "src/lib/util/clock.h" |
| #include "src/public/lib/statusor/status_macros.h" |
| |
| namespace cobalt::util { |
| |
| namespace { |
| |
| MetricDefinition MetricWithTimeZonePolicy(MetricDefinition::TimeZonePolicy policy) { |
| MetricDefinition metric; |
| metric.set_time_zone_policy(policy); |
| return metric; |
| } |
| |
| } // namespace |
| |
| // This is an alternate definition of CalendarDateToDayIndex from the one |
| // in datetime_util.cc. It appears to be simpler than the one we chose. The |
| // reasons we did not choose this implementation are: |
| // (a) It uses the function timegm which is nonstandard and may not be supported |
| // on all platforms we want to support. See: |
| // http://man7.org/linux/man-pages/man3/timegm.3.html#CONFORMING_TO |
| // (b) In order to implement timegm ourselves using standard library functions |
| // it is necessary to invoke functions that make use of the local timezone |
| // and that are not thread-safe because they use global state. Both of these |
| // things are unnecessary. It seemed like a better idea to use a pure |
| // algorithm that would work anywhere. |
| uint32_t CalendarDateToDayIndexAltImpl(const CalendarDate& calendar_date) { |
| struct tm time_info; |
| time_info.tm_sec = 0; |
| time_info.tm_min = 0; |
| time_info.tm_hour = 0; |
| time_info.tm_mday = static_cast<int>(calendar_date.day_of_month); |
| time_info.tm_mon = static_cast<int>(calendar_date.month) - 1; |
| time_info.tm_year = static_cast<int>(calendar_date.year) - 1900; |
| time_t unix_time = timegm(&time_info); |
| return static_cast<uint32_t>(unix_time / kNumUnixSecondsPerDay); |
| } |
| |
| TEST(DatetimeUtilTest, CalendarDateToDayIndex) { |
| CalendarDate calendar_date; |
| |
| // January 1, 1970 |
| calendar_date.day_of_month = 1; |
| calendar_date.month = 1; |
| calendar_date.year = 1970; |
| EXPECT_EQ(0u, CalendarDateToDayIndex(calendar_date)); |
| EXPECT_EQ(0u, CalendarDateToDayIndexAltImpl(calendar_date)); |
| |
| // January 2, 1970 |
| calendar_date.day_of_month = 2; |
| EXPECT_EQ(1u, CalendarDateToDayIndex(calendar_date)); |
| EXPECT_EQ(1u, CalendarDateToDayIndexAltImpl(calendar_date)); |
| |
| // January 31, 1970 |
| calendar_date.day_of_month = 31; |
| EXPECT_EQ(30u, CalendarDateToDayIndex(calendar_date)); |
| EXPECT_EQ(30u, CalendarDateToDayIndexAltImpl(calendar_date)); |
| |
| // Febrary 1, 1970 |
| calendar_date.month = 2; |
| calendar_date.day_of_month = 1; |
| EXPECT_EQ(31u, CalendarDateToDayIndex(calendar_date)); |
| EXPECT_EQ(31u, CalendarDateToDayIndexAltImpl(calendar_date)); |
| |
| // 1972 Was a leap year... |
| |
| // January 1, 1972 |
| calendar_date.day_of_month = 1; |
| calendar_date.month = 1; |
| calendar_date.year = 1972; |
| EXPECT_EQ(365 * 2u, CalendarDateToDayIndex(calendar_date)); |
| EXPECT_EQ(365 * 2u, CalendarDateToDayIndexAltImpl(calendar_date)); |
| |
| // February 1, 1972 |
| calendar_date.day_of_month = 1; |
| calendar_date.month = 2; |
| calendar_date.year = 1972; |
| EXPECT_EQ(365 * 2u + 31, CalendarDateToDayIndex(calendar_date)); |
| EXPECT_EQ(365 * 2u + 31, CalendarDateToDayIndexAltImpl(calendar_date)); |
| |
| // February 28, 1972 |
| calendar_date.day_of_month = 28; |
| calendar_date.month = 2; |
| calendar_date.year = 1972; |
| EXPECT_EQ(365 * 2u + 31 + 27, CalendarDateToDayIndex(calendar_date)); |
| EXPECT_EQ(365 * 2u + 31 + 27, CalendarDateToDayIndexAltImpl(calendar_date)); |
| |
| // February 29, 1972 |
| calendar_date.day_of_month = 29; |
| calendar_date.month = 2; |
| calendar_date.year = 1972; |
| EXPECT_EQ(365 * 2u + 31 + 28, CalendarDateToDayIndex(calendar_date)); |
| EXPECT_EQ(365 * 2u + 31 + 28, CalendarDateToDayIndexAltImpl(calendar_date)); |
| |
| // March 1, 1972 |
| calendar_date.day_of_month = 1; |
| calendar_date.month = 3; |
| calendar_date.year = 1972; |
| EXPECT_EQ(365 * 2u + 31 + 29, CalendarDateToDayIndex(calendar_date)); |
| EXPECT_EQ(365 * 2u + 31 + 29, CalendarDateToDayIndexAltImpl(calendar_date)); |
| |
| // January 1, 1973 |
| calendar_date.day_of_month = 1; |
| calendar_date.month = 1; |
| calendar_date.year = 1973; |
| EXPECT_EQ(365 * 2u + 366, CalendarDateToDayIndex(calendar_date)); |
| EXPECT_EQ(365 * 2u + 366, CalendarDateToDayIndexAltImpl(calendar_date)); |
| |
| // March 1, 2000 (2000 years after March 1, year 0) |
| calendar_date.day_of_month = 1; |
| calendar_date.month = 3; |
| calendar_date.year = 2000; |
| // See comments at KEpochOffset. |
| EXPECT_EQ(11017u, CalendarDateToDayIndex(calendar_date)); |
| EXPECT_EQ(11017u, CalendarDateToDayIndexAltImpl(calendar_date)); |
| |
| // October 18, 2016 |
| calendar_date.day_of_month = 18; |
| calendar_date.month = 10; |
| calendar_date.year = 2016; |
| EXPECT_EQ(17092u, CalendarDateToDayIndex(calendar_date)); |
| EXPECT_EQ(17092u, CalendarDateToDayIndexAltImpl(calendar_date)); |
| } |
| |
| void doDayIndexToCalendarDateTest(uint32_t day_index, uint32_t expected_month, |
| uint32_t expected_day_of_month, uint32_t expected_year) { |
| auto calendar_date = DayIndexToCalendarDate(day_index); |
| EXPECT_EQ(expected_day_of_month, calendar_date.day_of_month) << "day_index=" << day_index; |
| EXPECT_EQ(expected_month, calendar_date.month) << "day_index=" << day_index; |
| EXPECT_EQ(expected_year, calendar_date.year) << "day_index=" << day_index; |
| } |
| |
| TEST(DatetimeUtilTest, DayIndexToCalendarDate) { |
| // January 1, 1970 |
| doDayIndexToCalendarDateTest(0, 1, 1, 1970); |
| |
| // January 2, 1970 |
| doDayIndexToCalendarDateTest(1, 1, 2, 1970); |
| |
| // January 31, 1970 |
| doDayIndexToCalendarDateTest(30, 1, 31, 1970); |
| |
| // Febrary 1, 1970 |
| doDayIndexToCalendarDateTest(31, 2, 1, 1970); |
| |
| // 1972 Was a leap year... |
| |
| // January 1, 1972 |
| doDayIndexToCalendarDateTest(365 * 2, 1, 1, 1972); |
| |
| // February 1, 1972 |
| doDayIndexToCalendarDateTest(365 * 2 + 31, 2, 1, 1972); |
| |
| // February 28, 1972 |
| doDayIndexToCalendarDateTest(365 * 2 + 31 + 27, 2, 28, 1972); |
| |
| // February 29, 1972 |
| doDayIndexToCalendarDateTest(365 * 2 + 31 + 28, 2, 29, 1972); |
| |
| // March 1, 1972 |
| doDayIndexToCalendarDateTest(365 * 2 + 31 + 29, 3, 1, 1972); |
| |
| // January 1, 1973 |
| doDayIndexToCalendarDateTest(365 * 2 + 366, 1, 1, 1973); |
| |
| // March 1, 2000 |
| doDayIndexToCalendarDateTest(11017, 3, 1, 2000); |
| |
| // October 18, 2016 |
| doDayIndexToCalendarDateTest(17092, 10, 18, 2016); |
| } |
| |
| TEST(DatetimeUtilTest, DayIndexCalendarDateInversesTest) { |
| for (uint32_t day_index = 16000; day_index < 19000; day_index++) { |
| CalendarDate calendar_date = DayIndexToCalendarDate(day_index); |
| EXPECT_EQ(day_index, CalendarDateToDayIndex(calendar_date)); |
| EXPECT_EQ(day_index, CalendarDateToDayIndexAltImpl(calendar_date)); |
| } |
| } |
| |
| TEST(DatetimeUtilTest, DayIndexToWeekIndex) { |
| // This is the day index for Friday Dec 2, 2016 |
| static const uint32_t kSomeDayIndex = 17137; |
| // The week index for the week containing that day. |
| static const uint32_t kSomeWeekIndex = 2448; |
| EXPECT_EQ(kSomeWeekIndex, DayIndexToWeekIndex(kSomeDayIndex)); |
| } |
| |
| TEST(DatetimeUtilTest, CalendarDateToWeekIndex) { |
| CalendarDate calendar_date; |
| |
| // Thurs, January 1, 1970 |
| calendar_date.day_of_month = 1; |
| calendar_date.month = 1; |
| calendar_date.year = 1970; |
| EXPECT_EQ(0u, CalendarDateToWeekIndex(calendar_date)); |
| |
| // Fri, January 2, 1970 |
| calendar_date.day_of_month = 2; |
| EXPECT_EQ(0u, CalendarDateToWeekIndex(calendar_date)); |
| |
| // Sat, January 3, 1970 |
| calendar_date.day_of_month = 3; |
| EXPECT_EQ(0u, CalendarDateToWeekIndex(calendar_date)); |
| |
| // Sun, January 4, 1970 |
| calendar_date.day_of_month = 4; |
| EXPECT_EQ(1u, CalendarDateToWeekIndex(calendar_date)); |
| |
| // Mon January 5, 1970 |
| calendar_date.day_of_month = 5; |
| EXPECT_EQ(1u, CalendarDateToWeekIndex(calendar_date)); |
| |
| // Sat January 10, 1970 |
| calendar_date.day_of_month = 10; |
| EXPECT_EQ(1u, CalendarDateToWeekIndex(calendar_date)); |
| |
| // Sun January 11, 1970 |
| calendar_date.day_of_month = 11; |
| EXPECT_EQ(2u, CalendarDateToWeekIndex(calendar_date)); |
| |
| // Mon January 12, 1970 |
| calendar_date.day_of_month = 12; |
| EXPECT_EQ(2u, CalendarDateToWeekIndex(calendar_date)); |
| |
| // Wed March 4, 1970 |
| calendar_date.day_of_month = 4; |
| calendar_date.month = 3; |
| EXPECT_EQ(9u, CalendarDateToWeekIndex(calendar_date)); |
| |
| // Sat March 7, 1970 |
| calendar_date.day_of_month = 7; |
| EXPECT_EQ(9u, CalendarDateToWeekIndex(calendar_date)); |
| |
| // Sun March 8, 1970 |
| calendar_date.day_of_month = 8; |
| EXPECT_EQ(10u, CalendarDateToWeekIndex(calendar_date)); |
| } |
| |
| void doWeekIndexToCalendarDateTest(uint32_t week_index, uint32_t expected_month, |
| uint32_t expected_day_of_month, uint32_t expected_year) { |
| auto calendar_date = WeekIndexToCalendarDate(week_index); |
| EXPECT_EQ(expected_day_of_month, calendar_date.day_of_month) << "week_index=" << week_index; |
| EXPECT_EQ(expected_month, calendar_date.month) << "week_index=" << week_index; |
| EXPECT_EQ(expected_year, calendar_date.year) << "week_index=" << week_index; |
| } |
| |
| TEST(DatetimeUtilTest, WeekIndexToCalendarDate) { |
| // January 1, 1970 |
| doWeekIndexToCalendarDateTest(0, 1, 1, 1970); |
| |
| // January 4, 1970 |
| doWeekIndexToCalendarDateTest(1, 1, 4, 1970); |
| |
| // January 11, 1970 |
| doWeekIndexToCalendarDateTest(2, 1, 11, 1970); |
| |
| // Marcy 8, 1970 |
| doWeekIndexToCalendarDateTest(10, 3, 8, 1970); |
| |
| // Marcy 15, 1970 |
| doWeekIndexToCalendarDateTest(11, 3, 15, 1970); |
| } |
| |
| TEST(DatetimeUtilTest, WeekIndexCalendarDateInversesTest) { |
| for (uint32_t week_index = 2000; week_index < 3000; week_index++) { |
| CalendarDate calendar_date = WeekIndexToCalendarDate(week_index); |
| EXPECT_EQ(week_index, CalendarDateToWeekIndex(calendar_date)); |
| } |
| } |
| |
| TEST(DatetimeUtilTest, CalendarDateToMonthIndex) { |
| CalendarDate calendar_date; |
| |
| // January 1, 1970 |
| calendar_date.day_of_month = 1; |
| calendar_date.month = 1; |
| calendar_date.year = 1970; |
| EXPECT_EQ(0u, CalendarDateToMonthIndex(calendar_date)); |
| |
| // January 31, 1970 |
| calendar_date.day_of_month = 31; |
| EXPECT_EQ(0u, CalendarDateToMonthIndex(calendar_date)); |
| |
| // February 1, 1970 |
| calendar_date.month = 2; |
| calendar_date.day_of_month = 1; |
| EXPECT_EQ(1u, CalendarDateToMonthIndex(calendar_date)); |
| |
| // December 31, 1970 |
| calendar_date.month = 12; |
| calendar_date.day_of_month = 31; |
| EXPECT_EQ(11u, CalendarDateToMonthIndex(calendar_date)); |
| |
| // January 1, 1971 |
| calendar_date.month = 1; |
| calendar_date.day_of_month = 1; |
| calendar_date.year = 1971; |
| EXPECT_EQ(12u, CalendarDateToMonthIndex(calendar_date)); |
| |
| // March 4, 1971 |
| calendar_date.month = 3; |
| calendar_date.day_of_month = 4; |
| calendar_date.year = 1971; |
| EXPECT_EQ(14u, CalendarDateToMonthIndex(calendar_date)); |
| |
| // March 4, 1976 |
| calendar_date.month = 3; |
| calendar_date.day_of_month = 4; |
| calendar_date.year = 1976; |
| EXPECT_EQ(74u, CalendarDateToMonthIndex(calendar_date)); |
| } |
| |
| TEST(DatetimeUtilTest, DayIndexToMonthIndex) { |
| // This is the day index for Friday Dec 2, 2016 |
| static const uint32_t kSomeDayIndex = 17137; |
| // The month index for December, 2016. |
| static const uint32_t kSomeMonthIndex = 563; |
| EXPECT_EQ(kSomeMonthIndex, DayIndexToMonthIndex(kSomeDayIndex)); |
| } |
| |
| void doMonthIndexToCalendarDateTest(uint32_t month_index, uint32_t expected_month, |
| uint32_t expected_year) { |
| auto calendar_date = MonthIndexToCalendarDate(month_index); |
| EXPECT_EQ(1u, calendar_date.day_of_month) << "month_index=" << month_index; |
| EXPECT_EQ(expected_month, calendar_date.month) << "month_index=" << month_index; |
| EXPECT_EQ(expected_year, calendar_date.year) << "month_index=" << month_index; |
| } |
| |
| TEST(DatetimeUtilTest, MonthIndexToCalendarDate) { |
| // January, 1970 |
| doMonthIndexToCalendarDateTest(0, 1, 1970); |
| |
| // February, 1970 |
| doMonthIndexToCalendarDateTest(1, 2, 1970); |
| |
| // March, 1970 |
| doMonthIndexToCalendarDateTest(2, 3, 1970); |
| |
| // April, 1978 |
| doMonthIndexToCalendarDateTest(123, 4, 1980); |
| } |
| |
| TEST(DatetimeUtilTest, MonthIndexCalendarDateInversesTest) { |
| for (uint32_t month_index = 500; month_index < 1000; month_index++) { |
| CalendarDate calendar_date = MonthIndexToCalendarDate(month_index); |
| EXPECT_EQ(month_index, CalendarDateToMonthIndex(calendar_date)); |
| } |
| } |
| |
| TEST(DatetimeUtilTest, TimeToHourIdTest) { |
| // This unix timestamp corresponds to: |
| // UTC: 00:00:00 on August 6, 2019 |
| // PDT: 17:00:00 on August 5, 2019 |
| static const time_t kSummerExactHourTimestamp = 1565049600; |
| static const uint32_t kUtcSummerExactHourId = |
| (18114 /* Days */ * kHourIdsPerDay) + 1 /* Not DST */; |
| static const uint32_t kPacificSummerExactHourId = |
| (18113 /* Days */ * kHourIdsPerDay) + (17 * 2) /* DST */; |
| // This unix timestamp corresponds to: |
| // UTC: 13:42:07 on August 6, 2019 |
| // PDT: 06:42:07 on August 6, 2019 |
| static const time_t kSummerOffHourTimestamp = 1565098927; |
| static const uint32_t kUtcSummerOffHourId = |
| (18114 /* Days */ * kHourIdsPerDay) + (13 * 2) + 1 /* Not DST */; |
| static const uint32_t kPacificSummerOffHourId = |
| (18114 /* Days */ * kHourIdsPerDay) + (6 * 2) /* DST */; |
| // This unix timestamp corresponds to: |
| // UTC: 00:00:00 on January 6, 2019 |
| // PST: 16:00:00 on January 5, 2019 |
| static const time_t kWinterTimestamp = 1546732800; |
| static const uint32_t kUtcWinterHourId = (17902 /* Days */ * kHourIdsPerDay) + 1 /* Not DST */; |
| static const uint32_t kPacificWinterHourId = |
| (17901 /* Days */ * kHourIdsPerDay) + (16 * 2) + 1 /* Not DST*/; |
| |
| EXPECT_EQ(kUtcSummerExactHourId, TimeToHourId(kSummerExactHourTimestamp, MetricDefinition::UTC)); |
| EXPECT_EQ(kUtcSummerOffHourId, TimeToHourId(kSummerOffHourTimestamp, MetricDefinition::UTC)); |
| EXPECT_EQ(kUtcWinterHourId, TimeToHourId(kWinterTimestamp, MetricDefinition::UTC)); |
| // Only perform the following check when running this test in the Pacific |
| // timezone. Note that |timezone| is a global variable defined in <ctime> |
| // that stores difference between UTC and the latest local standard time, in |
| // seconds west of UTC. This value is not adjusted for daylight saving. |
| // See https://www.gnu.org/software/libc/manual/html_node/ \ |
| // Time-Zone-Functions.html#Time-Zone-Functions |
| if (timezone / 3600 == 8) { |
| EXPECT_EQ(kPacificSummerExactHourId, |
| TimeToHourId(kSummerExactHourTimestamp, MetricDefinition::LOCAL)); |
| EXPECT_EQ(kPacificSummerOffHourId, |
| TimeToHourId(kSummerOffHourTimestamp, MetricDefinition::LOCAL)); |
| EXPECT_EQ(kPacificWinterHourId, TimeToHourId(kWinterTimestamp, MetricDefinition::LOCAL)); |
| } |
| } |
| |
| TEST(DatetimeUtilTest, TimePointToHourIdWithUtcConverter) { |
| UtcTimeConverter converter = UtcTimeConverter(); |
| MetricDefinition utc_metric = MetricWithTimeZonePolicy(MetricDefinition::UTC); |
| MetricDefinition local_metric = MetricWithTimeZonePolicy(MetricDefinition::LOCAL); |
| MetricDefinition other_tz_metric = MetricWithTimeZonePolicy(MetricDefinition::OTHER_TIME_ZONE); |
| |
| std::tm kSummerExactHourTime = { |
| .tm_sec = 0, |
| .tm_min = 0, |
| .tm_hour = 0, |
| .tm_mday = 6, // 6th |
| .tm_mon = 7, // August |
| .tm_year = 119, // 2019 |
| }; |
| // This Unix time point corresponds to: |
| // UTC: 00:00:00 on August 6, 2019 |
| // PDT: 00:00:00 on August 5, 2019 |
| const std::chrono::time_point<std::chrono::system_clock> kSummerExactHourTimePoint = |
| std::chrono::system_clock::from_time_t(timegm(&kSummerExactHourTime)); |
| static const uint32_t kUtcSummerExactHourId = |
| (18114 /* Days */ * kHourIdsPerDay) + 1 /* Not DST */; |
| static const uint32_t kPacificSummerExactHourId = |
| (18113 /* Days */ * kHourIdsPerDay) + (17 * 2) /* DST */; |
| |
| std::tm kSummerOffHourTime = { |
| .tm_sec = 7, |
| .tm_min = 42, |
| .tm_hour = 13, |
| .tm_mday = 6, // 6th |
| .tm_mon = 7, // August |
| .tm_year = 119, // 2019 |
| }; |
| // This Unix time point corresponds to: |
| // UTC: 13:42:07 on August 6, 2019 |
| // PDT: 06:42:07 on August 6, 2019 |
| const std::chrono::time_point<std::chrono::system_clock> kSummerOffHourTimePoint = |
| std::chrono::system_clock::from_time_t(timegm(&kSummerOffHourTime)); |
| static const uint32_t kUtcSummerOffHourId = |
| (18114 /* Days */ * kHourIdsPerDay) + (13 * 2) + 1 /* Not DST */; |
| static const uint32_t kPacificSummerOffHourId = |
| (18114 /* Days */ * kHourIdsPerDay) + (6 * 2) /* DST */; |
| |
| std::tm kWinterTime = { |
| .tm_sec = 0, |
| .tm_min = 0, |
| .tm_hour = 0, |
| .tm_mday = 6, // 6th |
| .tm_mon = 0, // January |
| .tm_year = 119, // 2019 |
| }; |
| // This Unix time point corresponds to: |
| // UTC: 00:00:00 on January 6, 2019 |
| // PST: 16:00:00 on January 5, 2019 |
| const std::chrono::time_point<std::chrono::system_clock> kWinterTimePoint = |
| std::chrono::system_clock::from_time_t(timegm(&kWinterTime)); |
| static const uint32_t kUtcWinterHourId = (17902 /* Days */ * kHourIdsPerDay) + 1 /* Not DST */; |
| static const uint32_t kPacificWinterHourId = |
| (17901 /* Days */ * kHourIdsPerDay) + (16 * 2) + 1 /* Not DST*/; |
| |
| CB_ASSERT_OK_AND_ASSIGN(uint32_t summer_exact_hour, |
| TimePointToHourId(kSummerExactHourTimePoint, converter, utc_metric)); |
| EXPECT_EQ(kUtcSummerExactHourId, summer_exact_hour); |
| |
| CB_ASSERT_OK_AND_ASSIGN(uint32_t summer_off_hour, |
| TimePointToHourId(kSummerOffHourTimePoint, converter, utc_metric)); |
| EXPECT_EQ(kUtcSummerOffHourId, summer_off_hour); |
| |
| CB_ASSERT_OK_AND_ASSIGN(uint32_t winter, |
| TimePointToHourId(kWinterTimePoint, converter, utc_metric)); |
| EXPECT_EQ(kUtcWinterHourId, winter); |
| |
| // Only perform the following check when running this test in the Pacific |
| // timezone. Note that |timezone| is a global variable defined in <ctime> |
| // that stores difference between UTC and the latest local standard time, in |
| // seconds west of UTC. This value is not adjusted for daylight saving. |
| // See https://www.gnu.org/software/libc/manual/html_node/ \ |
| // Time-Zone-Functions.html#Time-Zone-Functions |
| if (timezone / 3600 == 8) { |
| CB_ASSERT_OK_AND_ASSIGN(uint32_t pacific_summer_exact_hour, |
| TimePointToHourId(kSummerExactHourTimePoint, converter, local_metric)); |
| EXPECT_EQ(kPacificSummerExactHourId, pacific_summer_exact_hour); |
| |
| CB_ASSERT_OK_AND_ASSIGN(uint32_t pacific_summer_off_hour, |
| TimePointToHourId(kSummerOffHourTimePoint, converter, local_metric)); |
| EXPECT_EQ(kPacificSummerOffHourId, pacific_summer_off_hour); |
| |
| CB_ASSERT_OK_AND_ASSIGN(uint32_t pacific_winter, |
| TimePointToHourId(kWinterTimePoint, converter, local_metric)); |
| EXPECT_EQ(kPacificWinterHourId, pacific_winter); |
| } |
| |
| // Since |converter| converts time points to time structs with respect to UTC, the hour IDs for |
| // |other_tz_metric| should agree with those for |utc_metric|. |
| CB_ASSERT_OK_AND_ASSIGN(uint32_t other_summer_exact_hour, |
| TimePointToHourId(kSummerExactHourTimePoint, converter, other_tz_metric)); |
| EXPECT_EQ(kUtcSummerExactHourId, other_summer_exact_hour); |
| |
| CB_ASSERT_OK_AND_ASSIGN(uint32_t other_summer_off_hour, |
| TimePointToHourId(kSummerOffHourTimePoint, converter, other_tz_metric)); |
| EXPECT_EQ(kUtcSummerOffHourId, other_summer_off_hour); |
| |
| CB_ASSERT_OK_AND_ASSIGN(uint32_t other_winter, |
| TimePointToHourId(kWinterTimePoint, converter, other_tz_metric)); |
| EXPECT_EQ(kUtcWinterHourId, other_winter); |
| } |
| |
| TEST(DatetimeUtilTest, TimePointToHourIdWithOffsetConverter) { |
| // A CivilTimeConverterInterface that mimics conversion of a time_point to a time struct with |
| // respect to Pacific Daylight Time. |
| FakeCivilTimeConverter summer_converter = FakeCivilTimeConverter(-7, true); |
| // A CivilTimeConverterInterface that mimics conversion of a time_point to a time struct with |
| // respect to Pacific Standard Time. |
| FakeCivilTimeConverter winter_converter = FakeCivilTimeConverter(-8, false); |
| MetricDefinition other_tz_metric = MetricWithTimeZonePolicy(MetricDefinition::OTHER_TIME_ZONE); |
| |
| std::tm kSummerExactHourTime = { |
| .tm_sec = 0, |
| .tm_min = 0, |
| .tm_hour = 0, |
| .tm_mday = 6, // 6th |
| .tm_mon = 7, // August |
| .tm_year = 119, // 2019 |
| }; |
| // This Unix time point corresponds to: |
| // UTC: 00:00:00 on August 6, 2019 |
| // PDT: 00:00:00 on August 5, 2019 |
| const std::chrono::time_point<std::chrono::system_clock> kSummerExactHourTimePoint = |
| std::chrono::system_clock::from_time_t(timegm(&kSummerExactHourTime)); |
| static const uint32_t kPacificSummerExactHourId = |
| (18113 /* Days */ * kHourIdsPerDay) + (17 * 2) /* DST */; |
| |
| std::tm kSummerOffHourTime = { |
| .tm_sec = 7, |
| .tm_min = 42, |
| .tm_hour = 13, |
| .tm_mday = 6, // 6th |
| .tm_mon = 7, // August |
| .tm_year = 119, // 2019 |
| }; |
| // This Unix time point corresponds to: |
| // UTC: 13:42:07 on August 6, 2019 |
| // PDT: 06:42:07 on August 6, 2019 |
| const std::chrono::time_point<std::chrono::system_clock> kSummerOffHourTimePoint = |
| std::chrono::system_clock::from_time_t(timegm(&kSummerOffHourTime)); |
| static const uint32_t kPacificSummerOffHourId = |
| (18114 /* Days */ * kHourIdsPerDay) + (6 * 2) /* DST */; |
| |
| std::tm kWinterTime = { |
| .tm_sec = 0, |
| .tm_min = 0, |
| .tm_hour = 0, |
| .tm_mday = 6, // 6th |
| .tm_mon = 0, // January |
| .tm_year = 119, // 2019 |
| }; |
| // This Unix time point corresponds to: |
| // UTC: 00:00:00 on January 6, 2019 |
| // PST: 16:00:00 on January 5, 2019 |
| const std::chrono::time_point<std::chrono::system_clock> kWinterTimePoint = |
| std::chrono::system_clock::from_time_t(timegm(&kWinterTime)); |
| static const uint32_t kPacificWinterHourId = |
| (17901 /* Days */ * kHourIdsPerDay) + (16 * 2) + 1 /* Not DST*/; |
| |
| CB_ASSERT_OK_AND_ASSIGN( |
| uint32_t summer_exact_hour, |
| TimePointToHourId(kSummerExactHourTimePoint, summer_converter, other_tz_metric)); |
| EXPECT_EQ(kPacificSummerExactHourId, summer_exact_hour); |
| |
| CB_ASSERT_OK_AND_ASSIGN( |
| uint32_t summer_off_hour, |
| TimePointToHourId(kSummerOffHourTimePoint, summer_converter, other_tz_metric)); |
| EXPECT_EQ(kPacificSummerOffHourId, summer_off_hour); |
| |
| CB_ASSERT_OK_AND_ASSIGN(uint32_t winter, |
| TimePointToHourId(kWinterTimePoint, winter_converter, other_tz_metric)); |
| EXPECT_EQ(kPacificWinterHourId, winter); |
| } |
| |
| TEST(DatetimeUtilTest, HourIdToDayIndexTest) { |
| static const uint32_t kDayIndex = 18114; |
| static const uint32_t kHourIdMidnight = kDayIndex * kHourIdsPerDay; |
| static const uint32_t kHourIdNoon = (kDayIndex * kHourIdsPerDay) + (12 * 2); |
| EXPECT_EQ(kDayIndex, HourIdToDayIndex(kHourIdMidnight)); |
| EXPECT_EQ(kDayIndex, HourIdToDayIndex(kHourIdNoon)); |
| } |
| |
| TEST(DatetimeUtilTest, TimeToDayIndexTest) { |
| // This unix timestamp corresponds to Friday Dec 2, 2016 in UTC |
| // and Thursday Dec 1, 2016 in Pacific time. |
| static const time_t kSomeTimestamp = 1480647356; |
| |
| // This is the day index for Friday Dec 2, 2016 |
| static const uint32_t kUtcDayIndex = 17137; |
| |
| // This is the day index for Thurs Dec 1, 2016 |
| static const uint32_t kPacificDayIndex = 17136; |
| |
| EXPECT_EQ(kUtcDayIndex, TimeToDayIndex(kSomeTimestamp, MetricDefinition::UTC)); |
| // Only perform the following check when running this test in the Pacific |
| // timezone. Note that |timezone| is a global variable defined in <ctime> |
| // that stores difference between UTC and the latest local standard time, in |
| // seconds west of UTC. This value is not adjusted for daylight saving. |
| // See https://www.gnu.org/software/libc/manual/html_node/ \ |
| // Time-Zone-Functions.html#Time-Zone-Functions |
| if (timezone / 3600 == 8) { |
| EXPECT_EQ(kPacificDayIndex, TimeToDayIndex(kSomeTimestamp, MetricDefinition::LOCAL)); |
| } |
| } |
| |
| TEST(DatetimeUtilTest, TimePointToDayIndexWithUtcConverterTest) { |
| UtcTimeConverter converter = UtcTimeConverter(); |
| MetricDefinition utc_metric = MetricWithTimeZonePolicy(MetricDefinition::UTC); |
| MetricDefinition local_metric = MetricWithTimeZonePolicy(MetricDefinition::LOCAL); |
| MetricDefinition other_tz_metric = MetricWithTimeZonePolicy(MetricDefinition::OTHER_TIME_ZONE); |
| |
| std::tm kTimeStruct = { |
| .tm_sec = 0, |
| .tm_min = 0, |
| .tm_hour = 0, |
| .tm_mday = 2, // 2nd |
| .tm_mon = 11, // December |
| .tm_year = 116, // 2016 |
| }; |
| // This unix timestamp corresponds to Friday Dec 2, 2016 in UTC |
| // and Thursday Dec 1, 2016 in Pacific time. |
| const std::chrono::time_point<std::chrono::system_clock> kTimePoint = |
| std::chrono::system_clock::from_time_t(timegm(&kTimeStruct)); |
| |
| // This is the day index for Friday Dec 2, 2016 |
| static const uint32_t kUtcDayIndex = 17137; |
| |
| // This is the day index for Thurs Dec 1, 2016 |
| static const uint32_t kPacificDayIndex = 17136; |
| |
| CB_ASSERT_OK_AND_ASSIGN(uint32_t utc_day_index, |
| TimePointToDayIndex(kTimePoint, converter, utc_metric)); |
| EXPECT_EQ(kUtcDayIndex, utc_day_index); |
| |
| // Only perform the following check when running this test in the Pacific |
| // timezone. Note that |timezone| is a global variable defined in <ctime> |
| // that stores difference between UTC and the latest local standard time, in |
| // seconds west of UTC. This value is not adjusted for daylight saving. |
| // See https://www.gnu.org/software/libc/manual/html_node/ \ |
| // Time-Zone-Functions.html#Time-Zone-Functions |
| if (timezone / 3600 == 8) { |
| CB_ASSERT_OK_AND_ASSIGN(uint32_t pacific_day_index, |
| TimePointToDayIndex(kTimePoint, converter, local_metric)); |
| EXPECT_EQ(kPacificDayIndex, pacific_day_index); |
| } |
| |
| // Since |converter| converts time points to time structs with respect to UTC, the day index for |
| // |other_tz_metric| should agree with that for |utc_metric|. |
| CB_ASSERT_OK_AND_ASSIGN(uint32_t other_tz_day_index, |
| TimePointToDayIndex(kTimePoint, converter, other_tz_metric)); |
| EXPECT_EQ(kUtcDayIndex, other_tz_day_index); |
| } |
| |
| TEST(DatetimeUtilTest, TimePointToDayIndexWithOffsetConverterTest) { |
| // A CivilTimeConverterInterface that mimics conversion of a time_point to a time struct with |
| // respect to Pacific Standard Time. |
| FakeCivilTimeConverter converter = FakeCivilTimeConverter(-8, false); |
| MetricDefinition other_tz_metric = MetricWithTimeZonePolicy(MetricDefinition::OTHER_TIME_ZONE); |
| |
| std::tm kTimeStruct = { |
| .tm_sec = 0, |
| .tm_min = 0, |
| .tm_hour = 0, |
| .tm_mday = 2, // 2nd |
| .tm_mon = 11, // December |
| .tm_year = 116, // 2016 |
| }; |
| // This unix timestamp corresponds to Friday Dec 2, 2016 in UTC |
| // and Thursday Dec 1, 2016 in Pacific time. |
| const std::chrono::time_point<std::chrono::system_clock> kTimePoint = |
| std::chrono::system_clock::from_time_t(timegm(&kTimeStruct)); |
| |
| // This is the day index for Thurs Dec 1, 2016. |
| static const uint32_t kPacificDayIndex = 17136; |
| |
| CB_ASSERT_OK_AND_ASSIGN(uint32_t pacific_day_index, |
| TimePointToDayIndex(kTimePoint, converter, other_tz_metric)); |
| EXPECT_EQ(kPacificDayIndex, pacific_day_index); |
| } |
| |
| TEST(DatetimeUtilTest, TimeInfoFromTimePointUtcConverter) { |
| UtcTimeConverter converter = UtcTimeConverter(); |
| MetricDefinition metric = MetricWithTimeZonePolicy(MetricDefinition::UTC); |
| |
| std::chrono::system_clock::time_point time = |
| std::chrono::system_clock::time_point(std::chrono::hours(kNumHoursPerDay * 100)); |
| CB_ASSERT_OK_AND_ASSIGN(TimeInfo time_info_now, TimeInfo::FromTimePoint(time, converter, metric)); |
| |
| EXPECT_EQ(time_info_now.day_index, 100u); |
| EXPECT_EQ(time_info_now.hour_id, kNumHoursPerDay * kHourIdsPerHour * 100u + 1); |
| EXPECT_EQ(TimePointToDayIndexUtc(time), 100u); |
| EXPECT_EQ(TimePointToHourIdUtc(time), kNumHoursPerDay * kHourIdsPerHour * 100u + 1); |
| } |
| |
| TEST(DatetimeUtilTest, TimeInfoFromTimePointFakeCivilTimeConverter) { |
| MetricDefinition metric = MetricWithTimeZonePolicy(MetricDefinition::OTHER_TIME_ZONE); |
| std::chrono::system_clock::time_point start = |
| std::chrono::system_clock::time_point(std::chrono::hours(kNumHoursPerDay * 100)); |
| |
| FakeCivilTimeConverter converter = FakeCivilTimeConverter( |
| /*start_utc_offset=*/-8, /*start_isdst=*/false, /*end_utc_offset=*/-7, /*end_isdst=*/true, |
| /*threshold=*/start + std::chrono::hours(1)); |
| |
| CB_ASSERT_OK_AND_ASSIGN(TimeInfo start_time_info, |
| TimeInfo::FromTimePoint(start, converter, metric)); |
| ASSERT_EQ(start_time_info.day_index, 99u); |
| ASSERT_EQ(start_time_info.hour_id, kHourIdsPerHour * (kNumHoursPerDay * 100u - 8u) + 1); |
| |
| CB_ASSERT_OK_AND_ASSIGN( |
| TimeInfo end_time_info, |
| TimeInfo::FromTimePoint(start + std::chrono::hours(1), converter, metric)); |
| ASSERT_EQ(end_time_info.day_index, 99u); |
| ASSERT_EQ(end_time_info.hour_id, kHourIdsPerHour * (kNumHoursPerDay * 100u - 6u)); |
| } |
| |
| TEST(DatetimeUtilTest, FailingCivilTimeConverter) { |
| MetricDefinition metric = MetricWithTimeZonePolicy(MetricDefinition::OTHER_TIME_ZONE); |
| std::chrono::system_clock::time_point time = |
| std::chrono::system_clock::time_point(std::chrono::hours(kNumHoursPerDay * 100)); |
| |
| // A CivilTimeConverterInterface that always returns an error status. |
| class FailingCivilTimeConverter : public util::CivilTimeConverterInterface { |
| [[nodiscard]] lib::statusor::StatusOr<std::tm> civil_time( |
| std::chrono::system_clock::time_point /*time*/, |
| const std::string& /*time_zone*/) const override { |
| return Status(StatusCode::INVALID_ARGUMENT, "failed to convert time"); |
| } |
| }; |
| |
| FailingCivilTimeConverter converter; |
| ASSERT_FALSE(TimeInfo::FromTimePoint(time, converter, metric).ok()); |
| } |
| |
| TEST(DatetimeUtilTest, TimeInfoFromDayIndex) { |
| const uint32_t day_index = 100; |
| TimeInfo info = TimeInfo::FromDayIndex(day_index); |
| EXPECT_EQ(info.day_index, day_index); |
| // The hour ID should be day_index * util::kHourIdsPerDay + 1, since this function assumes that |
| // DST is not in force. |
| EXPECT_EQ(info.hour_id, 4801u); |
| } |
| |
| TEST(DatetimeUtilTest, TimeInfoFromHourId) { |
| const uint32_t hour_id_dst = 4800; |
| const uint32_t hour_id_nodst = 4801; |
| TimeInfo info_dst = TimeInfo::FromHourId(hour_id_dst); |
| TimeInfo info_nodst = TimeInfo::FromHourId(hour_id_nodst); |
| EXPECT_EQ(info_dst.day_index, 100u); |
| EXPECT_EQ(info_dst.hour_id, hour_id_dst); |
| EXPECT_EQ(info_nodst.day_index, 100u); |
| EXPECT_EQ(info_nodst.hour_id, hour_id_nodst); |
| } |
| |
| TEST(DatetimeUtilTest, TimeInfoFromTimePointDeprecated) { |
| const uint32_t day_index = 100; |
| auto time = FromUnixSeconds(static_cast<int64_t>(day_index) * util::kNumUnixSecondsPerDay); |
| |
| auto time_info_now = TimeInfo::FromTimePoint(time, MetricDefinition::UTC); |
| |
| ASSERT_EQ(time_info_now.day_index, day_index); |
| // Hour ID is 1 more than day_index * util::kHourIdsPerDay since UTC does |
| // not have daylight savings time. |
| ASSERT_EQ(time_info_now.hour_id, day_index * util::kHourIdsPerDay + 1); |
| } |
| |
| } // namespace cobalt::util |