Description: Replace removed fmt::localtime with a reentrant local-time helper
 Use localtime_r with independent tm storage, preserving local timezone and
 DST conversion and fmt 11's format_error on out-of-range time_t values.
 Keep the existing date format strings unchanged.
Author: Aron Xu <aron@debian.org>
Forwarded: no
Last-Update: 2026-10-15

--- a/src/bali-phy/files.cc
+++ b/src/bali-phy/files.cc
@@ -2,6 +2,7 @@
 #include <iostream>
 
 #include "util/io.H"
+#include "util/time.H"
 #include "util/io/vector.H"
 #include "util/string/convert.H"
 #include "files.H"
@@ -141,7 +142,7 @@
 	command.push_back(argv[i]);
 
     auto now = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
-    string start_time = fmt::format("{:%c}", fmt::localtime(now));
+    string start_time = fmt::format("{:%c}", to_local_tm(now));
     rtrim(start_time);
 
     json::object env;
--- a/src/bali-phy/bali-phy.cc
+++ b/src/bali-phy/bali-phy.cc
@@ -271,8 +271,8 @@
     {
         cout<<endl;
         cout<<"Setup:"<<endl;
-        cout<<"  start: "<<fmt::format("{:%c}", fmt::localtime(start_time_t))<<endl;
-	cout<<"    end: "<<fmt::format("{:%c}", fmt::localtime(start_work_time_t))<<endl;
+        cout<<"  start: "<<fmt::format("{:%c}", to_local_tm(start_time_t))<<endl;
+	cout<<"    end: "<<fmt::format("{:%c}", to_local_tm(start_work_time_t))<<endl;
         cout<<"  total (elapsed) time: "<<duration_string( start_work_time-start_time )<<endl;
         cout<<"  total (CPU) time: "<<duration_string( start_work_cpu_time )<<endl;
     }
@@ -281,8 +281,8 @@
     {
         cout<<endl;
         cout<<"Work:"<<endl;
-        cout<<"  start: "<<fmt::format("{:%c}", fmt::localtime(start_work_time_t))<<endl;
-        cout<<"    end: "<<fmt::format("{:%c}", fmt::localtime(end_time_t))<<endl;
+        cout<<"  start: "<<fmt::format("{:%c}", to_local_tm(start_work_time_t))<<endl;
+        cout<<"    end: "<<fmt::format("{:%c}", to_local_tm(end_time_t))<<endl;
         cout<<"  total (elapsed) time: "<<duration_string( end_time - start_work_time )<<endl;
         cout<<"  total (CPU) time: "<<duration_string( end_cpu_time - start_work_cpu_time )<<endl;
     }
--- a/src/util/include/util/time.H
+++ b/src/util/include/util/time.H
@@ -39,8 +39,11 @@
 
 #include <string>
 #include <chrono>
+#include <ctime>
 
 typedef std::chrono::nanoseconds duration_t;
+
+std::tm to_local_tm(std::time_t);
 
 duration_t total_cpu_time();
 
--- a/src/util/time.cc
+++ b/src/util/time.cc
@@ -47,6 +47,14 @@
 
 const auto start_cpu_time = process_user_cpu_clock::now();
 
+std::tm to_local_tm(std::time_t time)
+{
+    std::tm result;
+    if (not localtime_r(&time, &result))
+        throw fmt::format_error("time_t value out of range");
+    return result;
+}
+
 duration_t total_cpu_time()
 {
     // std::chrono doesn't have any way of measuring cpu time.
