281 lines
8.6 KiB
C++
Executable File
281 lines
8.6 KiB
C++
Executable File
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#include "core/hle/service/psc/time/time_zone.h"
|
|
|
|
namespace Service::PSC::Time {
|
|
namespace {
|
|
constexpr Result ValidateRule(const Tz::Rule& rule) {
|
|
if (rule.typecnt > static_cast<s32>(Tz::TZ_MAX_TYPES) ||
|
|
rule.timecnt > static_cast<s32>(Tz::TZ_MAX_TIMES) ||
|
|
rule.charcnt > static_cast<s32>(Tz::TZ_MAX_CHARS)) {
|
|
R_RETURN(ResultTimeZoneOutOfRange);
|
|
}
|
|
|
|
for (s32 i = 0; i < rule.timecnt; i++) {
|
|
if (rule.types[i] >= rule.typecnt) {
|
|
R_RETURN(ResultTimeZoneOutOfRange);
|
|
}
|
|
}
|
|
|
|
for (s32 i = 0; i < rule.typecnt; i++) {
|
|
if (rule.ttis[i].tt_desigidx >= static_cast<s32>(rule.chars.size())) {
|
|
R_RETURN(ResultTimeZoneOutOfRange);
|
|
}
|
|
}
|
|
R_SUCCEED();
|
|
}
|
|
|
|
constexpr bool GetTimeZoneTime(s64& out_time, const Tz::Rule& rule, s64 time, s32 index,
|
|
s32 index_offset) {
|
|
s32 found_idx{};
|
|
s32 expected_index{index + index_offset};
|
|
s64 time_to_find{time + rule.ttis[rule.types[index]].tt_utoff -
|
|
rule.ttis[rule.types[expected_index]].tt_utoff};
|
|
|
|
if (rule.timecnt > 1 && rule.ats[0] <= time_to_find) {
|
|
s32 low{1};
|
|
s32 high{rule.timecnt};
|
|
|
|
while (low < high) {
|
|
auto mid{(low + high) / 2};
|
|
if (rule.ats[mid] <= time_to_find) {
|
|
low = mid + 1;
|
|
} else if (rule.ats[mid] > time_to_find) {
|
|
high = mid;
|
|
}
|
|
}
|
|
found_idx = low - 1;
|
|
}
|
|
|
|
if (found_idx == expected_index) {
|
|
out_time = time_to_find;
|
|
}
|
|
return found_idx == expected_index;
|
|
}
|
|
} // namespace
|
|
|
|
void TimeZone::SetTimePoint(SteadyClockTimePoint& time_point) {
|
|
std::scoped_lock l{m_mutex};
|
|
m_steady_clock_time_point = time_point;
|
|
}
|
|
|
|
void TimeZone::SetTotalLocationNameCount(u32 count) {
|
|
std::scoped_lock l{m_mutex};
|
|
m_total_location_name_count = count;
|
|
}
|
|
|
|
void TimeZone::SetRuleVersion(RuleVersion& rule_version) {
|
|
std::scoped_lock l{m_mutex};
|
|
m_rule_version = rule_version;
|
|
}
|
|
|
|
Result TimeZone::GetLocationName(LocationName& out_name) {
|
|
std::scoped_lock l{m_mutex};
|
|
R_UNLESS(m_initialized, ResultClockUninitialized);
|
|
out_name = m_location;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result TimeZone::GetTotalLocationCount(u32& out_count) {
|
|
std::scoped_lock l{m_mutex};
|
|
if (!m_initialized) {
|
|
return ResultClockUninitialized;
|
|
}
|
|
|
|
out_count = m_total_location_name_count;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result TimeZone::GetRuleVersion(RuleVersion& out_rule_version) {
|
|
std::scoped_lock l{m_mutex};
|
|
if (!m_initialized) {
|
|
return ResultClockUninitialized;
|
|
}
|
|
out_rule_version = m_rule_version;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result TimeZone::GetTimePoint(SteadyClockTimePoint& out_time_point) {
|
|
std::scoped_lock l{m_mutex};
|
|
if (!m_initialized) {
|
|
return ResultClockUninitialized;
|
|
}
|
|
out_time_point = m_steady_clock_time_point;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result TimeZone::ToCalendarTime(CalendarTime& out_calendar_time,
|
|
CalendarAdditionalInfo& out_additional_info, s64 time,
|
|
const Tz::Rule& rule) {
|
|
std::scoped_lock l{m_mutex};
|
|
R_RETURN(ToCalendarTimeImpl(out_calendar_time, out_additional_info, time, rule));
|
|
}
|
|
|
|
Result TimeZone::ToCalendarTimeWithMyRule(CalendarTime& calendar_time,
|
|
CalendarAdditionalInfo& calendar_additional, s64 time) {
|
|
// This is checked outside the mutex. Bug?
|
|
if (!m_initialized) {
|
|
return ResultClockUninitialized;
|
|
}
|
|
|
|
std::scoped_lock l{m_mutex};
|
|
R_RETURN(ToCalendarTimeImpl(calendar_time, calendar_additional, time, m_my_rule));
|
|
}
|
|
|
|
Result TimeZone::ParseBinary(LocationName& name, std::span<const u8> binary) {
|
|
std::scoped_lock l{m_mutex};
|
|
|
|
Tz::Rule tmp_rule{};
|
|
R_TRY(ParseBinaryImpl(tmp_rule, binary));
|
|
|
|
m_my_rule = tmp_rule;
|
|
m_location = name;
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result TimeZone::ParseBinaryInto(Tz::Rule& out_rule, std::span<const u8> binary) {
|
|
std::scoped_lock l{m_mutex};
|
|
R_RETURN(ParseBinaryImpl(out_rule, binary));
|
|
}
|
|
|
|
Result TimeZone::ToPosixTime(u32& out_count, std::span<s64> out_times, u32 out_times_count,
|
|
CalendarTime& calendar, const Tz::Rule& rule) {
|
|
std::scoped_lock l{m_mutex};
|
|
|
|
auto res = ToPosixTimeImpl(out_count, out_times, out_times_count, calendar, rule, -1);
|
|
|
|
if (res != ResultSuccess) {
|
|
if (res == ResultTimeZoneNotFound) {
|
|
res = ResultSuccess;
|
|
out_count = 0;
|
|
}
|
|
} else if (out_count == 2 && out_times[0] > out_times[1]) {
|
|
std::swap(out_times[0], out_times[1]);
|
|
}
|
|
R_RETURN(res);
|
|
}
|
|
|
|
Result TimeZone::ToPosixTimeWithMyRule(u32& out_count, std::span<s64> out_times,
|
|
u32 out_times_count, CalendarTime& calendar) {
|
|
std::scoped_lock l{m_mutex};
|
|
|
|
auto res = ToPosixTimeImpl(out_count, out_times, out_times_count, calendar, m_my_rule, -1);
|
|
|
|
if (res != ResultSuccess) {
|
|
if (res == ResultTimeZoneNotFound) {
|
|
res = ResultSuccess;
|
|
out_count = 0;
|
|
}
|
|
} else if (out_count == 2 && out_times[0] > out_times[1]) {
|
|
std::swap(out_times[0], out_times[1]);
|
|
}
|
|
R_RETURN(res);
|
|
}
|
|
|
|
Result TimeZone::ParseBinaryImpl(Tz::Rule& out_rule, std::span<const u8> binary) {
|
|
if (Tz::ParseTimeZoneBinary(out_rule, binary)) {
|
|
R_RETURN(ResultTimeZoneParseFailed);
|
|
}
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result TimeZone::ToCalendarTimeImpl(CalendarTime& out_calendar_time,
|
|
CalendarAdditionalInfo& out_additional_info, s64 time,
|
|
const Tz::Rule& rule) {
|
|
R_TRY(ValidateRule(rule));
|
|
|
|
Tz::CalendarTimeInternal calendar_internal{};
|
|
time_t time_tmp{static_cast<time_t>(time)};
|
|
if (Tz::localtime_rz(&calendar_internal, &rule, &time_tmp)) {
|
|
R_RETURN(ResultOverflow);
|
|
}
|
|
|
|
out_calendar_time.year = static_cast<s16>(calendar_internal.tm_year + 1900);
|
|
out_calendar_time.month = static_cast<s8>(calendar_internal.tm_mon + 1);
|
|
out_calendar_time.day = static_cast<s8>(calendar_internal.tm_mday);
|
|
out_calendar_time.hour = static_cast<s8>(calendar_internal.tm_hour);
|
|
out_calendar_time.minute = static_cast<s8>(calendar_internal.tm_min);
|
|
out_calendar_time.second = static_cast<s8>(calendar_internal.tm_sec);
|
|
|
|
out_additional_info.day_of_week = calendar_internal.tm_wday;
|
|
out_additional_info.day_of_year = calendar_internal.tm_yday;
|
|
|
|
std::memcpy(out_additional_info.name.data(), calendar_internal.tm_zone.data(),
|
|
out_additional_info.name.size());
|
|
out_additional_info.name[out_additional_info.name.size() - 1] = '\0';
|
|
|
|
out_additional_info.is_dst = calendar_internal.tm_isdst;
|
|
out_additional_info.ut_offset = calendar_internal.tm_utoff;
|
|
|
|
R_SUCCEED();
|
|
}
|
|
|
|
Result TimeZone::ToPosixTimeImpl(u32& out_count, std::span<s64> out_times, u32 out_times_count,
|
|
CalendarTime& calendar, const Tz::Rule& rule, s32 is_dst) {
|
|
R_TRY(ValidateRule(rule));
|
|
|
|
calendar.month -= 1;
|
|
calendar.year -= 1900;
|
|
|
|
Tz::CalendarTimeInternal internal{
|
|
.tm_sec = calendar.second,
|
|
.tm_min = calendar.minute,
|
|
.tm_hour = calendar.hour,
|
|
.tm_mday = calendar.day,
|
|
.tm_mon = calendar.month,
|
|
.tm_year = calendar.year,
|
|
.tm_wday = 0,
|
|
.tm_yday = 0,
|
|
.tm_isdst = is_dst,
|
|
.tm_zone = {},
|
|
.tm_utoff = 0,
|
|
.time_index = 0,
|
|
};
|
|
time_t time_tmp{};
|
|
auto res = Tz::mktime_tzname(&time_tmp, &rule, &internal);
|
|
s64 time = static_cast<s64>(time_tmp);
|
|
|
|
if (res == 1) {
|
|
R_RETURN(ResultOverflow);
|
|
} else if (res == 2) {
|
|
R_RETURN(ResultTimeZoneNotFound);
|
|
}
|
|
|
|
if (internal.tm_sec != calendar.second || internal.tm_min != calendar.minute ||
|
|
internal.tm_hour != calendar.hour || internal.tm_mday != calendar.day ||
|
|
internal.tm_mon != calendar.month || internal.tm_year != calendar.year) {
|
|
R_RETURN(ResultTimeZoneNotFound);
|
|
}
|
|
|
|
if (res != 0) {
|
|
ASSERT(false);
|
|
}
|
|
|
|
out_times[0] = time;
|
|
if (out_times_count < 2) {
|
|
out_count = 1;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
s64 time2{};
|
|
if (internal.time_index > 0 && GetTimeZoneTime(time2, rule, time, internal.time_index, -1)) {
|
|
out_times[1] = time2;
|
|
out_count = 2;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
if (((internal.time_index + 1) < rule.timecnt) &&
|
|
GetTimeZoneTime(time2, rule, time, internal.time_index, 1)) {
|
|
out_times[1] = time2;
|
|
out_count = 2;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
out_count = 1;
|
|
R_SUCCEED();
|
|
}
|
|
|
|
} // namespace Service::PSC::Time
|