Datum: der Tag kommt aus der Ortszeit, nicht aus UTC

GEFUNDEN UM 01:22, von pruef-kreislauf -- und nur, weil nachts
gearbeitet wurde.

Die Pruefung legt einen Wunsch mit dem HEUTIGEN Datum an und macht
daraus einen Termin. Gemessen:

    geschickt:   datum = 2026-10-01   (heuteLokal auf dem Server)
    gespeichert: datum = 2026-09-30

Der Termin lag also in der Vergangenheit. Auf „Was ansteht" sortiert
er sich damit in den Abschnitt „Vorbei" ein -- und der ist mit
Absicht zugeklappt. Beim Wunsch stand „Daraus wurde ein Termin", auf
dem Brett war er nicht zu sehen. Zwei Stunden jede Nacht (im Winter
eine), genau in den Stunden, in denen hier gearbeitet wird.

URSACHE

    const jetzt = () => new Date().toISOString();   // UTC
    ... jetzt().slice(0, 10) ...                     // UTC-Tag

ES WAR NICHT EINE STELLE. Nachgemessen: SECHZEHN in vierzehn Modulen,
davon ZEHN, die den falschen Tag in die Datenbank schreiben --
Termine, Wuensche, Highlights, Talente, Leads, Videos, Vorlagen --
und sechs, die „heute" vergleichen (ueberfaellige Aufgaben, Berichte,
die Frist einer Entwicklungsaufgabe).

NICHT ANGEFASST, WEIL RICHTIG: Rechnungen auf einem Datumstext mit
fester Uhrzeit (`Date.parse(tag + "T12:00:00Z") + n * 86400000`). Die
bleiben in jeder Zeitzone am selben Kalendertag -- kalender, teamlage
und serien machen es so, und das bleibt.

WARUM ES NIEMAND GEMERKT HAT

pruef-struktur sucht dieses Muster seit dem 06.09.2026. Aber:
  - sie sah NUR in die `pruef-*.mjs`, nie in die Anwendung
  - sie kannte die Schreibweise ueber eine FUNKTION nicht
    (`const jetzt = () => ...` statt `const jetzt = ...`)

Die Wache stand vor den Pruefungen, nicht vor dem Haus -- derselbe
Fehler wie heute Nacht bei den Messports: eine Sicherung, die nur die
halbe Menge kennt, faellt in der anderen Haelfte aus, und zwar
lautlos, denn sie meldet ja „nichts gefunden".

Jetzt sieht sie in beides und kennt beide Schreibweisen. Beim ersten
scharfen Lauf fand sie sofort 23 weitere Stellen in den Pruefdateien
selbst -- dieselben Zeitbomben, gegen die sie gebaut worden war.

EINE ZWEITE WACHE, WEIL ICH SELBST HINEINGELAUFEN BIN

Mein Umbauwerkzeug hat in elf Modulen `heuteLokal()` eingesetzt und
die Einfuhr weggelassen: Es hat erst ersetzt und DANN gefragt, ob der
Name schon in der Datei steht -- da stand er, mein eigener Aufruf.
`node --check` sagt dazu nichts, „Laedt jedes Server-Modul?" auch
nicht: Die Datei ist syntaktisch tadellos. Erst der Aufruf faellt um
mit `ReferenceError: heuteLokal is not defined`. Gefunden hat es
pruef-video, zufaellig. Die anderen zehn waeren durchgerutscht.

Deshalb neu: „Ruft ein Modul etwas, das es nie eingefuehrt hat?" --
die Namen des Hauses aus den export-Zeilen gelesen, nicht
aufgezaehlt. 460 Aufrufe in 350 Dateien, alle mit Einfuhr.

pruef-kreislauf STELLT JETZT DIE RICHTIGE FRAGE

Sie war rot und hat den Fehler dabei nur gestreift: „`.kette` wird
nicht sichtbar", Zeitsperre nach 15 s. Das klingt nach der Anzeige
und schickt einen zur falschen Stelle. Neu:
  - eine Zeile fragt das DATUM (ohne Browser, nennt den Fehler beim
    Namen)
  - der Browserteil klappt zu, was zu ist, und misst dann die Kette;
    „gar nicht da" wird von „da und unsichtbar" unterschieden

NEBENBEFUND IN pruef-ics

Die Probe „fast richtig" war `echt.slice(0, -1) + "A"`. Der
Schluessel ist base64url; sein letztes Zeichen ist eines von
sechzehn. Endet er auf „A", IST die Probe der echte Schluessel, der
Server antwortet zu Recht mit 200, und die Pruefung meldet ein Loch,
das es nicht gibt -- einmal je sechzehn Laeufe. Heute Nacht zweimal
hintereinander, und die Suche ging eine halbe Stunde in eine
Aenderung, die damit nichts zu tun hatte.

GEPRUEFT

  pruef-struktur   44 -> 59 Pruefungen, 0 Fehler
  pruef-ics        37 -> 38, 0 Fehler
  pruef-kreislauf  Absturz bei Nr. 17 -> 25 Pruefungen, 0 Fehler

  und gruen geblieben: treff 85, arten 28, video 74, uebernahme 39,
  entwicklung 79, content 45, vorlagen 24, zuteilung 75,
  scout-zuteilung 37, unterstuetzung 70, aufbewahrung 45,
  bewerbung 91, treff-start 42, uebergang 65, nachwuchs 262,
  auskunft 46, modi-ideen 30, neue-seiten 109, spicy 85,
  wege-nach-draussen 67, aufgabenbrett 49, agentur 62,
  bereiche-lesend 37 -- beide Haeuser

GEGENPROBEN, DIE WIRKLICH ROT WERDEN

  - den UTC-Tag im `daraus`-Weg wieder eingebaut: pruef-kreislauf
    meldet „er liegt HEUTE, nicht gestern (2026-09-30, heute ist
    2026-10-01)", 25 Pruefungen, 1 Fehler -- und der Browserteil
    bleibt gruen, weil er jetzt aufklappt. Jede Frage bei ihrer
    eigenen Pruefung.
  - eine Einfuhr aus workspace-video.js entfernt: die neue Wache
    meldet „workspace-video.js: heuteLokal() (aus helfer-tag.mjs)"
  - beide Erkennungen je gegen einen gebauten Rueckschritt geprueft
    (Funktion, Variable, zwei Schritte, Date.now-Rechnung) und gegen
    das, was NICHT anschlagen darf (UTC-Mittag, voller Zeitstempel,
    fremdes Date, Name im Kommentar, Eigenschaft am Objekt)

EIN FEHLER BEIM UMBAU, HIER FESTGEHALTEN: Mein erster Lauf ueber die
Pruefdateien hat stumpf ersetzt und dabei KOMMENTARE umgeschrieben --
in sieben Dateien stand die alte Schreibweise als Beleg in der
Begruendung, und daraus wurde das Gegenteil. Bemerkt hat es der
Vergleich der Zahlen (34 Stellen statt der gemessenen 24), nicht die
Absicht. Zurueckgenommen und mit Schutz fuer Kommentare und
Zeichenketten wiederholt.

Datenbank vorher gesichert. Keine Schemaaenderung.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-10-01 01:45:16 +02:00
co-authored by Claude Opus 5
parent f6225d7341
commit 7dc5356c80
28 changed files with 444 additions and 97 deletions
+238 -49
View File
@@ -772,74 +772,263 @@ console.log(`\n=== Laedt jedes Server-Modul? ===`);
Deshalb wird es hier gesucht, statt darauf zu warten, dass es wieder
jemanden trifft. Gemeint ist nur das TAGESDATUM; ein Zeitstempel
(`erstellt`) darf und soll UTC sein. */
console.log(`\n=== Rechnen die Pruefungen mit Ortszeit? ===`);
console.log(`\n=== Ruft ein Modul etwas, das es nie eingefuehrt hat? ===`);
/* ===================================================================
AM 01.10.2026 SELBST GEBAUT UND FAST AUSGELIEFERT.
Ein Werkzeug von mir hat in elf Modulen `heuteLokal()` eingesetzt
und die Einfuhr weggelassen. `node --check` sagt dazu nichts, und
„Laedt jedes Server-Modul?" ein Stueck weiter oben auch nicht: Die
Datei ist syntaktisch tadellos. Erst der AUFRUF faellt um:
ReferenceError: heuteLokal is not defined
Gefunden hat es pruef-video -- zufaellig, weil dort genau diese
Zeile geprueft wird. Die anderen zehn Module waeren durchgerutscht,
und bei einer Zeile, die nur nachts laeuft, faellt das erst Wochen
spaeter jemandem auf.
DAS IST EINE DRITTE SORTE FEHLER an dieser Stelle: nicht „die Datei
ist kaputt" und nicht „die Datei tut das Falsche", sondern „die
Datei faellt erst um, wenn jemand genau diese Zeile braucht".
ABGELEITET, NICHT AUFGEZAEHLT: Die Namen des Hauses kommen aus den
`export`-Zeilen von workspace.js und der helfer-Module. Eine
gepflegte Liste waere hier genau die, die beim naechsten Export
nicht mitwaechst -- derselbe Fehler, an dem dieses Haus schon
viermal haengengeblieben ist.
=================================================================== */
{
const dateien = readdirSync(SERVER).filter((n) => n.startsWith("pruef-") && n.endsWith(".mjs"));
ok(dateien.length >= 30, `${dateien.length} Pruefdateien durchgesehen`);
/** Kommentare und Zeichenketten weg, Zeilen behalten. Sonst zaehlt
* ein Name in einem SQL-Text oder in einer Begruendung als Aufruf. */
const nurCode = (text) => text
.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " "))
.replace(/^\s*\/\/.*$/gm, "")
.replace(/`(?:[^`\\]|\\.)*`/g, (m) => m.replace(/[^\n]/g, " "))
.replace(/"(?:[^"\\\n]|\\.)*"/g, "\"\"")
.replace(/'(?:[^'\\\n]|\\.)*'/g, "''");
/** Was eine Datei nach aussen gibt -- aus ihren eigenen Zeilen. */
const exporte = (text) => {
const raus = new Set();
for (const t of text.matchAll(
/^export\s+(?:async\s+)?(?:function|const|let|class)\s+(\w+)/gm)) raus.add(t[1]);
return raus;
};
const quellen = ["workspace.js", "helfer-zeit.mjs", "helfer-tag.mjs"];
const hausNamen = new Map(); // Name -> woher
for (const q of quellen) {
for (const n of exporte(readFileSync(join(SERVER, q), "utf8"))) hausNamen.set(n, q);
}
ok(hausNamen.size >= 30,
`${hausNamen.size} ausgeleitete Namen aus ${quellen.length} Quellen gelesen`);
/** Welche Namen kennt diese Datei? Eingefuehrte, selbst erklaerte
* und aus einer Zerlegung entnommene -- alles, was nicht zu einem
* ReferenceError fuehren kann. */
const bekannt = (code) => {
const kennt = new Set();
for (const t of code.matchAll(/import\s*\{([^}]*)\}\s*from/g)) {
for (const stueck of t[1].split(",")) {
const n = stueck.trim().split(/\s+as\s+/).pop().trim();
if (n) kennt.add(n);
}
}
for (const t of code.matchAll(/(?:const|let|var)\s*\{([^}]*)\}\s*=/g)) {
for (const stueck of t[1].split(",")) {
const n = stueck.trim().split(":").pop().trim();
if (n) kennt.add(n);
}
}
for (const t of code.matchAll(
/(?:^|\s)(?:const|let|var|function|class)\s+(\w+)/g)) kennt.add(t[1]);
for (const t of code.matchAll(/import\s+(\w+)\s+from/g)) kennt.add(t[1]);
return kennt;
};
const dateien = readdirSync(SERVER)
.filter((n) => (n.endsWith(".js") || n.endsWith(".mjs")) && !quellen.includes(n));
const fehlt = [];
let gesucht = 0;
for (const datei of dateien) {
if (datei === "pruef-struktur.mjs") continue; /* siehe Gegenprobe */
const code = nurCode(readFileSync(join(SERVER, datei), "utf8"));
const kennt = bekannt(code);
for (const [name, woher] of hausNamen) {
/* Nur ein AUFRUF zaehlt. Ein Name in einer Eigenschaft
(`x.heuteLokal`) ist etwas anderes und gehoert nicht hierher. */
const ruft = new RegExp(`(^|[^\\w.])${name}\\s*\\(`, "m").test(code);
if (!ruft) continue;
gesucht++;
if (!kennt.has(name)) fehlt.push(`${datei}: ${name}() (aus ${woher})`);
}
}
ok(gesucht > 50, `${gesucht} Aufrufe von Hausnamen in ${dateien.length} Dateien angesehen`);
ok(fehlt.length === 0,
fehlt.length
? `rufen etwas, das sie nicht eingefuehrt haben — das faellt erst beim`
+ ` Aufruf um, nicht beim Laden:\n ` + fehlt.slice(0, 12).join("\n ")
: "jeder Aufruf eines Hausnamens hat auch seine Einfuhr");
/* ---- GEGENPROBEN --------------------------------------------------
Die Zeile darueber meldet „in Ordnung", solange sie nichts findet.
Heute Nacht haette sie elf Dateien gefunden -- also einmal zeigen,
dass sie das auch kann. */
{
const probeFehlt = (code, name) => {
const c = nurCode(code);
const ruft = new RegExp(`(^|[^\\w.])${name}\\s*\\(`, "m").test(c);
return ruft && !bekannt(c).has(name);
};
ok(probeFehlt('const t = heuteLokal();', "heuteLokal"),
"Gegenprobe: ein Aufruf ohne Einfuhr faellt auf");
ok(!probeFehlt('import { heuteLokal } from "./workspace.js";\nconst t = heuteLokal();',
"heuteLokal"), " mit Einfuhr NICHT");
ok(!probeFehlt('const { heuteLokal } = await import("./workspace.js");\n'
+ 'const t = heuteLokal();', "heuteLokal"), " und ueber eine Zerlegung auch nicht");
ok(!probeFehlt('function heuteLokal() { return 1; }\nconst t = heuteLokal();',
"heuteLokal"), " wer ihn selbst erklaert, ebenfalls nicht");
ok(!probeFehlt('/* heuteLokal() waere hier falsch */\nconst t = 1;', "heuteLokal"),
" und ein Name im Kommentar ist kein Aufruf");
ok(!probeFehlt('const t = uhr.heuteLokal();', "heuteLokal"),
" eine Eigenschaft an einem Objekt auch nicht");
}
}
console.log(`\n=== Wird der TAG in Ortszeit gebildet -- ueberall? ===`);
/* ===================================================================
AM 01.10.2026 UM 01:22 GEWACHSEN, weil diese Wache versagt hatte.
Sie sah seit dem 06.09. nur in die `pruef-*.mjs`. In der ANWENDUNG
standen zur selben Zeit SECHZEHN Stellen, die einen Kalendertag aus
UTC bilden -- zehn davon schrieben ihn in die Datenbank.
Aufgefallen ist es nicht hier, sondern in pruef-kreislauf: Ein
Wunsch mit dem heutigen Datum wurde zu einem Termin, und der trug
`2026-09-30` statt `2026-10-01`. Auf dem Brett „Was ansteht" landete
er damit unter „Vorbei" -- einem Abschnitt, der von sich aus
zugeklappt ist. Der Termin war da und war nicht zu sehen.
Eine Sicherung, die nur die halbe Menge kennt, faellt in der
anderen Haelfte aus -- lautlos, denn sie meldet ja „nichts
gefunden". Deshalb sieht sie jetzt in BEIDES: in die Anwendung und
in die Pruefungen.
=================================================================== */
{
const pruefungen = readdirSync(SERVER)
.filter((n) => (n.startsWith("pruef-") || n.startsWith("mess-")) && n.endsWith(".mjs"));
/* DIE ANWENDUNG -- abgeleitet, nicht aufgezaehlt: alles, was der
Dienst laedt, liegt als `.js` im Serverordner. Eine Liste von
Dateinamen waere die naechste, die beim uebernaechsten Modul
nicht mitwaechst. */
const anwendung = readdirSync(SERVER).filter((n) => n.endsWith(".js"));
const dateien = [...anwendung, ...pruefungen];
ok(anwendung.length >= 30 && pruefungen.length >= 30,
`${anwendung.length} Dateien der Anwendung und ${pruefungen.length} Pruef-/Messdateien durchgesehen`);
/* EINE UHRZEIT IM AUSDRUCK HEISST: DER TAG STEHT SCHON FEST.
`Date.parse(tag + "T12:00:00Z") + n * 86400000` bleibt in jeder
Zeitzone am selben Kalendertag -- das ist der dokumentierte Weg
des Hauses (workspace-kalender, workspace-teamlage,
workspace-serien). Erkannt wird das am Ausdruck selbst; eine
Liste erlaubter Ausnahmen braucht es dadurch nicht, und eine
Liste, die niemand pflegt, waere die naechste Stelle, an der es
auseinanderlaeuft. */
const festeUhrzeit = /T\d{2}:\d{2}:\d{2}Z/;
/** Alle Stellen einer Datei, an denen aus einem ZEITPUNKT ein
* Kalendertag wird. Gibt Zeilennummern zurueck. */
function utcTage(text) {
const treffer = [];
const zeile = (i) => text.slice(0, i).split("\n").length;
/* (1) Direkt: new Date(...).toISOString().slice(0, 10) */
for (const t of text.matchAll(
/new Date\(([^;]*?)\)\s*\.\s*toISOString\(\)\s*\.\s*slice\(\s*0\s*,\s*10\s*\)/g)) {
if (festeUhrzeit.test(t[1])) continue;
treffer.push(zeile(t.index));
}
/* (2) Ueber einen NAMEN. Zuerst sammeln, welche Namen einen
UTC-Zeitstempel liefern -- als Funktion ODER als Wert. Genau
diese Schreibweise fehlte bis heute:
const jetzt = () => new Date().toISOString();
... jetzt().slice(0, 10) ...
Und in zwei Schritten kommt sie auch vor:
const jetztIso = jetzt();
... jetztIso.slice(0, 10) ... */
const utcNamen = new Set();
for (const t of text.matchAll(
/(?:const|let)\s+(\w+)\s*=\s*(?:\(\)\s*=>\s*)?new Date\(\)\.toISOString\(\)/g)) {
utcNamen.add(t[1]);
}
for (let runde = 0; runde < 2; runde++) {
for (const t of text.matchAll(/(?:const|let)\s+(\w+)\s*=\s*(\w+)\(\)\s*;/g)) {
if (utcNamen.has(t[2])) utcNamen.add(t[1]);
}
}
for (const t of text.matchAll(/(\w+)(\(\))?\s*\.\s*slice\(\s*0\s*,\s*10\s*\)/g)) {
if (utcNamen.has(t[1])) treffer.push(zeile(t.index));
}
return treffer;
}
const verdaechtig = [];
for (const datei of dateien) {
/* Die eigene Datei bleibt aussen vor: Die Gegenprobe unten MUSS
das gesuchte Muster als Text enthalten, sonst könnte sie nicht
beweisen, dass sie es erkennt. Sie würde sich sonst selbst
anzeigen -- eine Warnung, die immer kommt, und die liest bald
niemand mehr. */
/* Die eigene Datei bleibt aussen vor: Die Gegenproben unten
MUESSEN das gesuchte Muster als Text enthalten, sonst koennten
sie nicht beweisen, dass sie es erkennen. Sie wuerde sich sonst
selbst anzeigen -- eine Warnung, die immer kommt. */
if (datei === "pruef-struktur.mjs") continue;
/* KOMMENTARE RAUS, ZEILEN BEHALTEN (16.09.2026).
Hier stand `replace(…, " ")` -- ein Blockkommentar ueber zwanzig
Zeilen wurde also zu einem Leerzeichen, und ab da zaehlte
`split("\n").length` falsch. Gemeldet wurden Zeilennummern, die
auf voellig anderen Code zeigten: pruef-eskalation.mjs:43 ist
eine Zeile ueber Kekse.
Wer einer solchen Meldung nachgeht, findet dort nichts, haelt
die Pruefung fuer kaputt und sieht beim naechsten Mal gar nicht
mehr nach. Jetzt bleibt je Zeile eine Zeile stehen. */
auf voellig anderen Code zeigten. Jetzt bleibt je Zeile eine
Zeile stehen. */
const text = readFileSync(join(SERVER, datei), "utf8")
.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " "));
/* Das Muster ist eng gefasst: NUR toISOString() gefolgt von einem
Schnitt auf zehn Zeichen -- also genau die Bildung eines
Tagesdatums. `new Date().toISOString()` allein (ein voller
Zeitstempel) ist voellig in Ordnung und faellt nicht darunter. */
for (const treffer of text.matchAll(/toISOString\(\)\s*\.\s*slice\(\s*0\s*,\s*10\s*\)/g)) {
const zeile = text.slice(0, treffer.index).split("\n").length;
verdaechtig.push(`${datei}:${zeile}`);
}
/* Und die zweite Schreibweise: erst in eine Variable, dann geschnitten. */
for (const treffer of text.matchAll(/=\s*(\w+)\s*\.\s*slice\(\s*0\s*,\s*10\s*\)/g)) {
const name = treffer[1];
/* Nur melden, wenn diese Variable aus toISOString() stammt. */
if (new RegExp(`(const|let)\\s+${name}\\s*=\\s*new Date\\(\\)\\.toISOString\\(\\)`).test(text)) {
const zeile = text.slice(0, treffer.index).split("\n").length;
verdaechtig.push(`${datei}:${zeile} (über ${name})`);
}
}
.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, " "))
.replace(/^\s*\/\/.*$/gm, "");
for (const z of utcTage(text)) verdaechtig.push(`${datei}:${z}`);
}
ok(verdaechtig.length === 0,
verdaechtig.length
? `bilden ihr Tagesdatum aus UTC statt Ortszeit — nachts zwischen `
+ `00:00 und 02:00 schlagen sie fälschlich an:\n `
+ `00:00 und 02:00 liegt es einen Tag zurueck:\n `
+ verdaechtig.join("\n ")
: `keine Prüfung bildet ihr Tagesdatum aus UTC (alle nutzen helfer-zeit.mjs)`);
: `keine der ${dateien.length} Dateien bildet einen Kalendertag aus UTC`);
/* GEGENPROBE: Findet das Muster überhaupt etwas? Ohne diesen
Nachweis wäre "0 Treffer" auch dann grün, wenn der reguläre
Ausdruck gar nichts erkennt. */
const probe = "const jetzt = new Date().toISOString();\nconst heute = jetzt.slice(0, 10);";
const erkanntA = /toISOString\(\)\s*\.\s*slice\(\s*0\s*,\s*10\s*\)/
.test("new Date().toISOString().slice(0, 10)");
const erkanntB = /=\s*(\w+)\s*\.\s*slice\(\s*0\s*,\s*10\s*\)/.test(probe)
&& /(const|let)\s+jetzt\s*=\s*new Date\(\)\.toISOString\(\)/.test(probe);
ok(erkanntA && erkanntB,
"Gegenprobe: beide Schreibweisen werden erkannt (direkt und über eine Variable)");
/* ---- GEGENPROBEN -------------------------------------------------
Eine Erkennung, die nichts findet, sieht genauso aus wie eine, die
nichts finden KANN. Heute Nacht war sie genau das: Die Schreibweise
mit einer Funktion kannte sie nicht, und die Anwendung sah sie gar
nicht an. Beides steht jetzt hier als Probe. */
const probe = (code) => utcTage(code).length;
ok(probe('const h = new Date().toISOString().slice(0, 10);') === 1,
"Gegenprobe: die direkte Schreibweise faellt auf");
ok(probe('const jetzt = () => new Date().toISOString();\nconst h = jetzt().slice(0, 10);') === 1,
" die ueber eine FUNKTION ebenfalls — genau die, die heute Nacht durchrutschte");
ok(probe('const jetzt = new Date().toISOString();\nconst h = jetzt.slice(0, 10);') === 1,
" und die ueber eine Variable");
ok(probe('const jetzt = () => new Date().toISOString();\n'
+ 'const iso = jetzt();\nconst h = iso.slice(0, 10);') === 1,
" auch ueber zwei Schritte");
ok(probe('const g = new Date(Date.now() - 30 * 86400000).toISOString().slice(0, 10);') === 1,
" und eine Rechnung auf Date.now()");
/* Und ein harmloser Zeitstempel darf NICHT anschlagen -- sonst wäre
die Prüfung eine Warnung, die immer kommt. */
const harmlos = /toISOString\(\)\s*\.\s*slice\(\s*0\s*,\s*10\s*\)/
.test("const jetzt = new Date().toISOString();");
ok(!harmlos, "Gegenprobe: ein voller Zeitstempel (ohne Schnitt) schlägt nicht an");
ok(probe('const jetzt = new Date().toISOString();') === 0,
"Gegenprobe: ein voller Zeitstempel ohne Schnitt schlaegt NICHT an");
ok(probe('const b = new Date(Date.parse(tag + "T12:00:00Z") + n * 86400000)'
+ '.toISOString().slice(0, 10);') === 0,
" und die Rechnung auf UTC-Mittag ebenfalls nicht — die ist richtig so");
ok(probe('const alsText = (d) => d.toISOString().slice(0, 10);') === 0,
" und ein Date, das von aussen kommt, auch nicht");
}
/* =======================================================================