Summary
In controller/State.ts, the "still running from yesterday" branch of ScheduleTime.calcScheduleDate assigns times.endTime = ytimes.startTime instead of ytimes.endTime. The result is a window of zero duration ending in the past, which collapses shouldBeOn to false until tm finally enters today's ttimes window.
The symptom is most visible with a schedule whose run window spans local midnight (for example a 12:00 PM → 12:00 PM schedule, which the engine synthesizes to today-noon → tomorrow-noon via the endTime <= startTime wraparound at line 1276). In the AM hours, tm is past today's start-of-day but not yet inside today's ttimes; it does fall inside ytimes (yesterday-noon → today-noon-plus-59s). The yesterday-branch is taken and clobbers endTime with startTime.
Affected code
controller/State.ts, lines ~1307–1314 (commit 8961410):
// First check if we are still running yesterday. This will ensure we have
// the first runtime.
if (fnInRange(tm, ytimes)) {
// Check the dow.
let sd = schedDays.find(elem => elem.dow === ytimes.startTime.getDay());
if (typeof sd !== 'undefined' && (sched.scheduleDays & sd.bitval) !== 0) {
times.startTime = ytimes.startTime;
times.endTime = ytimes.startTime; // ← should be ytimes.endTime
return times;
}
}
Compare with the today-branch (line 1301: times.endTime = ttimes.endTime) and the tomorrow-branch (line 1331: times.endTime = ntimes.endTime). Only the yesterday-branch is broken.
Trace (Schedule 12:00 PM → 12:00 PM, all 7 days, evaluated at Wed 09:28)
calcSchedule recalcs because sod (Wed 00:00) !== dtCalc (Tue 00:00).
calcScheduleDate(Wed 09:28, B) computes:
ttimes = Wed 12:00 → Thu 12:00:59.999
ytimes = Tue 12:00 → Wed 12:00:59.999
- Check 1 (
tm in ttimes): Wed 09:28 < Wed 12:00 → miss.
- Check 2 (
tm in ytimes): Tue 12:00 ≤ Wed 09:28 ≤ Wed 12:00:59.999 → hit. Bug fires: returns { Tue 12:00, Tue 12:00 }.
- Back in
calcSchedule:1365, times.endTime (Tue 12:00) > currentTime (Wed 09:28) → false → falls into fast-forward else-branch.
- The else-branch eventually re-enters
calcScheduleDate with tm = Thu 00:00; that call also lands in ytimes (Wed 12:00 → Thu 12:00:59.999) and the bug fires again, leaving this.startTime = this.endTime = Wed 12:00.
_calcShouldBeOn(Wed 09:28): time < tmStart → returns false.
Result: the schedule shows greyed/inactive in dashPanel from local midnight until today's ttimes.startTime (noon in this example), at which point Check 1 finally succeeds and the window normalizes.
The bug is in the schedule-time math only; it does not by itself turn a circuit off (the trigger loop ORs shouldBeOn across all schedules on the circuit), but it makes overlapping/handoff schedules display wrong and silently disables single midnight-crossing schedules during the AM portion of their window.
Reproduction
- Create a single schedule with start
12:00 PM and end 12:00 PM (any day mask that includes the previous and current local day).
- Wait until the next morning (any time strictly between local midnight and noon).
- Observe in dashPanel: the schedule's tile shows inactive even though its synthesized 24-hour window covers the current moment.
Proposed fix
One-character correction on line 1312:
times.endTime = ytimes.endTime;
I'll open a PR with this fix.
Environment
- njspc commit 8961410 (master, 2026-05-15)
- Discovered while diagnosing related schedule rollover behavior with @pawmmm
Summary
In
controller/State.ts, the "still running from yesterday" branch ofScheduleTime.calcScheduleDateassignstimes.endTime = ytimes.startTimeinstead ofytimes.endTime. The result is a window of zero duration ending in the past, which collapsesshouldBeOntofalseuntiltmfinally enters today'sttimeswindow.The symptom is most visible with a schedule whose run window spans local midnight (for example a
12:00 PM → 12:00 PMschedule, which the engine synthesizes to today-noon → tomorrow-noon via theendTime <= startTimewraparound at line 1276). In the AM hours,tmis past today's start-of-day but not yet inside today'sttimes; it does fall insideytimes(yesterday-noon → today-noon-plus-59s). The yesterday-branch is taken and clobbersendTimewithstartTime.Affected code
controller/State.ts, lines ~1307–1314 (commit 8961410):Compare with the today-branch (line 1301:
times.endTime = ttimes.endTime) and the tomorrow-branch (line 1331:times.endTime = ntimes.endTime). Only the yesterday-branch is broken.Trace (Schedule 12:00 PM → 12:00 PM, all 7 days, evaluated at Wed 09:28)
calcSchedulerecalcs becausesod (Wed 00:00) !== dtCalc (Tue 00:00).calcScheduleDate(Wed 09:28, B)computes:ttimes= Wed 12:00 → Thu 12:00:59.999ytimes= Tue 12:00 → Wed 12:00:59.999tm in ttimes): Wed 09:28 < Wed 12:00 → miss.tm in ytimes): Tue 12:00 ≤ Wed 09:28 ≤ Wed 12:00:59.999 → hit. Bug fires: returns{ Tue 12:00, Tue 12:00 }.calcSchedule:1365,times.endTime (Tue 12:00) > currentTime (Wed 09:28)→ false → falls into fast-forward else-branch.calcScheduleDatewithtm = Thu 00:00; that call also lands inytimes(Wed 12:00 → Thu 12:00:59.999) and the bug fires again, leavingthis.startTime = this.endTime = Wed 12:00._calcShouldBeOn(Wed 09:28):time < tmStart→ returnsfalse.Result: the schedule shows greyed/inactive in dashPanel from local midnight until today's
ttimes.startTime(noon in this example), at which point Check 1 finally succeeds and the window normalizes.The bug is in the schedule-time math only; it does not by itself turn a circuit off (the trigger loop ORs
shouldBeOnacross all schedules on the circuit), but it makes overlapping/handoff schedules display wrong and silently disables single midnight-crossing schedules during the AM portion of their window.Reproduction
12:00 PMand end12:00 PM(any day mask that includes the previous and current local day).Proposed fix
One-character correction on line 1312:
I'll open a PR with this fix.
Environment