blob: 47bda4cbce48df90732875f6528a4ae0c0c2d3b3 [file] [log] [blame]
// 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