Datum- & Zeit-Formatierung: Serial-Zahlen, Auto-Erkennung, DATE/TIME/TODAY/NOW

Wie in jeder Tabellenkalkulation ist ein Datum intern einfach eine Zahl
("Serial": Tage seit 1899-12-30, Uhrzeit als Bruchteil eines Tages) --
Datums-Arithmetik (=fällig - rechnungsdatum, =rechnungsdatum + 30)
funktioniert dadurch automatisch, ganz ohne Änderung an Operatoren.

- Neues Modul engine/dates.ts: dateToSerial/timeSerial (Bausteine für
  DATE()/TIME(), Überlauf wie Excel erlaubt), todaySerial/nowSerial
  (lokales Kalenderdatum), serialToUtcDate (Anzeige), parseDateTimeLiteral
  (Eingabe-Erkennung -- bewusst nur ISO/Punkt-Daten + Uhrzeit, KEINE
  Schrägstrich-Daten, da Tag-/Monat-zuerst über die unterstützten Locales
  nicht eindeutig auflösbar ist; ein zu einer ungültigen/rollierten
  Kalenderdatum führender Kandidat wird strikt verworfen statt lautlos
  zu überlaufen).
- workbook.ts: literalValue() erkennt Datum/Zeit vor dem Zahl-Test und
  liefert direkt den Serial.
- functions.ts: neue eingebaute Funktionen DATE(jahr;monat;tag),
  TIME(std;min;sek), TODAY(), NOW() -- TODAY/NOW sind bewusst nicht
  volatil (dokumentierte Grenze: diese Engine rechnet rein
  vorgänger-getrieben neu, ohne Timer).
- store/types.ts + format.ts: CellNumFmt um 'date'/'time'/'datetime'
  erweitert; formatValue rendert über Intl.DateTimeFormat (UTC-Zeitzone,
  pro Locale), 'plain' bekommt eine manuelle, locale-neutrale
  Formatierung statt der Browser-Locale.
- store/document.ts: setCellInput erkennt eine getippte Datums-/
  Zeit-Eingabe automatisch und setzt das passende Zellformat -- wie
  Excel/Numbers --, überschreibt aber nie ein bereits explizit gesetztes
  Format.
- Zahlenformat-Dropdown (SettingsPanel) und JSON-Validierung
  (isCellNumFmt) brauchten keine Änderung -- beide sind bereits generisch
  über NUM_FORMATS.

Tests: 32 neue (dates.spec.ts neu; workbook.spec.ts: literalValue +
DATE/TIME/TODAY/NOW-Formeln inkl. Datums-Arithmetik; format.spec.ts neu:
formatValue über mehrere Locales; document.spec.ts: Auto-Format beim
Eintippen, überschreibt nie ein gesetztes Format, Namenskollision mit
den neuen Funktionen). 265 -> 297 Tests, alle grün. vue-tsc/eslint/build
sauber. Live per Playwright verifiziert (Auto-Erkennung, Formel-Ergebnisse
ohne Auto-Format, Datums-Arithmetik, Schrägstrich-Daten bleiben Text).

READMEs aktualisiert (Funktionen, Zahlenformat, neuer "Datum & Zeit"-
Abschnitt in engine/README.md, Bekannte Grenzen, Testzahl; nebenbei eine
veraltete Aussage zu Vergleichsoperatoren in "Bekannte Grenzen" korrigiert
-- die gibt es seit der IF-Funktion bereits).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
wkh
2026-09-02 23:24:39 +02:00
co-authored by Claude Sonnet 5
parent ec85c371e8
commit e6cb18ab3c
13 changed files with 636 additions and 11 deletions
+50 -5
View File
@@ -13,7 +13,7 @@ Vorbild von **Apple Numbers**. Zwei Dinge stehen im Mittelpunkt:
```bash
npm install
npm run dev # Vite-Devserver
npm run test # Vitest-Suite (Engine + Store + IO), 265 Tests
npm run test # Vitest-Suite (Engine + Store + IO), 297 Tests
npm run build # Typecheck + Produktionsbuild
npm run lint # ESLint (flat config)
```
@@ -263,7 +263,8 @@ Auswahl (Zelle, Bereich, Mehrfachauswahl, ganze Zeile/Spalte über den Kopf) →
Inspector unter **Objekt · Auswahl**: horizontale **Ausrichtung**
(links/mitte/rechts),
**Zahlenformat** (`Automatisch`, `Ohne`, Ganzzahl, 1/2 Nachkommastellen,
**Prozent** 0/2 Stellen — kombiniert mit der Locale unten), **Zell-Textfarbe**,
**Prozent** 0/2 Stellen, **Datum**/**Zeit**/**Datum + Zeit** — kombiniert mit
der Locale unten), **Zell-Textfarbe**,
**Zell-Füllfarbe**, **Fett**/**Kursiv**, „Formatierung entfernen". Auch exakte
**Spaltenbreite** und **Zeilenhöhe** der Fokuszelle.
@@ -399,7 +400,8 @@ darauf, `$` hin oder her. `F4` schaltet den Bezug unter dem Cursor durch die
vier Kombinationen.
**Funktionen** (case-insensitiv, Trenner `;`): `SUM`, `AVG` (Alias `AVERAGE`),
`MIN`, `MAX`, `COUNT`, `IF(Bedingung; Dann; Sonst?)` — plus alle über
`MIN`, `MAX`, `COUNT`, `IF(Bedingung; Dann; Sonst?)`, `DATE(Jahr; Monat;
Tag)`, `TIME(Std; Min; Sek)`, `TODAY()`, `NOW()` — plus alle über
**„𝑓 Funktionen"** angelegten benutzerdefinierten Funktionen (siehe
„Benutzerdefinierte Funktionen" oben), die sich für den Aufrufer identisch
verhalten. Argumente gemischt: Einzelzellen, Bereiche (lokal oder `Name!…`),
@@ -416,6 +418,33 @@ Zahl außer `0` sowie jeder nicht-leere Text gilt als wahr, z. B.
Bedingung `0`. Ein benutzerdefiniertes „IF" ist nicht anlegbar — der Name
kollidiert mit der eingebauten Funktion.
**Datum & Zeit**: ein Datum ist — wie in jeder Tabellenkalkulation — intern
einfach eine **Zahl** (Tage seit dem 30.12.1899, Uhrzeit als Bruchteil eines
Tages), keine eigene Zellart. Deshalb funktioniert Datums-Arithmetik ganz
ohne Zusatzaufwand: `=fällig - rechnungsdatum` zählt Tage, `=rechnungsdatum
+ 30` addiert 30 Tage. Zwei Wege zu einem Datumswert:
- **Eingabe erkennen**: ein eindeutig lesbares Datum/eine Uhrzeit als reine
Zelleingabe (nicht als Formel) wird beim Eintippen automatisch erkannt
und sofort mit dem passenden Format angezeigt — genau wie in Excel/Numbers.
Erkannt werden ISO (`2026-09-02`, auch mit `14:30`/`14:30:15` angehängt)
und Punkt-Daten (`02.09.2026`, `02.09.26`, ebenfalls mit Uhrzeit), sowie
reine Uhrzeiten (`14:30`). **Absichtlich nicht** erkannt: Schrägstrich-Daten
(`02/09/2026`) — Tag-zuerst vs. Monat-zuerst ist über die unterstützten
Locales (en-US ist monatzuerst, alle anderen tagzuerst) nicht eindeutig
auflösbar; falsches Raten würde das Datum lautlos verfälschen, deshalb
bleibt so ein Text unangetastet. Die automatische Formatierung überschreibt
nie ein bereits explizit gesetztes Zellformat.
- **Formel**: `DATE(Jahr; Monat; Tag)` und `TIME(Std; Min; Sek)` bauen einen
Datums-/Zeitwert (Überlauf wie in Excel erlaubt: `DATE(2026;13;1)` =
01.01.2027, `TIME(25;0;0)` = 01:00 des Folgetags-Anteils); `TODAY()` /
`NOW()` liefern das aktuelle Datum bzw. Datum+Uhrzeit. Formel-Ergebnisse
bekommen **kein** automatisches Zellformat (wie bei jeder anderen Formel
auch) — das Zahlenformat-Dropdown auf „Datum"/„Zeit"/„Datum + Zeit" stellen.
`TODAY`/`NOW` sind **nicht volatil**: diese Engine rechnet rein
vorgänger-getrieben neu, ohne Timer — eine Zelle mit `=TODAY()`
aktualisiert sich erst bei ihrer eigenen nächsten Neuberechnung, nicht von
selbst beim Tageswechsel (siehe „Bekannte Grenzen" unten).
**Fehlermodell** (propagiert an abhängige Zellen):
`#REF` (ungültiger/gelöschter Bezug, unbekannter Tabellen-/Blattname, oder ein
unqualifizierter Tabellenname, der auf mehreren Blättern existiert und auch
@@ -532,7 +561,7 @@ Grundrechenarten: `Übersicht!B2 = =SUM(Umsatz!B2:E2)`, `D2 = =B2-C2`,
## Tests
`npm run test` — Vitest, 265 Tests:
`npm run test` — Vitest, 297 Tests:
- **lexer / parser** — Präzedenz & Assoziativität (`^` vor `*/` vor `+-`, `^`
rechts, `-2^2 = -(2^2)`), lokale/qualifizierte/quoted Bezüge, `!` **und** `::`,
@@ -547,6 +576,15 @@ Grundrechenarten: `Übersicht!B2 = =SUM(Umsatz!B2:E2)`, `D2 = =B2-C2`,
Cross-Table, Case-/`::`-Toleranz, jeder Fehlerfall, Fehler-Propagierung,
`IF` (beide Zweige, 2-/3-Argument-Form, Kurzschluss-Beweis, Vergleiche als
Bedingung, Fehler in der Bedingung).
- **Datum & Zeit** — `dates.ts`: `dateToSerial`/`timeSerial`-Rundtrip gegen
bekannte Werte, Überlauf-Verhalten, `parseDateTimeLiteral` für jede
erkannte Form (ISO/Punkt, mit/ohne Uhrzeit) sowie Ablehnung von
Schrägstrich-Daten und ungültigen/rollierten Daten; `literalValue` wandelt
eine erkannte Eingabe in ihren Serial um; `DATE`/`TIME`/`TODAY`/`NOW` als
Formel (Basisfälle, Arity-/Typ-Fehler, Datums-Arithmetik); `formatValue`
über mehrere Locales inkl. `plain`; Store: Auto-Format beim Eintippen
(überschreibt nie ein bereits gesetztes Format), Namenskollision mit den
neuen eingebauten Funktionen.
- **graph** — `affected`, topologische Ordnung, Selbst-/2er-/längerer Zyklus.
- **workbook / store** — Seed-Aggregate, Cross-Table-Summen, Edit-Ripple durch
die ganze Kette, `#REF` nach Tabellen-/Blatt-Löschung, neues Tabellen-Objekt
@@ -683,9 +721,16 @@ Grundrechenarten: `Übersicht!B2 = =SUM(Umsatz!B2:E2)`, `D2 = =B2-C2`,
reaktiv), **kein** echter Web Worker.
- **xlsx-Formel-Übernahme** ist heuristisch (Separator-Swap unter Beachtung von
Quotes); exotische Excel-Formeln fallen auf den Wert zurück.
- Kein Text-Verkettungs-/Vergleichsoperator, keine benannten Bereiche (nur
- Kein Text-Verkettungsoperator, keine benannten Bereiche (nur
benutzerdefinierte *Funktionen*, keine benannten *Zellen/Bereiche*), kein
Snapping der Canvas-Objekte.
- **Datum & Zeit**: die automatische Erkennung beim Eintippen erfasst nur ISO-
und Punkt-Daten, absichtlich **nicht** Schrägstrich-Daten (Tag-zuerst vs.
Monat-zuerst ist über die unterstützten Locales nicht eindeutig auflösbar,
s. o.). Formel-Ergebnisse (`=DATE(...)`, `=TODAY()`, `=A1+30`) bekommen kein
automatisches Zellformat — wie jedes andere Formelergebnis auch. `TODAY()`/
`NOW()` sind nicht volatil: sie aktualisieren sich erst bei der nächsten
Neuberechnung ihrer eigenen Zelle, nicht spontan mit der Zeit.
- **Benutzerdefinierte Funktionen** (Phase 1, siehe oben) bewusst ohne:
Autovervollständigen des Funktionsnamens im Editor, Live-Vorschau des
Ergebnisses im Bearbeiten-Dialog, variadische Parameter/Default-Werte, und