JavaScript Temporal Is Coming
developer.mozilla.org
developer.mozilla.org
Why?!
The thing I hate most about most SQL DateTime/Timestamps are their choice to default to TZ ambiguity. Too often it means everything must actually be UTC nearly all the time, or you get messy data that could be any zone. I.e. It's a lossy data type.
TZ unaware should not be an option. Or if there's truly a case for it then it should at least be some non default, opt in, with 'dangerous' in the type name(s).
I'm sure it's been discussed here before, but for calendar events you don't necessarily want a timezone attached, you want a location - "this event happens at 9am according to whatever timezone is currently in effect in Auckland, NZ". That's a thing that UTC or timezone-aware Datetimes can't help with.
Alarms are something of a special case, and with TZ aware types one could still adapt at the application later.
For a gift card system I once had relative expiration, enforced using the zone of the merchant location. Using a relative type actually felt like more work because from the merchant perspective, all that mattered was the point in time relative to their zone. It would've been simpler to do the offsetting in the app layer checks. And ultimately no one cared enough to keep maintaining it anyway, having an absolute point in time is usually good enough or even preferred.
I do agree that "with attached Timezone" or "as UTC" are absolutely the sensible defaults, I'm just suggesting that sometimes "plain" datetimes are semantically the correct choice.
Modern datetime systems (including Temporal) use identifiers rather than offsets, which are almost as good as a location as long as governments don't redraw the boundaries. And Auckland is in fact the canonical city for the main New Zealand timezone, so specifying your timezone as "Pacific/Auckland" will get you pretty much the thing you want. In Temporal, this would be e.g.
Temporal.PlainDateTime.from({ year: 2025, month: 1, day: 1, hour: 9 })
.toZonedDateTime('Pacific/Auckland') >> zdt = Temporal.Now.zonedDateTimeISO()
>> zdt.toJSON()
"2025-01-24T19:57:01.089557834-05:00[America/New_York]"
>> zdt.toString()
"2025-01-24T19:57:01.089557834-05:00[America/New_York]"
Temporal will even check that the offset is still correct for the time zone when you deserialize.These aren't "TZ ambiguous". These are "this thing does not logically have a specific associated timezone". For things which do logically have a specific associated timezone, there's ZonedDateTime.
PlainDateTime is there because it is necessary sometimes. The Temporal docs even outline some cases where you might want to use it: https://tc39.es/proposal-temporal/docs/plaindatetime.html
Temporal also makes it pretty hard to misuse its types. For example:
>> dt = Temporal.PlainDateTime.from("2025-01-25T00:00Z")
Uncaught RangeError: Z designator not supported for PlainDateTime
ToTemporalDateTime ecmascript.mjs:1198
from plaindatetime.mjs:286
<anonymous> debugger eval code:1