feat: summer time - Settings > Summer time (Europe, North America, Australia, New Zealand) puts the clock an hour ahead and back by itself; every local time goes through tzHoursAt(), the alarm keeps its time across the change (a skipped time rings an hour later, a repeated one once)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Jakub
2026-10-29 14:09:25 +01:00
co-authored by Claude Opus 5.5
parent c46a0a2b55
commit cdc2ff25c9
17 changed files with 119 additions and 50 deletions
+6 -2
View File
@@ -7,7 +7,7 @@ board supports are shown.
| Section | What's there |
| ------- | ------------ |
| **Display** | Brightness, auto-off, wake on message, the message alert (banner, corner envelope or off), lock screen, the lock clock's look and the [PIN](./lock.md), battery as icon / % / volts, time zone, clock format and seconds; on e-ink also rotation and full refresh, and on a 4.26" panel the text and icon size |
| **Display** | Brightness, auto-off, wake on message, the message alert (banner, corner envelope or off), lock screen, the lock clock's look and the [PIN](./lock.md), battery as icon / % / volts, time zone and summer time, clock format and seconds; on e-ink also rotation and full refresh, and on a 4.26" panel the text and icon size |
| **Sound** | Buzzer on / off / auto (quiet while the app is connected), volume, quiet hours, the melody for messages, channels and adverts |
| **Home Pages** | Order of the home pages, and which are shown |
| **Radio** | TX power, preset, frequency, SF / BW / CR, saved presets, Auto pwr, the scope list, then the advanced options (below) |
@@ -26,6 +26,10 @@ A few notes:
apart. The default one is used for direct messages and repeating, and a channel
follows it unless it picks its own (see [Messages](./messages.md#channels)). Anyone who
types the same name gets the same scope; it isn't encryption.
- **Summer time** puts the clock an hour ahead and back again by itself, by
the rule of your region: Europe (the last Sundays of March and October),
North America, south-east Australia or New Zealand. The time zone stays
the winter one, e.g. UTC+1 for Poland. The alarm and quiet hours follow it.
- **Low battery** shuts the device down at the voltage you choose, which is
also 0 % on the battery indicator.
- **Batt curve** sets the cell voltage at 0, 10 … 100 % for a battery that
@@ -124,7 +128,7 @@ so its documentation applies:
| `agc.reset.interval` | Seconds between radio gain resets (in steps of 4, `0` off), with `get` / `set` |
| `get name`, `set name …` | The device name |
| `get pin`, `set pin 123456` | The Bluetooth PIN, `0` for a random one; used from the next start |
| `get tz.offset`, `set tz.offset 2` | Time zone, in hours from UTC |
| `get tz.offset`, `set tz.offset 1` | Time zone, in hours from UTC (the winter one when summer time is on) |
| `get lat`, `set lat …`, `get lon`, `set lon …` | The fixed position |
| `ver`, `board` | Firmware version, hardware |
| `reboot`, `poweroff` | Restart or turn off |
+2 -2
View File
@@ -18,8 +18,8 @@ The **Clock** home page shows the time with a bar under it that fills with
the seconds (Settings › Display › Clock seconds), the date, and up to three
fields you choose (hold Enter): battery, temperature, humidity, pressure, altitude
(barometric or GPS), light, CO₂, GPS position, satellites, contacts, unread
messages. The time comes from GPS or the phone app; the time zone is in
Settings.
messages. The time comes from GPS or the phone app; the time zone and summer
time are in Settings.
**Enter** on the clock opens the clock tools:
+1
View File
@@ -349,6 +349,7 @@ static void sanitize(NodePrefs& p) {
flag(p.fav_sort, 1);
flag(p.msg_wake, 1);
if (p.contact_expiry_idx >= NodePrefs::CONTACT_EXPIRY_COUNT) p.contact_expiry_idx = 0;
if (p.dst_rule >= NodePrefs::DST_COUNT) p.dst_rule = NodePrefs::DST_OFF;
if (p.display_brightness_pct > 100) p.display_brightness_pct = 0;
flag(p.quiet_hours, 0);
if (p.quiet_from > 23) p.quiet_from = 22;
+6 -5
View File
@@ -124,7 +124,8 @@ void MyMesh::botDmRecord(const uint8_t* pubkey) {
// wraps past midnight.
bool MyMesh::botInQuietHours() const {
int h;
if (!localHour(getRTCClock()->getCurrentTime(), _prefs.tz_offset_hours, h)) return false; // clock not set -- don't suppress
const uint32_t now = getRTCClock()->getCurrentTime();
if (!localHour(now, tzHoursAt(_prefs, now), h)) return false; // clock not set -- don't suppress
return hourInWindow(h, _prefs.bot_quiet_start, _prefs.bot_quiet_end);
}
@@ -188,7 +189,7 @@ void MyMesh::tryBotReplyDM(const ContactInfo& from, const char* text, uint8_t ho
expandMsg(_prefs.bot_reply_dm, expanded, sizeof(expanded),
sensors.node_lat, sensors.node_lon,
sensors.node_lat != 0.0 || sensors.node_lon != 0.0,
ts, _prefs.tz_offset_hours,
ts, tzHoursAt(_prefs, ts),
&sensors, (float)board.getBattMilliVolts() / 1000.0f,
from.name, hops, board.getMCUTemperature(), &s_bot_in);
uint32_t expected_ack, est_timeout;
@@ -230,7 +231,7 @@ void MyMesh::tryBotReplyChannel(uint8_t channel_idx, const char* text, uint8_t h
expandMsg(_prefs.bot_reply_ch, expanded, sizeof(expanded),
sensors.node_lat, sensors.node_lon,
sensors.node_lat != 0.0 || sensors.node_lon != 0.0,
ts, _prefs.tz_offset_hours,
ts, tzHoursAt(_prefs, ts),
&sensors, (float)board.getBattMilliVolts() / 1000.0f,
sender_name, hops, board.getMCUTemperature(), &s_bot_in);
// Anti-loop: don't echo a message identical to our own reply (e.g. another bot
@@ -275,7 +276,7 @@ void MyMesh::tryBotReplyRoom(const ContactInfo& from, const uint8_t* sender_pref
expandMsg(_prefs.bot_reply_room, expanded, sizeof(expanded),
sensors.node_lat, sensors.node_lon,
sensors.node_lat != 0.0 || sensors.node_lon != 0.0,
ts, _prefs.tz_offset_hours,
ts, tzHoursAt(_prefs, ts),
&sensors, (float)board.getBattMilliVolts() / 1000.0f,
sender_name, hops, board.getMCUTemperature(), &s_bot_in);
// Anti-loop, mirrors the channel path: don't echo a message identical to
@@ -437,7 +438,7 @@ bool MyMesh::botCommandReply(const char* cmd, const char* arg, const char* arg2,
expandMsg(tmpl, out, out_len,
sensors.node_lat, sensors.node_lon,
sensors.node_lat != 0.0 || sensors.node_lon != 0.0,
ts, _prefs.tz_offset_hours,
ts, tzHoursAt(_prefs, ts),
&sensors, (float)board.getBattMilliVolts() / 1000.0f,
sender_name, hops, board.getMCUTemperature(), &s_bot_in);
if (strchr(out, '{')) strcpy(out, "n/a"); // requested sensor not present
+49 -2
View File
@@ -268,6 +268,11 @@ struct NodePrefsFields {
// ── Clock & alarm ─────────────────────────────────────────────────────
uint8_t clock_hide_seconds; // 0=show HH:MM:SS/refresh 1s (default), 1=hide/refresh 60s
uint8_t clock_12h; // 0=24h (default), 1=12h with AM/PM
// Summer time: the rule that puts the clock an hour ahead for part of the
// year, on top of tz_offset_hours (then the standard, winter offset). Every
// local time goes through tzHoursAt() below.
enum : uint8_t { DST_OFF, DST_EU, DST_US, DST_AU, DST_NZ, DST_COUNT };
uint8_t dst_rule;
// Alarm clock — a wake alarm, configured from the Clock page (Enter › Alarm).
// Stored as a local time-of-day; the actual fire instant is (re)computed as an
// absolute time in tickBackground(), which is what makes it robust to RTC
@@ -830,7 +835,7 @@ class NodePrefs : public NodePrefsFields, public ConfigSerializer {
s.def("kb_main", keyboard_main_alphabet); s.def("kb_cardkb", keyboard_cardkb_compact);
}
void clockSolo(Section& s) {
s.def("hide_sec", clock_hide_seconds); s.def("h12", clock_12h);
s.def("hide_sec", clock_hide_seconds); s.def("h12", clock_12h); s.def("dst", dst_rule);
s.def("alarm", alarm_on); s.def("alarm_h", alarm_hour); s.def("alarm_m", alarm_min);
s.def("alarm_rep", alarm_repeat_mask);
}
@@ -996,6 +1001,48 @@ static inline bool hourInWindow(int h, int start, int end) {
if (start == end) return false;
return start < end ? (h >= start && h < end) : (h >= start || h < end);
}
// Days since 1970-01-01 of a calendar date (H. Hinnant's days_from_civil).
static inline int32_t daysFromCivil(int y, int m, int d) {
y -= m <= 2;
const int era = (y >= 0 ? y : y - 399) / 400;
const int yoe = y - era * 400;
const int doy = (153 * (m + (m > 2 ? -3 : 9)) + 2) / 5 + d - 1;
return era * 146097 + yoe * 365 + yoe / 4 - yoe / 100 + doy - 719468;
}
// The day (since 1970) of the n-th Sunday of a month, n = 1..4, 5 = the last.
static inline int32_t nthSunday(int y, int m, int n) {
const int32_t first = daysFromCivil(y, m, 1);
int32_t d = first + (3 - first % 7 + 7) % 7 + (n - 1) * 7; // 1970-01-01 was a Thursday
if (n == 5 && d >= daysFromCivil(m == 12 ? y + 1 : y, m == 12 ? 1 : m + 1, 1)) d -= 7;
return d;
}
// Summer time in force at `utc`. The changes, per NodePrefs::dst_rule: Europe
// (EU / UK) last Sunday of March - last Sunday of October at 01:00 UTC; North
// America second Sunday of March - first Sunday of November; south-east
// Australia first Sunday of October - first Sunday of April; New Zealand last
// Sunday of September - first Sunday of April; those three forward at 02:00,
// back at 02:00 summer time in America and at 03:00 in the other two.
static inline bool inSummerTime(const NodePrefs& p, uint32_t utc) {
// Hours in local standard time, or UTC with `utc` set.
struct Rule { uint8_t on_mon, on_nth, on_hour, off_mon, off_nth, off_hour, utc; };
static const Rule RULES[NodePrefs::DST_COUNT - 1] = {
{ 3, 5, 1, 10, 5, 1, 1 }, { 3, 2, 2, 11, 1, 1, 0 }, { 10, 1, 2, 4, 1, 2, 0 }, { 9, 5, 2, 4, 1, 2, 0 },
};
if (p.dst_rule == NodePrefs::DST_OFF || p.dst_rule >= NodePrefs::DST_COUNT || utc < 1000000000UL) return false;
const Rule& r = RULES[p.dst_rule - 1];
const int64_t std_off = r.utc ? 0 : (int64_t)p.tz_offset_hours * 3600;
time_t lt = (time_t)((int64_t)utc + (int64_t)p.tz_offset_hours * 3600);
const int y = gmtime(&lt)->tm_year + 1900;
const int64_t on = (int64_t)nthSunday(y, r.on_mon, r.on_nth) * 86400 + r.on_hour * 3600 - std_off;
const int64_t off = (int64_t)nthSunday(y, r.off_mon, r.off_nth) * 86400 + r.off_hour * 3600 - std_off;
const int64_t t = utc;
return on < off ? (t >= on && t < off) : (t >= on || t < off); // southern summers span New Year
}
// The offset from UTC in force at `utc`, in hours: the time zone plus summer time.
static inline int8_t tzHoursAt(const NodePrefs& p, uint32_t utc) {
return (int8_t)(p.tz_offset_hours + (inSummerTime(p, utc) ? 1 : 0));
}
static inline int8_t tzHoursAt(const NodePrefs* p, uint32_t utc) { return p ? tzHoursAt(*p, utc) : 0; }
// The local hour from a UTC time; false while the clock isn't set.
static inline bool localHour(uint32_t utc, int8_t tz_hours, int& h) {
if (utc < 1000000000UL) return false;
@@ -1012,7 +1059,7 @@ static inline bool localTm(uint32_t utc, int8_t tz_hours, struct tm& out) {
// Settings > Sound > Quiet hours in force at `utc` (never with the clock unset).
static inline bool inQuietHours(const NodePrefs& p, uint32_t utc) {
int h;
return p.quiet_hours && localHour(utc, p.tz_offset_hours, h) && hourInWindow(h, p.quiet_from, p.quiet_to);
return p.quiet_hours && localHour(utc, tzHoursAt(p, utc), h) && hourInWindow(h, p.quiet_from, p.quiet_to);
}
// Bounds for a usable repeater radio profile. The frequency range is passed in
+34 -22
View File
@@ -64,34 +64,44 @@ private:
_events->push(UiEventType::ClockAlert, label);
}
// The UTC instant a local wall time first happens. Summer time starting
// skips an hour: a time inside it comes as long after the jump as it was
// meant to be after its start (02:30 rings at 03:30). Summer time ending
// repeats one: the first of the two counts.
int64_t wallToUtc(int64_t local) const {
const int8_t tz = _prefs->tz_offset_hours;
for (int8_t h = tz + 1; h >= tz; h--) { // the summer offset gives the earlier instant
int64_t u = local - (int64_t)h * 3600;
if (tzHoursAt(_prefs, (uint32_t)u) == h) return u;
}
return local - (int64_t)tz * 3600; // skipped
}
// Next absolute wall instant matching alarm_hour:alarm_min in local time,
// strictly after now_wall (an alarm set to the current minute waits a day).
// With alarm_repeat_mask == 0 that's just tomorrow's occurrence (one-shot).
// With a repeat mask set, scan today..+6 days for the next weekday whose bit
// is set (struct tm's tm_wday convention, same as the mask) -- today counts
// only if its time hasn't already passed.
// With alarm_repeat_mask == 0 that's just the next occurrence (one-shot).
// With a repeat mask set, scan today..+7 days for the next weekday whose bit
// is set (struct tm's tm_wday convention, same as the mask). Each day's time
// is turned into UTC with that day's offset, so summer time starting or
// ending in between doesn't move it.
uint32_t computeAlarmNextFire(uint32_t now_wall) const {
int tz = _prefs->tz_offset_hours;
int64_t now_local = (int64_t)now_wall + (int64_t)tz * 3600;
int64_t now_local = (int64_t)now_wall + (int64_t)tzHoursAt(_prefs, now_wall) * 3600;
time_t t = (time_t)now_local;
struct tm* ti = gmtime(&t);
int64_t sod = ti->tm_hour * 3600 + ti->tm_min * 60 + ti->tm_sec; // secs since local midnight
int64_t midnight = now_local - sod;
int64_t time_of_day = (int64_t)_prefs->alarm_hour * 3600 + (int64_t)_prefs->alarm_min * 60;
const int wday = ti->tm_wday;
uint8_t mask = _prefs->alarm_repeat_mask;
if (mask != 0) {
for (int d = 0; d < 7; d++) {
if (mask & (1 << ((ti->tm_wday + d) % 7))) {
int64_t target = midnight + (int64_t)d * 86400 + time_of_day;
if (target > now_local) return (uint32_t)(target - (int64_t)tz * 3600);
}
}
// Mask had no bit set (shouldn't happen -- the UI only offers non-empty
// presets) -- fall through to the one-shot calculation so it still fires.
// A mask with no weekday bit (shouldn't happen -- the UI only offers
// non-empty presets) falls back to one-shot so it still fires.
if ((mask & 0x7F) == 0) mask = 0x7F;
for (int d = 0; d <= 7; d++) {
if (!(mask & (1 << ((wday + d) % 7)))) continue;
int64_t u = wallToUtc(midnight + (int64_t)d * 86400 + time_of_day);
if (u > (int64_t)now_wall) return (uint32_t)u;
}
int64_t target = midnight + time_of_day;
if (target <= now_local) target += 86400;
return (uint32_t)(target - (int64_t)tz * 3600);
return (uint32_t)wallToUtc(midnight + 86400 + time_of_day); // not reached
}
void evaluateAlarm() {
@@ -101,9 +111,11 @@ private:
_alarm_check_ms = now_ms;
uint32_t now_wall = rtc_clock.getCurrentTime();
if (now_wall < 1000000000UL) return; // need a real time sync first
// A time zone change (Settings, CLI tz.offset, the app) moves the local
// alarm time: reschedule here rather than trusting every caller to say so.
if (_alarm_tz != _prefs->tz_offset_hours) { _alarm_tz = _prefs->tz_offset_hours; _alarm_next_fire = 0; }
// A time zone change (Settings, CLI tz.offset, the app) or summer time
// starting / ending moves the local alarm time: reschedule here rather
// than trusting every caller to say so.
const int8_t tz = tzHoursAt(_prefs, now_wall);
if (_alarm_tz != tz) { _alarm_tz = tz; _alarm_next_fire = 0; }
if (_alarm_next_fire == 0) _alarm_next_fire = computeAlarmNextFire(now_wall);
if (now_wall < _alarm_next_fire) return;
if (now_wall - _alarm_next_fire < ALARM_CATCHUP_SECS) {
@@ -127,7 +139,7 @@ private:
NodePrefs* _prefs = nullptr;
UiEventQueue* _events = nullptr;
uint32_t _alarm_next_fire = 0; // unix; 0 = (re)compute lazily once time is valid
int8_t _alarm_tz = 0; // the tz_offset_hours _alarm_next_fire was computed with
int8_t _alarm_tz = 0; // the offset (tzHoursAt) _alarm_next_fire was computed with
uint32_t _alarm_check_ms = 0; // throttle the wall-clock read to ~2 Hz
bool _timer_running = false;
uint32_t _timer_deadline_ms = 0;
@@ -88,8 +88,9 @@ static void expand(const char* tmpl, char* out, int n, const NodePrefs* p) {
}
#endif
float batt = (float)board.getBattMilliVolts() / 1000.0f;
::expandMsg(tmpl, out, n, lat, lon, gps_valid, rtc_clock.getCurrentTime(),
p ? p->tz_offset_hours : 0, &sensors, batt, nullptr, -1, board.getMCUTemperature());
const uint32_t now = rtc_clock.getCurrentTime();
::expandMsg(tmpl, out, n, lat, lon, gps_valid, now,
tzHoursAt(p, now), &sensors, batt, nullptr, -1, board.getMCUTemperature());
}
// A typed message: a reply's leading "@[nick] " stays as it is (a nick may
@@ -26,7 +26,7 @@ static const Key KEYS[] = {
K("display.alert", msg_alert), K("lock", auto_lock), K("lock.clock", lock_compact),
K("display.text.size", ui_text_scale), K("display.icon.size", ui_icon_scale),
K("batt.display", batt_display_mode), K("batt.shutdown", low_batt_mv), K("gps.interval", gps_interval),
K("clock.12h", clock_12h), K("clock.seconds", clock_hide_seconds), K("units.imperial", units_imperial),
K("clock.12h", clock_12h), K("clock.summer", dst_rule), K("clock.seconds", clock_hide_seconds), K("units.imperial", units_imperial),
K("sound.volume", buzzer_volume), K("quiet", quiet_hours), K("quiet.from", quiet_from), K("quiet.until", quiet_to),
K("melody.dm", notif_melody_dm), K("melody.channel", notif_melody_ch), K("melody.advert", notif_melody_ad),
K("advert.sound", advert_sound_scope),
@@ -157,6 +157,10 @@ static void optLockLook(uint8_t v, char* b, int n, const NodePrefs&) { snprintf(
static void optExpiry(uint8_t v, char* b, int n, const NodePrefs&) {
snprintf(b, n, "%s", NodePrefs::contactExpiryLabel(v));
}
static void optDst(uint8_t v, char* b, int n, const NodePrefs&) {
static const char* L[NodePrefs::DST_COUNT] = { "Off", "Europe", "North America", "Australia", "New Zealand" };
snprintf(b, n, "%s", L[v < NodePrefs::DST_COUNT ? v : 0]);
}
static void optChSort(uint8_t v, char* b, int n, const NodePrefs&) {
static const char* L[NodePrefs::CH_SORT_COUNT] = { "Slots", "Newest", "Name" };
snprintf(b, n, "%s", L[v < NodePrefs::CH_SORT_COUNT ? v : 0]);
@@ -276,6 +280,7 @@ static const Setting ALL[] = {
MAP("GPS power saving", "Sleep between fixes", SEC_POWER, gps_interval, GPS_DUTY, optGpsDuty, applyGpsDuty),
MAP("Time zone", "For the clock and the alarm", SEC_TIME, tz_offset_hours, TZ, optTz, nullptr),
IDX("Summer time", "An hour ahead, changed by itself", SEC_TIME, dst_rule, NodePrefs::DST_COUNT, optDst, nullptr),
SW("12-hour clock", "AM / PM instead of 24 h", SEC_TIME, clock_12h, nullptr),
MAP("Clock seconds", "On the home clock", SEC_TIME, clock_hide_seconds, INVERTED, nullptr, nullptr),
+1 -1
View File
@@ -100,7 +100,7 @@ void UITask::buildBot() {
if (log.count() == 0) label(g, "No replies yet", THEME_FONT_BODY, theme::TEXT_MUTED);
for (int i = 0; i < log.count(); i++) {
const BotLogEntry& e = log.at(i);
const uint32_t t = e.ts + (int32_t)p->tz_offset_hours * 3600;
const uint32_t t = e.ts + (int32_t)tzHoursAt(p, e.ts) * 3600;
static const char* const WHERE[] = { "DM", "channel", "room" };
char hd[64];
snprintf(hd, sizeof(hd), "%02u:%02u %s (%s)", (unsigned)(t / 3600 % 24), (unsigned)(t / 60 % 60),
+1 -1
View File
@@ -1378,7 +1378,7 @@ void UITask::flowHistoryPopup() {
const bool named = f.node[0] >= 0 && the_mesh.getContactByIdx(f.node[0], c) && c.name[0];
snprintf(title, sizeof(title), "%s%s%s%.24s", FLOW_KINDS[h.kind < PacketFlow::K_COUNT ? h.kind : PacketFlow::K_OTHER],
named ? " " : "", named && !f.origin ? "via " : "", named ? c.name : "");
const uint32_t t = h.ts + (int32_t)_prefs->tz_offset_hours * 3600;
const uint32_t t = h.ts + (int32_t)tzHoursAt(_prefs, h.ts) * 3600;
int o = snprintf(sub, sizeof(sub), "%02u:%02u:%02u ", (unsigned)(t / 3600 % 24), (unsigned)(t / 60 % 60), (unsigned)(t % 60));
if (h.hops == 0) o += snprintf(sub + o, sizeof(sub) - o, "direct");
else o += snprintf(sub + o, sizeof(sub) - o, "%u hop%s", (unsigned)h.hops, h.hops == 1 ? "" : "s");
@@ -632,8 +632,8 @@ void UITask::settingsGroup(int i) {
g = group(body, "SYSTEM");
listRow(g, LV_SYMBOL_EDIT " Name", the_mesh.getNodeName(), onNodeName, NULL);
if (_prefs) {
snprintf(sub, sizeof(sub), "%s, %s", settingText(*_prefs, SETTING(tz_offset_hours), v1, sizeof(v1)),
_prefs->clock_12h ? "12 h" : "24 h");
snprintf(sub, sizeof(sub), "%s%s, %s", settingText(*_prefs, SETTING(tz_offset_hours), v1, sizeof(v1)),
inSummerTime(*_prefs, rtc_clock.getCurrentTime()) ? " summer" : "", _prefs->clock_12h ? "12 h" : "24 h");
listRow(g, UI_SYMBOL_CLOCK " Time", sub, onOpenSchemaPage, (void*)(uintptr_t)PG_TIME);
schemaRow(g, SETTING(units_imperial));
}
+2 -2
View File
@@ -1166,9 +1166,9 @@ static void tileBadge(lv_obj_t* tile, int n) {
lv_obj_align(lv_obj_get_child(tile, -1), LV_ALIGN_TOP_RIGHT, -2, 2);
}
// Local time (NodePrefs::tz_offset_hours), now or at `utc`; false before the clock is set.
// Local time (time zone + summer time), now or at `utc`; false before the clock is set.
static bool localTime(const NodePrefs* p, struct tm& out, uint32_t utc = rtc_clock.getCurrentTime()) {
return localTm(utc, p ? p->tz_offset_hours : 0, out);
return localTm(utc, tzHoursAt(p, utc), out);
}
static const char* const MONTHS[] = { "Jan", "Feb", "Mar", "Apr", "May", "Jun",
"Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
+1 -2
View File
@@ -153,12 +153,11 @@ public:
display.drawTextCentered(display.width() / 2, display.height() / 2, "No replies yet");
return 2000;
}
const int8_t tz = _prefs ? _prefs->tz_offset_hours : 0;
int mq_delay = 0;
drawList(display, log.count(), _sel, _scroll, [&](int i, int y, bool sel, int reserve) {
drawRowSelection(display, y, sel, reserve);
char line[200];
botcfg::logLine(log.at(i), tz, line, sizeof(line));
botcfg::logLine(log.at(i), tzHoursAt(_prefs, log.at(i).ts), line, sizeof(line));
char filt[200];
display.translateUTF8ToBlocks(filt, line, sizeof(filt));
int r = display.drawTextEllipsized(2, y, display.width() - 2 - reserve, filt, sel);
@@ -263,7 +263,7 @@ class ClockToolsScreen : public UIScreen {
// screen), the grid goes tight and the month and year stand beside it.
int renderCalendar(DisplayDriver& d) {
struct tm t;
if (!localTm(rtc_clock.getCurrentTime(), _prefs ? _prefs->tz_offset_hours : 0, t) || t.tm_year < 120) {
if (!localTm(rtc_clock.getCurrentTime(), tzHoursAt(_prefs, rtc_clock.getCurrentTime()), t) || t.tm_year < 120) {
drawScreenHeader(d, "Calendar");
d.drawTextCentered(d.width() / 2, d.height() / 2 - d.getLineHeight() / 2, "Clock not set");
return 60000;
+3 -3
View File
@@ -1002,7 +1002,7 @@ public:
void drawClockPage(DisplayDriver& d, uint32_t unix_ts) {
struct tm ti;
localTm(unix_ts, _node_prefs ? _node_prefs->tz_offset_hours : 0, ti);
localTm(unix_ts, tzHoursAt(_node_prefs, unix_ts), ti);
const bool h12 = _node_prefs && _node_prefs->clock_12h;
const bool show_sec = !Features::IS_EINK && (!_node_prefs || !_node_prefs->clock_hide_seconds);
const int W = d.width(), H = d.height();
@@ -1086,7 +1086,7 @@ public:
const uint32_t unix_ts = _rtc->getCurrentTime();
const bool synced = unix_ts >= 1000000000UL;
struct tm ti;
if (synced) localTm(unix_ts, _node_prefs ? _node_prefs->tz_offset_hours : 0, ti);
if (synced) localTm(unix_ts, tzHoursAt(_node_prefs, unix_ts), ti);
const bool h12 = _node_prefs && _node_prefs->clock_12h;
const bool compact = _node_prefs && _node_prefs->lock_compact && W > H;
// The month and the rows under the big clock: on a tall screen (portrait
@@ -1338,7 +1338,7 @@ public:
uint32_t now_ts = _rtc->getCurrentTime();
if (now_ts >= 1000000000UL) {
struct tm lt;
localTm(now_ts, _node_prefs->tz_offset_hours, lt);
localTm(now_ts, tzHoursAt(_node_prefs, now_ts), lt);
int hh = lt.tm_hour;
if (_node_prefs->clock_12h) { hh %= 12; if (hh == 0) hh = 12; }
snprintf(filtered_name, sizeof(filtered_name), "%d:%02d", hh, lt.tm_min);
@@ -632,14 +632,13 @@ private:
_list_max_scroll = max_scroll;
NodePrefs* p = _task->getNodePrefs();
int32_t tz_off = (int32_t)(p ? p->tz_offset_hours : 0) * 3600;
for (int i = 0; i < visible; i++) {
int idx = total - 1 - (_list_scroll + i);
if (idx < 0) break;
const TrailPoint& pt = _store->at(idx);
time_t lt = (time_t)((int32_t)pt.ts + tz_off);
time_t lt = (time_t)((int32_t)pt.ts + (int32_t)tzHoursAt(p, pt.ts) * 3600);
struct tm* ti = gmtime(&lt);
char dist[12];