Build1 distinct publisher3 min readUpdated
Rails' t.datetime and Prisma's DateTime both emit PostgreSQL's naive timestamp. The offending statements merged years ago, which is exactly why line-level tooling never sees them.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
A post on dev.to from the team behind Schemity, a desktop ERD tool, argues that most PostgreSQL schemas carry the wrong timestamp type in bulk, and that the cause is not disagreement but defaults: Rails' `t.datetime` and Prisma's `DateTime` both emit the naive type unless you go out of your way [1]. It matters because both types occupy 8 bytes, so the wrong choice bought nothing [2], and because nothing about the resulting column ever looks invalid. That is the awkward property of this defect class. A `varchar(20)` that should have been `text` announces itself the first time a value does not fit [4]. A `timestamp` column behaves perfectly for years and then produces an hour of wrong answers at a daylight saving boundary, or shifts an entire reporting dashboard the day someone changes the server's time zone [3]. PostgreSQL's own wiki puts "don't use timestamp (without time zone)" on its Don't Do This page, alongside its advice against char(n), money and serial [5]. The wiki's framing is the useful one: `timestamp` stores "a date and time you give it", compared to a picture of a calendar and a clock, while `timestamptz` "records a single moment in time" and does the right thing with arithmetic between values entered in different zones [6]. The standard objection is that `timestamptz` stores a zone and therefore costs something. It does not. The PostgreSQL date/time documentation states that an input string with an explicit zone "will be converted to UTC using the appropriate offset for that time zone", and that "in either case, the value is stored internally as UTC, and the originally stated or assumed time zone is not retained" [7]. The types table gives both variants 8 bytes, both spanning 4713 BC to 294276 AD at 1 microsecond resolution [8]. The storage delta between right and wrong is zero [20]. If you genuinely need to know that a booking was entered in Europe/Berlin, that is a second column holding the zone name, and a modelling decision rather than a type choice [9]. The decision rule is short. `created_at`, `deleted_at`, `published_at`, `last_seen_at`, every audit column and every event time are instants, and belong in `timestamptz` [10]. A shop that opens at 09:00 in whatever zone it stands in, a birthday, an alarm a user wants at 07:00 in any country: those are wall-clock readings, and `timestamp` is the correct type for them [11]. The second set is much smaller than the number of `timestamp` columns in a typical database, which is the tell [12]. Rails is the documented case. `t.datetime` translates to `timestamp without time zone` on PostgreSQL [13]. The `datetime_type` setting that lets you switch it to `:timestamptz` arrived in Rails 7.0, more than six years after the issue titled "standard migrations will generate a 'timestamp without timezone' field" was opened on 4 August 2015, whose complaint was that `AT TIME ZONE` arithmetic then quietly uses the database server's zone rather than the intended one [14] [15]. The default itself has not changed, so an application generated today still produces naive columns [16]. Which is the tooling gap underneath all of this: a migration linter inspects statements as they land, and these statements landed years ago, so by the time the cost appears there is nothing left to lint [17]. Schemity's stated answer is to lint the whole open ERD instead, marking each offending field row in the margin, with the conversion routed through a migration SQL diff you review first [18]. That is a vendor claim on a vendor blog, disclosed as such by the author [19], and worth treating as a description of one approach rather than evidence it works.
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
Rails' t.datetime and Prisma's DateTime both emit the naive timestamp type on PostgreSQL unless the developer goes out of the way to change it, which is how a schema ends up with hundreds of columns nobody chose.
Both timestamp and timestamptz occupy 8 bytes, so the correct one is free.
A timestamp column behaves perfectly for years, then produces an hour of wrong answers at a daylight saving boundary, or shifts an entire reporting dashboard the day someone changes the server's time zone; nothing in the schema looks wrong at any point because nothing in the schema is invalid.
A varchar(20) that should have been text announces itself the first time a value does not fit.
PostgreSQL's wiki has a page called Don't Do This where 'don't use timestamp (without time zone)' sits alongside its advice against char(n), money and serial.
The PostgreSQL wiki says timestamp stores 'a date and time you give it', comparing it to a picture of a calendar and a clock, while timestamptz 'records a single moment in time' and does the right thing with arithmetic between values entered in different zones.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Primary-doc grounded on the technical core, vendor-only on the remedy
The type-behaviour claims are anchored in quoted PostgreSQL wiki and date/time documentation text (8-byte storage for both types, UTC storage with the originating zone discarded) and in a specifically dated Rails issue plus a named Rails 7.0 setting - all checkable against primary sources. The weaker half is the tooling claim: the description of Schemity's ERD-wide lint and reviewable migration SQL diff rests solely on the vendor's own post, and the cluster contains no second publisher, demonstration or user report.
No uptake data for either the practice or the tool
The cluster describes framework default behaviour but supplies no deployment, download, customer or usage figures for Schemity, and no measurement of how many projects have adopted timestamptz or the Rails datetime_type setting. The one dated ecosystem event available is a bug report, which evidences persistence of a default rather than adoption of any remedy, so no adoption score can be derived without guessing.
Sound advice, mildly overstated remedy
The technical substance is, if anything, understated: it repeats PostgreSQL's own 'Don't Do This' guidance and quotes the documentation, so the advice itself is not inflated. The overstatement sits in the framing and the fix - the categorical claim that a migration linter 'cannot find' these columns, and the implication that the vendor's ERD lint is the answer, arrive with no independent verification, no adoption evidence and no accounting of the cost of converting hundreds of live columns. That pushes the cluster modestly into overstated territory rather than far.
Vendor-authored content marketing, disclosed
The author states in the first line that he builds Schemity, that the post is from the company blog, and that the examples use the tool; the article's closing turn is from general advice to 'which of the two hundred date columns' need fixing, i.e. the problem the product solves. The incentive to promote is therefore strong and structural, mitigated only by the explicit disclosure and by the heavy reliance on quotable third-party documentation instead of proprietary assertions.
Confident on the type facts, thin on everything else
Confidence is high for the documented PostgreSQL semantics and the Rails default history because those are quoted from primary sources and dated. It is low for the tooling and impact claims: one publisher, one item, no independent corroboration, no adoption measurement, and a disclosed commercial interest in the conclusion. The net is middling.
build
A foreign-key cycle where every column is NOT NULL cannot take its first row1 distinct publisher
build
The 680 MB database that was really a 17 GB disk: self-hosted support platforms fail at month six1 distinct publisher
build
Four indexes, none of them covering: the 78-second page and the one index that fixed it1 distinct publisher
build
DuckDB is growing a server, and someone on your team will have to run it1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 18, 2026