| // 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 <iostream> |
| |
| #include "src/logging.h" |
| #include "src/public/lib/statusor/status_macros.h" |
| #include "src/public/lib/statusor/statusor.h" |
| #include "src/registry/metric_definition.pb.h" |
| |
| namespace cobalt::util { |
| |
| namespace { |
| |
| // The algorithm in this file was copied from |
| // http://howardhinnant.github.io/date_algorithms.html |
| // See that site for further explanation. |
| |
| // Recall that a year is a leap year if it is a multiple of 4 that is not |
| // a multiple of 100 unless it is a multiple of 400. Thus the pattern of |
| // leap years is periodic with period 400 years. |
| |
| // There are 146097 days in every 400 year era: |
| // 146097 = 365 * 400 + 100 - 3. Of the integers in the range [0, 399] |
| // there are 100 multiples of 4 but 3 of those are multiples of 100 |
| // and not multiples of 400, namely 100, 200 and 300. |
| constexpr uint32_t kNumDaysPerEra = 146097; |
| |
| // The algorithm below uses an epoch of March 1, year 0 |
| // whereas our API uses an epoch of January 1, 1970. |
| // kEpochOffset is the number of days from 0-3-1 to 1970-1-1 |
| // proof: |
| // The number of days from 0-3-1 to 2000-3-1 is 5*kNumDaysPerEra |
| // The number of days from 1970-3-1 to 2000-3-1 is 30*365 + 8 |
| // because the following years were leap years: '72 '76 '80 '84 '88 '92 '96 2000 |
| // The number of days from 1970-1-1 to 1970-3-1 is 59. |
| constexpr uint32_t kEpochOffset = kNumDaysPerEra * 5L - 30L * 365L - 8L - 59L; |
| |
| using TimeStruct = struct tm; |
| |
| constexpr uint32_t kEpochYearZero = 1970; |
| constexpr uint32_t kTimeStructYearZero = 1900; |
| constexpr uint32_t kMonthsPerYear = 12; |
| constexpr uint32_t kDaysPerWeek = 7; |
| constexpr uint32_t kMaxDaysPerMonth = 31; |
| constexpr uint32_t kMaxCalendarYear = 10000; |
| |
| CalendarDate TimeStructToCalendarDate(const TimeStruct& time_struct) { |
| CalendarDate calendar_date; |
| calendar_date.day_of_month = static_cast<uint32_t>(time_struct.tm_mday); |
| calendar_date.month = static_cast<uint32_t>(time_struct.tm_mon) + 1; |
| calendar_date.year = static_cast<uint32_t>(time_struct.tm_year) + kTimeStructYearZero; |
| return calendar_date; |
| } |
| |
| lib::statusor::StatusOr<std::tm> TimePointToTimeStruct( |
| std::chrono::system_clock::time_point time_point, |
| const util::CivilTimeConverterInterface& civil_time_converter, const MetricDefinition& metric) { |
| if (metric.time_zone_policy() == MetricDefinition::OTHER_TIME_ZONE) { |
| return civil_time_converter.civil_time(time_point, metric.other_time_zone()); |
| } |
| std::tm time_struct; |
| std::time_t time = std::chrono::system_clock::to_time_t(time_point); |
| switch (metric.time_zone_policy()) { |
| case MetricDefinition::LOCAL: |
| localtime_r(&time, &time_struct); |
| break; |
| case MetricDefinition::UTC: |
| gmtime_r(&time, &time_struct); |
| break; |
| default: |
| return Status(StatusCode::INVALID_ARGUMENT, "invalid time_zone_policy"); |
| } |
| return time_struct; |
| } |
| |
| } // namespace |
| |
| lib::statusor::StatusOr<uint32_t> TimePointToHourId( |
| std::chrono::system_clock::time_point time_point, |
| const util::CivilTimeConverterInterface& civil_time_converter, const MetricDefinition& metric) { |
| CB_ASSIGN_OR_RETURN(TimeInfo time_info, |
| TimeInfo::FromTimePoint(time_point, civil_time_converter, metric)); |
| return time_info.hour_id; |
| } |
| |
| lib::statusor::StatusOr<uint32_t> TimePointToDayIndex( |
| std::chrono::system_clock::time_point time_point, |
| const util::CivilTimeConverterInterface& civil_time_converter, const MetricDefinition& metric) { |
| CB_ASSIGN_OR_RETURN(TimeInfo time_info, |
| TimeInfo::FromTimePoint(time_point, civil_time_converter, metric)); |
| return time_info.day_index; |
| } |
| |
| uint32_t TimePointToHourIdUtc(std::chrono::system_clock::time_point time_point) { |
| TimeInfo time_info = TimeInfo::FromTimePointUtc(time_point); |
| return time_info.hour_id; |
| } |
| |
| uint32_t TimePointToDayIndexUtc(std::chrono::system_clock::time_point time_point) { |
| TimeInfo time_info = TimeInfo::FromTimePointUtc(time_point); |
| return time_info.day_index; |
| } |
| |
| template <typename T> |
| inline uint32_t TimeToHourId(time_t time, typename T::TimeZonePolicy time_zone) { |
| TimeStruct time_struct; |
| switch (time_zone) { |
| case T::LOCAL: |
| localtime_r(&time, &time_struct); |
| break; |
| case T::UTC: |
| gmtime_r(&time, &time_struct); |
| break; |
| default: |
| return UINT32_MAX; |
| } |
| return (kHourIdsPerDay * CalendarDateToDayIndex(TimeStructToCalendarDate(time_struct))) + |
| static_cast<uint32_t>(time_struct.tm_hour * 2) + (time_struct.tm_isdst ? 0 : 1); |
| } |
| |
| uint32_t TimeToHourId(time_t time, MetricDefinition::TimeZonePolicy time_zone) { |
| return TimeToHourId<MetricDefinition>(time, time_zone); |
| } |
| |
| template <typename T> |
| inline uint32_t TimeToDayIndex(time_t time, typename T::TimeZonePolicy time_zone) { |
| TimeStruct time_struct; |
| switch (time_zone) { |
| case T::LOCAL: |
| localtime_r(&time, &time_struct); |
| break; |
| case T::UTC: |
| gmtime_r(&time, &time_struct); |
| break; |
| default: |
| return UINT32_MAX; |
| } |
| return CalendarDateToDayIndex(TimeStructToCalendarDate(time_struct)); |
| } |
| |
| uint32_t TimeToDayIndex(time_t time, MetricDefinition::TimeZonePolicy time_zone) { |
| return TimeToDayIndex<MetricDefinition>(time, time_zone); |
| } |
| |
| uint32_t CalendarDateToDayIndex(const CalendarDate& calendar_date) { |
| // This implementation was copied from |
| // http://howardhinnant.github.io/date_algorithms.html#days_from_civil. |
| // This same code is also used in the function DayOrdinal() in |
| // Google's civil_time.cc. |
| // |
| // There is an ostensibly simpler but less portable solution to this |
| // problem presented in CalendarDateToDayIndexAltImpl() in |
| // datetime_util_test.cc. |
| |
| if (calendar_date.year < kEpochYearZero || calendar_date.year >= kMaxCalendarYear || |
| calendar_date.month < 1 || calendar_date.month > kMonthsPerYear || |
| calendar_date.day_of_month < 1 || calendar_date.day_of_month > kMaxDaysPerMonth) { |
| return kInvalidIndex; |
| } |
| |
| // This algorithm counts years as beginning on March 1. Convert to that now. |
| // This trick has the advantage that a leap day is the last day of the year. |
| const uint32_t year = calendar_date.year - (calendar_date.month <= 2 ? 1 : 0); |
| |
| // Which 400-year era is it? |
| const uint32_t era = year / 400; |
| |
| // The year of the era is the year mod 400. |
| const uint32_t yoe = year - era * 400; |
| |
| // Now we compute the day of the year, where March 1 is day number 1. |
| // The main idea is the following trick which uses integer division to |
| // capture the set of months that have 30 instead of 31 days. Let n be a month |
| // number where n = 1 means March, n = 2 means April, etc. Notice that |
| // For n = 1...10, using integer division, we have that |
| // (3n+2)/5 = 1, 1, 2, 2, 3, 4, 4, 5, 5, 6. It follows that the expression |
| // (153*n + 2)/5 yields the number of days from March 1 through the end |
| // of month number n. To see this note that (153*n + 2)/5 = 30n + (3n+2)/5. |
| // Plugging in to the formula we have |
| // n=1 (March) -> 30 + 1 |
| // n=2 (April) -> 30*2 + 1 |
| // n=3 (May) -> 30*3 + 2 |
| // n=4 (June) -> 30*4 + 2 |
| // n=5 (July) -> 30*5 + 3 |
| // n=6 (August) -> 30*6 + 4 |
| // etc. |
| const auto n = static_cast<uint32_t>(static_cast<int>(calendar_date.month) + |
| (calendar_date.month > 2 ? -3 : 9)); |
| const uint32_t doy = (153 * n + 2) / 5 + calendar_date.day_of_month - 1; |
| |
| // Now we compute the day of the era. This is relatively easy using |
| // the formula for leap years described at the top of this file. |
| const uint32_t doe = yoe * 365 + yoe / 4 - yoe / 100 + doy; |
| |
| // shift epoch from 0-03-01 to 1970-01-01 |
| return era * kNumDaysPerEra + doe - kEpochOffset; |
| } |
| |
| time_t MidnightUtcFromDayIndex(uint32_t day_index) { |
| return static_cast<time_t>(day_index) * kNumUnixSecondsPerDay; |
| } |
| |
| CalendarDate DayIndexToCalendarDate(uint32_t day_index) { |
| // This function is an inverse to CalendarDateToDayIndex. |
| // But because gmtime_r is a standard function unlike timegm, we |
| // use the more straightforward implementation in this direction. |
| time_t unix_time = static_cast<time_t>(day_index) * kNumUnixSecondsPerDay; |
| TimeStruct time_struct; |
| gmtime_r(&unix_time, &time_struct); |
| return TimeStructToCalendarDate(time_struct); |
| } |
| |
| uint32_t DayIndexToWeekIndex(uint32_t day_index) { |
| // Day zero was a Thursday which is 4 days after Sunday. |
| return (day_index + 4) / kDaysPerWeek; |
| } |
| |
| uint32_t DayIndexToHourIdNoDst(uint32_t day_index) { return day_index * util::kHourIdsPerDay + 1; } |
| uint32_t HourIdToDayIndex(uint32_t hour_id) { return (hour_id / kHourIdsPerDay); } |
| |
| uint32_t CalendarDateToWeekIndex(const CalendarDate& calendar_date) { |
| return DayIndexToWeekIndex(CalendarDateToDayIndex(calendar_date)); |
| } |
| |
| CalendarDate WeekIndexToCalendarDate(uint32_t week_index) { |
| // Day zero was a Thursday which is 4 days after Sunday. |
| return DayIndexToCalendarDate(week_index * kDaysPerWeek - (week_index > 0 ? 4 : 0)); |
| } |
| |
| uint32_t DayIndexToMonthIndex(uint32_t day_index) { |
| return CalendarDateToMonthIndex(DayIndexToCalendarDate(day_index)); |
| } |
| |
| uint32_t CalendarDateToMonthIndex(const CalendarDate& calendar_date) { |
| if (calendar_date.year < kEpochYearZero || calendar_date.month < 1 || |
| calendar_date.month > kMonthsPerYear) { |
| return UINT32_MAX; |
| } |
| return kMonthsPerYear * (calendar_date.year - kEpochYearZero) + calendar_date.month - 1; |
| } |
| |
| CalendarDate MonthIndexToCalendarDate(uint32_t month_index) { |
| CalendarDate calendar_date; |
| calendar_date.day_of_month = 1; |
| calendar_date.month = (month_index % kMonthsPerYear) + 1; |
| calendar_date.year = month_index / kMonthsPerYear + kEpochYearZero; |
| return calendar_date; |
| } |
| |
| // Returns the the given time as a number of seconds since the Unix epoch. |
| int64_t ToUnixSeconds(std::chrono::system_clock::time_point t) { |
| return (std::chrono::duration_cast<std::chrono::seconds>(t.time_since_epoch())).count(); |
| } |
| |
| int64_t DayIndexToUnixSeconds(uint32_t day_index) { |
| return static_cast<int64_t>(day_index) * kNumUnixSecondsPerDay; |
| } |
| |
| int64_t HourIdToUnixSeconds(uint32_t hour_id) { |
| int64_t unix_seconds = DayIndexToUnixSeconds(HourIdToDayIndex(hour_id)); |
| // Divide by 2 as there are 48 hour_ids in a day. Also removes the possibility of dst. |
| uint32_t remaining_hours = (hour_id % kHourIdsPerDay) / 2; |
| unix_seconds += static_cast<int64_t>(remaining_hours * kNumUnixSecondsPerHour); |
| return unix_seconds; |
| } |
| |
| // Returns the given number of seconds since the Unix epoch as a time_point. |
| std::chrono::system_clock::time_point FromUnixSeconds(int64_t seconds) { |
| return std::chrono::system_clock::time_point( |
| std::chrono::system_clock::duration(std::chrono::seconds(seconds))); |
| } |
| |
| TimeInfo TimeInfo::FromHourId(uint32_t hour_id) { |
| TimeInfo data; |
| data.hour_id = hour_id; |
| data.day_index = HourIdToDayIndex(data.hour_id); |
| return data; |
| } |
| |
| TimeInfo TimeInfo::FromDayIndex(uint32_t day_index) { |
| TimeInfo data; |
| data.day_index = day_index; |
| data.hour_id = DayIndexToHourIdNoDst(day_index); |
| return data; |
| } |
| |
| lib::statusor::StatusOr<TimeInfo> TimeInfo::FromTimePoint( |
| std::chrono::system_clock::time_point time, |
| const util::CivilTimeConverterInterface& civil_time_converter, const MetricDefinition& metric) { |
| TimeInfo data; |
| CB_ASSIGN_OR_RETURN(std::tm time_struct, |
| TimePointToTimeStruct(time, civil_time_converter, metric)); |
| |
| data.day_index = CalendarDateToDayIndex(TimeStructToCalendarDate(time_struct)); |
| data.hour_id = (kHourIdsPerDay * data.day_index) + |
| static_cast<uint32_t>(time_struct.tm_hour * 2) + (time_struct.tm_isdst ? 0 : 1); |
| return data; |
| } |
| |
| TimeInfo TimeInfo::FromTimePointUtc(std::chrono::system_clock::time_point time) { |
| TimeInfo data; |
| std::time_t as_time_t = std::chrono::system_clock::to_time_t(time); |
| std::tm time_struct; |
| gmtime_r(&as_time_t, &time_struct); |
| data.day_index = CalendarDateToDayIndex(TimeStructToCalendarDate(time_struct)); |
| data.hour_id = (kHourIdsPerDay * data.day_index) + |
| static_cast<uint32_t>(time_struct.tm_hour * 2) + (time_struct.tm_isdst ? 0 : 1); |
| return data; |
| } |
| |
| TimeInfo TimeInfo::FromTimePoint(std::chrono::system_clock::time_point time, |
| MetricDefinition::TimeZonePolicy time_zone) { |
| time_t current_time_t = std::chrono::system_clock::to_time_t(time); |
| TimeInfo data; |
| data.day_index = TimeToDayIndex(current_time_t, time_zone); |
| data.hour_id = TimeToHourId(current_time_t, time_zone); |
| return data; |
| } |
| |
| std::ostream& operator<<(std::ostream& o, const TimeInfo& a) { |
| o << "TimeInfo(" << a.day_index << "," << a.hour_id << ")"; |
| return o; |
| } |
| |
| } // namespace cobalt::util |