diff --git a/src/gui/gui.cc b/src/gui/gui.cc index 9d9c22432..ae301b220 100644 --- a/src/gui/gui.cc +++ b/src/gui/gui.cc @@ -63,7 +63,6 @@ extern "C" { #include "core/sstate.h" #include "core/web-server.h" #include "flags.h" -#include "fmt/chrono.h" #include "gui/gui.h" #include "gui/luaimguiextra.h" #include "gui/luanvg.h" @@ -2413,16 +2412,17 @@ bool PCSX::GUI::about() { ImGui::EndDisabled(); ImGui::TextUnformatted(_("No version information.\n\nProbably built from source.")); } else { + auto timestamp = version.formatTimestamp("{:%Y-%m-%d %H:%M:%S}"); if (ImGui::Button(_("Copy to clipboard"))) { if (version.buildId.has_value()) { clip::set_text( - fmt::format("Version: {}\nBuild: {}\nChangeset: {}\nDate & time: {:%Y-%m-%d %H:%M:%S}", + fmt::format("Version: {}\nBuild: {}\nChangeset: {}\nDate & time: {}", version.version, version.buildId.value(), version.changeset, - fmt::localtime(version.timestamp))); + timestamp)); } else { - clip::set_text(fmt::format("Version: {}\nChangeset: {}\nDate & time: {:%Y-%m-%d %H:%M:%S}", + clip::set_text(fmt::format("Version: {}\nChangeset: {}\nDate & time: {}", version.version, version.changeset, - fmt::localtime(version.timestamp))); + timestamp)); } } ImGui::Text(_("Version: %s"), version.version.c_str()); @@ -2434,8 +2434,6 @@ bool PCSX::GUI::about() { if (ImGui::SmallButton(version.changeset.c_str())) { openUrl(fmt::format("https://github.com/grumpycoders/pcsx-redux/commit/{}", version.changeset)); } - std::tm tm = fmt::localtime(version.timestamp); - std::string timestamp = fmt::format("{:%Y-%m-%d %H:%M:%S}", tm); ImGui::Text(_("Date & time: %s"), timestamp.c_str()); } ImGui::EndTabItem(); diff --git a/src/main/main.cc b/src/main/main.cc index 6afb2554f..0f104a72b 100644 --- a/src/main/main.cc +++ b/src/main/main.cc @@ -32,7 +32,6 @@ #include "core/sstate.h" #include "core/ui.h" #include "flags.h" -#include "fmt/chrono.h" #include "gui/gui.h" #include "lua/extra.h" #include "lua/luawrapper.h" @@ -217,8 +216,8 @@ int pcsxMain(int argc, char **argv) { } fmt::print( "{{\n \"version\": \"{}\",\n \"changeset\": \"{}\",\n \"timestamp\": \"{}\",\n \"timestampDecoded\": " - "\"{:%Y-%m-%d %H:%M:%S}\"\n}}\n", - version.version, version.changeset, version.timestamp, fmt::localtime(version.timestamp)); + "\"{}\"\n}}\n", + version.version, version.changeset, version.timestamp, version.formatTimestamp("{:%Y-%m-%d %H:%M:%S}")); return 0; } diff --git a/src/support/version.cc b/src/support/version.cc index ecaca978d..670d50990 100644 --- a/src/support/version.cc +++ b/src/support/version.cc @@ -27,6 +27,9 @@ SOFTWARE. #include "support/version.h" #include +#include +#include +#include #include "json.hpp" #include "support/container-file.h" @@ -65,6 +68,12 @@ void PCSX::VersionInfo::loadFromFile(IO file) { updateStorageUrl = getString("updateStorageUrl"); } +std::string PCSX::VersionInfo::formatTimestamp(const std::string& format) const { + auto timepoint = std::chrono::system_clock::from_time_t(timestamp); + auto local = std::chrono::current_zone()->to_local(timepoint); + return fmt::format(fmt::runtime(format), floor(local)); +} + bool PCSX::Update::downloadUpdateInfo(const VersionInfo& versionInfo, std::function callback, uv_loop_t* loop) { if (versionInfo.failed() || !versionInfo.hasUpdateInfo()) return false; diff --git a/src/support/version.h b/src/support/version.h index e79dd9b87..965af0b75 100644 --- a/src/support/version.h +++ b/src/support/version.h @@ -60,6 +60,7 @@ struct VersionInfo { } return (updateMethod == "appcenter"); } + std::string formatTimestamp(const std::string& format) const; void clear() { version.clear(); buildId = std::nullopt; diff --git a/tests/support/version.cc b/tests/support/version.cc new file mode 100644 index 000000000..47bc38b8b --- /dev/null +++ b/tests/support/version.cc @@ -0,0 +1,65 @@ +/*************************************************************************** + * Copyright (C) 2025 PCSX-Redux authors * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * + ***************************************************************************/ + +#include +#include +#include "support/version.h" +#include "gtest/gtest.h" + +using namespace PCSX; + +class VersionInfoTest: public ::testing::Test { +protected: + VersionInfo vi{ + "06fdac53", + 159, + "06fdac536d1d8301fdc626fe89d1084a3ad241ad", + 1737185273, + }; + static std::string formatWithStdLocal(std::time_t ts, const char* fmt_str) { + auto tm_ptr = std::localtime(&ts); + if (!tm_ptr) { + return {}; + } + return fmt::format(fmt::runtime(fmt_str), *tm_ptr); + } +}; + +TEST_F(VersionInfoTest, TimestampFormatsDateAndTime) { + auto format = "{:%Y-%m-%d %H:%M:%S}"; + EXPECT_EQ(vi.formatTimestamp(format), formatWithStdLocal(vi.timestamp, format)); +} + +TEST_F(VersionInfoTest, TimestampNullFormatReturnsNullString) { + EXPECT_EQ(vi.formatTimestamp(""), ""); +} + +TEST_F(VersionInfoTest, TimestampFormatNoPlaceholderReturnsCopy) { + EXPECT_EQ(vi.formatTimestamp("abc"), "abc"); +} + +TEST_F(VersionInfoTest, TimestampFormatReturnsNonPlaceholderText) { + auto format = "123 {}"; + EXPECT_EQ(vi.formatTimestamp(format), formatWithStdLocal(vi.timestamp, format)); +} + +TEST_F(VersionInfoTest, TimestampFormatHandlesNegativeNumber) { + vi.timestamp = -1; + EXPECT_EQ(vi.formatTimestamp("{}"), formatWithStdLocal(vi.timestamp, "{}")); +} \ No newline at end of file diff --git a/third_party/fmt b/third_party/fmt index e69e5f977..e424e3f2e 160000 --- a/third_party/fmt +++ b/third_party/fmt @@ -1 +1 @@ -Subproject commit e69e5f977d458f2650bb346dadf2ad30c5320281 +Subproject commit e424e3f2e607da02742f73db84873b8084fc714c