Files
TimbreESP32/lib/timbre_core/CivilTime.cpp
Angel Ivan 2a9eabce14 Fist commit
2026-10-02 12:08:21 -06:00

199 lines
7.3 KiB
C++
Executable File

#include "CivilTime.h"
#include <limits>
namespace timbre_core {
namespace {
constexpr std::int64_t kSecondsPerDay = 86400;
std::int64_t floorDiv(std::int64_t value, std::int64_t divisor) noexcept {
const std::int64_t quotient = value / divisor;
const std::int64_t remainder = value % divisor;
return remainder != 0 && ((remainder < 0) != (divisor < 0)) ? quotient - 1 : quotient;
}
} // namespace
bool CivilTime::isLeapYear(std::int32_t year) noexcept {
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}
std::uint8_t CivilTime::daysInMonth(std::int32_t year, std::uint8_t month) noexcept {
static constexpr std::uint8_t days[] = {
0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31
};
if (month < 1 || month > 12) {
return 0;
}
if (month == 2 && isLeapYear(year)) {
return 29;
}
return days[month];
}
bool CivilTime::isValidDate(std::int32_t year, std::uint8_t month, std::uint8_t day) noexcept {
// The conversion routines are useful well beyond the current product
// lifetime, but keep the public civil value inside int32_t.
if (year < std::numeric_limits<std::int32_t>::min() ||
year > std::numeric_limits<std::int32_t>::max()) {
return false;
}
return day >= 1 && day <= daysInMonth(year, month);
}
std::uint8_t CivilTime::weekdayForDate(std::int32_t year, std::uint8_t month,
std::uint8_t day) noexcept {
if (!isValidDate(year, month, day)) {
return 0;
}
// 1970-01-01 was Thursday (4 with Sunday == 0).
const std::int64_t days = daysFromCivil(year, month, day);
std::int64_t weekday = (days + 4) % 7;
if (weekday < 0) {
weekday += 7;
}
return static_cast<std::uint8_t>(weekday);
}
std::int64_t CivilTime::daysFromCivil(std::int32_t year, std::uint8_t month,
std::uint8_t day) noexcept {
// Howard Hinnant's civil calendar transform. It is valid for the full
// practical range of the public model and does not depend on time_t.
std::int64_t y = year;
y -= month <= 2 ? 1 : 0;
const std::int64_t era = (y >= 0 ? y : y - 399) / 400;
const std::int64_t yearOfEra = y - era * 400;
const std::int64_t monthPrime = month + (month > 2 ? -3 : 9);
const std::int64_t dayOfYear = (153 * monthPrime + 2) / 5 + day - 1;
const std::int64_t dayOfEra = yearOfEra * 365 + yearOfEra / 4 - yearOfEra / 100 + dayOfYear;
return era * 146097 + dayOfEra - 719468;
}
bool CivilTime::civilFromDays(std::int64_t days, std::int32_t& year,
std::uint8_t& month, std::uint8_t& day) noexcept {
// The inverse transform is kept separate so weekday/date validation and
// the scheduler can use exactly the same arithmetic.
days += 719468;
const std::int64_t era = (days >= 0 ? days : days - 146096) / 146097;
const std::int64_t dayOfEra = days - era * 146097;
const std::int64_t yearOfEra =
(dayOfEra - dayOfEra / 1460 + dayOfEra / 36524 - dayOfEra / 146096) / 365;
std::int64_t y = yearOfEra + era * 400;
const std::int64_t dayOfYear =
dayOfEra - (365 * yearOfEra + yearOfEra / 4 - yearOfEra / 100);
const std::int64_t monthPrime =
(5 * dayOfYear + 2) / 153;
const std::int64_t d = dayOfYear - (153 * monthPrime + 2) / 5 + 1;
const std::int64_t m = monthPrime + (monthPrime < 10 ? 3 : -9);
y += m <= 2;
if (y < std::numeric_limits<std::int32_t>::min() ||
y > std::numeric_limits<std::int32_t>::max()) {
return false;
}
year = static_cast<std::int32_t>(y);
month = static_cast<std::uint8_t>(m);
day = static_cast<std::uint8_t>(d);
return true;
}
CivilDateTime CivilTime::fromUnixSeconds(std::int64_t utcSeconds) noexcept {
CivilDateTime result;
(void)fromUnixSeconds(utcSeconds, result);
return result;
}
bool CivilTime::fromUnixSeconds(std::int64_t utcSeconds, CivilDateTime& result) noexcept {
// Use floor seconds so epochs before 1970 are not biased toward 1969.
const std::int64_t localSeconds = utcSeconds + kUtcOffsetSeconds;
const std::int64_t days = floorDiv(localSeconds, kSecondsPerDay);
std::int64_t secondOfDay = localSeconds - days * kSecondsPerDay;
if (secondOfDay < 0) {
secondOfDay += kSecondsPerDay;
}
std::int32_t year = 0;
std::uint8_t month = 0;
std::uint8_t day = 0;
if (!civilFromDays(days, year, month, day)) {
return false;
}
result.year = year;
result.month = month;
result.day = day;
result.hour = static_cast<std::uint8_t>(secondOfDay / 3600);
result.minute = static_cast<std::uint8_t>((secondOfDay / 60) % 60);
result.second = static_cast<std::uint8_t>(secondOfDay % 60);
result.weekday = weekdayForDate(year, month, day);
return true;
}
CivilDateTime CivilTime::fromUnixMilliseconds(std::int64_t utcMilliseconds) noexcept {
const std::int64_t seconds = floorDiv(utcMilliseconds, 1000);
return fromUnixSeconds(seconds);
}
std::int64_t CivilTime::toUnixSeconds(const CivilDateTime& localTime) noexcept {
std::int64_t result = 0;
(void)toUnixSeconds(localTime, result);
return result;
}
bool CivilTime::toUnixSeconds(const CivilDateTime& localTime, std::int64_t& result) noexcept {
if (!localTime.valid()) {
return false;
}
const std::int64_t localSeconds =
CivilTime::daysFromCivil(localTime.year, localTime.month, localTime.day) * kSecondsPerDay +
static_cast<std::int64_t>(localTime.hour) * 3600 +
static_cast<std::int64_t>(localTime.minute) * 60 +
static_cast<std::int64_t>(localTime.second);
result = localSeconds - kUtcOffsetSeconds;
return true;
}
std::int64_t CivilTime::toUnixMilliseconds(const CivilDateTime& localTime) noexcept {
return toUnixSeconds(localTime) * 1000;
}
bool CivilDateTime::valid() const noexcept {
return CivilTime::isValidDate(year, month, day) && hour < 24 &&
minute < 60 && second < 60;
}
std::uint16_t CivilDateTime::minuteOfDay() const noexcept {
return static_cast<std::uint16_t>(static_cast<std::uint16_t>(hour) * 60u + minute);
}
std::int64_t CivilDateTime::dateKey() const noexcept {
return static_cast<std::int64_t>(year) * 10000LL +
static_cast<std::int64_t>(month) * 100LL + day;
}
std::int64_t CivilDateTime::minuteSerial() const noexcept {
return static_cast<std::int64_t>(CivilTime::daysFromCivil(year, month, day)) * 1440LL +
minuteOfDay();
}
bool CivilDateTime::operator==(const CivilDateTime& other) const noexcept {
return year == other.year && month == other.month && day == other.day &&
hour == other.hour && minute == other.minute && second == other.second;
}
std::int64_t LocalMinuteKey::serial() const noexcept {
const std::int64_t y = date / 10000LL;
const std::int64_t m = (date / 100LL) % 100LL;
const std::int64_t d = date % 100LL;
if (y < std::numeric_limits<std::int32_t>::min() ||
y > std::numeric_limits<std::int32_t>::max() || m < 1 || m > 12 || d < 1 || d > 31) {
return std::numeric_limits<std::int64_t>::min();
}
return CivilTime::daysFromCivil(static_cast<std::int32_t>(y),
static_cast<std::uint8_t>(m),
static_cast<std::uint8_t>(d)) * 1440LL + minuteOfDay;
}
} // namespace timbre_core