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.
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.000Z3. 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.
- RFC 3339 — Date and Time on the Internet
RFC Editor
- ECMAScript Language Specification
Ecma International
Related articles
10/13-Digit Timestamps: Year 1970 Bugs
Explain second (10-digit) vs. millisecond (13-digit) Unix timestamp unit differences in JavaScript Date parsing, providing normalization helpers.
Best practicesUnix Timestamps: UTC, CST and Daylight Saving Time
Decouple physical timestamps from localized timezone rendering, addressing CST ambiguities and Daylight Saving Time shifts with IANA timezone names.
TroubleshootingFix btoa Errors: UTF-8, Chinese Text and Emoji
Analyze window.btoa Latin-1 (ASCII 0-255) limits, providing TextEncoder-based UTF-8 Unicode Base64 encoding while clarifying Base64 is not encryption.
Keep exploring
Use the related browser tool to test the workflow with your own sample data.
Open related tool