Date-Only Fields
Store and edit calendar dates (due dates, birthdays, invoice dates) without timezone shifts
Date-Only Fields
TL;DR: For fields that represent a calendar day (not a moment in time), use
<DatePicker asString />. Store the value asYYYY-MM-DDin a Postgresdatecolumn.
When You Need This
Anywhere the value is "a day on the calendar" rather than "a moment in time":
- Due dates, invoice dates, birthdays, contract start/end
- Postgres
datecolumns (Drizzle returns them asstring)
If the field represents a moment ("when was this submitted"), use a regular timestamp — this recipe doesn't apply.
asString Prop on DatePicker
When the underlying field is string | null, tell the picker to read and write YYYY-MM-DD directly:
import { DatePicker } from "@/components/ui/date-picker"
<DatePicker
asString
value={invoice.issuedAt} // string | null — from DB
onChange={(value) => update({ issuedAt: value })}
/>This is a discriminated union — TypeScript enforces that value is string | null and onChange receives string | null whenever asString is true. Without asString, both use Date (the old behavior).
Helpers for Server / Manual Use
When you need to convert outside the picker — server-side, integrations, one-off formatting:
import { toDateOnlyString, fromDateOnlyString } from "@/lib/utils/format"
toDateOnlyString(someDate) // Date → "2026-04-29"
fromDateOnlyString("2026-04-29") // "2026-04-29" → DateThe Full Checklist
When adding a date-only field to an entity:
| # | File | What to do |
|---|---|---|
| 1 | src/lib/db/schema/<entity>.ts | Use date() (Postgres date column) — not timestamp() |
| 2 | src/features/<entity>/validation.ts | Zod: z.string().regex(/^\d{4}-\d{2}-\d{2}$/) or z.iso.date() |
| 3 | src/features/<entity>/queries.ts | Type as string | null — Drizzle returns date columns as strings |
| 4 | UI forms | <DatePicker asString value={...} onChange={...} /> |
| 5 | Server comparisons | Wrap with fromDateOnlyString(str) before passing to gte / lte on a timestamp column |
Examples
React Hook Form
import { Controller } from "react-hook-form"
import { DatePicker } from "@/components/ui/date-picker"
<Controller
control={form.control}
name="dueDate"
render={({ field }) => (
<DatePicker
asString
value={field.value}
onChange={field.onChange}
placeholder="Pick a due date"
/>
)}
/>Inline Edit in a DataTable
import { DataTableCellEditDate } from "@/components/data-table"
{
accessorKey: "deliveryDate",
cell: ({ row, table }) => {
const meta = table.options.meta as { onUpdate?: (id: string, data: any) => Promise<void> }
return (
<DataTableCellEditDate
value={row.original.deliveryDate}
onSave={(val) => meta?.onUpdate?.(row.original.id, { deliveryDate: val })}
/>
)
},
}DataTableCellEditDate uses string mode under the hood — no extra configuration.
Filtering by a Date Column
Date filters store their range as YYYY-MM-DD..YYYY-MM-DD. If the column you're filtering is a timestamp, convert both ends with fromDateOnlyString:
import { gte, lte } from "drizzle-orm"
import { parseDateRange } from "@/lib/url-state/filters"
import { fromDateOnlyString } from "@/lib/utils/format"
if (params.issuedAt?.[0]) {
const { from, to } = parseDateRange(params.issuedAt[0])
if (from) conditions.push(gte(invoices.issuedAt, fromDateOnlyString(from)))
if (to) conditions.push(lte(invoices.issuedAt, fromDateOnlyString(to)))
}If the column is itself a date column, pass the YYYY-MM-DD string through — Drizzle handles it.
Common Mistakes
Don't mix modes on the same field:
// ❌ Wrong — value is a string, but picker expects Date
<DatePicker value={invoice.issuedAt} onChange={setValue} />
// ✅ Correct
<DatePicker asString value={invoice.issuedAt} onChange={setValue} />Don't convert dates with .toISOString().split("T")[0]:
// ❌ Wrong — in any timezone east of UTC this returns the previous day
const dateOnly = date.toISOString().split("T")[0]
// ✅ Correct
const dateOnly = toDateOnlyString(date)Don't parse bare YYYY-MM-DD with new Date:
// ❌ Wrong — parsed as UTC midnight, rolls backward east of UTC
new Date("2026-04-29")
// ✅ Correct
fromDateOnlyString("2026-04-29")Related
- Add Field to Entity — adding a date field to an existing entity
- Add Filter — date range filter for list pages