blob: a4b080d13456592875dbb7154eaccbd4ca92fa17 [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 <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