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

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions