Day.js ISO 8601 Strict Parsing Fails: Why & How to Fix
Discover why Day.js strict ISO 8601 parsing fails for valid strings like 2024-03-10T15:30:00Z. Learn reliable validation using built-in parsers, customParseFormat, regex, UTC plugin, and code examples for robust date handling.
Day.js strict ISO-8601 parsing fails for valid ISO strings — why and how to validate?
I’m trying to validate ISO-8601 date strings using Day.js (v1.11.19) with strict parsing in Node.js, but some strings that appear to be valid ISO-8601 are being marked invalid.
Example:
import dayjs from "dayjs";
import customParseFormat from "dayjs/plugin/customParseFormat";
dayjs.extend(customParseFormat);
const dateStr = "2024-03-10T15:30:00Z";
if (!dayjs(dateStr, "YYYY-MM-DDTHH:mm:ssZ", true).isValid()) {
console.log("Invalid date!");
} else {
console.log("Valid date!");
}
I also saw references to dayjs.ISO_8601, but Day.js doesn’t seem to expose that constant.
Questions:
- Why does strict ISO-8601 parsing fail in Day.js for some valid ISO strings (for example the timestamp above)?
- What is the correct way to strictly validate all ISO-8601 strings in Day.js (including UTC “Z” and other valid ISO variants)?
Day.js ISO 8601 strict parsing often fails because strict mode (via the customParseFormat plugin) does an exact, token-by-token match and treats timezone tokens differently — a literal “Z” and numeric offsets aren’t interchangeable. To validate reliably, either use Day.js’s built-in ISO parser (e.g., dayjs(dateStr).isValid() or dayjs.utc(...)), or pre-normalize / pre-validate the string (replace trailing Z with +00:00, use a focused ISO-8601 regex, or try multiple exact formats with customParseFormat) before applying strict checks.
Contents
- Why Day.js ISO 8601 strict parsing fails
- How to strictly validate ISO 8601 with Day.js
- Practical code patterns and examples
- Edge cases and tips
- Sources
- Conclusion
Day.js ISO 8601 strict parsing — why it fails
Short answer: strict parsing compares your string to the exact format you provide, and ISO-8601 has many valid variants. Day.js has two parsing modes that behave differently:
- Built-in ISO parsing (no format argument) accepts common ISO forms like
2018-04-04T16:00:00.000Zand many offsets (see the official parse docs) — usedayjs(dateStr)for that behavior: https://day.js.org/docs/en/parse/string. - customParseFormat + strict mode (
dayjs(input, format, true)) requires the input to match the format exactly. That makes strict mode brittle if your format doesn’t match the exact ISO variant (literalZvs+00:00, presence/absence of fractional seconds, two-digit vs colon-separated offsets, etc.) — several community reports show exactly these failures (examples: https://github.com/iamkun/dayjs/issues/2712, https://github.com/iamkun/dayjs/issues/1205, https://github.com/iamkun/dayjs/issues/1173).
Concrete example you posted:
dayjs("2024-03-10T15:30:00Z", "YYYY-MM-DDTHH:mm:ssZ", true)fails because the format tokenZin strict custom parsing does not match the literal uppercaseZin the input in this Day.js behavior. The built-in parser (no format) would accept"2024-03-10T15:30:00Z"fine; the strict custom-format check will only succeed if the format exactly matches the variant (see the custom-parse-format docs) — https://day.js.org/docs/en/plugin/custom-parse-format.
Also: Day.js does not provide a Moment-like ISO_8601 constant (e.g., moment.ISO_8601) that you can pass into dayjs(..., <ISO_CONST>, true). That constant is a Moment API concept; Day.js expects either no format (built-in ISO) or explicit format strings / plugins.
How to strictly validate ISO 8601 with Day.js
Pick a strategy depending on what “strict” means for you (accept any ISO-8601 variant vs. accept only a single exact ISO layout).
- Quick, permissive ISO check (accept most ISO variants)
- Let Day.js parse the ISO string with its built-in parser and check
isValid():
import dayjs from "dayjs";
console.log(dayjs("2024-03-10T15:30:00Z").isValid()); // true
This is simple and handles Z and common offsets. See the official ISO parsing docs: https://day.js.org/docs/en/parse/string.
- Strict match to a specific ISO layout (you want exact tokens)
- Use
customParseFormatand a format that exactly matches the variant you expect. For a literalZ(UTC-designator), match the literalZwith brackets:
import dayjs from "dayjs";
import customParseFormat from "dayjs/plugin/customParseFormat";
dayjs.extend(customParseFormat);
dayjs("2024-03-10T15:30:00Z", "YYYY-MM-DD[T]HH:mm:ss[Z]", true).isValid(); // true
If you expect numeric offsets like +00:00, use the Z token (or ZZ depending on style):
dayjs("2024-03-10T15:30:00+00:00", "YYYY-MM-DD[T]HH:mm:ssZ", true).isValid(); // true
- Accept multiple ISO variants (practical strictness)
- Try several exact formats (loop through formats) or normalize the string before parsing:
const formats = [
"YYYY-MM-DD[T]HH:mm:ss[Z]",
"YYYY-MM-DD[T]HH:mm:ssZ",
"YYYY-MM-DD[T]HH:mm:ss.SSS[Z]",
"YYYY-MM-DD[T]HH:mm:ss.SSSZ",
];
const isStrict = formats.some(f => dayjs(dateStr, f, true).isValid());
This gives strict token matching while supporting multiple allowed variants.
- Full-spec / robust validation (recommended if you need to accept many ISO forms)
- Pre-validate the textual shape with a robust ISO-8601 regex, then ask Day.js to validate semantics (e.g., month/day ranges). A compact, practical regex that covers common timestamp forms is:
const isoRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:.\d+)?(?:Z|[+-]\d{2}(?::?\d{2})?)$/i;
function isStrictIso(dateStr) {
if (!isoRegex.test(dateStr)) return false; // textual shape OK?
return dayjs.utc(dateStr).isValid(); // semantic check (leap days, month bounds)
}
That hybrid approach (regex for format + Day.js for date validity) is reliable and flexible. The StackOverflow discussion shows similar regex-based patterns and deeper ISO regex samples: https://stackoverflow.com/questions/78474658/dayjs-parse-iso-string-without-converting-to-local-time.
- Use
utcplugin when you want consistent UTC handling
- If you need to treat
Zas UTC and avoid local-time conversion side effects, extendutc:
import utc from "dayjs/plugin/utc";
dayjs.extend(utc);
dayjs.utc("2024-03-10T15:30:00Z").isValid(); // true
Practical code patterns and examples
Example: normalize Z to +00:00 and use a single offset format:
import dayjs from "dayjs";
import customParseFormat from "dayjs/plugin/customParseFormat";
dayjs.extend(customParseFormat);
function normalizeZ(s) {
return s.replace(/Z$/, "+00:00");
}
const dateStr = "2024-03-10T15:30:00Z";
const normalized = normalizeZ(dateStr);
dayjs(normalized, "YYYY-MM-DD[T]HH:mm:ssZ", true).isValid(); // true
Example: full helper using regex + Day.js (recommended):
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
dayjs.extend(utc);
const isoRegex = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:.\d+)?(?:Z|[+-]\d{2}(?::?\d{2})?)$/i;
export function isIso8601Strict(s) {
if (!isoRegex.test(s)) return false;
// dayjs.utc avoids local conversion inconsistencies while validating
return dayjs.utc(s).isValid();
}
If you need to accept alternative ISO flavours (date-only, no seconds, week dates, etc.), add corresponding regex patterns or format strings to your checks.
Edge cases and tips
- Offsets with only hours (e.g.,
+00) are valid ISO variants and some systems (Postgres) emit them; Day.js custom strict formats often don’t accept that unless you normalize to+00:00. See the community report: https://github.com/iamkun/dayjs/issues/1205. Zvs+00:00: use bracketed literal[Z]in a format when you expect the literal letterZ, or normalizeZ→+00:00for offset formats. Community examples of this exact failure are at https://github.com/iamkun/dayjs/issues/2712.- Fractional seconds are optional — include
SSSvariants in your accepted formats if you need them. ThecustomParseFormatplugin docs show how to add fractional tokens: https://day.js.org/docs/en/plugin/custom-parse-format. - If you need full ISO-8601 coverage (week dates, ordinal dates, reduced precision, etc.), a single strict format won’t cover every legal ISO string. Use a combination: a validation regex (or a dedicated ISO validator) + Day.js for semantic checks.
- Remember:
dayjs(dateStr)uses the built-in ISO parser and is the easiest way to accept many common ISO formats (and to simply test validity). If you need token-level strictness, usecustomParseFormat+true, but be prepared to handle many format variants.
Sources
- Parse string (Day.js docs)
- CustomParseFormat plugin (Day.js docs)
- Format (Day.js docs)
- Issue: Parse date in ISO 8601 format with strict mode · iamkun/dayjs #1173
- Issue: Day.js doesn’t parse ISO 8601 timezone offset +NN · iamkun/dayjs #1205
- Issue: ISO 8601 “YYYY-MM-DDTHH:mm:ssZ” BUG · iamkun/dayjs #2712
- StackOverflow: dayjs() telling me invalid date for this date string
- StackOverflow: dayjs parse ISO string without converting to local time (regex example)
Conclusion
Day.js ISO 8601 strict parsing fails when your strict format doesn’t exactly match the input (timezone token differences like literal Z vs numeric offsets are the usual culprit). For robust validation, either rely on Day.js’s built-in ISO parser (dayjs(dateStr).isValid() / dayjs.utc(...)) or combine a focused ISO-8601 regex with Day.js for semantic checks; if you require strict token matching, normalize variants (e.g., convert Z → +00:00) or test multiple exact formats with customParseFormat + true.