blob: 804ebf1ab895856e0bb7fe704fbd753a6980d01a [file] [log] [blame]
/*
*
* 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.
*
*/
#ifndef __STDC_LIMIT_MACROS
#define __STDC_LIMIT_MACROS
#endif
#include <limits>
#include <stdint.h>
#include <type_traits>
#include <lib/core/CHIPCore.h>
#include <lib/support/SafeInt.h>
#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<uint16_t>((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<uint8_t>((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<uint8_t>(
(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<uint16_t>(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<uint16_t>(dayOfYear + kDaysFromMarch1ToDecember31);
else
dayOfYear = static_cast<uint16_t>(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<uint8_t>(dayOfYear - daysFromMarch1ToStartOfMonth + 1);
// Convert the month number to standard form (1=January...12=December).
month = static_cast<uint8_t>(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<uint8_t>(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<uint16_t>(dayOfYear + DaysToMarch1(year));
else
dayOfYear = static_cast<uint16_t>(dayOfYear - kDaysFromMarch1ToDecember31);
// Add in day of month, converting to base 1 in the process.
dayOfYear = static_cast<uint16_t>(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<uint8_t>(month + 9);
}
else
month = static_cast<uint8_t>(month - 3);
// Compute the days from March 1st to the start of the specified day.
uint16_t dayOfYear = static_cast<uint16_t>(MarchBasedMonthToDayOfYear(month) + (dayOfMonth - 1));
// Compute the 400-year Gregorian "cycle" within which the given year falls.
uint16_t cycle = static_cast<uint16_t>(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<std::remove_reference<decltype(year)>::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<uint16_t>((dayOfCycle - dayOfCycle / 1460 + dayOfCycle / 36524 - dayOfCycle / 146096) / kDaysPerStandardYear);
// Compute the relative day with the year.
uint16_t dayOfYear = static_cast<uint16_t>(
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<uint8_t>(dayOfYear - daysFromMarch1ToStartOfMonth + 1);
// Convert the month number to standard form (1=January...12=December).
month = static_cast<uint8_t>(month + (month < 10 ? 3 : -9));
// Compute the year, adjusting for the standard start of year (January).
year = static_cast<uint16_t>(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<int64_t>(daysSinceEpoch) + relativeDays;
if (!CanCastTo<uint32_t>(adjustedDays))
{
return false;
}
return DaysSinceUnixEpochToCalendarDate(static_cast<uint32_t>(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<uint32_t>(secondsSinceEpoch / kSecondsPerDay);
static_assert((static_cast<uint64_t>(UINT32_MAX) + kChipEpochSecondsSinceUnixEpoch) / kSecondsPerDay <=
std::numeric_limits<decltype(daysSinceEpoch)>::max(),
"daysSinceEpoch would overflow");
uint32_t timeOfDay = static_cast<uint32_t>(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<uint64_t>(UINT32_MAX) + kChipEpochSecondsSinceUnixEpoch) / (kDaysPerStandardYear * kSecondsPerDay) +
1 <=
std::numeric_limits<std::remove_reference<decltype(year)>::type>::max(),
"What happened to our year or day lengths?");
DaysSinceUnixEpochToCalendarDate(daysSinceEpoch, year, month, dayOfMonth);
hour = static_cast<uint8_t>(timeOfDay / kSecondsPerHour);
timeOfDay -= (hour * kSecondsPerHour);
minute = static_cast<uint8_t>(timeOfDay / kSecondsPerMinute);
timeOfDay -= (minute * kSecondsPerMinute);
second = static_cast<uint8_t>(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<uint64_t>(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<uint64_t>(chipEpochTime) + kChipEpochSecondsSinceUnixEpoch, year, month,
dayOfMonth, hour, minute, second);
}
bool UnixEpochToChipEpochTime(uint32_t unixEpochTime, uint32_t & chipEpochTime)
{
VerifyOrReturnError(unixEpochTime >= kChipEpochSecondsSinceUnixEpoch, false);
chipEpochTime = unixEpochTime - kChipEpochSecondsSinceUnixEpoch;
return true;
}
} // namespace chip