Skip to content

ZenStack 3.6: writes to @db.Time columns fail with PG 22007 (Date serialized as ISO datetime) #2633

@erwan-joly

Description

@erwan-joly

Summary

The write side of @db.Time is broken in 3.6.x. When a JS Date is sent to a @db.Time column, the underlying kysely pg driver serializes the parameter as the ISO datetime returned by Date.prototype.toISOString() (e.g. \"2026-04-29T21:00:00.000Z\"). Postgres TIME columns reject the string with:

error: invalid input syntax for type time: \"2026-04-29T21:00:00.000Z\"
  code: 22007

This is the write-side counterpart to #2590 (which fixed @db.Time reads silently returning raw strings).

Repro

model Parent {
  id       String  @id @default(uuid())
  children Child[]
}

model Child {
  id       String   @id @default(uuid())
  parentId String
  open     DateTime @db.Time(6)
  close    DateTime @db.Time(6)
  parent   Parent   @relation(fields: [parentId], references: [id])
}
await db.parent.create({
  data: {
    children: {
      createMany: {
        data: [{
          open:  new Date('1970-01-01T09:00:00Z'),
          close: new Date('1970-01-01T16:00:00Z'),
        }],
      },
    },
  },
});

The validator (after #2632) accepts the Date, but the resulting INSERT throws 22007.

What I think is happening

@zenstackhq/orm builds the query through kysely's pg dialect. Kysely's parameter binding is column-type-agnostic by design — when given a Date, it serializes via the standard pg-types path which produces an ISO timestamp string. Postgres' TIME column type rejects ISO timestamp input; it expects HH:MM:SS (with optional fractional seconds and timezone offset).

For @db.Date and @db.Timestamp this isn't a problem — Postgres accepts the ISO string for both. Only TIME is strict.

Suggested fix

Either:

  1. Pre-serialize at the ZenStack input layer for @db.Time columns: detect the column's native type and convert the Date (or string) into HH:MM:SS before kysely builds the query.
  2. Pre-serialize at the dialect layer: extend PostgresCrudDialect to wrap Date parameters bound to TIME columns with a sql.lit(...) value formatted as TIME.
  3. Document & expose a helper for users to opt into manual TIME formatting (similar to how @db.Bytes requires explicit Uint8Array).

Option 1 keeps the user surface unchanged — they pass a Date (or, after #2632, an ISO time-only string) and the query just works. Happy to send a PR if there's interest.

Workaround we're using

Migrating our TIME columns to TIMESTAMP. The application only reads time-of-day from the field anyway, so the date prefix is harmless. Not viable for users who genuinely need TIME semantics (range queries, etc.).

Versions

  • @zenstackhq/orm: 3.6.4
  • prisma: 7.8.0
  • kysely: 0.28.16
  • pg: 8.20.0
  • node: 22.x

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions