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 пытается интерпретировать любые цифры в строке как год, месяц или день, игнорируя контекст. Это приводит к:

  • Неправильному отображению пользовательского ввода.
  • Сбоям в бизнес‑логике (например, расчёт сроков, фильтрация по датам).
  • Трудностям в тестировании, потому что результаты зависят от локали и версии движка.

Хакерский подход, который часто используют разработчики, выглядит так:

  1. Получить строку от пользователя.
  2. Попробовать создать объект Date.
  3. Если получен 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 как потенциальное решение.

Возможные решения и рекомендации

  1. Отказаться от new Date(string) в качестве валидатора. Использовать строгие парсеры.
  2. Библиотеки date-fns или dayjs. Пример:
    
    import { parse, isValid } from 'date-fns';
    
    const result = parse(userInput, 'yyyy-MM-dd', new Date());
    if (isValid(result)) {
        // Дата корректна
    }
    
  3. Переход на Temporal. Пример:
    
    const { Temporal } = require('@js-temporal/polyfill');
    const date = Temporal.PlainDate.from('2020-01-23');
    
  4. Валидация на уровне UI. Добавлять маски ввода, подсказки и проверку формата до отправки на сервер.
  5. Статический анализ. Включить 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, тем самым предотвращая ошибочный парсинг.


Оригинал
PREVIOUS ARTICLE
NEXT ARTICLE