/* ===================================================================== test-termine.mjs — die Terminrechnung Diese Datei rechnet die Zahl aus, die der Kunde als Zusage liest. Sie hat keine Oberfläche, an der ein Fehler auffiele: Ein Termin, der drei Tage zu früh liegt, sieht genauso aus wie ein richtiger. Deshalb wird hier gegen NACHPRÜFBARE Daten geprüft — Osterdaten und Wochentage kann jeder in einem Kalender nachschlagen. Läuft ohne Datenbank, also auch auf dem Windows-Rechner. ===================================================================== */ import { terminVorschlag, werktageAddieren, werktageBis, istWerktag, feiertage, hatLiefertermin, laufzeitFuer, LAUFZEIT_WERKTAGE, } from "./lib/webdesign-termine.js"; let ok = 0, fehler = 0; const pruefe = (n, b, z) => b ? (ok++, console.log(" ok " + n)) : (fehler++, console.log(" FEHL " + n + (z !== undefined ? " -> " + z : ""))); console.log("\n1. OSTERN — gegen den Kalender nachgeschlagen"); /* Wenn diese Formel falsch ist, sind Ostermontag, Christi Himmelfahrt und Pfingstmontag ALLE falsch. Deshalb zuerst und über mehrere Jahre. */ const OSTERN = { 2024: "2024-03-31", 2025: "2025-04-20", 2026: "2026-04-05", 2027: "2027-03-28", 2028: "2028-04-16", 2030: "2030-04-21", }; for (const [jahr, datum] of Object.entries(OSTERN)) { /* Ostersonntag selbst ist kein Eintrag in der Feiertagsliste (er ist ein Sonntag). Geprüft wird deshalb der Ostermontag: einen Tag später. */ const montag = new Date(new Date(datum + "T00:00:00Z").getTime() + 864e5) .toISOString().slice(0, 10); pruefe("Ostermontag " + jahr + " ist der " + montag, feiertage(Number(jahr)).has(montag), Array.from(feiertage(Number(jahr))).sort().join(" ")); } console.log("\n2. DIE FESTEN FEIERTAGE LUXEMBURGS"); const f26 = feiertage(2026); for (const [name, tag] of [ ["Neujahr", "2026-01-01"], ["Tag der Arbeit", "2026-05-01"], ["Europatag", "2026-05-09"], ["Nationalfeiertag", "2026-06-23"], ["Mariä Himmelfahrt", "2026-08-15"], ["Allerheiligen", "2026-11-01"], ["Weihnachten", "2026-12-25"], ["Zweiter Weihnachtstag", "2026-12-26"], ]) pruefe(name + " (" + tag + ")", f26.has(tag)); pruefe("Christi Himmelfahrt 2026 ist der 14.05.", f26.has("2026-05-14")); pruefe("Pfingstmontag 2026 ist der 25.05.", f26.has("2026-05-25")); pruefe("2026 hat elf Feiertage", f26.size === 11, f26.size); console.log("\n3. WAS IST EIN WERKTAG?"); pruefe("Montag ist einer", istWerktag(new Date("2026-08-24T00:00:00Z"))); pruefe("Samstag nicht", !istWerktag(new Date("2026-08-22T00:00:00Z"))); pruefe("Sonntag nicht", !istWerktag(new Date("2026-08-23T00:00:00Z"))); pruefe("der Nationalfeiertag nicht, obwohl er ein Dienstag ist", !istWerktag(new Date("2026-06-23T00:00:00Z"))); console.log("\n4. WERKTAGE ADDIEREN"); /* Der 21.08.2026 ist ein Freitag. Ein Werktag weiter ist Montag, nicht Samstag -- genau der Fehler, den eine Kalendertagsrechnung macht. */ pruefe("Freitag + 1 Werktag = Montag", werktageAddieren("2026-08-21", 1) === "2026-08-24", werktageAddieren("2026-08-21", 1)); pruefe("Freitag + 5 Werktage = Freitag darauf", werktageAddieren("2026-08-21", 5) === "2026-08-28", werktageAddieren("2026-08-21", 5)); /* Über Feiertage hinweg: Vom 19.06.2026 (Freitag) sind fünf Werktage normalerweise der 26.06. Der 23.06. ist aber Nationalfeiertag, also verschiebt sich alles um einen Tag auf den 29.06. (Montag). */ pruefe("ein Feiertag dazwischen verschiebt den Termin", werktageAddieren("2026-06-19", 5) === "2026-06-29", werktageAddieren("2026-06-19", 5) + " (ohne Feiertag waere es der 26.06.)"); pruefe("der Starttag selbst zaehlt nicht mit", werktageAddieren("2026-08-24", 1) === "2026-08-25", werktageAddieren("2026-08-24", 1)); pruefe("null Werktage ergeben keinen Termin", werktageAddieren("2026-08-24", 0) === null); pruefe("Unsinn ergibt keinen Termin", werktageAddieren("2026-08-24", "viele") === null); console.log("\n5. DIE UHRZEIT DARF DAS ERGEBNIS NICHT VERSCHIEBEN"); /* Wer morgens annimmt, muss denselben Termin bekommen wie jemand, der abends annimmt. Ohne Normierung auf Mitternacht springt das Datum. */ const frueh = werktageAddieren("2026-08-24T06:00:00Z", 10); const spaet = werktageAddieren("2026-08-24T23:30:00Z", 10); pruefe("frueh und spaet am selben Tag ergeben denselben Termin", frueh === spaet, frueh + " vs. " + spaet); console.log("\n6. VERBLEIBENDE ZEIT"); pruefe("heute ist null", werktageBis("2026-08-24", "2026-08-24T10:00:00Z") === 0, werktageBis("2026-08-24", "2026-08-24T10:00:00Z")); pruefe("Montag bis Freitag sind vier Werktage", werktageBis("2026-08-28", "2026-08-24T10:00:00Z") === 4, werktageBis("2026-08-28", "2026-08-24T10:00:00Z")); /* Das Wochenende zaehlt nicht mit: Von Freitag bis Montag ist genau EIN Werktag, nicht drei Tage. */ pruefe("Freitag bis Montag ist ein Werktag", werktageBis("2026-08-24", "2026-08-21T10:00:00Z") === 1, werktageBis("2026-08-24", "2026-08-21T10:00:00Z")); pruefe("ein ueberschrittener Termin ergibt eine negative Zahl", werktageBis("2026-08-20", "2026-08-24T10:00:00Z") === -2, werktageBis("2026-08-20", "2026-08-24T10:00:00Z")); pruefe("ohne Termin kommt nichts heraus", werktageBis(null) === null); console.log("\n7. VORSCHLAG JE PAKET"); const v = terminVorschlag("website", "2026-08-24"); pruefe("Website bekommt 20 Werktage", v.werktage === 20, v.werktage); pruefe("und daraus ein echtes Datum", /^\d{4}-\d{2}-\d{2}$/.test(v.termin || ""), v.termin); pruefe("das Datum liegt auf einem Werktag", istWerktag(new Date(v.termin + "T00:00:00Z")), v.termin); pruefe("20 Werktage sind rund vier Wochen, nicht 20 Tage", (new Date(v.termin) - new Date("2026-08-24")) / 864e5 >= 26, Math.round((new Date(v.termin) - new Date("2026-08-24")) / 864e5) + " Kalendertage"); const b = terminVorschlag("betreuung", "2026-08-24"); pruefe("eine laufende Betreuung bekommt KEINEN Liefertermin", b.termin === null && b.laufend); pruefe("und wird auch so gekennzeichnet", hatLiefertermin("betreuung") === false); const u = terminVorschlag("gibtesnicht", "2026-08-24"); pruefe("ein unbekanntes Paket faellt auf den mittleren Wert zurueck", u.werktage === LAUFZEIT_WERKTAGE.unklar, u.werktage); pruefe("und bekommt trotzdem ein Datum -- kein stilles Projekt ohne Zusage", !!u.termin && u.laufend === false, String(u.termin)); pruefe("nur die laufende Betreuung ergibt null", laufzeitFuer("betreuung") === 0); pruefe("ein Tippfehler im Paketnamen ergibt NICHT null", laufzeitFuer("websiet") > 0, laufzeitFuer("websiet")); console.log("\n8. JEDES PAKET IST HINTERLEGT"); /* Fehlt eines, bekaeme es beim Annehmen still den Ersatzwert -- und niemand merkte, dass die Zusage geraten war. */ for (const p of ["onepager", "website", "shop", "verwaltung", "betreuung", "unklar"]) { pruefe("Laufzeit fuer " + p + " ist hinterlegt", Object.prototype.hasOwnProperty.call(LAUFZEIT_WERKTAGE, p), LAUFZEIT_WERKTAGE[p]); } console.log(`\n===== ${ok} bestanden, ${fehler} fehlgeschlagen =====`); process.exitCode = fehler ? 1 : 0;