Programming

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.

1 answer 1 view

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:

js
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:

  1. Why does strict ISO-8601 parsing fail in Day.js for some valid ISO strings (for example the timestamp above)?
  2. 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


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:

Concrete example you posted:

  • dayjs("2024-03-10T15:30:00Z", "YYYY-MM-DDTHH:mm:ssZ", true) fails because the format token Z in strict custom parsing does not match the literal uppercase Z in 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).

  1. Quick, permissive ISO check (accept most ISO variants)
  • Let Day.js parse the ISO string with its built-in parser and check isValid():
js
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.

  1. Strict match to a specific ISO layout (you want exact tokens)
  • Use customParseFormat and a format that exactly matches the variant you expect. For a literal Z (UTC-designator), match the literal Z with brackets:
js
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):

js
dayjs("2024-03-10T15:30:00+00:00", "YYYY-MM-DD[T]HH:mm:ssZ", true).isValid(); // true
  1. Accept multiple ISO variants (practical strictness)
  • Try several exact formats (loop through formats) or normalize the string before parsing:
js
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.

  1. 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:
js
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.

  1. Use utc plugin when you want consistent UTC handling
  • If you need to treat Z as UTC and avoid local-time conversion side effects, extend utc:
js
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:

js
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):

js
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.
  • Z vs +00:00: use bracketed literal [Z] in a format when you expect the literal letter Z, or normalize Z+00:00 for offset formats. Community examples of this exact failure are at https://github.com/iamkun/dayjs/issues/2712.
  • Fractional seconds are optional — include SSS variants in your accepted formats if you need them. The customParseFormat plugin 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, use customParseFormat + true, but be prepared to handle many format variants.

Sources


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.

Authors
Verified by moderation
Moderation
Day.js ISO 8601 Strict Parsing Fails: Why & How to Fix