Add util::formatClockDateTime/formatClockTime driven by a process-wide flag (setClock12h, synced from the time_format setting each frame). Switch the primary user-facing timestamp displays to it — the transaction list, wallet-state tx + banned-peer times, the explorer block time, and the block-info dialog — so one preference drives every clock. Log files, export filenames/content, console line prefixes, the market chart axis, and the worker-thread "last updated" string intentionally stay fixed 24h. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
111 lines
4.1 KiB
C++
111 lines
4.1 KiB
C++
// DragonX Wallet - ImGui Edition
|
|
// Copyright 2024-2026 The Hush Developers
|
|
// Released under the GPLv3
|
|
|
|
#include "text_format.h"
|
|
#include "i18n.h"
|
|
|
|
#include <cctype>
|
|
#include <cstdio>
|
|
#include <ctime>
|
|
|
|
namespace dragonx {
|
|
namespace util {
|
|
|
|
namespace {
|
|
std::int64_t secondsAgo(std::int64_t timestamp)
|
|
{
|
|
std::int64_t now = static_cast<std::int64_t>(std::time(nullptr));
|
|
std::int64_t diff = now - timestamp;
|
|
return diff < 0 ? 0 : diff;
|
|
}
|
|
bool g_clock12h = false; // app-wide 12-hour clock preference (synced from settings each frame)
|
|
} // namespace
|
|
|
|
void setClock12h(bool enabled) { g_clock12h = enabled; }
|
|
bool clock12h() { return g_clock12h; }
|
|
|
|
std::string formatClockDateTime(std::int64_t timestamp, bool withSeconds)
|
|
{
|
|
if (timestamp <= 0) return {};
|
|
std::time_t t = static_cast<std::time_t>(timestamp);
|
|
std::tm* tm = std::localtime(&t); // UI thread only
|
|
if (!tm) return {};
|
|
const char* fmt = g_clock12h
|
|
? (withSeconds ? "%Y-%m-%d %I:%M:%S %p" : "%Y-%m-%d %I:%M %p")
|
|
: (withSeconds ? "%Y-%m-%d %H:%M:%S" : "%Y-%m-%d %H:%M");
|
|
char buf[40];
|
|
return std::strftime(buf, sizeof(buf), fmt, tm) > 0 ? std::string(buf) : std::string();
|
|
}
|
|
|
|
std::string formatClockTime(std::int64_t timestamp, bool withSeconds)
|
|
{
|
|
if (timestamp <= 0) return {};
|
|
std::time_t t = static_cast<std::time_t>(timestamp);
|
|
std::tm* tm = std::localtime(&t); // UI thread only
|
|
if (!tm) return {};
|
|
const char* fmt = g_clock12h
|
|
? (withSeconds ? "%I:%M:%S %p" : "%I:%M %p")
|
|
: (withSeconds ? "%H:%M:%S" : "%H:%M");
|
|
char buf[24];
|
|
return std::strftime(buf, sizeof(buf), fmt, tm) > 0 ? std::string(buf) : std::string();
|
|
}
|
|
|
|
std::string formatTimeAgo(std::int64_t timestamp)
|
|
{
|
|
if (timestamp <= 0) return {};
|
|
const std::int64_t diff = secondsAgo(timestamp);
|
|
char buf[48];
|
|
if (diff < 60) std::snprintf(buf, sizeof(buf), tr("time_seconds_ago"), static_cast<int>(diff));
|
|
else if (diff < 3600) std::snprintf(buf, sizeof(buf), tr("time_minutes_ago"), static_cast<int>(diff / 60));
|
|
else if (diff < 86400) std::snprintf(buf, sizeof(buf), tr("time_hours_ago"), static_cast<int>(diff / 3600));
|
|
else std::snprintf(buf, sizeof(buf), tr("time_days_ago"), static_cast<int>(diff / 86400));
|
|
return buf;
|
|
}
|
|
|
|
std::string formatTimeAgoShort(std::int64_t timestamp)
|
|
{
|
|
if (timestamp <= 0) return {};
|
|
const std::int64_t diff = secondsAgo(timestamp);
|
|
char buf[32];
|
|
if (diff < 60) std::snprintf(buf, sizeof(buf), "%llds ago", static_cast<long long>(diff));
|
|
else if (diff < 3600) std::snprintf(buf, sizeof(buf), "%lldm ago", static_cast<long long>(diff / 60));
|
|
else if (diff < 86400) std::snprintf(buf, sizeof(buf), "%lldh ago", static_cast<long long>(diff / 3600));
|
|
else std::snprintf(buf, sizeof(buf), "%lldd ago", static_cast<long long>(diff / 86400));
|
|
return buf;
|
|
}
|
|
|
|
std::string truncateMiddle(const std::string& s, int maxLen)
|
|
{
|
|
// Guard maxLen <= 3 before the unsigned (maxLen - 3): otherwise it wraps and the
|
|
// trailing substr(length - half) throws std::out_of_range on short inputs.
|
|
if (maxLen <= 3 || s.length() <= static_cast<std::size_t>(maxLen)) return s;
|
|
int half = (maxLen - 3) / 2;
|
|
return s.substr(0, half) + "..." + s.substr(s.length() - half);
|
|
}
|
|
|
|
std::string truncateMiddle(const std::string& s, int front, int back)
|
|
{
|
|
if (s.length() <= static_cast<std::size_t>(front + back + 3)) return s;
|
|
return s.substr(0, front) + "..." + s.substr(s.length() - back);
|
|
}
|
|
|
|
bool containsIgnoreCase(std::string_view haystack, std::string_view needle)
|
|
{
|
|
if (needle.empty()) return true;
|
|
if (needle.size() > haystack.size()) return false;
|
|
const auto low = [](char c) { return static_cast<char>(std::tolower(static_cast<unsigned char>(c))); };
|
|
const std::size_t last = haystack.size() - needle.size();
|
|
for (std::size_t i = 0; i <= last; ++i) {
|
|
std::size_t j = 0;
|
|
for (; j < needle.size(); ++j) {
|
|
if (low(haystack[i + j]) != low(needle[j])) break;
|
|
}
|
|
if (j == needle.size()) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace util
|
|
} // namespace dragonx
|