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:
+57
-2
@@ -19,9 +19,10 @@ genau die genannte Tabelle. Siehe "Blatt-Qualifier" weiter unten.
|
||||
| `evaluator.ts` | AST + `EngineHost` → `CellValue`. Numerisches Auswertungsmodell, Fehler-Propagierung. Enthält auch den kleinen Interpreter für `kind: 'block'`-Funktionsaufrufe (`BlockCallNode`) – siehe „Schleifen" unten. |
|
||||
| `graph.ts` | Dokumentweiter Abhängigkeitsgraph: `affected()`, `topoOrder()`, Zirkel-Erkennung (iterative DFS, white/grey/black). |
|
||||
| `refs.ts` | A1 ↔ `(col,row)` inkl. `$`-Sperrachsen, Spaltenbuchstaben, Bereichs-Expansion, `F4`-Zyklus (`cycleReferenceLocks`). |
|
||||
| `functions.ts` | `SUM/AVG/MIN/MAX/COUNT` (+ Alias `AVERAGE`) als Tabelle `{ NAME → fn(values) }`. `IF` ist hier nur als Platzhalter registriert (für `hasFunction()`/Namensreservierung); die echte, kurzschließende Auswertung sitzt in `evaluator.ts`, s. u. Eingebaute Namen sind nie überschreibbar. |
|
||||
| `functions.ts` | `SUM/AVG/MIN/MAX/COUNT` (+ Alias `AVERAGE`), `DATE/TIME/TODAY/NOW` als Tabelle `{ NAME → fn(values) }`. `IF` ist hier nur als Platzhalter registriert (für `hasFunction()`/Namensreservierung); die echte, kurzschließende Auswertung sitzt in `evaluator.ts`, s. u. Eingebaute Namen sind nie überschreibbar. |
|
||||
| `dates.ts` | Datum/Zeit als Zahl ("Serial", Tage seit 1899-12-30): `dateToSerial`/`timeSerial` (Bausteine für `DATE`/`TIME`), `todaySerial`/`nowSerial`, `serialToUtcDate` (für die Anzeige), `parseDateTimeLiteral` (Eingabe-Erkennung, s. u. „Datum & Zeit"). |
|
||||
| `userFunctions.ts` | Benutzerdefinierte Funktionen (dokumentweite Bibliothek, angelegt über die UI — siehe Haupt-`README.md`). `expandUserFunctions()` setzt einen `kind: 'expression'`-Aufruf beim Parsen der aufrufenden Zelle als Kopie des rekursiv expandierten, parametersubstituierten Funktionskörpers in deren AST ein; einen `kind: 'block'`-Aufruf (FOR/IF/RETURN-Code) verpackt sie stattdessen in einen `BlockCallNode` mit bereits aufgelösten verschachtelten Aufrufen, siehe „Schleifen" unten. |
|
||||
| `workbook.ts` | Bindeglied: flache Liste von **Tabellen** + rohe Eingaben + ASTs + Werte + Graph. Inkrementelle Neuberechnung. Implementiert `EngineHost`. Ruft `expandUserFunctions()` direkt nach `parseFormula()`. |
|
||||
| `workbook.ts` | Bindeglied: flache Liste von **Tabellen** + rohe Eingaben + ASTs + Werte + Graph. Inkrementelle Neuberechnung. Implementiert `EngineHost`. Ruft `expandUserFunctions()` direkt nach `parseFormula()`. `literalValue()` erkennt Datums-/Zeit-Eingaben (`dates.ts`) vor dem generischen Zahl-Test. |
|
||||
| `types.ts` | Gemeinsame Typen, `FormulaError`, `keyOf()`. |
|
||||
|
||||
Die Engine kennt **kein** Vue. Der Vue-Store (`src/store/`) legt eine `Workbook`-
|
||||
@@ -315,6 +316,60 @@ dagegen erlaubt), `blockAlwaysReturns` sowie die Zyklus-Prüfung.
|
||||
benutzerdefiniertes „IF" ist nicht anlegbar (Namenskollision mit dem
|
||||
eingebauten `FUNCTIONS.IF`-Eintrag).
|
||||
|
||||
## Datum & Zeit
|
||||
|
||||
Kein eigener Zellwert-Typ — ein Datum ist wie in jeder Tabellenkalkulation
|
||||
einfach eine **Zahl** (`dates.ts`): das "Serial", Tage seit dem Nullpunkt
|
||||
**1899-12-30** (Google-Sheets-Konvention: wie Excel, aber ohne dessen
|
||||
historischen 1900-Schaltjahr-Bug), Uhrzeit als Bruchteil eines Tages
|
||||
(`0.5` = 12:00). Serial 1 = 1899-12-31, Serial 2 = 1900-01-01. Alle
|
||||
Berechnungen laufen über UTC-Felder (`Date.UTC`/`getUTC*`), damit ein Serial
|
||||
unabhängig von der Browser-Zeitzone exakt rundtrippt. Weil ein Datum einfach
|
||||
eine Zahl ist, funktionieren `+`/`-`/Vergleiche darauf automatisch — keine
|
||||
Änderung an Operatoren nötig.
|
||||
|
||||
- **`dateToSerial(y, mo, d, h?, mi?, s?)`** — Baustein für `DATE()`, reines
|
||||
`Date.UTC`, **Überlauf bewusst erlaubt** (`dateToSerial(2026, 13, 1)` =
|
||||
2027-01-01), wie Excels `DATE()`.
|
||||
- **`timeSerial(h, mi, s)`** — Baustein für `TIME()`, wrapt modulo 86400
|
||||
Sekunden wie Excels `TIME()` (`timeSerial(25,0,0)` = der `1/24`-Anteil des
|
||||
Folgetags).
|
||||
- **`todaySerial()`/`nowSerial()`** — nutzen das **lokale** Kalenderdatum
|
||||
(ein Datum ist ein Kalender-, kein Zeitzonen-Konzept), nicht UTC.
|
||||
- **`parseDateTimeLiteral(raw)`** — erkennt eine rohe Zelleingabe als Datum/
|
||||
Zeit/Datum+Zeit, genutzt sowohl von `workbook.ts`s `literalValue()` (Text →
|
||||
Serial) als auch vom Store (`document.ts`s `setCellInput`, Auto-Format
|
||||
beim Eintippen, s. u.). **Bewusst konservativ**: nur ISO (`2026-09-02`,
|
||||
optional `[ T]HH:MM[:SS]`) und Punkt-Daten (`02.09.2026`, `02.09.26` —
|
||||
2-stellig: 00–29 → 20xx, 30–99 → 19xx —, ebenfalls mit Uhrzeit) sowie reine
|
||||
`HH:MM[:SS]`-Zeiten werden erkannt. **Schrägstrich-Daten (`02/09/2026`)
|
||||
absichtlich nicht** — Tag-zuerst vs. Monat-zuerst ist über die
|
||||
unterstützten Locales (en-US monatzuerst, alle anderen tagzuerst) nicht
|
||||
eindeutig auflösbar; falsches Raten würde das Datum lautlos verfälschen,
|
||||
das bleibt schlechter als es als Text stehen zu lassen. Ein intern über
|
||||
`Date.UTC` gebauter Kandidat wird durch Rück-Dekomposition verifiziert, um
|
||||
stillen Überlauf abzulehnen (`31.04.2026`, `30.02.2026` → **kein** Treffer,
|
||||
bleibt Text) — anders als `dateToSerial`/`DATE()`, die Überlauf bewusst
|
||||
zulassen.
|
||||
- **`DATE`/`TIME`/`TODAY`/`NOW`** (`functions.ts`) — `DATE`/`TIME` erwarten
|
||||
genau 3 Argumente (sonst `#ERR`), koerzieren jedes wie `evaluator.ts`s
|
||||
`toNumber()` (null → 0, zahlenförmiger Text gecastet, sonst `#VALUE!`);
|
||||
`TODAY`/`NOW` erwarten 0 Argumente. **Nicht volatil**: diese Engine
|
||||
berechnet rein vorgänger-getrieben neu, ohne Timer/Hintergrundarbeit — eine
|
||||
Zelle mit `=TODAY()`/`=NOW()` aktualisiert sich erst bei ihrer eigenen
|
||||
nächsten Neuberechnung, nicht spontan mit der Zeit (dokumentierte Grenze).
|
||||
- **Anzeige** (`store/format.ts`) — `CellNumFmt`-Werte `date`/`time`/
|
||||
`datetime` rendern über `serialToUtcDate()` + `Intl.DateTimeFormat(locale,
|
||||
{ timeZone: 'UTC', ... })`; das Pseudo-Locale `'plain'` bekommt eine
|
||||
manuelle, locale-neutrale `YYYY-MM-DD`/`HH:MM`-Formatierung (bewusst nicht
|
||||
`Intl` mit `undefined`-Locale, das würde lautlos die Host-Locale des
|
||||
Browsers durchsickern lassen). Formel-Ergebnisse bekommen **kein**
|
||||
automatisches Format — wie jedes andere Formelergebnis auch.
|
||||
- **Auto-Format beim Eintippen** (`store/document.ts`s `setCellInput`) — eine
|
||||
erkannte Literal-Eingabe (nicht `=`-Formeln) setzt automatisch das
|
||||
passende `numFmt`, aber **nur** solange die Zelle noch `'auto'` ist; ein
|
||||
bereits explizit gesetztes Format wird nie überschrieben.
|
||||
|
||||
## Neuberechnung
|
||||
|
||||
1. `setCell` reparst **eine** Zelle, aktualisiert ihre Graph-Kanten.
|
||||
|
||||
Reference in New Issue
Block a user