Validate a Date String in JavaScript
To validate a date string, parse it with Temporal.PlainDate.from (or Instant.from) inside a try/catch — it throws on anything malformed, unlike new Date(), which sometimes returns a wrong date instead of failing. Part of Parsing ISO 8601 Strings Safely.
Why this scenario is tricky
Validating a date string is deceptively hard because "valid" has two independent meanings that people routinely conflate. A string can be syntactically well-formed — it matches the shape YYYY-MM-DD — while being semantically impossible, like 2024-02-30 or 2023-13-01. Legacy new Date() is useless as a validator on both counts: it accepts a huge range of loosely-formatted inputs, silently "corrects" impossible dates by rolling them over (February 30 becomes March 1 or 2), and returns an Invalid Date object for others rather than throwing, so a careless check lets bad data through. Building a reliable validator on top of new Date() means fighting all of these behaviors at once.
Temporal inverts the situation by being strict on purpose. Temporal.PlainDate.from(s, { overflow: 'reject' }) parses only genuine ISO date strings and, crucially, rejects impossible dates instead of rolling them over — February 30 throws rather than quietly becoming March. That single option turns parsing into validation: if it does not throw, the string is both well-formed and a real calendar date. The trick is knowing to pass overflow: 'reject', because the default constrain would clamp rather than reject and give a validator that accepts too much.
new Date(str) is a poor validator: it returns Invalid Date for some junk but happily reinterprets other junk ('2024-02-31' becomes 2 March in some engines, '01/02/2024' is ambiguous). Real validation means rejecting anything that is not exactly the format you accept, which is what Temporal's strict from() does.
Minimal working solution
Wrapping Temporal.PlainDate.from(s, { overflow: 'reject' }) in a try/catch gives a boolean validator: return true if it parses, false if it throws. This is concise and correct because it delegates both the syntactic and semantic checks to the parser — you are not maintaining a regex for the format and a separate table of month lengths for the day range, you are asking the calendar type whether the string denotes a real date. The overflow: 'reject' is what makes it a true validator rather than a permissive coercer; without it, an impossible day would be clamped and reported as valid.
Choose the Temporal type to match what you are validating. PlainDate.from validates a date; PlainTime.from validates a time-of-day; PlainDateTime.from validates a date-and-time; and if you require a zone or offset to be present, parsing as the appropriate zoned type enforces that too. Matching the type to the expected shape means the validator rejects not just malformed strings but strings of the wrong kind — a bare date where a full timestamp was required, for instance.
import { Temporal } from '@js-temporal/polyfill';
function isValidDate(s: string): boolean {
try { Temporal.PlainDate.from(s, { overflow: 'reject' }); return true; }
catch { return false; }
}
Full production version
A production validator usually needs to do more than return a boolean; it needs to say why a string was rejected, so the caller can surface a useful error. Catching the thrown RangeError and inspecting or wrapping its message lets you distinguish "not an ISO date at all" from "a well-formed date that does not exist," which are different messages for a user filling in a form. Returning a discriminated result — { ok: true, value } or { ok: false, reason } — is often cleaner than a bare boolean because it hands the validated, typed value back on success, so the caller does not parse twice.
Decide deliberately how strict to be about format. If your API contract is "ISO 8601 dates only," rejecting anything that is not exactly YYYY-MM-DD is correct and Temporal does it for you. If you must accept a looser human format, do the lenient parsing explicitly and separately — normalize to ISO first, then validate the normalized form — rather than relying on new Date()'s unpredictable leniency. The principle is to keep the lenient step visible and bounded, and to make the final acceptance decision on a strict parse, so that exactly which inputs you accept is a property of your code rather than of the runtime's parser.
import { Temporal } from '@js-temporal/polyfill';
// Return the parsed value or a typed error, not just a boolean.
function parseDate(s: string): Temporal.PlainDate {
try { return Temporal.PlainDate.from(s, { overflow: 'reject' }); }
catch { throw new TypeError(`Not a valid ISO date: ${s}`); }
}
Verification snippet
Common pitfalls
The dominant pitfall is validating with new Date() and checking isNaN(date.getTime()), which passes far too much: it accepts many non-ISO formats, and it silently rolls over impossible dates so 2024-02-30 reads as valid. Use Temporal.PlainDate.from with overflow: 'reject' instead. The second pitfall is forgetting the reject option, leaving the default constrain, which clamps out-of-range days and turns your validator into an accepter of impossible dates. The option is the whole point.
A third mistake is validating format with a regex alone, which can confirm the shape YYYY-MM-DD but cannot know that February has no 30th — the day-range check is calendar-dependent and a static regex cannot express it. Let the parser do the semantic check. A fourth is validating the wrong type: using PlainDate to validate a string that must include a time or zone, which accepts a bare date that your contract should reject. Match the Temporal type to the exact kind of value you require, and the validator enforces the full contract.
Frequently Asked Questions
How do I properly validate a date string?
Parse it with Temporal.PlainDate.from(s, { overflow: 'reject' }) inside a try/catch. It throws on malformed strings and on impossible dates like 2024-02-31, so a successful parse means the string is genuinely a valid ISO date.
Why is new Date() not enough to validate a date?
new Date() returns Invalid Date for some inputs but silently reinterprets others — 2024-02-31 rolls into March, and slash formats are engine-specific — so a non-NaN result does not guarantee the input was valid. Strict Temporal parsing rejects both cases.
How do I validate a date string in JavaScript?
Wrap Temporal.PlainDate.from(s, { overflow: 'reject' }) in a try/catch and return true if it parses, false if it throws. This checks both that the string is well-formed ISO and that it denotes a real calendar date, because overflow: 'reject' makes impossible dates like 2024-02-30 throw rather than roll over. Match the type to what you need — PlainTime, PlainDateTime, or a zoned type — to validate times, datetimes, or zone-bearing strings.
Why is new Date() a bad way to validate dates?
Because it is far too permissive: it accepts many non-ISO formats, silently rolls impossible dates over (February 30 becomes March 1 or 2) so they read as valid, and returns an Invalid Date object instead of throwing, which a careless check misses. A validator built on it accepts inputs it should reject. Temporal.PlainDate.from with overflow: 'reject' is strict on both format and calendar validity, so a successful parse is a genuine validation.
What does overflow: 'reject' do when validating?
It makes the parser throw on a date whose fields are out of range, such as day 30 in February or month 13, instead of the default 'constrain' behavior that clamps them to the nearest valid value. For validation you want 'reject', because clamping would accept an impossible date as valid. With reject, the parse succeeds only for strings that denote a real, in-range calendar date.