blob: 04db8befa38beff774cea4a9e247bcbbe102549d [file] [log] [blame]
// Copyright 2016 The Fuchsia Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <ctime>
#include "util/datetime_util.h"
namespace cobalt {
namespace 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.
static const 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.
static const uint32_t kEpochOffset =
kNumDaysPerEra * 5L - 30L * 365L - 8L - 59L;
typedef struct tm TimeInfo;
CalendarDate TimeInfoToCalendarDate(const TimeInfo& time_info) {
CalendarDate calendar_date;
calendar_date.day_of_month = time_info.tm_mday;
calendar_date.month = time_info.tm_mon + 1;
calendar_date.year = time_info.tm_year + 1900;
return calendar_date;
}
} // namespace
uint32_t TimeToDayIndex(time_t time, Metric::TimeZonePolicy time_zone) {
TimeInfo time_info;
switch (time_zone) {
case Metric::LOCAL:
localtime_r(&time, &time_info);
break;
case Metric::UTC:
gmtime_r(&time, &time_info);
break;
default:
return UINT32_MAX;
}
return CalendarDateToDayIndex(TimeInfoToCalendarDate(time_info));
}
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 < 1970 || calendar_date.year >= 10000 ||
calendar_date.month < 1 || calendar_date.month > 12 ||
calendar_date.day_of_month < 1 || calendar_date.day_of_month > 31) {
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 uint32_t doy =
(153 * (calendar_date.month + (calendar_date.month > 2 ? -3 : 9)) + 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 int 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 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 = day_index * kNumUnixSecondsPerDay;
TimeInfo time_info;
gmtime_r(&unix_time, &time_info);
return TimeInfoToCalendarDate(time_info);
}
uint32_t DayIndexToWeekIndex(uint32_t day_index) {
// Day zero was a Thursday which is 4 days after Sunday.
return (day_index + 4) / 7;
}
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 * 7 - (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 < 1970 || calendar_date.month < 1 ||
calendar_date.month > 12) {
return UINT32_MAX;
}
return 12 * (calendar_date.year - 1970) + calendar_date.month - 1;
}
CalendarDate MonthIndexToCalendarDate(uint32_t month_index) {
CalendarDate calendar_date;
calendar_date.day_of_month = 1;
calendar_date.month = (month_index % 12) + 1;
calendar_date.year = month_index / 12 + 1970;
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();
}
// 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)));
}
} // namespace util
} // namespace cobalt