10 шокирующих фактов о том, как JavaScript превращает любые строки в даты и как этого избежать
19 марта 2026 г.Вступление
В мире фронтенд‑разработки часто встречается привычка использовать встроенный конструктор Date для быстрой проверки и преобразования строк. На первый взгляд это удобно: достаточно передать строку, и получаем объект даты. Однако скрытая «щедрость» JavaScript в интерпретации дат может привести к неожиданным багам, особенно когда строка вовсе не содержит даты. В статье мы разберём реальный случай из Reddit, покажем, почему такой парсинг опасен, и предложим надёжные альтернативы.
Актуальность темы растёт вместе с появлением новых API, таких как Temporal, и с ростом количества приложений, работающих с пользовательским вводом (формы, адресные строки, названия компаний). Ошибки в парсинге дат могут превратить адрес в «01 января 90210 года», а это уже не просто курьёз, а реальная причина падения бизнес‑логики.
Путешествие в тысячу миль начинается с одного шага.
— Лао‑цзы
Японский хокку, отражающий суть проблемы
Тени цифр
В строках без смысла
Дата рождается
Пересказ Reddit‑поста своими словами
Автор поста в Reddit решил «поиграть» с конструктором new Date(), передавая ему разные строки:
new Date("2020-01-23")– получаемWed Jan 22 2020 19:00:00 GMT-0500. Это объясняется тем, что строка в формате ISO 8601 считается датой в полночь по UTC, а в часовом поясе GMT‑5 время сдвигается на пять часов назад.new Date("Today is 2020-01-23")– конструктор «выдёргивает» дату из предложения и возвращаетThu Jan 23 2020 00:00:00 GMT-0500. Время уже не сдвинулось, что выглядит странно.new Date("Route 66")– неожиданно интерпретирует какSat Jan 01 1966 00:00:00 GMT-0500, будто «66» – это год.new Date("Beverly Hills, 90210")– выдаётMon Jan 01 90210 00:00:00 GMT-0500, то есть «90210» воспринимается как год, а не как часть почтового индекса.
Эти примеры продемонстрировали, что большинство движков JavaScript (V8, SpiderMonkey, JavaScriptCore) используют устаревший «legacy parser», который пытается «помочь» пользователю, находя в любой строке потенциальную дату. В результате в приложении автора адреса и названия компаний начали отображаться как даты, потому что разработчики использовали Date как «универсальный» парсер для всех непредвиденных форматов.
Баг был быстро исправлен: вместо Date стали применять специализированные библиотеки. Но случай стал хорошим уроком – Date нельзя использовать как валидатор.
Суть проблемы, хакерский подход и основные тенденции
Ключевая проблема – непредсказуемый парсинг. Конструктор Date пытается интерпретировать любые цифры в строке как год, месяц или день, игнорируя контекст. Это приводит к:
- Неправильному отображению пользовательского ввода.
- Сбоям в бизнес‑логике (например, расчёт сроков, фильтрация по датам).
- Трудностям в тестировании, потому что результаты зависят от локали и версии движка.
Хакерский подход, который часто используют разработчики, выглядит так:
- Получить строку от пользователя.
- Попробовать создать объект
Date. - Если получен
Invalid Date, перейти к альтернативному парсеру.
Но такой «fallback» опасен: даже если строка не является датой, Date может вернуть валидный объект, как в примерах выше.
Тенденции в индустрии:
- Переход к Temporal API – современному, более предсказуемому способу работы с датами и временем.
- Широкое использование библиотек date-fns, dayjs, luxon, которые предоставляют строгие парсеры.
- Увеличение количества статических анализаторов (ESLint‑плагины), предупреждающих о прямом использовании
new Date(string)в продакшн‑коде.
Детальный разбор проблемы с разных сторон
Техническая сторона
В спецификации ECMAScript 2023 указано, что Date.parse должен поддерживать ISO 8601 и RFC 2822. Всё, что выходит за рамки этих форматов, попадает в «implementation‑dependent» часть, где каждый движок реализует собственный «legacy parser». Именно он отвечает за:
- Поиск чисел в строке.
- Определение порядка «год‑месяц‑день».
- Игнорирование нецифровых символов.
Эти правила не документированы, поэтому поведение может различаться между Chrome, Firefox и Safari.
Пользовательская сторона
Для конечного пользователя такие баги выглядят как «моя форма почему‑то превратила адрес в дату». Это подрывает доверие к приложению и приводит к дополнительным обращениям в поддержку.
Бизнес‑сторона
Если в системе хранятся даты оплаты, сроки доставки или юридические даты, неверный парсинг может вызвать финансовые потери, юридические риски и репутационный урон.
Практические примеры и кейсы
Рассмотрим два типичных сценария.
Кейс 1. Форма ввода адреса
Пользователь вводит «Beverly Hills, 90210». Вместо сохранения строки в базе, код делает:
const maybeDate = new Date(userInput);
if (!isNaN(maybeDate)) {
// Ошибочно считаем, что это дата
saveDate(maybeDate);
}
В результате в базе появляется запись «01‑01‑90210», а не «Beverly Hills, 90210».
Кейс 2. Парсинг CSV‑файла с датами и номерами
CSV содержит столбцы «Дата», «Номер телефона». При чтении строки «+1 (555) 123‑4567» конструктор Date может интерпретировать «123‑4567» как «123‑4567‑01‑01», что ломает дальнейшую обработку.
Экспертные мнения из комментариев
JavaScript 🤝 Excel 🤝 Incels
— ElectronRotoscope
Посмотрите на новый Temporal API. Он наконец‑то заменит Date. Не уверен в сроках внедрения, но он уже почти готов.
— gimmeslack12
Это спам‑пост про старую, давно обсуждаемую проблему. Пожалуйста, перестаньте его лайкать.
— CobaltVale
Me, eating a fig.
— erocuda
Result is non‑sensical, but what the fuck were you even trying to do here? Using the Date constructor as a fallback parser… And it's the Date constructor that's at fault? Holy shit haha.
— yyyyuuuuyyyyyyyyyy
Из комментариев видно, что сообщество уже давно обсуждает проблему, а также отмечает появление Temporal API как потенциальное решение.
Возможные решения и рекомендации
- Отказаться от
new Date(string)в качестве валидатора. Использовать строгие парсеры. - Библиотеки
date-fnsилиdayjs. Пример:import { parse, isValid } from 'date-fns'; const result = parse(userInput, 'yyyy-MM-dd', new Date()); if (isValid(result)) { // Дата корректна } - Переход на
Temporal. Пример:const { Temporal } = require('@js-temporal/polyfill'); const date = Temporal.PlainDate.from('2020-01-23'); - Валидация на уровне UI. Добавлять маски ввода, подсказки и проверку формата до отправки на сервер.
- Статический анализ. Включить ESLint‑правило
no-new-date(плагинeslint-plugin-no-new-date).
Заключение с прогнозом развития
Проблема «Date пытается распарсить всё» уже несколько лет считается «legacy», но из‑за обратной совместимости она будет жить в браузерах ещё некоторое время. Ожидается, что к 2025‑му году большинство крупных проектов перейдут на Temporal API, а старый конструктор будет использоваться только в строго ограниченных случаях (например, работа с уже готовыми объектами Date).
Тем временем разработчикам рекомендуется:
- Внедрять строгие парсеры уже сейчас.
- Обновлять зависимости до последних версий
date-fns,dayjs,luxon. - Следить за новостями о
Temporalи планировать миграцию.
Только так можно избежать «шокирующих» сюрпризов, когда обычный ввод превращается в дату 90210 года.
Практический пример на Python (моделирующий ситуацию)
Ниже представлен скрипт, который демонстрирует безопасный парсинг дат из произвольных строк, аналогично тому, как мы бы обходили проблему в JavaScript. Мы используем библиотеку dateutil для гибкого, но контролируемого парсинга, и явно проверяем, что найденный объект действительно является датой, а не случайным числом.
# -*- coding: utf-8 -*-
"""
Пример безопасного парсинга дат из произвольных строк.
Используется библиотека dateutil, которая умеет распознавать
только явно указанные даты, а не «скрытые» числа.
"""
from datetime import datetime
from dateutil import parser
import re
def is_likely_date(s: str) -> bool:
"""
Проверяет, выглядит ли строка как потенциальная дата.
Возвращает True, если в строке присутствует хотя бы
один из шаблонов: YYYY-MM-DD, DD/MM/YYYY, Month DD, YYYY и т.п.
"""
# Простейший регекс для поиска дат в формате ISO или европейском
patterns = [
r'\d{4}-\d{2}-\d{2}', # 2020-01-23
r'\d{2}/\d{2}/\d{4}', # 23/01/2020
r'(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\s+\d{1,2},?\s+\d{4}', # Jan 23, 2020
]
for pat in patterns:
if re.search(pat, s, re.IGNORECASE):
return True
return False
def safe_parse_date(s: str) -> datetime | None:
"""
Пытается безопасно распарсить дату.
Если строка не выглядит как дата – возвращаем None.
"""
if not is_likely_date(s):
# Строка явно не содержит дату – сразу отказываемся
return None
try:
# parser.parse умеет разбирать многие форматы,
# но бросит исключение, если не найдёт дату.
dt = parser.parse(s, fuzzy=False)
return dt
except (ValueError, OverflowError):
return None
# Примеры входных данных
samples = [
"2020-01-23",
"Today is 2020-01-23",
"Route 66",
"Beverly Hills, 90210",
"Meeting on 15/02/2021 at 10:00",
"Just a random string"
]
for txt in samples:
result = safe_parse_date(txt)
if result:
print(f"'{txt}' → {result.isoformat()}")
else:
print(f"'{txt}' → не удалось распознать дату")
Скрипт сначала проверяет наличие в строке типичного шаблона даты, а затем пытается её распарсить. Строки «Route 66» и «Beverly Hills, 90210» не проходят проверку и возвращают None, тем самым предотвращая ошибочный парсинг.
Оригинал