| // 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 |
| |
| template <typename T> |
| inline uint32_t TimeToDayIndex(time_t time, |
| typename T::TimeZonePolicy time_zone) { |
| TimeInfo time_info; |
| switch (time_zone) { |
| case T::LOCAL: |
| localtime_r(&time, &time_info); |
| break; |
| case T::UTC: |
| gmtime_r(&time, &time_info); |
| break; |
| default: |
| return UINT32_MAX; |
| } |
| return CalendarDateToDayIndex(TimeInfoToCalendarDate(time_info)); |
| } |
| |
| uint32_t TimeToDayIndex(time_t time, Metric::TimeZonePolicy time_zone) { |
| return TimeToDayIndex<Metric>(time, time_zone); |
| } |
| |
| 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 < 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 |