diff --git a/.gitmodules b/.gitmodules
index 95eae81097..89f2ad924f 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -52,3 +52,6 @@
 [submodule "libadrenotools"]
 	path = externals/libadrenotools
 	url = https://github.com/bylaws/libadrenotools
+[submodule "tzdb_to_nx"]
+	path = externals/nx_tzdb/tzdb_to_nx
+	url = https://github.com/lat9nq/tzdb_to_nx.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3d03bbf94e..6d3146c9ee 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -59,6 +59,8 @@ option(YUZU_CHECK_SUBMODULES "Check if submodules are present" ON)
 
 option(YUZU_ENABLE_LTO "Enable link-time optimization" OFF)
 
+option(YUZU_DOWNLOAD_TIME_ZONE_DATA "Always download time zone binaries" OFF)
+
 CMAKE_DEPENDENT_OPTION(YUZU_USE_FASTER_LD "Check if a faster linker is available" ON "NOT WIN32" OFF)
 
 # On Android, fetch and compile libcxx before doing anything else
diff --git a/externals/nx_tzdb/CMakeLists.txt b/externals/nx_tzdb/CMakeLists.txt
index 2f625c108e..d5a1c6317b 100644
--- a/externals/nx_tzdb/CMakeLists.txt
+++ b/externals/nx_tzdb/CMakeLists.txt
@@ -1,24 +1,60 @@
 # SPDX-FileCopyrightText: 2023 yuzu Emulator Project
 # SPDX-License-Identifier: GPL-2.0-or-later
 
-set(NX_TZDB_VERSION "220816")
-set(NX_TZDB_DOWNLOAD_URL "https://github.com/lat9nq/tzdb_to_nx/releases/download/${NX_TZDB_VERSION}/${NX_TZDB_VERSION}.zip")
-
-set(NX_TZDB_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/${NX_TZDB_VERSION}.zip")
-set(NX_TZDB_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb")
-
 set(NX_TZDB_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/include")
 
-if (NOT EXISTS ${NX_TZDB_ARCHIVE})
-    file(DOWNLOAD ${NX_TZDB_DOWNLOAD_URL} ${NX_TZDB_ARCHIVE})
+add_library(nx_tzdb INTERFACE)
+
+find_program(GIT git)
+find_program(GNU_MAKE make)
+find_program(GNU_DATE date)
+
+set(CAN_BUILD_NX_TZDB true)
+
+if (NOT GIT)
+    set(CAN_BUILD_NX_TZDB false)
+endif()
+if (NOT GNU_MAKE)
+    set(CAN_BUILD_NX_TZDB false)
+endif()
+if (NOT GNU_DATE)
+    set(CAN_BUILD_NX_TZDB false)
+endif()
+if (CMAKE_SYSTEM_NAME STREQUAL "Windows" OR ANDROID)
+    # tzdb_to_nx currently requires a posix-compliant host
+    # MinGW and Android are handled here due to the executable format being different from the host system
+    # TODO (lat9nq): cross-compiling support
+    set(CAN_BUILD_NX_TZDB false)
+endif()
+
+set(NX_TZDB_VERSION "220816")
+set(NX_TZDB_ARCHIVE "${CMAKE_CURRENT_BINARY_DIR}/${NX_TZDB_VERSION}.zip")
+
+set(NX_TZDB_ROMFS_DIR "${CMAKE_CURRENT_BINARY_DIR}/nx_tzdb")
+
+if ((NOT CAN_BUILD_NX_TZDB OR YUZU_DOWNLOAD_TIME_ZONE_DATA) AND NOT EXISTS ${NX_TZDB_ARCHIVE})
+    set(NX_TZDB_DOWNLOAD_URL "https://github.com/lat9nq/tzdb_to_nx/releases/download/${NX_TZDB_VERSION}/${NX_TZDB_VERSION}.zip")
+
+    message(STATUS "Downloading time zone data from ${NX_TZDB_DOWNLOAD_URL}...")
+    file(DOWNLOAD ${NX_TZDB_DOWNLOAD_URL} ${NX_TZDB_ARCHIVE}
+        STATUS NX_TZDB_DOWNLOAD_STATUS)
+    list(GET NX_TZDB_DOWNLOAD_STATUS 0 NX_TZDB_DOWNLOAD_STATUS_CODE)
+    if (NOT NX_TZDB_DOWNLOAD_STATUS_CODE EQUAL 0)
+        message(FATAL_ERROR "Time zone data download failed (status code ${NX_TZDB_DOWNLOAD_STATUS_CODE})")
+    endif()
+
     file(ARCHIVE_EXTRACT
         INPUT
             ${NX_TZDB_ARCHIVE}
         DESTINATION
-            ${NX_TZDB_DIR})
+            ${NX_TZDB_ROMFS_DIR})
+elseif (CAN_BUILD_NX_TZDB AND NOT YUZU_DOWNLOAD_TIME_ZONE_DATA)
+    add_subdirectory(tzdb_to_nx)
+    add_dependencies(nx_tzdb x80e)
+
+    set(NX_TZDB_ROMFS_DIR "${NX_TZDB_DIR}")
 endif()
 
-add_library(nx_tzdb INTERFACE)
 target_include_directories(nx_tzdb
     INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/include
     INTERFACE ${NX_TZDB_INCLUDE_DIR})
@@ -41,25 +77,25 @@ function(CreateHeader ZONE_PATH HEADER_NAME)
     target_sources(nx_tzdb PRIVATE ${HEADER_PATH})
 endfunction()
 
-CreateHeader(${NX_TZDB_DIR} base)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo zoneinfo)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/Africa africa)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/America america)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/America/Argentina america_argentina)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/America/Indiana america_indiana)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/America/Kentucky america_kentucky)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/America/North_Dakota america_north_dakota)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/Antartica antartica)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/Arctic arctic)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/Asia asia)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/Atlantic atlantic)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/Australia australia)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/Brazil brazil)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/Canada canada)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/Chile chile)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/Etc etc)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/Europe europe)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/Indian indian)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/Mexico mexico)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/Pacific pacific)
-CreateHeader(${NX_TZDB_DIR}/zoneinfo/US us)
+CreateHeader(${NX_TZDB_ROMFS_DIR} base)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo zoneinfo)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Africa africa)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America america)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/Argentina america_argentina)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/Indiana america_indiana)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/Kentucky america_kentucky)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/America/North_Dakota america_north_dakota)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Antarctica antarctica)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Arctic arctic)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Asia asia)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Atlantic atlantic)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Australia australia)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Brazil brazil)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Canada canada)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Chile chile)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Etc etc)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Europe europe)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Indian indian)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Mexico mexico)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/Pacific pacific)
+CreateHeader(${NX_TZDB_ROMFS_DIR}/zoneinfo/US us)
diff --git a/externals/nx_tzdb/NxTzdbCreateHeader.cmake b/externals/nx_tzdb/NxTzdbCreateHeader.cmake
index 69166aa5bc..8c29e1167b 100644
--- a/externals/nx_tzdb/NxTzdbCreateHeader.cmake
+++ b/externals/nx_tzdb/NxTzdbCreateHeader.cmake
@@ -15,7 +15,7 @@ set(DIRECTORY_NAME ${HEADER_NAME})
 
 set(FILE_DATA "")
 foreach(ZONE_FILE ${FILE_LIST})
-    if ("${ZONE_FILE}" STREQUAL "\n")
+    if (ZONE_FILE STREQUAL "\n")
         continue()
     endif()
 
@@ -26,13 +26,13 @@ foreach(ZONE_FILE ${FILE_LIST})
     foreach(I RANGE 0 ${ZONE_DATA_LEN} 2)
         math(EXPR BREAK_LINE "(${I} + 2) % 38")
 
-        string(SUBSTRING "${ZONE_DATA}" "${I}" "2" HEX_DATA)
-        if ("${HEX_DATA}" STREQUAL "")
+        string(SUBSTRING "${ZONE_DATA}" "${I}" 2 HEX_DATA)
+        if (NOT HEX_DATA)
             break()
         endif()
 
         string(APPEND FILE_DATA "0x${HEX_DATA},")
-        if ("${BREAK_LINE}" STREQUAL "0")
+        if (BREAK_LINE EQUAL 0)
             string(APPEND FILE_DATA "\n")
         else()
             string(APPEND FILE_DATA " ")
diff --git a/externals/nx_tzdb/include/nx_tzdb.h b/externals/nx_tzdb/include/nx_tzdb.h
index d7b1e43046..1f7c6069ae 100644
--- a/externals/nx_tzdb/include/nx_tzdb.h
+++ b/externals/nx_tzdb/include/nx_tzdb.h
@@ -9,7 +9,7 @@
 #include "nx_tzdb/america_indiana.h"
 #include "nx_tzdb/america_kentucky.h"
 #include "nx_tzdb/america_north_dakota.h"
-#include "nx_tzdb/antartica.h"
+#include "nx_tzdb/antarctica.h"
 #include "nx_tzdb/arctic.h"
 #include "nx_tzdb/asia.h"
 #include "nx_tzdb/atlantic.h"
diff --git a/externals/nx_tzdb/tzdb_to_nx b/externals/nx_tzdb/tzdb_to_nx
new file mode 160000
index 0000000000..34df65eff2
--- /dev/null
+++ b/externals/nx_tzdb/tzdb_to_nx
@@ -0,0 +1 @@
+Subproject commit 34df65eff295c2bd9ee9e6a077d662486d5cabb3
diff --git a/src/core/file_sys/system_archive/time_zone_binary.cpp b/src/core/file_sys/system_archive/time_zone_binary.cpp
index ceb0b41c67..7c17bbefa5 100644
--- a/src/core/file_sys/system_archive/time_zone_binary.cpp
+++ b/src/core/file_sys/system_archive/time_zone_binary.cpp
@@ -15,7 +15,7 @@ namespace FileSys::SystemArchive {
 const static std::map<std::string, const std::map<const char*, const std::vector<u8>>&>
     tzdb_zoneinfo_dirs = {{"Africa", NxTzdb::africa},
                           {"America", NxTzdb::america},
-                          {"Antartica", NxTzdb::antartica},
+                          {"Antarctica", NxTzdb::antarctica},
                           {"Arctic", NxTzdb::arctic},
                           {"Asia", NxTzdb::asia},
                           {"Atlantic", NxTzdb::atlantic},
diff --git a/src/core/hle/service/time/time_zone_manager.cpp b/src/core/hle/service/time/time_zone_manager.cpp
index e1728c06d5..63aacd19f1 100644
--- a/src/core/hle/service/time/time_zone_manager.cpp
+++ b/src/core/hle/service/time/time_zone_manager.cpp
@@ -849,8 +849,9 @@ static Result CreateCalendarTime(s64 time, int gmt_offset, CalendarTimeInternal&
 static Result 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])) {
+    ASSERT(rules.go_ahead ? rules.time_count > 0 : true);
+    if ((rules.go_back && time < rules.ats[0]) ||
+        (rules.go_ahead && time > rules.ats[rules.time_count - 1])) {
         s64 seconds{};
         if (time < rules.ats[0]) {
             seconds = rules.ats[0] - time;
diff --git a/src/core/hle/service/time/time_zone_service.cpp b/src/core/hle/service/time/time_zone_service.cpp
index e8273e152a..8171c82a5b 100644
--- a/src/core/hle/service/time/time_zone_service.cpp
+++ b/src/core/hle/service/time/time_zone_service.cpp
@@ -112,20 +112,14 @@ void ITimeZoneService::LoadTimeZoneRule(HLERequestContext& ctx) {
     LOG_DEBUG(Service_Time, "called, location_name={}", location_name);
 
     TimeZone::TimeZoneRule time_zone_rule{};
-    if (const Result result{
-            time_zone_content_manager.LoadTimeZoneRule(time_zone_rule, location_name)};
-        result != ResultSuccess) {
-        IPC::ResponseBuilder rb{ctx, 2};
-        rb.Push(result);
-        return;
-    }
+    const Result result{time_zone_content_manager.LoadTimeZoneRule(time_zone_rule, location_name)};
 
     std::vector<u8> time_zone_rule_outbuffer(sizeof(TimeZone::TimeZoneRule));
     std::memcpy(time_zone_rule_outbuffer.data(), &time_zone_rule, sizeof(TimeZone::TimeZoneRule));
     ctx.WriteBuffer(time_zone_rule_outbuffer);
 
     IPC::ResponseBuilder rb{ctx, 2};
-    rb.Push(ResultSuccess);
+    rb.Push(result);
 }
 
 void ITimeZoneService::ToCalendarTime(HLERequestContext& ctx) {