diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index d1ec8ff08..e6769a5f3 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -148,6 +148,8 @@ add_library(common STATIC thread.h thread_queue_list.h threadsafe_queue.h + time_zone.cpp + time_zone.h timer.cpp timer.h uint128.cpp diff --git a/src/common/time_zone.cpp b/src/common/time_zone.cpp new file mode 100644 index 000000000..ce239eb63 --- /dev/null +++ b/src/common/time_zone.cpp @@ -0,0 +1,49 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include +#include + +#include "common/logging/log.h" +#include "common/time_zone.h" + +namespace Common::TimeZone { + +std::string GetDefaultTimeZone() { + return "GMT"; +} + +static std::string GetOsTimeZoneOffset() { + const std::time_t t{std::time(nullptr)}; + const std::tm tm{*std::localtime(&t)}; + + std::stringstream ss; + ss << std::put_time(&tm, "%z"); // Get the current timezone offset, e.g. "-400", as a string + + return ss.str(); +} + +static int ConvertOsTimeZoneOffsetToInt(const std::string& timezone) { + try { + return std::stoi(timezone); + } catch (const std::invalid_argument&) { + LOG_CRITICAL(Common, "invalid_argument with {}!", timezone); + return 0; + } catch (const std::out_of_range&) { + LOG_CRITICAL(Common, "out_of_range with {}!", timezone); + return 0; + } +} + +std::chrono::seconds GetCurrentOffsetSeconds() { + const int offset{ConvertOsTimeZoneOffsetToInt(GetOsTimeZoneOffset())}; + + int seconds{(offset / 100) * 60 * 60}; // Convert hour component to seconds + seconds += (offset % 100) * 60; // Convert minute component to seconds + + return std::chrono::seconds{seconds}; +} + +} // namespace Common::TimeZone diff --git a/src/common/time_zone.h b/src/common/time_zone.h new file mode 100644 index 000000000..945daa09c --- /dev/null +++ b/src/common/time_zone.h @@ -0,0 +1,18 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include + +namespace Common::TimeZone { + +/// Gets the default timezone, i.e. "GMT" +std::string GetDefaultTimeZone(); + +/// Gets the offset of the current timezone (from the default), in seconds +std::chrono::seconds GetCurrentOffsetSeconds(); + +} // namespace Common::TimeZone diff --git a/src/core/hle/service/time/time_manager.cpp b/src/core/hle/service/time/time_manager.cpp index 9d6c55865..b4dfe45e5 100644 --- a/src/core/hle/service/time/time_manager.cpp +++ b/src/core/hle/service/time/time_manager.cpp @@ -5,6 +5,7 @@ #include #include +#include "common/time_zone.h" #include "core/hle/service/time/ephemeral_network_system_clock_context_writer.h" #include "core/hle/service/time/local_system_clock_context_writer.h" #include "core/hle/service/time/network_system_clock_context_writer.h" @@ -21,8 +22,16 @@ static std::chrono::seconds GetSecondsSinceEpoch() { Settings::values.custom_rtc_differential; } +static s64 GetExternalTimeZoneOffset() { + // With "auto" timezone setting, we use the external system's timezone offset + if (Settings::GetTimeZoneString() == "auto") { + return Common::TimeZone::GetCurrentOffsetSeconds().count(); + } + return 0; +} + static s64 GetExternalRtcValue() { - return GetSecondsSinceEpoch().count(); + return GetSecondsSinceEpoch().count() + GetExternalTimeZoneOffset(); } TimeManager::TimeManager(Core::System& system) diff --git a/src/core/hle/service/time/time_zone_content_manager.cpp b/src/core/hle/service/time/time_zone_content_manager.cpp index 78d4acd95..c070d6e97 100644 --- a/src/core/hle/service/time/time_zone_content_manager.cpp +++ b/src/core/hle/service/time/time_zone_content_manager.cpp @@ -5,6 +5,7 @@ #include #include "common/logging/log.h" +#include "common/time_zone.h" #include "core/core.h" #include "core/file_sys/content_archive.h" #include "core/file_sys/nca_metadata.h" @@ -14,6 +15,7 @@ #include "core/hle/service/filesystem/filesystem.h" #include "core/hle/service/time/time_manager.h" #include "core/hle/service/time/time_zone_content_manager.h" +#include "core/settings.h" namespace Service::Time::TimeZone { @@ -68,10 +70,22 @@ static std::vector BuildLocationNameCache(Core::System& system) { TimeZoneContentManager::TimeZoneContentManager(TimeManager& time_manager, Core::System& system) : system{system}, location_name_cache{BuildLocationNameCache(system)} { - if (FileSys::VirtualFile vfs_file; GetTimeZoneInfoFile("GMT", vfs_file) == RESULT_SUCCESS) { + + std::string location_name; + const auto timezone_setting = Settings::GetTimeZoneString(); + if (timezone_setting == "auto") { + location_name = Common::TimeZone::GetDefaultTimeZone(); + } else if (timezone_setting == "default") { + location_name = location_name; + } else { + location_name = timezone_setting; + } + + if (FileSys::VirtualFile vfs_file; + GetTimeZoneInfoFile(location_name, vfs_file) == RESULT_SUCCESS) { const auto time_point{ time_manager.GetStandardSteadyClockCore().GetCurrentTimePoint(system)}; - time_manager.SetupTimeZoneManager("GMT", time_point, location_name_cache.size(), {}, + time_manager.SetupTimeZoneManager(location_name, time_point, location_name_cache.size(), {}, vfs_file); } else { time_zone_manager.MarkAsInitialized(); @@ -113,6 +127,12 @@ ResultCode TimeZoneContentManager::GetTimeZoneInfoFile(const std::string& locati } vfs_file = zoneinfo_dir->GetFile(location_name); + if (!vfs_file) { + LOG_ERROR(Service_Time, "{:016X} has no file \"{}\"! Using default timezone.", + time_zone_binary_titleid, location_name); + vfs_file = zoneinfo_dir->GetFile(Common::TimeZone::GetDefaultTimeZone()); + } + if (!vfs_file) { LOG_ERROR(Service_Time, "{:016X} has no file \"{}\"!", time_zone_binary_titleid, location_name); diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 2b0bdc4d3..da53cde05 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -63,6 +63,21 @@ const std::array mapping = {{ Values values = {}; +std::string GetTimeZoneString() { + static constexpr std::array timezones{{ + "auto", "default", "CET", "CST6CDT", "Cuba", "EET", "Egypt", "Eire", + "EST", "EST5EDT", "GB", "GB-Eire", "GMT", "GMT+0", "GMT-0", "GMT0", + "Greenwich", "Hongkong", "HST", "Iceland", "Iran", "Israel", "Jamaica", "Japan", + "Kwajalein", "Libya", "MET", "MST", "MST7MDT", "Navajo", "NZ", "NZ-CHAT", + "Poland", "Portugal", "PRC", "PST8PDT", "ROC", "ROK", "Singapore", "Turkey", + "UCT", "Universal", "UTC", "W-SU", "WET", "Zulu", + }}; + + ASSERT(Settings::values.time_zone_index < timezones.size()); + + return timezones[Settings::values.time_zone_index]; +} + void Apply() { GDBStub::SetServerPort(values.gdbstub_port); GDBStub::ToggleServer(values.use_gdbstub); @@ -87,6 +102,7 @@ void LogSettings() { LogSetting("System_CurrentUser", Settings::values.current_user); LogSetting("System_LanguageIndex", Settings::values.language_index); LogSetting("System_RegionIndex", Settings::values.region_index); + LogSetting("System_TimeZoneIndex", Settings::values.time_zone_index); LogSetting("Core_UseMultiCore", Settings::values.use_multi_core); LogSetting("Renderer_UseResolutionFactor", Settings::values.resolution_factor); LogSetting("Renderer_UseFrameLimit", Settings::values.use_frame_limit); diff --git a/src/core/settings.h b/src/core/settings.h index 163900f0b..c1266b341 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -394,6 +394,7 @@ struct Values { s32 current_user; s32 language_index; s32 region_index; + s32 time_zone_index; s32 sound_index; // Controls @@ -490,6 +491,9 @@ struct Values { bool IsGPULevelExtreme(); bool IsGPULevelHigh(); +std::string GetTimeZoneString(); + void Apply(); void LogSettings(); + } // namespace Settings diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index 75c6cf20b..27775701d 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -687,6 +687,8 @@ void Config::ReadSystemValues() { Settings::values.region_index = ReadSetting(QStringLiteral("region_index"), 1).toInt(); + Settings::values.time_zone_index = ReadSetting(QStringLiteral("time_zone_index"), 0).toInt(); + const auto rng_seed_enabled = ReadSetting(QStringLiteral("rng_seed_enabled"), false).toBool(); if (rng_seed_enabled) { Settings::values.rng_seed = ReadSetting(QStringLiteral("rng_seed"), 0).toULongLong(); @@ -1126,6 +1128,7 @@ void Config::SaveSystemValues() { WriteSetting(QStringLiteral("current_user"), Settings::values.current_user, 0); WriteSetting(QStringLiteral("language_index"), Settings::values.language_index, 1); WriteSetting(QStringLiteral("region_index"), Settings::values.region_index, 1); + WriteSetting(QStringLiteral("time_zone_index"), Settings::values.time_zone_index, 0); WriteSetting(QStringLiteral("rng_seed_enabled"), Settings::values.rng_seed.has_value(), false); WriteSetting(QStringLiteral("rng_seed"), Settings::values.rng_seed.value_or(0), 0); diff --git a/src/yuzu/configuration/configure_system.cpp b/src/yuzu/configuration/configure_system.cpp index f49cd4c8f..10315e7a6 100644 --- a/src/yuzu/configuration/configure_system.cpp +++ b/src/yuzu/configuration/configure_system.cpp @@ -57,6 +57,7 @@ void ConfigureSystem::SetConfiguration() { ui->combo_language->setCurrentIndex(Settings::values.language_index); ui->combo_region->setCurrentIndex(Settings::values.region_index); + ui->combo_time_zone->setCurrentIndex(Settings::values.time_zone_index); ui->combo_sound->setCurrentIndex(Settings::values.sound_index); ui->rng_seed_checkbox->setChecked(Settings::values.rng_seed.has_value()); @@ -84,6 +85,7 @@ void ConfigureSystem::ApplyConfiguration() { Settings::values.language_index = ui->combo_language->currentIndex(); Settings::values.region_index = ui->combo_region->currentIndex(); + Settings::values.time_zone_index = ui->combo_time_zone->currentIndex(); Settings::values.sound_index = ui->combo_sound->currentIndex(); if (ui->rng_seed_checkbox->isChecked()) { diff --git a/src/yuzu/configuration/configure_system.h b/src/yuzu/configuration/configure_system.h index d8fa2d2cc..26d42d5c5 100644 --- a/src/yuzu/configuration/configure_system.h +++ b/src/yuzu/configuration/configure_system.h @@ -37,5 +37,6 @@ private: int language_index = 0; int region_index = 0; + int time_zone_index = 0; int sound_index = 0; }; diff --git a/src/yuzu/configuration/configure_system.ui b/src/yuzu/configuration/configure_system.ui index 4e2c7e76e..9c8cca6dc 100644 --- a/src/yuzu/configuration/configure_system.ui +++ b/src/yuzu/configuration/configure_system.ui @@ -22,14 +22,14 @@ System Settings - + Sound output mode - + Console ID: @@ -174,14 +174,255 @@ - + + + + Time Zone: + + + + + + + + Auto + + + + + Default + + + + + CET + + + + + CST6CDT + + + + + Cuba + + + + + EET + + + + + Egypt + + + + + Eire + + + + + EST + + + + + EST5EDT + + + + + GB + + + + + GB-Eire + + + + + GMT + + + + + GMT+0 + + + + + GMT-0 + + + + + GMT0 + + + + + Greenwich + + + + + Hongkong + + + + + HST + + + + + Iceland + + + + + Iran + + + + + Israel + + + + + Jamaica + + + + + Japan + + + + + Kwajalein + + + + + Libya + + + + + MET + + + + + MST + + + + + MST7MDT + + + + + Navajo + + + + + NZ + + + + + NZ-CHAT + + + + + Poland + + + + + Portugal + + + + + PRC + + + + + PST8PDT + + + + + ROC + + + + + ROK + + + + + Singapore + + + + + Turkey + + + + + UCT + + + + + Universal + + + + + UTC + + + + + W-SU + + + + + WET + + + + + Zulu + + + + + RNG Seed - + @@ -207,7 +448,7 @@ - + @@ -223,14 +464,14 @@ - + Custom RTC - + @@ -244,7 +485,7 @@ - + diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index 8476a5a16..2348e6e0d 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -367,6 +367,9 @@ void Config::ReadValues() { Settings::values.custom_rtc = std::nullopt; } + Settings::values.language_index = sdl2_config->GetInteger("System", "language_index", 1); + Settings::values.time_zone_index = sdl2_config->GetInteger("System", "time_zone_index", 0); + // Core Settings::values.use_multi_core = sdl2_config->GetBoolean("Core", "use_multi_core", false); @@ -409,8 +412,6 @@ void Config::ReadValues() { Settings::values.audio_device_id = sdl2_config->Get("Audio", "output_device", "auto"); Settings::values.volume = static_cast(sdl2_config->GetReal("Audio", "volume", 1)); - Settings::values.language_index = sdl2_config->GetInteger("System", "language_index", 1); - // Miscellaneous Settings::values.log_filter = sdl2_config->Get("Miscellaneous", "log_filter", "*:Trace"); Settings::values.use_dev_keys = sdl2_config->GetBoolean("Miscellaneous", "use_dev_keys", false); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 60b1a62fa..ae94b51c4 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -262,6 +262,10 @@ language_index = # -1: Auto-select (default), 0: Japan, 1: USA, 2: Europe, 3: Australia, 4: China, 5: Korea, 6: Taiwan region_value = +# The system time zone that yuzu will use during emulation +# 0: Auto-select (default), 1: Default (system archive value), Others: Index for specified time zone +time_zone_index = + [Miscellaneous] # A filter which removes logs below a certain logging level. # Examples: *:Debug Kernel.SVC:Trace Service.*:Critical