From 6ea8d9d5fc8e9e5d58914c33824a6224bc40f43e Mon Sep 17 00:00:00 2001 From: acidburns Date: Mon, 16 Feb 2026 11:14:30 +0100 Subject: [PATCH] Use configured local timezone in web UI and drop legacy history CSV parsing --- src/web_server.cpp | 71 +++++++++++++++++++--------------------------- 1 file changed, 29 insertions(+), 42 deletions(-) diff --git a/src/web_server.cpp b/src/web_server.cpp index 920fb52..c68dbb3 100644 --- a/src/web_server.cpp +++ b/src/web_server.cpp @@ -57,26 +57,23 @@ static HistoryJob g_history = {}; static constexpr size_t SD_LIST_MAX_FILES = 200; static constexpr size_t SD_DOWNLOAD_MAX_PATH = 160; -static String format_utc_hms(uint32_t ts_utc) { +static String format_local_hms(uint32_t ts_utc) { if (ts_utc == 0) { return "n/a"; } time_t t = static_cast(ts_utc); - struct tm tm_utc; - gmtime_r(&t, &tm_utc); - char buf[16]; - snprintf(buf, sizeof(buf), "%02d:%02d:%02d UTC", - tm_utc.tm_hour, - tm_utc.tm_min, - tm_utc.tm_sec); + struct tm tm_local; + localtime_r(&t, &tm_local); + char buf[24]; + strftime(buf, sizeof(buf), "%H:%M:%S %Z", &tm_local); return String(buf); } -static String format_epoch_hms(uint32_t ts_utc) { +static String format_epoch_local_hms(uint32_t ts_utc) { if (ts_utc == 0) { return "n/a"; } - return String(ts_utc) + " (" + format_utc_hms(ts_utc) + ")"; + return String(ts_utc) + " (" + format_local_hms(ts_utc) + ")"; } static uint32_t timestamp_age_seconds(uint32_t ts_utc) { @@ -277,12 +274,12 @@ static bool history_parse_line(const char *line, uint32_t &ts_out, float &p_out) if (!line || line[0] < '0' || line[0] > '9') { return false; } - const char *comma = strchr(line, ','); - if (!comma) { + const char *comma1 = strchr(line, ','); + if (!comma1) { return false; } char ts_buf[16]; - size_t ts_len = static_cast(comma - line); + size_t ts_len = static_cast(comma1 - line); if (ts_len >= sizeof(ts_buf)) { return false; } @@ -293,32 +290,22 @@ static bool history_parse_line(const char *line, uint32_t &ts_out, float &p_out) if (end == ts_buf) { return false; } - auto parse_float_field = [](const char *start, const char *end, float &out) -> bool { - if (!start) { - return false; - } - char p_buf[16]; - size_t p_len = end ? static_cast(end - start) : strlen(start); - if (p_len == 0 || p_len >= sizeof(p_buf)) { - return false; - } - memcpy(p_buf, start, p_len); - p_buf[p_len] = '\0'; - char *endp = nullptr; - out = strtof(p_buf, &endp); - return endp != p_buf; - }; - - const char *field2_start = comma + 1; - const char *field2_end = strchr(field2_start, ','); - float p = 0.0f; - bool parsed_power = parse_float_field(field2_start, field2_end, p); - if (!parsed_power && field2_end) { - const char *field3_start = field2_end + 1; - const char *field3_end = strchr(field3_start, ','); - parsed_power = parse_float_field(field3_start, field3_end, p); + const char *comma2 = strchr(comma1 + 1, ','); + if (!comma2) { + return false; } - if (!parsed_power) { + const char *p_start = comma2 + 1; + const char *p_end = strchr(p_start, ','); + char p_buf[16]; + size_t p_len = p_end ? static_cast(p_end - p_start) : strlen(p_start); + if (p_len == 0 || p_len >= sizeof(p_buf)) { + return false; + } + memcpy(p_buf, p_start, p_len); + p_buf[p_len] = '\0'; + char *endp = nullptr; + float p = strtof(p_buf, &endp); + if (endp == p_buf) { return false; } ts_out = ts; @@ -418,7 +405,7 @@ static String render_sender_block(const SenderStatus &status) { if (!status.has_data) { s += "No data"; } else { - s += "Last update: " + format_epoch_hms(status.last_update_ts_utc); + s += "Last update: " + format_epoch_local_hms(status.last_update_ts_utc); if (time_is_synced()) { s += " (" + String(timestamp_age_seconds(status.last_update_ts_utc)) + "s ago)"; } @@ -437,7 +424,7 @@ static String render_sender_block(const SenderStatus &status) { duplicate_pct = (static_cast(duplicate_batches) * 100.0f) / static_cast(total_batches); } s += "
Dup batches: " + String(duplicate_batches) + "/" + String(total_batches) + " (" + String(duplicate_pct, 1) + "%)"; - s += " last: " + format_epoch_hms(status.rx_last_duplicate_ts_utc); + s += " last: " + format_epoch_local_hms(status.rx_last_duplicate_ts_utc); if (time_is_synced() && status.rx_last_duplicate_ts_utc > 0) { s += " (" + String(timestamp_age_seconds(status.rx_last_duplicate_ts_utc)) + "s ago)"; } @@ -665,14 +652,14 @@ static void handle_sender() { if (g_last_batch_count[i] > 0) { html += "

Last batch (" + String(g_last_batch_count[i]) + " samples)

"; html += ""; - html += ""; + html += ""; html += ""; for (uint8_t r = 0; r < g_last_batch_count[i]; ++r) { const MeterData &d = g_last_batch[i][r]; html += ""; html += ""; html += ""; - html += ""; + html += ""; html += ""; html += ""; html += "";
#ts_utcts_hms_utce_kwhp_wp1_wp2_wp3_w
#ts_utcts_hms_locale_kwhp_wp1_wp2_wp3_wbat_vbat_pctrssisnrerr_txerr_lastrx_reject
" + String(r) + "" + String(d.ts_utc) + "" + format_utc_hms(d.ts_utc) + "" + format_local_hms(d.ts_utc) + "" + String(d.energy_total_kwh, 2) + "" + String(round_power_w(d.total_power_w)) + "" + String(round_power_w(d.phase_power_w[0])) + "