Haltezeit als Zeitfeld: Stunden und Minuten statt einer Minutenzahl

Dasselbe <input type="time"> wie von/bis daneben, nur als Dauer gelesen -
"03:30" tippt sich leichter als "210" und steht in derselben Zeile wie die
beiden Uhrzeiten darueber. Dass es keine Uhrzeit meint, sagt der Text
ringsum ("erst nach ... am Stueck"); die Kopfzeile des Rahmens schreibt es
beim Tippen aus.

Die Obergrenze ist damit 23:59 statt 24 Stunden - genau das, was ein
Zeitfeld hergibt.

Dabei aufgefallen: haltezeitKurz() rundete auf Zehntelstunden und machte aus
23:59 die Auskunft "24 Std.". Eine Haltezeit, die laenger aussieht als sie
ist, ist genau die falsche Auskunft - krumme Werte stehen jetzt als
"23 Std. 59 Min." da, halbe behalten ihr Komma.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-21 09:26:00 +02:00
co-authored by Claude Opus 5
parent 229910bcb8
commit 95a61709d7
4 changed files with 73 additions and 36 deletions
+11 -8
View File
@@ -469,18 +469,21 @@ $nutzdaten = json_encode([
benutzen - Zeit - und trotzdem entgegengesetzt wirken: die Sperre benutzen - Zeit - und trotzdem entgegengesetzt wirken: die Sperre
bremst die Wiederholung, die Haltezeit den ersten Lauf. bremst die Wiederholung, die Haltezeit den ersten Lauf.
Freie Minuten statt einer Auswahlliste: Wonach man sucht, haengt am Frei einstellbar statt aus einer Auswahlliste: Wonach man sucht,
Messwert. Die Zeile über dem Rahmen rechnet beim Tippen mit und hängt am Messwert - eine offene Tür fällt nach zwei Minuten auf,
zeigt "erst nach 3,5 Std." - dort steht die lesbare Fassung, hier ein laufender Wasserhahn erst nach Stunden.
die eingebbare.
Dasselbe Zeitfeld wie von/bis darüber, nur als Dauer gelesen. Dass
es keine Uhrzeit meint, sagt der Text ringsum ("erst nach … am
Stück"); die Zeile über dem Rahmen schreibt es beim Tippen aus
("erst nach 3,5 Std.").
--> -->
<div class="col-sm-6"> <div class="col-sm-6">
<div class="input-group input-group-sm"> <div class="input-group input-group-sm">
<span class="input-group-text">Auslösen erst nach</span> <span class="input-group-text">Auslösen erst nach</span>
<input type="number" class="form-control" id="holdMin" <input type="time" class="form-control" id="holdZeit" value="00:00"
min="0" max="1440" step="5" value="0" title="Stunden und Minuten, die die Bedingung ohne Unterbrechung erfüllt sein muss. 00:00 = sofort.">
title="So lange muss die Bedingung ohne Unterbrechung erfüllt sein. 0 = sofort."> <span class="input-group-text">am Stück</span>
<span class="input-group-text">Min. am Stück</span>
</div> </div>
</div> </div>
</div> </div>
+2 -2
View File
@@ -170,7 +170,7 @@ sonst steht in der Übersicht etwas anderes als im Editor.
| `next_day` | Der Rahmen gilt für **morgen** (Vorabend-Form) | Uhrzeit und Fenster bleiben bei heute | | `next_day` | Der Rahmen gilt für **morgen** (Vorabend-Form) | Uhrzeit und Fenster bleiben bei heute |
| `window_from`/`window_to` | Zeitfenster; `von > bis` heißt über Mitternacht | | | `window_from`/`window_to` | Zeitfenster; `von > bis` heißt über Mitternacht | |
| `lockout_secs` | Sperrzeit nach einem Lauf (0 / 60 / 900) | | | `lockout_secs` | Sperrzeit nach einem Lauf (0 / 60 / 900) | |
| `hold_secs` | Haltezeit: so lange muss die Bedingung **ununterbrochen** erfüllt sein (0 = sofort, frei in Minuten bis 24 Std.) | Zählt im Speicher des Runners — ein Neustart fängt die Zeit von vorn an | | `hold_secs` | Haltezeit: so lange muss die Bedingung **ununterbrochen** erfüllt sein (0 = sofort, frei als Stunden:Minuten bis 23:59) | Zählt im Speicher des Runners — ein Neustart fängt die Zeit von vorn an |
| `once_per_day` | höchstens ein Lauf je Kalendertag | | | `once_per_day` | höchstens ein Lauf je Kalendertag | |
| `force_once` | am Ende des Fensters notfalls doch ausführen | greift nur, wenn im Fenster gar nichts lief | | `force_once` | am Ende des Fensters notfalls doch ausführen | greift nur, wenn im Fenster gar nichts lief |
@@ -448,5 +448,5 @@ räumt der Runner selbst weg.
| … verstehen, warum etwas lief | `automation_log`, dann die Zeitleiste des Tages (→ [zeitleiste.md](zeitleiste.md)) | | … verstehen, warum etwas lief | `automation_log`, dann die Zeitleiste des Tages (→ [zeitleiste.md](zeitleiste.md)) |
| … das Nachholfenster ändern | `catchup_minutes` in `config.ini` des Runners | | … das Nachholfenster ändern | `catchup_minutes` in `config.ini` des Runners |
| … eine weitere Stufe für die Sperre | `lockoutChoices()` in `restricted/automations.php` — eine Zeile, der Rest zieht nach | | … eine weitere Stufe für die Sperre | `lockoutChoices()` in `restricted/automations.php` — eine Zeile, der Rest zieht nach |
| … die Grenzen der Haltezeit ändern | `haltezeitPruefen()` in `restricted/automations.php` **und** `max` am Feld `holdMin` | | … die Grenzen der Haltezeit ändern | `haltezeitPruefen()` in `restricted/automations.php` **und** `haltezeitAusUhr()` im Editor |
| … auf Dauerzustände reagieren (Wasser läuft, Tür steht offen) | Haltezeit im Rahmen, dazu eine Aktion des Geräts „Benachrichtigungen“ (→ [benachrichtigungen.md](benachrichtigungen.md)) | | … auf Dauerzustände reagieren (Wasser läuft, Tür steht offen) | Haltezeit im Rahmen, dazu eine Aktion des Geräts „Benachrichtigungen“ (→ [benachrichtigungen.md](benachrichtigungen.md)) |
+36 -13
View File
@@ -1136,10 +1136,36 @@ function rahmenKurzText() {
return teile.join(" · "); return teile.join(" · ");
} }
/** Wie haltezeitKurz() in automations.php: Minuten bis eine Stunde, dann Stunden. */ /**
* Haltezeit und Zeitfeld ineinander umrechnen.
*
* Das Feld ist ein gewoehnliches <input type="time">, sein Wert also "03:30".
* Gemeint ist keine Uhrzeit, sondern eine Dauer - deshalb hier zwei eigene
* Funktionen und nicht die Behandlung von window_from/window_to, die den Text
* unveraendert durchreichen.
*/
function haltezeitAlsUhr(sekunden) {
const min = Math.round(sekunden / 60);
return String(Math.floor(min / 60)).padStart(2, "0") + ":"
+ String(min % 60).padStart(2, "0");
}
/** Leer oder unsinnig ist 0; die Obergrenze ist 23:59, siehe haltezeitPruefen(). */
function haltezeitAusUhr(text) {
const teile = String(text || "").split(":");
const sek = ((Number(teile[0]) || 0) * 60 + (Number(teile[1]) || 0)) * 60;
return Math.min(Math.max(sek, 0), 86340);
}
/** Wie haltezeitKurz() in automations.php - dort steht, warum nicht gerundet wird. */
function haltezeitKurz(sekunden) { function haltezeitKurz(sekunden) {
if (sekunden < 3600) return Math.floor(sekunden / 60) + " Min."; const minuten = Math.floor(sekunden / 60);
return (sekunden / 3600).toFixed(1).replace(/[.,]0$/, "").replace(".", ",") + " Std."; if (minuten < 60) return minuten + " Min.";
const stunden = Math.floor(minuten / 60);
const rest = minuten % 60;
if (rest === 0) return stunden + " Std.";
if (rest === 30) return stunden + ",5 Std.";
return stunden + " Std. " + rest + " Min.";
} }
function rahmenIstVorgabe() { function rahmenIstVorgabe() {
@@ -1181,7 +1207,7 @@ function fuelleFormular() {
const sperre = document.getElementById("lockoutSecs"); const sperre = document.getElementById("lockoutSecs");
optionListe(sperre, autoLockouts.map(o => ({ value: o.value, text: o.label })), optionListe(sperre, autoLockouts.map(o => ({ value: o.value, text: o.label })),
autoModel.lockout_secs); autoModel.lockout_secs);
document.getElementById("holdMin").value = autoModel.hold_secs / 60; document.getElementById("holdZeit").value = haltezeitAlsUhr(autoModel.hold_secs);
document.getElementById("autoName").value = autoModel.name; document.getElementById("autoName").value = autoModel.name;
// Kennt die Liste die Etage nicht, bleibt eine Auswahlliste sonst leer // Kennt die Liste die Etage nicht, bleibt eine Auswahlliste sonst leer
// stehen und schickt beim Speichern "" mit. // stehen und schickt beim Speichern "" mit.
@@ -1216,10 +1242,7 @@ function leseFormular() {
autoModel.once_per_day = document.getElementById("oncePerDay").checked; autoModel.once_per_day = document.getElementById("oncePerDay").checked;
autoModel.next_day = document.getElementById("nextDay").checked; autoModel.next_day = document.getElementById("nextDay").checked;
autoModel.lockout_secs = Number(document.getElementById("lockoutSecs").value); autoModel.lockout_secs = Number(document.getElementById("lockoutSecs").value);
// Minuten im Feld, Sekunden im Modell - und nichts Unmoegliches: ein autoModel.hold_secs = haltezeitAusUhr(document.getElementById("holdZeit").value);
// leeres Feld ist 0, ein Tag ist die Obergrenze (siehe haltezeitPruefen()).
const halten = Math.round(Number(document.getElementById("holdMin").value) || 0);
autoModel.hold_secs = Math.min(Math.max(halten, 0), 1440) * 60;
} }
/** /**
@@ -1227,15 +1250,15 @@ function leseFormular() {
* Ohne das stuende dort "rund um die Uhr", waehrend darunter 08:45 steht. * Ohne das stuende dort "rund um die Uhr", waehrend darunter 08:45 steht.
*/ */
function rahmenFelderVerdrahten() { function rahmenFelderVerdrahten() {
["tSpanFrom", "tSpanTo", "lockoutSecs", "holdMin", ["tSpanFrom", "tSpanTo", "lockoutSecs", "holdZeit",
"runOnce", "oncePerDay", "nextDay"].forEach(id => { "runOnce", "oncePerDay", "nextDay"].forEach(id => {
const feld = document.getElementById(id); const feld = document.getElementById(id);
if (feld) feld.addEventListener("change", () => { leseFormular(); renderRahmen(); }); if (feld) feld.addEventListener("change", () => { leseFormular(); renderRahmen(); });
}); });
// Bei der Haltezeit schon beim Tippen: aus "210" wird in der Kopfzeile // Bei der Haltezeit schon beim Tippen: aus "03:30" wird in der Kopfzeile
// "erst nach 3,5 Std." - genau dafuer gibt man die Zahl ein. "change" // "erst nach 3,5 Std.". "change" allein kaeme erst beim Verlassen des
// allein kaeme erst beim Verlassen des Feldes. // Feldes, und ein Zeitfeld verlaesst man ungern, um nachzusehen.
document.getElementById("holdMin") document.getElementById("holdZeit")
.addEventListener("input", () => { leseFormular(); renderRahmen(); }); .addEventListener("input", () => { leseFormular(); renderRahmen(); });
// Zugeklappt steht in der Kopfzeile, was gilt; aufgeklappt stehen dort die // Zugeklappt steht in der Kopfzeile, was gilt; aufgeklappt stehen dort die
+24 -13
View File
@@ -232,11 +232,12 @@ function lockoutChoices()
* Wasserhahn erst nach Stunden. Eine Stufenliste traefe immer nur die Haelfte * Wasserhahn erst nach Stunden. Eine Stufenliste traefe immer nur die Haelfte
* der Faelle. * der Faelle.
* *
* Grenzen gibt es trotzdem. Unter null ist keine Zeit; oberhalb eines Tages * Grenzen gibt es trotzdem. Unter null ist keine Zeit; oberhalb von 23:59
* waere es keine Haltezeit mehr, sondern ein Dauerzustand, den man besser * waere es keine Haltezeit mehr, sondern ein Dauerzustand, den man besser
* anders abfragt - und das Zeitfenster schneidet sie ohnehin ab. Gerechnet * anders abfragt - und das Zeitfenster schneidet sie ohnehin ab. Die Obergrenze
* wird in ganzen Minuten, weil der Editor Minuten anbietet und Sekunden hier * ist genau das, was ein Zeitfeld hergibt. Gerechnet wird in ganzen Minuten,
* nichts gewinnen: die Messwerte kommen viel seltener. * weil der Editor Stunden und Minuten anbietet und Sekunden hier nichts
* gewinnen: die Messwerte kommen viel seltener.
*/ */
function haltezeitPruefen($sekunden) function haltezeitPruefen($sekunden)
{ {
@@ -244,26 +245,36 @@ function haltezeitPruefen($sekunden)
if ($sekunden < 0) { if ($sekunden < 0) {
$sekunden = 0; $sekunden = 0;
} }
if ($sekunden > 86400) { if ($sekunden > 86340) { // 23:59, mehr kann das Zeitfeld nicht
$sekunden = 86400; $sekunden = 86340;
} }
return intdiv($sekunden, 60) * 60; return intdiv($sekunden, 60) * 60;
} }
/** /**
* Eine Haltezeit in Worten - "45 Min.", "1 Std.", "3,5 Std." * Eine Haltezeit in Worten - "45 Min.", "1 Std.", "3,5 Std.", "23 Std. 59 Min."
* *
* Minuten bis eine Stunde, darueber Stunden: "210 Min." muss man umrechnen, * Minuten bis eine Stunde, darueber Stunden: "210 Min." muss man umrechnen,
* "3,5 Std." nicht. Die Nachkommastelle faellt weg, wenn sie null ist. * "3,5 Std." nicht. Halbe Stunden bekommen ihr Komma, alles andere Krumme die
* Minuten ausgeschrieben - gerundet wird nicht. Aus 23:59 wurde sonst
* "24 Std.", und eine Haltezeit, die laenger aussieht als sie ist, waere
* genau die falsche Auskunft.
*/ */
function haltezeitKurz($sekunden) function haltezeitKurz($sekunden)
{ {
$sekunden = intval($sekunden); $minuten = intdiv(intval($sekunden), 60);
if ($sekunden < 3600) { if ($minuten < 60) {
return intdiv($sekunden, 60) . " Min."; return $minuten . " Min.";
} }
$zahl = number_format($sekunden / 3600, 1, ",", ""); $stunden = intdiv($minuten, 60);
return rtrim(rtrim($zahl, "0"), ",") . " Std."; $rest = $minuten % 60;
if ($rest === 0) {
return $stunden . " Std.";
}
if ($rest === 30) {
return $stunden . ",5 Std.";
}
return $stunden . " Std. " . $rest . " Min.";
} }
/** /**