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:
@@ -1,5 +1,7 @@
|
|||||||
#include "date_format.h"
|
#include "date_format.h"
|
||||||
|
|
||||||
|
#include <cctype>
|
||||||
|
#include <cstdio>
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
|
||||||
namespace radiantwave::datetime {
|
namespace radiantwave::datetime {
|
||||||
@@ -34,4 +36,35 @@ std::string formatTime(std::time_t t) {
|
|||||||
return formatDate(t, "%H:%M:%S");
|
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
|
} // namespace radiantwave::datetime
|
||||||
|
|||||||
@@ -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.
|
// Render `t` as a 24-hour clock: HH:MM:SS.
|
||||||
std::string formatTime(std::time_t t);
|
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
|
} // namespace radiantwave::datetime
|
||||||
|
|||||||
+1
-1
@@ -249,7 +249,7 @@ void Home::render(SDL_Renderer *renderer)
|
|||||||
internet_ ? std::string("Connected") : std::string("Offline"),
|
internet_ ? std::string("Connected") : std::string("Offline"),
|
||||||
audioStatus(),
|
audioStatus(),
|
||||||
radiantwave::datetime::formatDate(now, dateFormat_),
|
radiantwave::datetime::formatDate(now, dateFormat_),
|
||||||
radiantwave::datetime::formatTime(now),
|
radiantwave::datetime::formatTime12h(now),
|
||||||
};
|
};
|
||||||
std::vector<SDL_Color> valueColors(values.size(), footerColor);
|
std::vector<SDL_Color> valueColors(values.size(), footerColor);
|
||||||
std::vector<bool> valueBold(values.size(), false);
|
std::vector<bool> valueBold(values.size(), false);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "log_viewer_page.h"
|
#include "log_viewer_page.h"
|
||||||
#include "app_state.h"
|
#include "app_state.h"
|
||||||
|
#include "date_format.h"
|
||||||
#include "timezone_helper.h"
|
#include "timezone_helper.h"
|
||||||
#include "logical_size.h"
|
#include "logical_size.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
@@ -256,7 +257,11 @@ void LogViewerPage::rebuildWrapped(float maxLineWidth) {
|
|||||||
using namespace radiantwave::logger;
|
using namespace radiantwave::logger;
|
||||||
|
|
||||||
for (const auto& entry : rawEntries_) {
|
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;
|
std::string fullLineStr = "[" + timeStr + "] [" + std::string(logLevelToString(entry.level)) + "] [" + entry.component + "] " + entry.message;
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "timezone_page.h"
|
#include "timezone_page.h"
|
||||||
#include "app_state.h"
|
#include "app_state.h"
|
||||||
|
#include "date_format.h"
|
||||||
#include "logical_size.h"
|
#include "logical_size.h"
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
@@ -237,7 +238,13 @@ void TimeZonePage::render(SDL_Renderer* renderer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
time_t now = std::time(nullptr);
|
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);
|
sampleText_.ensure(renderer, fontHeader_, localSample, green);
|
||||||
if (sampleText_.texture) {
|
if (sampleText_.texture) {
|
||||||
SDL_FRect dst{700.0f, currentY, sampleText_.width, sampleText_.height};
|
SDL_FRect dst{700.0f, currentY, sampleText_.width, sampleText_.height};
|
||||||
|
|||||||
@@ -59,3 +59,59 @@ TEST_F(DateFormatTest, OptionsIncludeDefault) {
|
|||||||
EXPECT_EQ(options[0].pattern, std::string(DEFAULT_DATE_FORMAT));
|
EXPECT_EQ(options[0].pattern, std::string(DEFAULT_DATE_FORMAT));
|
||||||
EXPECT_EQ(options[0].name, "Month/Day/Year");
|
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");
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user