common: Move system time zone string detection
Moves it from Settings to Common::TimeZone, since this algorithm doesn't depend on the setting. It also lets us use it in other libraries. common: Various fixes time_zone: Don't double up the std::abs Too many absolute values were causing mirrored time zones to resolve as the same.
This commit is contained in:
		| @@ -3,18 +3,14 @@ | ||||
|  | ||||
| #if __cpp_lib_chrono >= 201907L | ||||
| #include <chrono> | ||||
| #else | ||||
| #include <ctime> | ||||
| #include <limits> | ||||
| #endif | ||||
| #include <string_view> | ||||
| #include <fmt/chrono.h> | ||||
| #include <fmt/core.h> | ||||
|  | ||||
| #include "common/assert.h" | ||||
| #include "common/fs/path_util.h" | ||||
| #include "common/logging/log.h" | ||||
| #include "common/settings.h" | ||||
| #include "common/time_zone.h" | ||||
|  | ||||
| namespace Settings { | ||||
|  | ||||
| @@ -22,83 +18,22 @@ Values values; | ||||
| static bool configuring_global = true; | ||||
|  | ||||
| std::string GetTimeZoneString() { | ||||
|     static constexpr std::array timezones{ | ||||
|         "GMT",       "GMT",       "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", | ||||
|     }; | ||||
|  | ||||
|     const auto time_zone_index = static_cast<std::size_t>(values.time_zone_index.GetValue()); | ||||
|     ASSERT(time_zone_index < timezones.size()); | ||||
|     ASSERT(time_zone_index < Common::TimeZone::GetTimeZoneStrings().size()); | ||||
|  | ||||
|     std::string location_name; | ||||
|     switch (time_zone_index) { | ||||
|     case 0: { // Auto | ||||
|     if (time_zone_index == 0) { // Auto | ||||
| #if __cpp_lib_chrono >= 201907L | ||||
|         const struct std::chrono::tzdb& time_zone_data = std::chrono::get_tzdb(); | ||||
|         const std::chrono::time_zone* current_zone = time_zone_data.current_zone(); | ||||
|         std::string_view current_zone_name = current_zone->name(); | ||||
|         location_name = current_zone_name; | ||||
| #elif defined(MINGW) | ||||
|         // MinGW has broken strftime -- https://sourceforge.net/p/mingw-w64/bugs/793/ | ||||
|         // e.g. fmt::format("{:%z}") -- returns "Eastern Daylight Time" when it should be "-0400" | ||||
|         location_name = timezones[0]; | ||||
|         break; | ||||
| #else | ||||
|         static constexpr std::array offsets{ | ||||
|             0,     0,     3600,   -21600, -19768, 7200,   7509,  -1521,  -18000, -18000, | ||||
|             -75,   -75,   0,      0,      0,      0,      0,     27402,  -36000, -968, | ||||
|             12344, 8454,  -18430, 33539,  40160,  3164,   3600,  -25200, -25200, -25196, | ||||
|             41944, 44028, 5040,   -2205,  29143,  -28800, 29160, 30472,  24925,  6952, | ||||
|             0,     0,     0,      9017,   0,      0, | ||||
|         }; | ||||
|  | ||||
|         static constexpr std::array dst{ | ||||
|             false, false, true,  true,  true,  true,  true,  true,  false, true,  true, true, | ||||
|             false, false, false, false, false, true,  false, false, true,  true,  true, true, | ||||
|             false, true,  true,  false, true,  true,  true,  true,  true,  true,  true, true, | ||||
|             true,  true,  true,  true,  false, false, false, true,  true,  false, | ||||
|         }; | ||||
|  | ||||
|         const auto now = std::time(nullptr); | ||||
|         const struct std::tm& local = *std::localtime(&now); | ||||
|         const std::string clock_offset_s = fmt::format("{:%z}", local); | ||||
|         if (clock_offset_s.empty()) { | ||||
|             location_name = timezones[0]; | ||||
|             break; | ||||
|         } | ||||
|         const int hours_offset = std::stoi(clock_offset_s) / 100; | ||||
|         const int minutes_offset = std::stoi(clock_offset_s) - hours_offset * 100; | ||||
|         const int system_offset = | ||||
|             hours_offset * 3600 + minutes_offset * 60 - (local.tm_isdst ? 3600 : 0); | ||||
|  | ||||
|         int min = std::numeric_limits<int>::max(); | ||||
|         int min_index = -1; | ||||
|         for (u32 i = 2; i < offsets.size(); i++) { | ||||
|             // Skip if system is celebrating DST but considered time zone does not | ||||
|             if (local.tm_isdst && !dst[i]) { | ||||
|                 continue; | ||||
|             } | ||||
|  | ||||
|             const auto offset = offsets[i]; | ||||
|             const int difference = std::abs(std::abs(offset) - std::abs(system_offset)); | ||||
|             if (difference < min) { | ||||
|                 min = difference; | ||||
|                 min_index = i; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         location_name = timezones[min_index]; | ||||
|         location_name = Common::TimeZone::FindSystemTimeZone(); | ||||
| #endif | ||||
|         break; | ||||
|     } else { | ||||
|         location_name = Common::TimeZone::GetTimeZoneStrings()[time_zone_index]; | ||||
|     } | ||||
|     default: | ||||
|         location_name = timezones[time_zone_index]; | ||||
|         break; | ||||
|     } | ||||
|  | ||||
|     return location_name; | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -4,12 +4,29 @@ | ||||
| #include <chrono> | ||||
| #include <iomanip> | ||||
| #include <sstream> | ||||
| #include <fmt/chrono.h> | ||||
| #include <fmt/core.h> | ||||
|  | ||||
| #include "common/logging/log.h" | ||||
| #include "common/settings.h" | ||||
| #include "common/time_zone.h" | ||||
|  | ||||
| namespace Common::TimeZone { | ||||
|  | ||||
| // Time zone strings | ||||
| constexpr std::array timezones{ | ||||
|     "GMT",       "GMT",       "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", | ||||
| }; | ||||
|  | ||||
| const std::array<const char*, 46>& GetTimeZoneStrings() { | ||||
|     return timezones; | ||||
| } | ||||
|  | ||||
| std::string GetDefaultTimeZone() { | ||||
|     return "GMT"; | ||||
| } | ||||
| @@ -18,10 +35,7 @@ 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(); | ||||
|     return fmt::format("{:%z}", tm); | ||||
| } | ||||
|  | ||||
| static int ConvertOsTimeZoneOffsetToInt(const std::string& timezone) { | ||||
| @@ -45,4 +59,57 @@ std::chrono::seconds GetCurrentOffsetSeconds() { | ||||
|     return std::chrono::seconds{seconds}; | ||||
| } | ||||
|  | ||||
| std::string FindSystemTimeZone() { | ||||
| #if defined(MINGW) | ||||
|     // MinGW has broken strftime -- https://sourceforge.net/p/mingw-w64/bugs/793/ | ||||
|     // e.g. fmt::format("{:%z}") -- returns "Eastern Daylight Time" when it should be "-0400" | ||||
|     return timezones[0]; | ||||
| #else | ||||
|     // Time zone offset in seconds from GMT | ||||
|     constexpr std::array offsets{ | ||||
|         0,     0,     3600,  -21600, -19768, 7200,   7509,   -1521, -18000, -18000, -75,    -75, | ||||
|         0,     0,     0,     0,      0,      27402,  -36000, -968,  12344,  8454,   -18430, 33539, | ||||
|         40160, 3164,  3600,  -25200, -25200, -25196, 41944,  44028, 5040,   -2205,  29143,  -28800, | ||||
|         29160, 30472, 24925, 6952,   0,      0,      0,      9017,  0,      0, | ||||
|     }; | ||||
|  | ||||
|     // If the time zone recognizes Daylight Savings Time | ||||
|     constexpr std::array dst{ | ||||
|         false, false, true,  true,  true,  true,  true,  true,  false, true,  true, true, | ||||
|         false, false, false, false, false, true,  false, false, true,  true,  true, true, | ||||
|         false, true,  true,  false, true,  true,  true,  true,  true,  true,  true, true, | ||||
|         true,  true,  true,  true,  false, false, false, true,  true,  false, | ||||
|     }; | ||||
|  | ||||
|     static std::string system_time_zone_cached{}; | ||||
|     if (!system_time_zone_cached.empty()) { | ||||
|         return system_time_zone_cached; | ||||
|     } | ||||
|  | ||||
|     const auto now = std::time(nullptr); | ||||
|     const struct std::tm& local = *std::localtime(&now); | ||||
|  | ||||
|     const s64 system_offset = GetCurrentOffsetSeconds().count() - (local.tm_isdst ? 3600 : 0); | ||||
|  | ||||
|     int min = std::numeric_limits<int>::max(); | ||||
|     int min_index = -1; | ||||
|     for (u32 i = 2; i < offsets.size(); i++) { | ||||
|         // Skip if system is celebrating DST but considered time zone does not | ||||
|         if (local.tm_isdst && !dst[i]) { | ||||
|             continue; | ||||
|         } | ||||
|  | ||||
|         const auto offset = offsets[i]; | ||||
|         const int difference = static_cast<int>(std::abs(offset - system_offset)); | ||||
|         if (difference < min) { | ||||
|             min = difference; | ||||
|             min_index = i; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     system_time_zone_cached = GetTimeZoneStrings()[min_index]; | ||||
|     return system_time_zone_cached; | ||||
| #endif | ||||
| } | ||||
|  | ||||
| } // namespace Common::TimeZone | ||||
|   | ||||
| @@ -3,15 +3,21 @@ | ||||
|  | ||||
| #pragma once | ||||
|  | ||||
| #include <array> | ||||
| #include <chrono> | ||||
| #include <string> | ||||
|  | ||||
| namespace Common::TimeZone { | ||||
|  | ||||
| [[nodiscard]] const std::array<const char*, 46>& GetTimeZoneStrings(); | ||||
|  | ||||
| /// Gets the default timezone, i.e. "GMT" | ||||
| [[nodiscard]] std::string GetDefaultTimeZone(); | ||||
|  | ||||
| /// Gets the offset of the current timezone (from the default), in seconds | ||||
| [[nodiscard]] std::chrono::seconds GetCurrentOffsetSeconds(); | ||||
|  | ||||
| /// Searches time zone offsets for the closest offset to the system time zone | ||||
| [[nodiscard]] std::string FindSystemTimeZone(); | ||||
|  | ||||
| } // namespace Common::TimeZone | ||||
|   | ||||
		Reference in New Issue
	
	Block a user