From 78f977c980e125e92b86261335447d0a254f18ee Mon Sep 17 00:00:00 2001 From: bunnei Date: Sun, 22 Dec 2019 17:49:51 -0500 Subject: service: time: Rewrite implementation of glue services. --- src/core/hle/service/time/time_zone_manager.cpp | 1038 +++++++++++++++++++++++ 1 file changed, 1038 insertions(+) create mode 100644 src/core/hle/service/time/time_zone_manager.cpp (limited to 'src/core/hle/service/time/time_zone_manager.cpp') diff --git a/src/core/hle/service/time/time_zone_manager.cpp b/src/core/hle/service/time/time_zone_manager.cpp new file mode 100644 index 000000000..c06ca538a --- /dev/null +++ b/src/core/hle/service/time/time_zone_manager.cpp @@ -0,0 +1,1038 @@ +// Copyright 2019 yuzu emulator team +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include + +#include + +#include "common/assert.h" +#include "common/logging/log.h" +#include "core/file_sys/content_archive.h" +#include "core/file_sys/nca_metadata.h" +#include "core/file_sys/registered_cache.h" +#include "core/file_sys/romfs.h" +#include "core/file_sys/system_archive/system_archive.h" +#include "core/hle/service/time/time_zone_manager.h" + +namespace Service::Time::TimeZone { + +static constexpr s32 epoch_year{1970}; +static constexpr s32 year_base{1900}; +static constexpr s32 epoch_week_day{4}; +static constexpr s32 seconds_per_minute{60}; +static constexpr s32 minutes_per_hour{60}; +static constexpr s32 hours_per_day{24}; +static constexpr s32 days_per_week{7}; +static constexpr s32 days_per_normal_year{365}; +static constexpr s32 days_per_leap_year{366}; +static constexpr s32 months_per_year{12}; +static constexpr s32 seconds_per_hour{seconds_per_minute * minutes_per_hour}; +static constexpr s32 seconds_per_day{seconds_per_hour * hours_per_day}; +static constexpr s32 years_per_repeat{400}; +static constexpr s64 average_seconds_per_year{31556952}; +static constexpr s64 seconds_per_repeat{years_per_repeat * average_seconds_per_year}; + +struct Rule { + enum class Type : u32 { JulianDay, DayOfYear, MonthNthDayOfWeek }; + Type rule_type{}; + s32 day{}; + s32 week{}; + s32 month{}; + s32 transition_time{}; +}; + +struct CalendarTimeInternal { + s64 year{}; + s8 month{}; + s8 day{}; + s8 hour{}; + s8 minute{}; + s8 second{}; + int Compare(const CalendarTimeInternal& other) const { + if (year != other.year) { + if (year < other.year) { + return -1; + } + return 1; + } + if (month != other.month) { + return month - other.month; + } + if (day != other.day) { + return day - other.day; + } + if (hour != other.hour) { + return hour - other.hour; + } + if (minute != other.minute) { + return minute - other.minute; + } + if (second != other.second) { + return second - other.second; + } + return {}; + } +}; + +template +static bool SafeAdd(TResult& result, TOperand op) { + const boost::safe_numerics::safe safe_result{result}; + const boost::safe_numerics::safe safe_op{op}; + try { + result = safe_result + safe_op; + } catch (const std::exception&) { + return {}; // Failed with undefined behavior + } + return true; +} + +template +static bool SafeNormalize(TResult& result, TUnit& unit, TBase base) { + TUnit delta{}; + if (unit >= 0) { + delta = unit / base; + } else { + delta = -1 - (-1 - unit) / base; + } + unit -= delta * base; + return SafeAdd(result, delta); +} + +template +static constexpr bool IsLeapYear(T year) { + return ((year) % 4) == 0 && (((year) % 100) != 0 || ((year) % 400) == 0); +} + +template +static constexpr T GetYearLengthInDays(T year) { + return IsLeapYear(year) ? days_per_leap_year : days_per_normal_year; +} + +static constexpr s64 GetLeapDaysFromYearPositive(s64 year) { + return year / 4 - year / 100 + year / years_per_repeat; +} + +static constexpr s64 GetLeapDaysFromYear(s64 year) { + if (year < 0) { + return -1 - GetLeapDaysFromYearPositive(-1 - year); + } else { + return GetLeapDaysFromYearPositive(year); + } +} + +static constexpr int GetMonthLength(bool is_leap_year, int month) { + constexpr std::array month_lengths{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31}; + constexpr std::array month_lengths_leap{31, 29, 31, 30, 31, 30, + 31, 31, 30, 31, 30, 31}; + return is_leap_year ? month_lengths_leap[month] : month_lengths[month]; +} + +static constexpr bool IsDigit(char value) { + return value >= '0' && value <= '9'; +} + +static constexpr int GetQZName(const char* name, int offset, char delimiter) { + while (name[offset] != '\0' && name[offset] != delimiter) { + offset++; + } + return offset; +} + +static constexpr int GetTZName(const char* name, int offset) { + for (char value{name[offset]}; + value != '\0' && !IsDigit(value) && value != ',' && value != '-' && value != '+'; + offset++) { + value = name[offset]; + } + return offset; +} + +static constexpr bool GetInteger(const char* name, int& offset, int& value, int min, int max) { + value = 0; + char temp{name[offset]}; + if (!IsDigit(temp)) { + return {}; + } + do { + value = value * 10 + (temp - '0'); + if (value > max) { + return {}; + } + temp = name[offset]; + } while (IsDigit(temp)); + + return value >= min; +} + +static constexpr bool GetSeconds(const char* name, int& offset, int& seconds) { + seconds = 0; + int value{}; + if (!GetInteger(name, offset, value, 0, hours_per_day * days_per_week - 1)) { + return {}; + } + seconds = value * seconds_per_hour; + + if (name[offset] == ':') { + offset++; + if (!GetInteger(name, offset, value, 0, minutes_per_hour - 1)) { + return {}; + } + seconds += value * seconds_per_minute; + if (name[offset] == ':') { + offset++; + if (!GetInteger(name, offset, value, 0, seconds_per_minute)) { + return {}; + } + seconds += value; + } + } + return true; +} + +static constexpr bool GetOffset(const char* name, int& offset, int& value) { + bool is_negative{}; + if (name[offset] == '-') { + is_negative = true; + offset++; + } else if (name[offset] == '+') { + offset++; + } + if (!GetSeconds(name, offset, value)) { + return {}; + } + if (is_negative) { + value = -value; + } + return true; +} + +static constexpr bool GetRule(const char* name, int& position, Rule& rule) { + bool is_valid{}; + if (name[position] == 'J') { + position++; + rule.rule_type = Rule::Type::JulianDay; + is_valid = GetInteger(name, position, rule.day, 1, days_per_normal_year); + } else if (name[position] == 'M') { + position++; + rule.rule_type = Rule::Type::MonthNthDayOfWeek; + is_valid = GetInteger(name, position, rule.month, 1, months_per_year); + if (!is_valid) { + return {}; + } + if (name[position++] != '.') { + return {}; + } + is_valid = GetInteger(name, position, rule.week, 1, 5); + if (!is_valid) { + return {}; + } + if (name[position++] != '.') { + return {}; + } + is_valid = GetInteger(name, position, rule.day, 0, days_per_week - 1); + } else if (isdigit(name[position])) { + rule.rule_type = Rule::Type::DayOfYear; + is_valid = GetInteger(name, position, rule.day, 0, days_per_leap_year - 1); + } else { + return {}; + } + if (!is_valid) { + return {}; + } + if (name[position] == '/') { + position++; + return GetOffset(name, position, rule.transition_time); + } else { + rule.transition_time = 2 * seconds_per_hour; + } + return true; +} + +static constexpr int TransitionTime(int year, Rule rule, int offset) { + int value{}; + switch (rule.rule_type) { + case Rule::Type::JulianDay: + value = (rule.day - 1) * seconds_per_day; + if (IsLeapYear(year) && rule.day >= 60) { + value += seconds_per_day; + } + break; + case Rule::Type::DayOfYear: + value = rule.day * seconds_per_day; + break; + case Rule::Type::MonthNthDayOfWeek: { + // Use Zeller's Congruence (https://en.wikipedia.org/wiki/Zeller%27s_congruence) to + // calculate the day of the week for any Julian or Gregorian calendar date. + const int m1{(rule.month + 9) % 12 + 1}; + const int yy0{(rule.month <= 2) ? (year - 1) : year}; + const int yy1{yy0 / 100}; + const int yy2{yy0 % 100}; + int day_of_week{((26 * m1 - 2) / 10 + 1 + yy2 + yy2 / 4 + yy1 / 4 - 2 * yy1) % 7}; + + if (day_of_week < 0) { + day_of_week += days_per_week; + } + int day{rule.day - day_of_week}; + if (day < 0) { + day += days_per_week; + } + for (int i{1}; i < rule.week; i++) { + if (day + days_per_week >= GetMonthLength(IsLeapYear(year), rule.month - 1)) { + break; + } + day += days_per_week; + } + + value = day * seconds_per_day; + for (int index{}; index < rule.month - 1; ++index) { + value += GetMonthLength(IsLeapYear(year), index) * seconds_per_day; + } + break; + } + default: + UNREACHABLE(); + } + return value + rule.transition_time + offset; +} + +static bool ParsePosixName(const char* name, TimeZoneRule& rule) { + constexpr char default_rule[]{",M4.1.0,M10.5.0"}; + const char* std_name{name}; + int std_len{}; + int offset{}; + int std_offset{}; + + if (name[offset] == '<') { + offset++; + std_name = name + offset; + const int std_name_offset{offset}; + offset = GetQZName(name, offset, '>'); + if (name[offset] != '>') { + return {}; + } + std_len = offset - std_name_offset; + offset++; + } else { + offset = GetTZName(name, offset); + std_len = offset; + } + if (!std_len) { + return {}; + } + if (!GetOffset(name, offset, std_offset)) { + return {}; + } + + int char_count{std_len + 1}; + int dest_len{}; + int dest_offset{}; + const char* dest_name{name + offset}; + if (rule.chars.size() < char_count) { + return {}; + } + + if (name[offset] != '\0') { + if (name[offset] == '<') { + dest_name = name + (++offset); + const int dest_name_offset{offset}; + offset = GetQZName(name, offset, '>'); + if (name[offset] != '>') { + return {}; + } + dest_len = offset - dest_name_offset; + offset++; + } else { + dest_name = name + (offset); + offset = GetTZName(name, offset); + dest_len = offset; + } + if (dest_len == 0) { + return {}; + } + char_count += dest_len + 1; + if (rule.chars.size() < char_count) { + return {}; + } + if (name[offset] != '\0' && name[offset] != ',' && name[offset] != ';') { + if (!GetOffset(name, offset, dest_offset)) { + return {}; + } + } else { + dest_offset = std_offset - seconds_per_hour; + } + if (name[offset] == '\0') { + name = default_rule; + offset = 0; + } + if (name[offset] == ',' || name[offset] == ';') { + offset++; + + Rule start{}; + if (!GetRule(name, offset, start)) { + return {}; + } + if (name[offset++] != ',') { + return {}; + } + + Rule end{}; + if (!GetRule(name, offset, end)) { + return {}; + } + if (name[offset] != '\0') { + return {}; + } + + rule.type_count = 2; + rule.ttis[0].gmt_offset = -dest_offset; + rule.ttis[0].is_dst = true; + rule.ttis[0].abbreviation_list_index = std_len + 1; + rule.ttis[1].gmt_offset = -std_offset; + rule.ttis[1].is_dst = false; + rule.ttis[1].abbreviation_list_index = 0; + rule.default_type = 0; + + s64 jan_first{}; + int time_count{}; + int jan_offset{}; + int year_beginning{epoch_year}; + do { + const int year_seconds{GetYearLengthInDays(year_beginning - 1) * seconds_per_day}; + year_beginning--; + if (!SafeAdd(jan_first, -year_seconds)) { + jan_offset = -year_seconds; + break; + } + } while (epoch_year - years_per_repeat / 2 < year_beginning); + + int year_limit{year_beginning + years_per_repeat + 1}; + int year{}; + for (year = year_beginning; year < year_limit; year++) { + int start_time{TransitionTime(year, start, std_offset)}; + int end_time{TransitionTime(year, end, dest_offset)}; + const int year_seconds{GetYearLengthInDays(year) * seconds_per_day}; + const bool is_reversed{end_time < start_time}; + if (is_reversed) { + int swap{start_time}; + start_time = end_time; + end_time = swap; + } + + if (is_reversed || + (start_time < end_time && + (end_time - start_time < (year_seconds + (std_offset - dest_offset))))) { + if (rule.ats.size() - 2 < time_count) { + break; + } + + rule.ats[time_count] = jan_first; + if (SafeAdd(rule.ats[time_count], jan_offset + start_time)) { + rule.types[time_count++] = is_reversed ? 1 : 0; + } else if (jan_offset != 0) { + rule.default_type = is_reversed ? 1 : 0; + } + + rule.ats[time_count] = jan_first; + if (SafeAdd(rule.ats[time_count], jan_offset + end_time)) { + rule.types[time_count++] = is_reversed ? 0 : 1; + year_limit = year + years_per_repeat + 1; + } else if (jan_offset != 0) { + rule.default_type = is_reversed ? 0 : 1; + } + } + if (!SafeAdd(jan_first, jan_offset + year_seconds)) { + break; + } + jan_offset = 0; + } + rule.time_count = time_count; + if (time_count == 0) { + rule.type_count = 1; + } else if (years_per_repeat < year - year_beginning) { + rule.go_back = true; + rule.go_ahead = true; + } + } else { + if (name[offset] == '\0') { + return {}; + } + + s64 their_std_offset{}; + for (int index{}; index < rule.time_count; ++index) { + const s8 type{rule.types[index]}; + if (rule.ttis[type].is_standard_time_daylight) { + their_std_offset = -rule.ttis[type].gmt_offset; + } + } + + s64 their_offset{their_std_offset}; + for (int index{}; index < rule.time_count; ++index) { + const s8 type{rule.types[index]}; + rule.types[index] = rule.ttis[type].is_dst ? 1 : 0; + if (!rule.ttis[type].is_gmt) { + if (!rule.ttis[type].is_standard_time_daylight) { + rule.ats[index] += dest_offset - their_std_offset; + } else { + rule.ats[index] += std_offset - their_std_offset; + } + } + their_offset = -rule.ttis[type].gmt_offset; + if (!rule.ttis[type].is_dst) { + their_std_offset = their_offset; + } + } + rule.ttis[0].gmt_offset = -std_offset; + rule.ttis[0].is_dst = false; + rule.ttis[0].abbreviation_list_index = 0; + rule.ttis[1].gmt_offset = -dest_offset; + rule.ttis[1].is_dst = true; + rule.ttis[1].abbreviation_list_index = std_len + 1; + rule.type_count = 2; + rule.default_type = 0; + } + } else { + // Default is standard time + rule.type_count = 1; + rule.time_count = 0; + rule.default_type = 0; + rule.ttis[0].gmt_offset = -std_offset; + rule.ttis[0].is_dst = false; + rule.ttis[0].abbreviation_list_index = 0; + } + + rule.char_count = char_count; + for (int index{}; index < std_len; ++index) { + rule.chars[index] = std_name[index]; + } + + rule.chars[std_len++] = '\0'; + if (dest_len != 0) { + for (int index{}; index < dest_len; ++index) { + rule.chars[std_len + index] = dest_name[index]; + } + rule.chars[std_len + dest_len] = '\0'; + } + + return true; +} + +static bool ParseTimeZoneBinary(TimeZoneRule& time_zone_rule, FileSys::VirtualFile& vfs_file) { + TzifHeader header{}; + if (vfs_file->ReadObject(&header) != sizeof(TzifHeader)) { + return {}; + } + + constexpr s32 time_zone_max_leaps{50}; + constexpr s32 time_zone_max_chars{50}; + if (!(0 <= header.leap_count && header.leap_count < time_zone_max_leaps && + 0 < header.type_count && header.type_count < time_zone_rule.ttis.size() && + 0 <= header.time_count && header.time_count < time_zone_rule.ats.size() && + 0 <= header.char_count && header.char_count < time_zone_max_chars && + (header.ttis_std_count == header.type_count || header.ttis_std_count == 0) && + (header.ttis_gmt_count == header.type_count || header.ttis_gmt_count == 0))) { + return {}; + } + time_zone_rule.time_count = header.time_count; + time_zone_rule.type_count = header.type_count; + time_zone_rule.char_count = header.char_count; + + int time_count{}; + u64 read_offset = sizeof(TzifHeader); + for (int index{}; index < time_zone_rule.time_count; ++index) { + s64_be at{}; + vfs_file->ReadObject(&at, read_offset); + time_zone_rule.types[index] = 1; + if (time_count != 0 && at <= time_zone_rule.ats[time_count - 1]) { + if (at < time_zone_rule.ats[time_count - 1]) { + return {}; + } + time_zone_rule.types[index - 1] = 0; + time_count--; + } + time_zone_rule.ats[time_count++] = at; + read_offset += sizeof(s64_be); + } + time_count = 0; + for (int index{}; index < time_zone_rule.time_count; ++index) { + const u8 type{*vfs_file->ReadByte(read_offset)}; + read_offset += sizeof(u8); + if (time_zone_rule.time_count <= type) { + return {}; + } + if (time_zone_rule.types[index] != 0) { + time_zone_rule.types[time_count++] = type; + } + } + time_zone_rule.time_count = time_count; + for (int index{}; index < time_zone_rule.type_count; ++index) { + TimeTypeInfo& ttis{time_zone_rule.ttis[index]}; + u32_be gmt_offset{}; + vfs_file->ReadObject(&gmt_offset, read_offset); + read_offset += sizeof(u32_be); + ttis.gmt_offset = gmt_offset; + + const u8 dst{*vfs_file->ReadByte(read_offset)}; + read_offset += sizeof(u8); + if (dst >= 2) { + return {}; + } + ttis.is_dst = dst != 0; + + const s32 abbreviation_list_index{*vfs_file->ReadByte(read_offset)}; + read_offset += sizeof(u8); + if (abbreviation_list_index >= time_zone_rule.char_count) { + return {}; + } + ttis.abbreviation_list_index = abbreviation_list_index; + } + + vfs_file->ReadArray(time_zone_rule.chars.data(), time_zone_rule.char_count, read_offset); + time_zone_rule.chars[time_zone_rule.char_count] = '\0'; + read_offset += time_zone_rule.char_count; + for (int index{}; index < time_zone_rule.type_count; ++index) { + if (header.ttis_std_count == 0) { + time_zone_rule.ttis[index].is_standard_time_daylight = false; + } else { + const u8 dst{*vfs_file->ReadByte(read_offset)}; + read_offset += sizeof(u8); + if (dst >= 2) { + return {}; + } + time_zone_rule.ttis[index].is_standard_time_daylight = dst != 0; + } + } + + for (int index{}; index < time_zone_rule.type_count; ++index) { + if (header.ttis_std_count == 0) { + time_zone_rule.ttis[index].is_gmt = false; + } else { + const u8 dst{*vfs_file->ReadByte(read_offset)}; + read_offset += sizeof(u8); + if (dst >= 2) { + return {}; + } + time_zone_rule.ttis[index].is_gmt = dst != 0; + } + } + + const u64 position{(read_offset - sizeof(TzifHeader))}; + const std::size_t bytes_read{vfs_file->GetSize() - sizeof(TzifHeader) - position}; + if (bytes_read < 0) { + return {}; + } + constexpr s32 time_zone_name_max{255}; + if (bytes_read > (time_zone_name_max + 1)) { + return {}; + } + + std::array temp_name{}; + vfs_file->ReadArray(temp_name.data(), bytes_read, read_offset); + if (bytes_read > 2 && temp_name[0] == '\n' && temp_name[bytes_read - 1] == '\n' && + time_zone_rule.type_count + 2 <= time_zone_rule.ttis.size()) { + temp_name[bytes_read - 1] = '\0'; + + std::array name{}; + std::memcpy(name.data(), temp_name.data() + 1, bytes_read - 1); + + TimeZoneRule temp_rule; + if (ParsePosixName(name.data(), temp_rule)) { + UNIMPLEMENTED(); + } + } + if (time_zone_rule.type_count == 0) { + return {}; + } + if (time_zone_rule.time_count > 1) { + UNIMPLEMENTED(); + } + + s32 default_type{}; + + for (default_type = 0; default_type < time_zone_rule.time_count; default_type++) { + if (time_zone_rule.types[default_type] == 0) { + break; + } + } + + default_type = default_type < time_zone_rule.time_count ? -1 : 0; + if (default_type < 0 && time_zone_rule.time_count > 0 && + time_zone_rule.ttis[time_zone_rule.types[0]].is_dst) { + default_type = time_zone_rule.types[0]; + while (--default_type >= 0) { + if (!time_zone_rule.ttis[default_type].is_dst) { + break; + } + } + } + if (default_type < 0) { + default_type = 0; + while (time_zone_rule.ttis[default_type].is_dst) { + if (++default_type >= time_zone_rule.type_count) { + default_type = 0; + break; + } + } + } + time_zone_rule.default_type = default_type; + return true; +} + +static ResultCode CreateCalendarTime(s64 time, int gmt_offset, CalendarTimeInternal& calendar_time, + CalendarAdditionalInfo& calendar_additional_info) { + s64 year{epoch_year}; + s64 time_days{time / seconds_per_day}; + s64 remaining_seconds{time % seconds_per_day}; + while (time_days < 0 || time_days >= GetYearLengthInDays(year)) { + s64 delta = time_days / days_per_leap_year; + if (!delta) { + delta = time_days < 0 ? -1 : 1; + } + s64 new_year{year}; + if (!SafeAdd(new_year, delta)) { + return ERROR_OUT_OF_RANGE; + } + time_days -= (new_year - year) * days_per_normal_year; + time_days -= GetLeapDaysFromYear(new_year - 1) - GetLeapDaysFromYear(year - 1); + year = new_year; + } + + s64 day_of_year{time_days}; + remaining_seconds += gmt_offset; + while (remaining_seconds < 0) { + remaining_seconds += seconds_per_day; + day_of_year--; + } + + while (remaining_seconds >= seconds_per_day) { + remaining_seconds -= seconds_per_day; + day_of_year++; + } + + while (day_of_year < 0) { + if (!SafeAdd(year, -1)) { + return ERROR_OUT_OF_RANGE; + } + day_of_year += GetYearLengthInDays(year); + } + + while (day_of_year >= GetYearLengthInDays(year)) { + day_of_year -= GetYearLengthInDays(year); + if (!SafeAdd(year, 1)) { + return ERROR_OUT_OF_RANGE; + } + } + + calendar_time.year = year; + calendar_additional_info.day_of_year = static_cast(day_of_year); + s64 day_of_week{ + (epoch_week_day + + ((year - epoch_year) % days_per_week) * (days_per_normal_year % days_per_week) + + GetLeapDaysFromYear(year - 1) - GetLeapDaysFromYear(epoch_year - 1) + day_of_year) % + days_per_week}; + if (day_of_week < 0) { + day_of_week += days_per_week; + } + + calendar_additional_info.day_of_week = static_cast(day_of_week); + calendar_time.hour = static_cast((remaining_seconds / seconds_per_hour) % seconds_per_hour); + remaining_seconds %= seconds_per_hour; + calendar_time.minute = static_cast(remaining_seconds / seconds_per_minute); + calendar_time.second = static_cast(remaining_seconds % seconds_per_minute); + + for (calendar_time.month = 0; + day_of_year >= GetMonthLength(IsLeapYear(year), calendar_time.month); + ++calendar_time.month) { + day_of_year -= GetMonthLength(IsLeapYear(year), calendar_time.month); + } + + calendar_time.day = static_cast(day_of_year + 1); + calendar_additional_info.is_dst = false; + calendar_additional_info.gmt_offset = gmt_offset; + + return RESULT_SUCCESS; +} + +static ResultCode ToCalendarTimeInternal(const TimeZoneRule& rules, s64 time, + CalendarTimeInternal& calendar_time, + CalendarAdditionalInfo& calendar_additional_info) { + if ((rules.go_ahead && time < rules.ats[0]) || + (rules.go_back && time > rules.ats[rules.time_count - 1])) { + s64 seconds{}; + if (time < rules.ats[0]) { + seconds = rules.ats[0] - time; + } else { + seconds = time - rules.ats[rules.time_count - 1]; + } + seconds--; + + const s64 years{(seconds / seconds_per_repeat + 1) * years_per_repeat}; + seconds = years * average_seconds_per_year; + + s64 new_time{time}; + if (time < rules.ats[0]) { + new_time += seconds; + } else { + new_time -= seconds; + } + if (new_time < rules.ats[0] && new_time > rules.ats[rules.time_count - 1]) { + return ERROR_TIME_NOT_FOUND; + } + if (const ResultCode result{ + ToCalendarTimeInternal(rules, new_time, calendar_time, calendar_additional_info)}; + result != RESULT_SUCCESS) { + return result; + } + if (time < rules.ats[0]) { + calendar_time.year -= years; + } else { + calendar_time.year += years; + } + + return RESULT_SUCCESS; + } + + s32 tti_index{}; + if (rules.time_count == 0 || time < rules.ats[0]) { + tti_index = rules.default_type; + } else { + s32 low{1}; + s32 high{rules.time_count}; + while (low < high) { + s32 mid{(low + high) >> 1}; + if (time < rules.ats[mid]) { + high = mid; + } else { + low = mid + 1; + } + } + tti_index = rules.types[low - 1]; + } + + if (const ResultCode result{CreateCalendarTime(time, rules.ttis[tti_index].gmt_offset, + calendar_time, calendar_additional_info)}; + result != RESULT_SUCCESS) { + return result; + } + + calendar_additional_info.is_dst = rules.ttis[tti_index].is_dst; + const char* time_zone{&rules.chars[rules.ttis[tti_index].abbreviation_list_index]}; + for (int index{}; time_zone[index] != '\0'; ++index) { + calendar_additional_info.timezone_name[index] = time_zone[index]; + } + return RESULT_SUCCESS; +} + +static ResultCode ToCalendarTimeImpl(const TimeZoneRule& rules, s64 time, CalendarInfo& calendar) { + CalendarTimeInternal calendar_time{}; + const ResultCode result{ + ToCalendarTimeInternal(rules, time, calendar_time, calendar.additiona_info)}; + calendar.time.year = static_cast(calendar_time.year); + calendar.time.month = calendar_time.month; + calendar.time.day = calendar_time.day; + calendar.time.hour = calendar_time.hour; + calendar.time.minute = calendar_time.minute; + calendar.time.second = calendar_time.second; + return result; +} + +TimeZoneManager::TimeZoneManager() = default; +TimeZoneManager::~TimeZoneManager() = default; + +ResultCode TimeZoneManager::ToCalendarTime(const TimeZoneRule& rules, s64 time, + CalendarInfo& calendar) const { + return ToCalendarTimeImpl(rules, time, calendar); +} + +ResultCode TimeZoneManager::SetDeviceLocationNameWithTimeZoneRule(const std::string& location_name, + FileSys::VirtualFile& vfs_file) { + TimeZoneRule rule{}; + if (ParseTimeZoneBinary(rule, vfs_file)) { + device_location_name = location_name; + time_zone_rule = rule; + return RESULT_SUCCESS; + } + return ERROR_TIME_ZONE_CONVERSION_FAILED; +} + +ResultCode TimeZoneManager::SetUpdatedTime(const Clock::SteadyClockTimePoint& value) { + time_zone_update_time_point = value; + return RESULT_SUCCESS; +} + +ResultCode TimeZoneManager::ToCalendarTimeWithMyRules(s64 time, CalendarInfo& calendar) const { + if (is_initialized) { + return ToCalendarTime(time_zone_rule, time, calendar); + } else { + return ERROR_UNINITIALIZED_CLOCK; + } +} + +ResultCode TimeZoneManager::ParseTimeZoneRuleBinary(TimeZoneRule& rules, + FileSys::VirtualFile& vfs_file) const { + if (!ParseTimeZoneBinary(rules, vfs_file)) { + return ERROR_TIME_ZONE_CONVERSION_FAILED; + } + return RESULT_SUCCESS; +} + +ResultCode TimeZoneManager::ToPosixTime(const TimeZoneRule& rules, + const CalendarTime& calendar_time, s64& posix_time) const { + posix_time = 0; + + CalendarTimeInternal internal_time{}; + internal_time.year = calendar_time.year; + internal_time.month = calendar_time.month; + internal_time.day = calendar_time.day; + internal_time.hour = calendar_time.hour; + internal_time.minute = calendar_time.minute; + internal_time.second = calendar_time.second; + + s32 hour{internal_time.hour}; + s32 minute{internal_time.minute}; + if (!SafeNormalize(hour, minute, minutes_per_hour)) { + return ERROR_OVERFLOW; + } + internal_time.minute = static_cast(minute); + + s32 day{internal_time.day}; + if (!SafeNormalize(day, hour, hours_per_day)) { + return ERROR_OVERFLOW; + } + internal_time.day = static_cast(day); + internal_time.hour = static_cast(hour); + + s64 year{internal_time.year}; + s64 month{internal_time.month}; + if (!SafeNormalize(year, month, months_per_year)) { + return ERROR_OVERFLOW; + } + internal_time.month = static_cast(month); + + if (!SafeAdd(year, year_base)) { + return ERROR_OVERFLOW; + } + + while (day <= 0) { + if (!SafeAdd(year, -1)) { + return ERROR_OVERFLOW; + } + s64 temp_year{year}; + if (1 < internal_time.month) { + ++temp_year; + } + day += static_cast(GetYearLengthInDays(temp_year)); + } + + while (day > days_per_leap_year) { + s64 temp_year{year}; + if (1 < internal_time.month) { + temp_year++; + } + day -= static_cast(GetYearLengthInDays(temp_year)); + if (!SafeAdd(year, 1)) { + return ERROR_OVERFLOW; + } + } + + while (true) { + const s32 month_length{GetMonthLength(IsLeapYear(year), internal_time.month)}; + if (day <= month_length) { + break; + } + day -= month_length; + internal_time.month++; + if (internal_time.month >= months_per_year) { + internal_time.month = 0; + if (!SafeAdd(year, 1)) { + return ERROR_OVERFLOW; + } + } + } + internal_time.day = static_cast(day); + + if (!SafeAdd(year, -year_base)) { + return ERROR_OVERFLOW; + } + internal_time.year = year; + + s32 saved_seconds{}; + if (internal_time.second >= 0 && internal_time.second < seconds_per_minute) { + saved_seconds = 0; + } else if (year + year_base < epoch_year) { + s32 second{internal_time.second}; + if (!SafeAdd(second, 1 - seconds_per_minute)) { + return ERROR_OVERFLOW; + } + saved_seconds = second; + internal_time.second = 1 - seconds_per_minute; + } else { + saved_seconds = internal_time.second; + internal_time.second = 0; + } + + s64 low{LLONG_MIN}; + s64 high{LLONG_MAX}; + while (true) { + s64 pivot{low / 2 + high / 2}; + if (pivot < low) { + pivot = low; + } else if (pivot > high) { + pivot = high; + } + s32 direction{}; + CalendarTimeInternal candidate_calendar_time{}; + CalendarAdditionalInfo unused{}; + if (ToCalendarTimeInternal(rules, pivot, candidate_calendar_time, unused) != + RESULT_SUCCESS) { + if (pivot > 0) { + direction = 1; + } else { + direction = -1; + } + } else { + direction = candidate_calendar_time.Compare(internal_time); + } + if (!direction) { + const s64 time_result{pivot + saved_seconds}; + if ((time_result < pivot) != (saved_seconds < 0)) { + return ERROR_OVERFLOW; + } + posix_time = time_result; + break; + } else { + if (pivot == low) { + if (pivot == LLONG_MAX) { + return ERROR_TIME_NOT_FOUND; + } + pivot++; + low++; + } else if (pivot == high) { + if (pivot == LLONG_MIN) { + return ERROR_TIME_NOT_FOUND; + } + pivot--; + high--; + } + if (low > high) { + return ERROR_TIME_NOT_FOUND; + } + if (direction > 0) { + high = pivot; + } else { + low = pivot; + } + } + } + return RESULT_SUCCESS; +} + +ResultCode TimeZoneManager::GetDeviceLocationName(LocationName& value) const { + if (!is_initialized) { + return ERROR_UNINITIALIZED_CLOCK; + } + std::memcpy(value.data(), device_location_name.c_str(), device_location_name.size()); + return RESULT_SUCCESS; +} + +} // namespace Service::Time::TimeZone -- cgit v1.2.3