From 3e68a284ae885c6747b9eae6a13a80c86c3e4f2e Mon Sep 17 00:00:00 2001
From: lat9nq <22451773+lat9nq@users.noreply.github.com>
Date: Sun, 21 May 2023 15:43:01 -0400
Subject: settings: Always report a valid time zone

Prevents needing to deduce the non-Switch setting in core. Instead, we
deduce the meaning of this setting where the heresy is committed, in
common.

settings: Remove strftime usage

GetTimeZoneString: Use standard features

Also forces GMT on MinGW due to broken strftime.
---
 src/common/settings.cpp | 78 +++++++++++++++++++++++++++++++++++++++++++++++--
 1 file changed, 76 insertions(+), 2 deletions(-)

(limited to 'src/common/settings.cpp')

diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index ff53e80bb4..ed15b8b7d0 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -1,7 +1,15 @@
 // SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
 // SPDX-License-Identifier: GPL-2.0-or-later
 
+#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"
@@ -15,7 +23,7 @@ static bool configuring_global = true;
 
 std::string GetTimeZoneString() {
     static constexpr std::array timezones{
-        "auto",      "default",   "CET", "CST6CDT", "Cuba",    "EET",    "Egypt",     "Eire",
+        "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",
@@ -25,7 +33,73 @@ std::string GetTimeZoneString() {
 
     const auto time_zone_index = static_cast<std::size_t>(values.time_zone_index.GetValue());
     ASSERT(time_zone_index < timezones.size());
-    return timezones[time_zone_index];
+    std::string location_name;
+    switch (time_zone_index) {
+    case 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];
+#endif
+        break;
+    }
+    default:
+        location_name = timezones[time_zone_index];
+        break;
+    }
+
+    return location_name;
 }
 
 void LogSettings() {
-- 
cgit v1.2.3-70-g09d2