Skip to content

fix: DATE values encoded as days-since-epoch instead of INT32 yyyyMMdd - #15

Merged
HTHou merged 2 commits into
developfrom
fix/date-encoding-yyyymmdd
Jul 13, 2026
Merged

fix: DATE values encoded as days-since-epoch instead of INT32 yyyyMMdd#15
HTHou merged 2 commits into
developfrom
fix/date-encoding-yyyymmdd

Conversation

@CritasWang

Copy link
Copy Markdown
Contributor

Fixes #14

Problem

The Node.js client serializes DATE (TSDataType 9) values as days since Unix epoch and deserializes them the same way. IoTDB's actual DATE wire format is an INT32 encoded as year*10000 + month*100 + day (e.g. 2026-07-1320260713), as implemented by:

  • Java: DateUtils.parseDateExpressionToInt / parseIntToDate (iotdb-core)
  • C# client: same yyyyMMdd INT32 encoding
  • Rust client: verified via a live adjudication test against IoTDB 2.0.6 — a row inserted via SQL literal '2026-07-13' reads back as raw i32 20260713 on the wire

Consequences of the old behavior: dates written via insertTablet from Node.js were stored as small integers (~20655 for 2026), which other clients decode as year-0002 nonsense; dates written by any other client (~2026xxxx) decoded in Node.js as dates around year 57000.

Fix

  • New shared utils parseDateToInt / parseIntToDate in src/utils/DataTypes.ts (exported from the package index). Calendar components are taken in UTC, matching Java LocalDate semantics (no timezone math).
  • Applied in all four DATE paths: Session.serializeColumn (tablet write), FastSerializer.serializeDateColumn (fast write path), Session.deserializeColumn (TSQueryDataSet read), ColumnDecoder/parseTsBlock (TsBlock read).
  • Additional fix discovered during verification: TsBlock headers report DATE columns with wire type INT32 (1); the DATE type is only in the response metadata. parseTsBlock now converts such columns to Date objects using dataTypeList, so query results return Date instead of raw integers.
  • Null handling unchanged (null → 0 on write, bitmap-null → null on read).

Verification

  • Unit tests: yyyyMMdd round-trip, exact serialization byte vector (2026071301 35 27 69 big-endian), TsBlock column decode. 124/124 pass.
  • Live IoTDB 2.0.6: a Date('2026-07-13') written via binary tablet and a '2026-07-13' SQL literal now read back identical (2026-07-13T00:00:00.000Z); previously the tablet row was corrupted.
  • npm run build and existing e2e data-type suites pass against a live server.

⚠️ Behavior change: code that relied on the old (incorrect) days-since-epoch numbers for DATE columns will see different values. This is a wire-format correctness fix required for interoperability with the Java, C#, Python, and Rust clients.

IoTDB's DATE wire format is an INT32 encoded as year*10000 + month*100 + day
(e.g. 2026-07-13 -> 20260713), matching the Java client's
DateUtils.parseDateExpressionToInt and the C# client. The Node.js client was
writing and reading days-since-epoch, so DATE values written via tablets were
corrupted and unreadable by other clients (and vice versa).

Adjudicated against a live IoTDB 2.0.6 server: a row inserted via SQL literal
'2026-07-13' reads back as INT32 20260713 on the wire.

Changes:
- Add shared parseDateToInt / parseIntToDate utils in src/utils/DataTypes.ts
  (UTC calendar components, matching Java LocalDate semantics) and export them
- Session.serializeColumn case 9: write yyyyMMdd (was days-since-epoch)
- FastSerializer.serializeDateColumn: same fix on the fast path
- Session.deserializeColumn case 9 (TSQueryDataSet): decode yyyyMMdd -> Date
- ColumnDecoder Int32ArrayColumnDecoder: decode DATE (type 9) columns to Date
- Session.parseTsBlock: DATE columns arrive typed as INT32 (1) in the TsBlock
  header; convert them to Date using the metadata dataTypeList
- Unit tests: yyyyMMdd round-trip, exact wire bytes for 2026-07-13
  (0x01352769), TsBlock decode; updated existing DATE serialization tests

Verified: tablet-written Date and SQL-literal date now read back identical
against a live IoTDB 2.0.6 (both 2026-07-13T00:00:00.000Z).

@HTHou HTHou left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found three correctness and public-contract issues in the DATE wire-format change. Details are inline.

Comment thread src/client/Session.ts Outdated
Comment thread src/utils/DataTypes.ts
Comment thread src/utils/DataTypes.ts
Addresses three review comments from HTHou on PR #15:

1. parseTsBlock DATE conversion now respects
   columnIndex2TsBlockColumnIndexList. dataTypeList is ordered by LOGICAL
   response columns while physical TsBlock columns may be deduplicated or
   reordered, so converting physical column i based on dataTypes[i] could
   convert the wrong column. The mapping (logical -> physical, -1 = time
   column) is now passed into parseQueryResult from both call sites
   (executeQueryStatement initial batch and SessionDataSet.fetchNextBatch)
   and inverted into per-physical-column logical types. DATE conversion
   only applies when a physical column's logical type is unambiguously
   DATE; conflicting duplicate mappings log a warning and skip conversion
   instead of crashing. Absent mapping keeps identity behavior.

2. Docs updated to the yyyyMMdd contract: docs/data-types.md and the
   English/Chinese tree user guides no longer describe numeric DATE as
   days since epoch; they now document the INT32 yyyyMMdd encoding with
   examples (20240101 for 2024-01-01).

3. parseDateToInt/parseIntToDate now validate like the Java client's
   DateUtils/LocalDate: invalid Date objects (NaN time), non-integer or
   non-finite numbers, years outside 1000-9999, and impossible calendar
   dates (e.g. 20230229, month 13, day 0) throw a descriptive Error
   instead of being silently normalized or serialized as garbage.
   null/undefined handling is unchanged (callers filter nulls first).

Unit tests added: non-identity mapping TsBlock conversion (crafted
TsBlock buffers with INT32 wire columns + DATE logical type), dedup and
conflicting-mapping cases, and validation boundary tests (leap day
20240229 ok, 20230229/20241301/20240100/year 999/year 10000/NaN
Date/non-integer/legacy days-epoch 19723 all throw).
@HTHou
HTHou merged commit 31601ee into develop Jul 13, 2026
2 checks passed
@HTHou
HTHou deleted the fix/date-encoding-yyyymmdd branch July 13, 2026 06:27
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

DATE values encoded as days-since-epoch instead of INT32 yyyyMMdd

2 participants