Javascript is required
TroubleshootingPublished 2026-07-28Updated 2026-08-086 min read

Safari Date Parsing: Invalid Dates and ISO Formats

YYYY-MM-DD is a standard date-only form. The dangerous inputs are non-standard strings such as 2026-07-28 12:00:00 and slash dates: they may work in one engine and fail or acquire different local/UTC semantics in another. Require a timezone-bearing ISO instant, or parse a declared local format into components explicitly.

By: Tiny
SafariDate ParsingInvalid DateCross-Browser

1. Problem: non-standard Date.parse input has no cross-engine contract

ECMAScript provides portable semantics only for its specified date-time string format. Inputs such as 2026-07-28 12:00:00 or 2026/07/28 may enter implementation-specific fallback parsing. Success in one Chrome version does not promise the same result in Safari, Firefox, Node.js, or future releases.

2. Minimal reproduction: Safari date parsing failures

This comparison illustrates parsing variances across Chrome and Safari for non-standard date strings:

/* Non-standard space-separated string "YYYY-MM-DD HH:mm:ss" */
const str1 = "2026-07-28 12:00:00";
// Chrome V8: Parses successfully -> Tue Jul 28 2026 12:00:00
// Safari JSC: Fails -> Invalid Date (NaN)!

/* Standard ISO 8601 string "YYYY-MM-DDTHH:mm:ssZ" ('T' separated) */
const str2 = "2026-07-28T12:00:00Z";
// Chrome & Safari: Both parse successfully -> 2026-07-28T12:00:00.000Z

3. Root cause: the standard format and implementation-defined fallback parsing

1. Portable instants: use T plus Z or an explicit offset. 2. Fallback parsing: spaces, slashes, and omitted fields may be interpreted differently across engines and versions, so a one-off compatibility test is not a durable guarantee. 3. UTC vs local: standard date-only YYYY-MM-DD is interpreted as UTC, while a standard date-time without an offset represents local time; mixing them causes date shifts across timezones.

4. Recommendation: separate instants from local wall-clock values

1. Instants: require ISO 8601 with Z or an explicit offset, such as 2026-07-28T12:00:00+08:00. 2. Local wall-clock values: when a field explicitly represents local civil time, split its declared format and construct new Date(year, monthIndex, ...). 3. Do not guess by replacement: changing a space to T does not supply a timezone, and changing hyphens to slashes is not a cross-browser standard.

5. Complete code: strictly separate ISO instants and local date-times

This parser accepts only three declared inputs: an ISO instant with a timezone, standard date-only input (UTC by specification), or strict local YYYY-MM-DD HH:mm:ss. It validates constructed components again so 2026-02-31 cannot silently roll into March.

function parseKnownDateFormat(input: string): Date {
  const value = input.trim();
  const instant = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,3})?(?:Z|[+-]\d{2}:\d{2})$/;
  const dateOnly = /^\d{4}-\d{2}-\d{2}$/;

  if (instant.test(value) || dateOnly.test(value)) {
    const date = new Date(value);
    if (Number.isNaN(date.getTime())) throw new RangeError("Invalid ISO date");
    return date;
  }

  const local = /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2})(?::(\d{2}))?$/.exec(value);
  if (!local) throw new TypeError("Unsupported date format");

  const [, y, m, d, hh, mm, ss = "0"] = local;
  const [year, month, day, hour, minute, second] = [y, m, d, hh, mm, ss].map(Number);
  const date = new Date(year, month - 1, day, hour, minute, second);

  if (date.getFullYear() !== year || date.getMonth() !== month - 1
      || date.getDate() !== day || date.getHours() !== hour
      || date.getMinutes() !== minute || date.getSeconds() !== second) {
    throw new RangeError("Local date components are out of range");
  }
  return date;
}

console.log(parseKnownDateFormat("2026-07-28T12:00:00+08:00").toISOString());
console.log(parseKnownDateFormat("2026-07-28 12:00:00").toString());

6. Incorrect approaches

Naive global replacement dateStr.replace(/-/g, '/') on composite ISO strings containing 'T' and 'Z'. Relying on non-standard date formats in production code.

7. Boundaries: date-only YYYY-MM-DD timezone offset shift trap

In UTC+8, new Date('2026-07-28') evaluates to UTC midnight, formatting locally to 08:00:00. In UTC-5, it evaluates to 2026-07-27 19:00:00 (shifting back a day!). Append local times or parse components explicitly.

8. Verification

Run the same vectors in Safari, Chrome, Firefox, and the server runtime. Cover leap years, impossible dates, DST transitions, local values, Z, and positive/negative offsets. Assert exact epoch milliseconds or explicit failures, not merely that the result is not Invalid Date.

9. FAQ

Why can one browser parse a space-separated date? That is an implementation-specific fallback and cannot be an API contract.

Does a date library make every string safe? Only when you call a declared-format parser in strict mode; handing unknown input back to native Date preserves the problem.

What should a backend emit? Emit timezone-bearing ISO strings or explicitly unit-tagged Unix timestamps for instants, and keep pure calendar dates as date-only fields.

10. Summary

The issue is not that Safari is 'too strict'; the application supplied a non-standard string to an implementation-defined parser. Use timezone-bearing ISO for instants, strict component parsing for local wall-clock values, and reverse validation for impossible dates.

Sources and further reading

Specifications and references for checking the article’s technical details.

Related articles

Keep exploring

Use the related browser tool to test the workflow with your own sample data.

Open related tool