#include "CivilTime.h" #include 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::min() || year > std::numeric_limits::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(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::min() || y > std::numeric_limits::max()) { return false; } year = static_cast(y); month = static_cast(m); day = static_cast(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(secondOfDay / 3600); result.minute = static_cast((secondOfDay / 60) % 60); result.second = static_cast(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(localTime.hour) * 3600 + static_cast(localTime.minute) * 60 + static_cast(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(static_cast(hour) * 60u + minute); } std::int64_t CivilDateTime::dateKey() const noexcept { return static_cast(year) * 10000LL + static_cast(month) * 100LL + day; } std::int64_t CivilDateTime::minuteSerial() const noexcept { return static_cast(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::min() || y > std::numeric_limits::max() || m < 1 || m > 12 || d < 1 || d > 31) { return std::numeric_limits::min(); } return CivilTime::daysFromCivil(static_cast(y), static_cast(m), static_cast(d)) * 1440LL + minuteOfDay; } } // namespace timbre_core