Kiosk: show all user-visible times as 12-hour AM/PM

Add datetime::formatHm12h/formatTime12h and use them for the Home footer
clock, the Timezone page's local-time sample, and the log viewer
timestamps. Storage and parsing remain 24-hour HH:MM. Extends the
date-format tests with the midnight/noon edge cases.
This commit is contained in:
NeilClack
2026-09-29 08:43:59 -05:00
parent 336f47c6ac
commit 59b499037b
6 changed files with 113 additions and 3 deletions
+33
View File
@@ -1,5 +1,7 @@
#include "date_format.h"
#include <cctype>
#include <cstdio>
#include <ctime>
namespace radiantwave::datetime {
@@ -34,4 +36,35 @@ std::string formatTime(std::time_t t) {
return formatDate(t, "%H:%M:%S");
}
std::string formatHm12h(const std::string& hm24) {
// Require exactly HH:MM with two decimal digits each.
if (hm24.size() != 5 || hm24[2] != ':' ||
!std::isdigit(static_cast<unsigned char>(hm24[0])) ||
!std::isdigit(static_cast<unsigned char>(hm24[1])) ||
!std::isdigit(static_cast<unsigned char>(hm24[3])) ||
!std::isdigit(static_cast<unsigned char>(hm24[4]))) {
return hm24;
}
const int hour24 = (hm24[0] - '0') * 10 + (hm24[1] - '0');
const int minute = (hm24[3] - '0') * 10 + (hm24[4] - '0');
if (hour24 > 23 || minute > 59) {
return hm24;
}
int hour12 = hour24 % 12;
if (hour12 == 0) {
hour12 = 12;
}
const char* period = hour24 < 12 ? "AM" : "PM";
char buffer[16];
std::snprintf(buffer, sizeof(buffer), "%d:%02d %s", hour12, minute, period);
return std::string(buffer);
}
std::string formatTime12h(std::time_t t) {
return formatHm12h(formatDate(t, "%H:%M"));
}
} // namespace radiantwave::datetime
+9
View File
@@ -26,4 +26,13 @@ std::string formatDate(std::time_t t, const std::string& pattern);
// Render `t` as a 24-hour clock: HH:MM:SS.
std::string formatTime(std::time_t t);
// Convert a 24-hour clock `HH:MM` string to a 12-hour display label, e.g.
// "00:00" -> "12:00 AM", "13:05" -> "1:05 PM". Storage and parsing remain
// 24-hour; this is display-only. Returns `hm24` unchanged when it is not a
// valid 24-hour time.
std::string formatHm12h(const std::string& hm24);
// Render `t` in local time as a 12-hour clock label, e.g. "7:05 PM".
std::string formatTime12h(std::time_t t);
} // namespace radiantwave::datetime
+1 -1
View File
@@ -249,7 +249,7 @@ void Home::render(SDL_Renderer *renderer)
internet_ ? std::string("Connected") : std::string("Offline"),
audioStatus(),
radiantwave::datetime::formatDate(now, dateFormat_),
radiantwave::datetime::formatTime(now),
radiantwave::datetime::formatTime12h(now),
};
std::vector<SDL_Color> valueColors(values.size(), footerColor);
std::vector<bool> valueBold(values.size(), false);
+6 -1
View File
@@ -1,5 +1,6 @@
#include "log_viewer_page.h"
#include "app_state.h"
#include "date_format.h"
#include "timezone_helper.h"
#include "logical_size.h"
#include <SDL3/SDL.h>
@@ -256,7 +257,11 @@ void LogViewerPage::rebuildWrapped(float maxLineWidth) {
using namespace radiantwave::logger;
for (const auto& entry : rawEntries_) {
std::string timeStr = radiantwave::timezone::formatUtcToTimezone(entry.timestamp, currentTzCode_, "%Y-%m-%d %H:%M:%S");
std::string timeStr =
radiantwave::timezone::formatUtcToTimezone(entry.timestamp, currentTzCode_, "%Y-%m-%d") +
" " +
radiantwave::datetime::formatHm12h(
radiantwave::timezone::formatUtcToTimezone(entry.timestamp, currentTzCode_, "%H:%M"));
std::string fullLineStr = "[" + timeStr + "] [" + std::string(logLevelToString(entry.level)) + "] [" + entry.component + "] " + entry.message;
+8 -1
View File
@@ -1,5 +1,6 @@
#include "timezone_page.h"
#include "app_state.h"
#include "date_format.h"
#include "logical_size.h"
#include <SDL3/SDL.h>
#include <algorithm>
@@ -237,7 +238,13 @@ void TimeZonePage::render(SDL_Renderer* renderer) {
}
time_t now = std::time(nullptr);
std::string localSample = "Local Time: " + radiantwave::timezone::formatUtcToTimezone(now, currentTzCode_);
// Show the configured zone's wall-clock time with an AM/PM label; storage
// and the rest of the app stay 24-hour.
std::string localSample = "Local Time: " +
radiantwave::timezone::formatUtcToTimezone(now, currentTzCode_, "%Y-%m-%d") + " " +
radiantwave::datetime::formatHm12h(
radiantwave::timezone::formatUtcToTimezone(now, currentTzCode_, "%H:%M")) +
" " + radiantwave::timezone::formatUtcToTimezone(now, currentTzCode_, "%Z");
sampleText_.ensure(renderer, fontHeader_, localSample, green);
if (sampleText_.texture) {
SDL_FRect dst{700.0f, currentY, sampleText_.width, sampleText_.height};
+56
View File
@@ -59,3 +59,59 @@ TEST_F(DateFormatTest, OptionsIncludeDefault) {
EXPECT_EQ(options[0].pattern, std::string(DEFAULT_DATE_FORMAT));
EXPECT_EQ(options[0].name, "Month/Day/Year");
}
TEST_F(DateFormatTest, Converts24HourTo12HourLabels) {
EXPECT_EQ(formatHm12h("00:00"), "12:00 AM");
EXPECT_EQ(formatHm12h("00:05"), "12:05 AM");
EXPECT_EQ(formatHm12h("11:59"), "11:59 AM");
EXPECT_EQ(formatHm12h("12:00"), "12:00 PM");
EXPECT_EQ(formatHm12h("12:30"), "12:30 PM");
EXPECT_EQ(formatHm12h("13:00"), "1:00 PM");
EXPECT_EQ(formatHm12h("23:59"), "11:59 PM");
}
TEST_F(DateFormatTest, LeavesMalformedInputUnchanged) {
EXPECT_EQ(formatHm12h(""), "");
EXPECT_EQ(formatHm12h("7:05"), "7:05");
EXPECT_EQ(formatHm12h("24:00"), "24:00");
EXPECT_EQ(formatHm12h("12:60"), "12:60");
EXPECT_EQ(formatHm12h("ab:cd"), "ab:cd");
}
TEST_F(DateFormatTest, Formats12HourClock) {
std::tm morning = {};
morning.tm_year = 2026 - 1900;
morning.tm_mon = 8 - 1;
morning.tm_mday = 4;
morning.tm_hour = 7;
morning.tm_min = 5;
morning.tm_sec = 0;
morning.tm_isdst = -1;
time_t tMorning = mktime(&morning);
ASSERT_NE(tMorning, static_cast<time_t>(-1));
EXPECT_EQ(formatTime12h(tMorning), "7:05 AM");
std::tm midnight = {};
midnight.tm_year = 2026 - 1900;
midnight.tm_mon = 8 - 1;
midnight.tm_mday = 4;
midnight.tm_hour = 0;
midnight.tm_min = 0;
midnight.tm_sec = 0;
midnight.tm_isdst = -1;
time_t tMidnight = mktime(&midnight);
ASSERT_NE(tMidnight, static_cast<time_t>(-1));
EXPECT_EQ(formatTime12h(tMidnight), "12:00 AM");
std::tm evening = {};
evening.tm_year = 2026 - 1900;
evening.tm_mon = 8 - 1;
evening.tm_mday = 4;
evening.tm_hour = 19;
evening.tm_min = 5;
evening.tm_sec = 0;
evening.tm_isdst = -1;
time_t tEvening = mktime(&evening);
ASSERT_NE(tEvening, static_cast<time_t>(-1));
EXPECT_EQ(formatTime12h(tEvening), "7:05 PM");
}