/* * * Copyright (c) 2020-2021 Project CHIP Authors * Copyright (c) 2013-2017 Nest Labs, Inc. * All rights reserved. * * 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. */ /** * @file * Various utility functions for dealing with time and dates. * */ #include #include #include #include #include #include "TimeUtils.h" namespace chip { enum { // Number of days during the invariant part of the year (after the leap day). kDaysFromMarch1ToDecember31 = 306, // Number of years in a Gregorian "cycle", where a cycle is the 400-year period // over which the Gregorian calendar repeats. kYearsPerCycle = 400, // Total number of days within cycle. kDaysPerCycle = 146097, // Total number of days between 0000/03/01 and 1970/01/01. kEpochOffsetDays = 719468 }; /* Returns the number of days between January 1st and March 1st for a given year. */ static inline uint8_t DaysToMarch1(uint16_t year) { if (IsLeapYear(year)) return 60; return 59; } /* Converts a March-based month number (0=March, 1=April, etc.) to a March-1st based day of year (0=March 1st, 1=March 2nd, etc.). * * NOTE: This is based on the math described in http://howardhinnant.github.io/date_algorithms.html. */ static uint16_t MarchBasedMonthToDayOfYear(uint8_t month) { return static_cast((153 * month + 2) / 5); } /* Converts a March-1st based day of year (0=March 1st, 1=March 2nd, etc.) to a March-based month number (0=March, 1=April, etc.). */ static uint8_t MarchBasedDayOfYearToMonth(uint16_t dayOfYear) { // This assumes dayOfYear is not using the full uint16_t range, so the cast // to uint8_t doesn't overflow. return static_cast((5 * dayOfYear + 2) / 153); } /** * @def IsLeapYear * * @brief * Returns true if the given year is a leap year according to the Gregorian calendar. * * @param year * Gregorian calendar year. * */ bool IsLeapYear(uint16_t year) { return (year % kLeapYearInterval) == 0 && ((year % kYearsPerCentury) != 0 || (year % kYearsPerCycle) == 0); } /** * @def DaysInMonth * * @brief * Returns the number of days in the given month/year. * * @param year * Gregorian calendar year. * * @param month * Month in standard form (1=January ... 12=December). * * @return * Number of days in the given month. */ uint8_t DaysInMonth(uint16_t year, uint8_t month) { static const uint8_t daysInMonth[] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; if (month == kFebruary && IsLeapYear(year)) return 29; if (month >= kJanuary && month <= kDecember) return daysInMonth[month - 1]; return 0; } /** * @def FirstWeekdayOfYear * * @brief * Returns the day of the week for January 1st of the given year. * * @param year * Gregorian calendar year. * * @return * The day-of-week (0=Sunday...6=Saturday). */ uint8_t FirstWeekdayOfYear(uint16_t year) { // Compute the day of the week for the first day of the given year using Gauss' algorithm. return static_cast( (1 + 5 * ((year - 1) % kLeapYearInterval) + 4 * ((year - 1) % kYearsPerCentury) + 6 * ((year - 1) % kYearsPerCycle)) % kDaysPerWeek); } /** * @def OrdinalDateToCalendarDate * * @brief * Convert an ordinal date (year/day-of-year) to a calendar date. * * @param year * Gregorian calendar year. * * @param dayOfYear * Ordinal day of year, base 1 (1=January 1st, 2=January 2nd, etc.). * * @param month * [OUTPUT] Corresponding month in standard form (1=January ... 12=December). * * @param dayOfMonth * [OUTPUT] Corresponding day-of-month in standard form (1=1st, 2=2nd, etc.). * */ void OrdinalDateToCalendarDate(uint16_t year, uint16_t dayOfYear, uint8_t & month, uint8_t & dayOfMonth) { uint8_t daysToMarch1 = DaysToMarch1(year); // Make dayOfYear base 0. dayOfYear = static_cast(dayOfYear - 1); // Adjust dayOfYear to a March 1st base (i.e. 0 = March 1, 1 = March 2, etc.). This numbers January // and February at the end of the range, with the benefit that day numbering is identical between // standard and leap years with the exception of the leap day itself. if (dayOfYear < daysToMarch1) dayOfYear = static_cast(dayOfYear + kDaysFromMarch1ToDecember31); else dayOfYear = static_cast(dayOfYear - daysToMarch1); // Compute a March-based month number (i.e. 0=March...11=February) from the day of year. This is based // on the logic in http://howardhinnant.github.io/date_algorithms.html. month = MarchBasedDayOfYearToMonth(dayOfYear); // Compute the days from March 1st to the start of the corresponding month. uint16_t daysFromMarch1ToStartOfMonth = MarchBasedMonthToDayOfYear(month); // Compute the day of month in standard form (1=1st, 2=2nd, etc.). dayOfMonth = static_cast(dayOfYear - daysFromMarch1ToStartOfMonth + 1); // Convert the month number to standard form (1=January...12=December). month = static_cast(month + (month < 10 ? 3 : -9)); } /** * @def CalendarDateToOrdinalDate * * @brief * Convert an calendar date to ordinal form (year/day-of-year). * * @param year * Gregorian calendar year. * * @param month * Month in standard form (1=January ... 12=December). * * @param dayOfMonth * Day-of-month in standard form (1=1st, 2=2nd, etc.). * * @param dayOfYear * [OUTPUT] Ordinal day of year, base 1 (1=January 1st, 2=January 2nd, etc.). * */ void CalendarDateToOrdinalDate(uint16_t year, uint8_t month, uint8_t dayOfMonth, uint16_t & dayOfYear) { // Convert month to a March-based month number (i.e. 0=March, 1=April, ...11=February). month = static_cast(month + (month > kFebruary ? -3 : 9)); // Compute the days from March 1st to the start of the corresponding month. dayOfYear = MarchBasedMonthToDayOfYear(month); // Adjust dayOfYear to be January-based (0=January 1st, 1=January 2nd...). if (dayOfYear < kDaysFromMarch1ToDecember31) dayOfYear = static_cast(dayOfYear + DaysToMarch1(year)); else dayOfYear = static_cast(dayOfYear - kDaysFromMarch1ToDecember31); // Add in day of month, converting to base 1 in the process. dayOfYear = static_cast(dayOfYear + dayOfMonth); } /** * @def CalendarDateToDaysSinceUnixEpoch * * @brief * Convert a calendar date to the number of days since 1970-01-01. * * @param year * Gregorian calendar year in the range 1970 to 28276. * * @param month * Month in standard form (1=January ... 12=December). * * @param dayOfMonth * Day-of-month in standard form (1=1st, 2=2nd, etc.). * * @param daysSinceEpoch * [OUTPUT] Number of days since 1970-01-01. * * @return * True if the date was converted successfully. False if the given year falls outside the * representable range. * * @note * This function makes no attempt to verify the correct range of any arguments other than year. * Therefore callers must make sure the supplied values are valid prior to calling the function. */ bool CalendarDateToDaysSinceUnixEpoch(uint16_t year, uint8_t month, uint8_t dayOfMonth, uint32_t & daysSinceEpoch) { // NOTE: This algorithm is based on the logic described in http://howardhinnant.github.io/date_algorithms.html. // Return immediately if the year is out of range. if (year < kUnixEpochYear || year > kMaxYearInDaysSinceUnixEpoch32) { daysSinceEpoch = UINT32_MAX; return false; } // Adjust the year and month to be March-based (i.e. 0=March, 1=April, ...11=February). if (month <= kFebruary) { year--; month = static_cast(month + 9); } else month = static_cast(month - 3); // Compute the days from March 1st to the start of the specified day. uint16_t dayOfYear = static_cast(MarchBasedMonthToDayOfYear(month) + (dayOfMonth - 1)); // Compute the 400-year Gregorian "cycle" within which the given year falls. uint16_t cycle = static_cast(year / kYearsPerCycle); // Compute the relative year within the cycle. uint32_t yearOfCycle = year - (cycle * kYearsPerCycle); // Compute the relative day within the cycle, accounting for leap-years. uint32_t dayOfCycle = (yearOfCycle * kDaysPerStandardYear) + dayOfYear - (yearOfCycle / kYearsPerCentury) + (yearOfCycle / kLeapYearInterval); // Compute the total number of days since the start of the logical calendar (0000-03-01). uint32_t daysSinceCalendarStart = (cycle * kDaysPerCycle) + dayOfCycle; // Adjust the days value to be days since 1970-01-01. daysSinceEpoch = daysSinceCalendarStart - kEpochOffsetDays; return true; } /** * @def DaysSinceUnixEpochToCalendarDate * * @brief * Convert the number of days since 1970-01-01 to a calendar date. * * @param daysSinceEpoch * Number of days since 1970-01-01. * * @param year * [OUTPUT] Gregorian calendar year. * * @param month * [OUTPUT] Month in standard form (1=January ... 12=December). * * @param dayOfMonth * [OUTPUT] Day-of-month in standard form (1=1st, 2=2nd, etc.). * * @return * True if the conversion was successful. False if the year would not fit * in uint16_t. */ bool DaysSinceUnixEpochToCalendarDate(uint32_t daysSinceEpoch, uint16_t & year, uint8_t & month, uint8_t & dayOfMonth) { // NOTE: This algorithm is based on the logic described in http://howardhinnant.github.io/date_algorithms.html. if (daysSinceEpoch / kDaysPerStandardYear + 1 > std::numeric_limits::type>::max()) { // Our year calculation will likely overflow. return false; } // Adjust days value to be relative to 0000-03-01. daysSinceEpoch += kEpochOffsetDays; // Compute the 400-year Gregorian cycle in which the given day resides. uint32_t cycle = daysSinceEpoch / kDaysPerCycle; // Compute the relative day within the cycle. uint32_t dayOfCycle = daysSinceEpoch - (cycle * kDaysPerCycle); // Compute the relative year within the cycle, adjusting for leap-years. uint16_t yearOfCycle = static_cast((dayOfCycle - dayOfCycle / 1460 + dayOfCycle / 36524 - dayOfCycle / 146096) / kDaysPerStandardYear); // Compute the relative day with the year. uint16_t dayOfYear = static_cast( dayOfCycle - (yearOfCycle * kDaysPerStandardYear + yearOfCycle / kLeapYearInterval - yearOfCycle / kYearsPerCentury)); // Compute a March-based month number (i.e. 0=March...11=February) from the day of year. month = MarchBasedDayOfYearToMonth(dayOfYear); // Compute the days from March 1st to the start of the corresponding month. uint16_t daysFromMarch1ToStartOfMonth = MarchBasedMonthToDayOfYear(month); // Compute the day of month in standard form (1=1st, 2=2nd, etc.). dayOfMonth = static_cast(dayOfYear - daysFromMarch1ToStartOfMonth + 1); // Convert the month number to standard form (1=January...12=December). month = static_cast(month + (month < 10 ? 3 : -9)); // Compute the year, adjusting for the standard start of year (January). year = static_cast(yearOfCycle + cycle * kYearsPerCycle); if (month <= kFebruary) year++; return true; } /** * @def AdjustCalendarDate * * @brief * Adjust a calendar date by a given number of days (positive or negative). * * @param year * [INPUT/OUTPUT] Gregorian calendar year. * * @param month * [INPUT/OUTPUT] Month in standard form (1=January ... 12=December). * * @param dayOfMonth * [INPUT/OUTPUT] Day-of-month in standard form (1=1st, 2=2nd, etc.). * * @param relativeDays * Number of days to add/subtract from given calendar date. * * @return * True if the adjustment succeeded. False if the adjustment would put us * outside the representable date range. * * @note * Given date must be equal to or greater than 1970-01-01. */ bool AdjustCalendarDate(uint16_t & year, uint8_t & month, uint8_t & dayOfMonth, int32_t relativeDays) { uint32_t daysSinceEpoch; if (!CalendarDateToDaysSinceUnixEpoch(year, month, dayOfMonth, daysSinceEpoch)) { return false; } // Make sure we can do our additions without overflowing. int64_t adjustedDays = static_cast(daysSinceEpoch) + relativeDays; if (!CanCastTo(adjustedDays)) { return false; } return DaysSinceUnixEpochToCalendarDate(static_cast(adjustedDays), year, month, dayOfMonth); } /** * @def CalendarTimeToSecondsSinceUnixEpoch * * @brief * Convert a calendar date and time to the number of seconds since 1970-01-01 00:00:00 UTC. * * @details * This function is roughly equivalent to the POSIX gmtime() function with the exception * that the output time value is limited to positive values up to 2^32-1. This limits the * representable date range to the year 2105. * * @note * This function makes no attempt to verify the correct range of any arguments other than year. * Therefore callers must make sure the supplied values are valid prior to invocation. * * @param secondsSinceEpoch * Number of seconds since 1970-01-01 00:00:00 UTC. Note: this value is compatible with * *positive* values of the POSIX time_t value up to the year 2105. * * @param year * Gregorian calendar year in the range 1970 to 2105. * * @param month * Month in standard form (1=January ... 12=December). * * @param dayOfMonth * Day-of-month in standard form (1=1st, 2=2nd, etc.). * * @param hour * Hour (0-23). * * @param minute * Minute (0-59). * * @param second * Second (0-59). * * @return * True if the date/time was converted successfully. False if the given year falls outside the * representable range. */ bool CalendarTimeToSecondsSinceUnixEpoch(uint16_t year, uint8_t month, uint8_t dayOfMonth, uint8_t hour, uint8_t minute, uint8_t second, uint32_t & secondsSinceEpoch) { uint32_t daysSinceEpoch; // Return immediately if the year is out of range. if (year < kUnixEpochYear || year > kMaxYearInSecondsSinceUnixEpoch32) { secondsSinceEpoch = UINT32_MAX; return false; } CalendarDateToDaysSinceUnixEpoch(year, month, dayOfMonth, daysSinceEpoch); secondsSinceEpoch = (daysSinceEpoch * kSecondsPerDay) + (hour * kSecondsPerHour) + (minute * kSecondsPerMinute) + second; return true; } /** * @brief * Convert the number of seconds since 1970-01-01 00:00:00 UTC to a calendar date and time. * * @note * If secondsSinceEpoch is large enough this function will generate bad result. The way it is * used in this file the generated result should be valid. Specifically, the largest * possible value of secondsSinceEpoch input is (UINT32_MAX + kChipEpochSecondsSinceUnixEpoch), * when it is called from ChipEpochToCalendarTime(). */ static void SecondsSinceUnixEpochToCalendarTime(uint64_t secondsSinceEpoch, uint16_t & year, uint8_t & month, uint8_t & dayOfMonth, uint8_t & hour, uint8_t & minute, uint8_t & second) { uint32_t daysSinceEpoch = static_cast(secondsSinceEpoch / kSecondsPerDay); static_assert((static_cast(UINT32_MAX) + kChipEpochSecondsSinceUnixEpoch) / kSecondsPerDay <= std::numeric_limits::max(), "daysSinceEpoch would overflow"); uint32_t timeOfDay = static_cast(secondsSinceEpoch - (daysSinceEpoch * kSecondsPerDay)); // Note: This call to DaysSinceUnixEpochToCalendarDate can't fail, because we // can't overflow a uint16_t year with a muximum possible value of the // secondsSinceEpoch input. static_assert((static_cast(UINT32_MAX) + kChipEpochSecondsSinceUnixEpoch) / (kDaysPerStandardYear * kSecondsPerDay) + 1 <= std::numeric_limits::type>::max(), "What happened to our year or day lengths?"); DaysSinceUnixEpochToCalendarDate(daysSinceEpoch, year, month, dayOfMonth); hour = static_cast(timeOfDay / kSecondsPerHour); timeOfDay -= (hour * kSecondsPerHour); minute = static_cast(timeOfDay / kSecondsPerMinute); timeOfDay -= (minute * kSecondsPerMinute); second = static_cast(timeOfDay); } /** * @def SecondsSinceUnixEpochToCalendarTime * * @brief * Convert the number of seconds since 1970-01-01 00:00:00 UTC to a calendar date and time. * * @details * This function is roughly equivalent to the POSIX mktime() function, with the following * exceptions: * * - Input time values are limited to positive values up to 2^32-1. This limits the * representable date range to the year 2105. * * - The output time is always UTC (unlike mktime() which outputs time in the process's * configured timezone). * * @param secondsSinceEpoch * Number of seconds since 1970-01-01 00:00:00 UTC. Note: this value is compatible with * *positive* values of the POSIX time_t value up to the year 2105. * * @param year * [OUTPUT] Gregorian calendar year. * * @param month * [OUTPUT] Month in standard form (1=January ... 12=December). * * @param dayOfMonth * [OUTPUT] Day-of-month in standard form (1=1st, 2=2nd, etc.). * * @param hour * [OUTPUT] Hour (0-23). * * @param minute * [OUTPUT] Minute (0-59). * * @param second * [OUTPUT] Second (0-59). */ void SecondsSinceUnixEpochToCalendarTime(uint32_t secondsSinceEpoch, uint16_t & year, uint8_t & month, uint8_t & dayOfMonth, uint8_t & hour, uint8_t & minute, uint8_t & second) { SecondsSinceUnixEpochToCalendarTime(static_cast(secondsSinceEpoch), year, month, dayOfMonth, hour, minute, second); } bool CalendarToChipEpochTime(uint16_t year, uint8_t month, uint8_t dayOfMonth, uint8_t hour, uint8_t minute, uint8_t second, uint32_t & chipEpochTime) { VerifyOrReturnError(year >= kChipEpochBaseYear && year <= kChipEpochMaxYear, false); uint32_t daysSinceUnixEpoch; CalendarDateToDaysSinceUnixEpoch(year, month, dayOfMonth, daysSinceUnixEpoch); chipEpochTime = ((daysSinceUnixEpoch - kChipEpochDaysSinceUnixEpoch) * kSecondsPerDay) + (hour * kSecondsPerHour) + (minute * kSecondsPerMinute) + second; return true; } void ChipEpochToCalendarTime(uint32_t chipEpochTime, uint16_t & year, uint8_t & month, uint8_t & dayOfMonth, uint8_t & hour, uint8_t & minute, uint8_t & second) { SecondsSinceUnixEpochToCalendarTime(static_cast(chipEpochTime) + kChipEpochSecondsSinceUnixEpoch, year, month, dayOfMonth, hour, minute, second); } bool ChipEpochToUnixEpochMicros(uint64_t chipEpochTimeMicros, uint64_t & outUnixEpochTimeMicros) { if ((chipEpochTimeMicros + kChipEpochUsSinceUnixEpoch) < kChipEpochUsSinceUnixEpoch) { return false; } outUnixEpochTimeMicros = chipEpochTimeMicros + kChipEpochUsSinceUnixEpoch; return true; } bool UnixEpochToChipEpochMicros(uint64_t unixEpochTimeMicros, uint64_t & outChipEpochTimeMicros) { VerifyOrReturnValue(unixEpochTimeMicros >= kChipEpochUsSinceUnixEpoch, false); outChipEpochTimeMicros = unixEpochTimeMicros - kChipEpochUsSinceUnixEpoch; return true; } bool UnixEpochToChipEpochTime(uint32_t unixEpochTimeSeconds, uint32_t & outChipEpochTimeSeconds) { VerifyOrReturnError(unixEpochTimeSeconds >= kChipEpochSecondsSinceUnixEpoch, false); outChipEpochTimeSeconds = unixEpochTimeSeconds - kChipEpochSecondsSinceUnixEpoch; return true; } } // namespace chip