199 lines
7.3 KiB
C++
Executable File
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
|