/* * Copyright (c) Atmosphère-NX * * This program is free software; you can redistribute it and/or modify it * under the terms and conditions of the GNU General Public License, * version 2, as published by the Free Software Foundation. * * This program is distributed in the hope it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for * more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #include namespace ams::time::impl::util { namespace { constexpr inline const int DaysPerMonth[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }; static_assert(std::accumulate(std::begin(DaysPerMonth), std::end(DaysPerMonth), 0) == 365); constexpr inline const std::array SumDaysPerMonth = [] { std::array days = {}; for (size_t i = 1; i < days.size(); ++i) { days[i] = days[i - 1] + DaysPerMonth[i - 1]; } return days; }(); static_assert(SumDaysPerMonth[ 0] == 0); static_assert(SumDaysPerMonth[11] + DaysPerMonth[11] == 365); constexpr bool IsLeapYearImpl(int year) { if ((year % 400) == 0) { return true; } else if ((year % 100) == 0) { return false; } else if ((year % 4) == 0) { return true; } else { return false; } } constexpr int DateToDaysImpl(int year, int month, int day) { /* Lightly validate input. */ AMS_ASSERT(year > 0); AMS_ASSERT(month > 0); AMS_ASSERT(day > 0); /* Adjust months within range. */ year += month / 12; month %= 12; if (month == 0) { month = 12; } AMS_ASSERT(1 <= month && month <= 12); /* Calculate days. */ int res = (year - 1) * 365; res += (year / 4) - (year / 100) + (year / 400); res += SumDaysPerMonth[month - 1] + day; /* Subtract leap day, if it hasn't happened yet. */ if (month < 3 && IsLeapYearImpl(year)) { res -= 1; } /* Subtract the current day. */ res -= 1; return res; } constexpr void DaysToDateImpl(int *out_year, int *out_month, int *out_day, int days) { /* Lightly validate input. */ AMS_ASSERT(days > 0); /* Declare unit conversion factors. */ constexpr int DaysPerYear = 365; constexpr int DaysPerFourYears = DaysPerYear * 4 + 1; constexpr int DaysPerCentury = DaysPerFourYears * 25 - 1; constexpr int DaysPerFourCenturies = DaysPerCentury * 4 + 1; /* Adjust date. */ days -= 59; days += 365; /* Determine various units. */ int four_centuries = days / DaysPerFourCenturies; int four_centuries_rem = days % DaysPerFourCenturies; if (four_centuries_rem < 0) { four_centuries_rem += DaysPerFourCenturies; --four_centuries; } int centuries = four_centuries_rem / DaysPerCentury; int centuries_rem = four_centuries_rem % DaysPerCentury; int four_years = centuries_rem / DaysPerFourYears; int four_years_rem = centuries_rem % DaysPerFourYears; int years = four_years_rem / DaysPerYear; int years_rem = four_years_rem % DaysPerYear; /* Adjust into range. */ int year = 400 * four_centuries + 100 * centuries + 4 * four_years + years; int month = (5 * years_rem + 2) / 153; int day = years_rem - (153 * month + 2) / 5 + 1; /* Adjust in case we fell into a pathological case. */ if (years == 4 || centuries == 4) { month = 11; day = 29; year -= 1; } /* Adjust month. */ if (month <= 9) { month += 3; } else { month -= 9; year += 1; } /* Set output. */ if (out_year) { *out_year = year; } if (out_month) { *out_month = month; } if (out_day) { *out_day = day; } } constexpr inline int EpochDays = DateToDaysImpl(1970, 1, 1); static_assert([]() -> bool { int year{}, month{}, day{}; DaysToDateImpl(std::addressof(year), std::addressof(month), std::addressof(day), EpochDays); return year == 1970 && month == 1 && day == 1; }()); } Result GetSpanBetween(s64 *out, const SteadyClockTimePoint &from, const SteadyClockTimePoint &to) { AMS_ASSERT(out != nullptr); R_UNLESS(out != nullptr, time::ResultInvalidPointer()); R_UNLESS(from.source_id == to.source_id, time::ResultNotComparable()); R_UNLESS(ams::util::TrySubtractWithoutOverflow(out, to.value, from.value), time::ResultOverflowed()); R_SUCCEED(); } bool IsValidDate(int year, int month, int day) { return 1 <= year && 1 <= month && month <= 12 && 1 <= day && day <= GetDaysInMonth(year, month); } bool IsLeapYear(int year) { AMS_ASSERT(year > 0); return IsLeapYearImpl(year); } int GetDaysInMonth(int year, int month) { /* Check pre-conditions. */ AMS_ASSERT(year > 0); AMS_ASSERT(1 <= month && month <= 12); if (month == 2 && IsLeapYear(year)) { return DaysPerMonth[month - 1] + 1; } else { return DaysPerMonth[month - 1]; } } int DateToDays(int year, int month, int day) { return DateToDaysImpl(year, month, day); } void DaysToDate(int *out_year, int *out_month, int *out_day, int days) { DaysToDateImpl(out_year, out_month, out_day, days); } CalendarTime ToCalendarTimeInUtc(const PosixTime &posix_time) { constexpr s64 SecondsPerDay = TimeSpan::FromDays(1).GetSeconds(); constexpr s64 SecondsPerHour = TimeSpan::FromHours(1).GetSeconds(); constexpr s64 SecondsPerMinute = TimeSpan::FromMinutes(1).GetSeconds(); /* Get year/month/day. */ int year, month, day; DaysToDate(std::addressof(year), std::addressof(month), std::addressof(day), static_cast(posix_time.value / SecondsPerDay) + EpochDays); /* Handle negative posix times. */ s64 posix_abs = posix_time.value >= 0 ? posix_time.value : -1 * posix_time.value; s64 posix_rem = posix_abs % SecondsPerDay; if (posix_time.value < 0) { posix_rem *= -1; } /* Adjust remainder if negative. */ if (posix_rem < 0) { if ((--day) <= 0) { if ((--month) <= 0) { --year; month = 12; } day = time::impl::util::GetDaysInMonth(year, month); } posix_rem += SecondsPerDay; } const int hour = posix_rem / SecondsPerHour; posix_rem %= SecondsPerHour; const int minute = posix_rem / SecondsPerMinute; posix_rem %= SecondsPerMinute; const int second = posix_rem; return CalendarTime { .year = static_cast(year), .month = static_cast(month), .day = static_cast(day), .hour = static_cast(hour), .minute = static_cast(minute), .second = static_cast(second), }; } PosixTime ToPosixTimeFromUtc(const CalendarTime &calendar_time) { /* Validate pre-conditions. */ AMS_ASSERT(IsValidDate(calendar_time.year, calendar_time.month, calendar_time.day)); AMS_ASSERT(0 <= calendar_time.hour && calendar_time.hour <= 23); AMS_ASSERT(0 <= calendar_time.minute && calendar_time.minute <= 59); AMS_ASSERT(0 <= calendar_time.second && calendar_time.second <= 59); /* Extract/convert fields. */ const s64 days = static_cast(time::impl::util::DateToDays(calendar_time.year, calendar_time.month, calendar_time.day)) - EpochDays; const s64 hours = calendar_time.hour; const s64 minutes = calendar_time.minute; const s64 seconds = calendar_time.second; return PosixTime { .value = ((((days * 24) + hours) * 60) + minutes) * 60 + seconds }; } }