Чтобы составить регулярное выражение, я сначала описываю нужную строку обычными словами, затем делю описание на небольшие части и превращаю каждую часть в отдельный фрагмент regex. Готовый шаблон появляется постепенно. Такой порядок помогает сразу увидеть, после какого изменения выражение перестало работать.
Разберём метод на дате 27.07.2026. Сначала найдём дату с точками, затем добавим строгую проверку всей строки, допустим другие разделители и разберёмся с пробелами. В конце отделим проверку формата от проверки настоящей календарной даты. Один regex не обязан решать обе задачи.
Переводим условие с русского языка на язык шаблонов
Начинать с набора специальных символов неудобно. Гораздо проще сначала записать структуру строки словами.
Дата 27.07.2026 состоит из пяти частей.
- Две цифры дня.
- Точка.
- Две цифры месяца.
- Ещё одна точка.
- Четыре цифры года.
Теперь каждой части можно сопоставить небольшой фрагмент регулярного выражения.
| Часть даты | Regex | Что означает |
|---|---|---|
| День |
[0-9]{2}
|
Ровно две цифры от 0 до 9 |
| Точка |
.
|
Обычная точка |
| Месяц |
[0-9]{2}
|
Ещё две цифры |
| Год |
[0-9]{4}
|
Ровно четыре цифры |
Я использую [0-9], а не сокращение d. Для JavaScript оба варианта подходят при поиске обычных цифр, но в некоторых движках d также совпадает с арабскими, персидскими и другими десятичными цифрами Unicode. Поведение зависит от языка, настроек и режима работы движка. Для машинного формата даты [0-9] точнее передаёт требование.
Различия между движками хорошо видны при сравнении справочника MDN и документации Python. Поэтому перед созданием сложного шаблона нужно выбрать конкретную среду. Regex для JavaScript, Python, Java, .NET, PHP и редактора текста может отличаться в деталях.
Точку приходится экранировать. Вне квадратных скобок точка означает почти любой символ. Выражение [0-9]{2}.[0-9]{2} найдёт не только 27.07, но также 27-07, 27/07 и 27X07.
Обратный слеш отменяет специальное значение точки.
.
После соединения всех частей получается первый рабочий вариант.
[0-9]{2}.[0-9]{2}.[0-9]{4}
Шаблон найдёт 27.07.2026 внутри строки Документ создан 27.07.2026 и отправлен в архив. Для поиска даты в большом тексте такого выражения часто достаточно.
Собираем regex по одному фрагменту
Я не пишу всё выражение сразу, даже когда итоговый вариант кажется очевидным. Каждый шаг должен добавлять одно правило, которое легко проверить отдельно.
Сначала ищем две цифры.
[0-9]{2}
На строке 27.07.2026 движок найдёт несколько совпадений. Среди них будут 27, 07, 20 и 26. Ошибки здесь нет. Пока шаблон знает только про две цифры.
Добавляем точку.
[0-9]{2}.
Теперь должны находиться фрагменты 27. и 07.. Если совпадений нет, проблему нужно искать в точке или экранировании, а не во всём будущем выражении.
Добавляем месяц.
[0-9]{2}.[0-9]{2}
Затем вторую точку.
[0-9]{2}.[0-9]{2}.
Последним добавляем год.
[0-9]{2}.[0-9]{2}.[0-9]{4}
Пошаговая сборка особенно помогает в длинных выражениях с группами, альтернативами и необязательными частями. Когда совпадение пропало после одного изменения, круг поиска уже ограничен последним добавленным фрагментом.
Одновременно с шаблоном я составляю набор примеров. Одной правильной строки недостаточно. Проверка должна включать значения, которые обязаны пройти, и значения, которые regex должен отклонить.
| Строка | Ожидаемый результат | Что проверяем |
|---|---|---|
27.07.2026
|
Совпадение | Основной формат |
7.07.2026
|
Нет совпадения | День должен содержать две цифры |
27.7.2026
|
Нет совпадения | Месяц должен содержать две цифры |
27-07-2026
|
Нет совпадения | Базовый шаблон принимает только точки |
27.07.26
|
Нет совпадения | Год должен содержать четыре цифры |
31.02.2026
|
Совпадение по формату | Календарь пока не проверяется |
Тестовые строки полезно хранить рядом с кодом. После изменения требований можно быстро увидеть, не сломал ли новый фрагмент старое поведение.
Добавляем границы, разделители и пробелы только по требованию
Поиск даты в тексте и проверка значения поля решают разные задачи. Выражение без границ находит подходящий фрагмент внутри большой строки.
[0-9]{2}.[0-9]{2}.[0-9]{4}
Для обычного однострочного поля в JavaScript можно потребовать совпадение со всей строкой.
^[0-9]{2}.[0-9]{2}.[0-9]{4}$
Символ ^ проверяет начало строки, а $ проверяет конец. Строка Дата 27.07.2026 больше не пройдёт. Пройдёт только 27.07.2026.
Поведение границ меняется в многострочном режиме. Флаг m разрешает ^ и $ работать возле переводов строки, а не только возле границ всего текста. Поэтому флаги регулярного выражения нужно считать частью шаблона и проверять вместе с ним.
Для поиска всех дат в тексте JavaScript использует флаг g.
const text = "Начало 27.07.2026, окончание 31.07.2026";
const dates = text.match(/[0-9]{2}.[0-9]{2}.[0-9]{4}/g);
console.log(dates);
К валидатору флаг g лучше не добавлять. Глобальный объект RegExp хранит позицию последнего совпадения в свойстве lastIndex. Повторные вызовы test() с одним объектом могут дать неожиданный результат.
const datePattern = /^[0-9]{2}.[0-9]{2}.[0-9]{4}$/g;
console.log(datePattern.test("27.07.2026")); // true
console.log(datePattern.test("27.07.2026")); // false
Для проверки одной строки достаточно выражения без g.
const datePattern = /^[0-9]{2}.[0-9]{2}.[0-9]{4}$/;
Если дата может использовать точки, дефисы и косые черты, перечисляем допустимые разделители в квадратных скобках.
[0-9]{2}([./-])[0-9]{2}1[0-9]{4}
Группа ([./-]) запоминает первый разделитель. Обратная ссылка 1 требует повторить тот же символ. Поэтому шаблон примет 27.07.2026, 27-07-2026 и 27/07/2026, но отклонит смешанный вариант 27.07-2026.
При вставке шаблона в JavaScript-литерал косую черту нужно экранировать, потому что такой символ закрывает литерал регулярного выражения.
const datePattern = /^[0-9]{2}([./-])[0-9]{2}1[0-9]{4}$/;
Если шаблон передаётся строкой в конструктор RegExp, обратные слеши приходится экранировать ещё раз.
const datePattern = new RegExp(
"^[0-9]{2}([./-])[0-9]{2}1[0-9]{4}$"
);
Поэтому regex иногда работает в онлайн-тестере, но ломается после переноса в программу. Ошибка находится не в самом выражении, а в строковом литерале языка.
Пробелы возле разделителей тоже нужно разрешать осознанно. Если допустимы только обычные пробелы и табуляция, можно использовать класс [ t]*.
^[0-9]{2}[ t]*([./-])[ t]*[0-9]{2}[ t]*1[ t]*[0-9]{4}$
Популярная конструкция s* шире. В зависимости от движка и режима s может включать переводы строк и дополнительные пробельные символы Unicode. Для однострочного поля такая гибкость часто не нужна.
Не нужно добавлять все возможные разделители, пробелы и необязательные нули заранее. Каждое послабление расширяет множество допустимых строк. Сначала следует определить реальные форматы входных данных, а потом разрешить только нужные варианты.
Regex проверяет форму, а код проверяет календарь
Выражение [0-9]{2}.[0-9]{2}.[0-9]{4} не знает, сколько месяцев в году и сколько дней в феврале. Оно примет 99.99.9999, потому что строка формально состоит из двух цифр, точки, двух цифр, точки и четырёх цифр.
День и месяц можно ограничить диапазонами.
^(?:0[1-9]|[12][0-9]|3[01]).(?:0[1-9]|1[0-2]).[0-9]{4}$
Группа дня принимает числа от 01 до 31. Группа месяца принимает числа от 01 до 12. Шаблон отклонит 99.99.9999, но всё ещё пропустит 31.02.2026, 31.04.2026 и 29.02.2025.
Календарные правила можно записать одним огромным регулярным выражением. Придётся разделить месяцы по количеству дней и отдельно обработать високосные годы. Такой regex трудно читать, тестировать и менять. Ошибка в одной группе может незаметно испортить проверку другой ветки.
Я оставляю регулярному выражению проверку внешнего формата, а существование даты проверяю кодом.
function parseRussianDate(input) {
const match =
/^(0[1-9]|[12][0-9]|3[01]).(0[1-9]|1[0-2]).([0-9]{4})$/
.exec(input);
if (!match) {
return null;
}
const day = Number(match[1]);
const month = Number(match[2]);
const year = Number(match[3]);
const probe = new Date(0);
probe.setUTCHours(0, 0, 0, 0);
probe.setUTCFullYear(year, month - 1, day);
const valid =
probe.getUTCFullYear() === year &&
probe.getUTCMonth() === month - 1 &&
probe.getUTCDate() === day;
if (!valid) {
return null;
}
return { day, month, year };
}
console.log(parseRussianDate("27.07.2026"));
console.log(parseRussianDate("31.02.2026"));
JavaScript нормализует значения компонентов даты. При попытке создать 31 февраля объект перейдёт на март. Обратное сравнение года, месяца и дня обнаружит такое изменение и отклонит строку.
Метод setUTCFullYear() выбран намеренно. Конструктор new Date(year, month, day) особым образом обрабатывает годы от 0 до 99 и прибавляет к ним 1900. UTC-методы также не зависят от локального часового пояса и переходов между летним и зимним временем.
Регулярное выражение не проверяет бизнес-правила. Даже существующая дата может быть недопустимой для конкретной системы. Сервис бронирования может запрещать прошлые даты, банковская форма может принимать только определённый период, а архив может ограничивать год диапазоном от 1900 до текущего. Такие условия лучше записывать обычным кодом после разбора даты.
Рабочий метод создания регулярных выражений сводится к последовательности. Сначала я формулирую формат обычными словами, затем делю строку на части, собираю минимальный шаблон и проверяю каждый новый фрагмент на правильных и неправильных примерах. Границы, группы, разные разделители и пробелы добавляю только после появления конкретного требования.
Для даты 27.07.2026 поиск начинается с выражения [0-9]{2}.[0-9]{2}.[0-9]{4}. Проверка всего поля добавляет границы. Диапазоны отсекают очевидно невозможные числа, а обычный код проверяет календарь. Такой подход даёт более короткий, понятный и надёжный результат, чем попытка сразу написать идеальный regex на все случаи жизни.
Как составить регулярное выражение с нуля?
Опишите нужную строку обычными словами, разделите описание на небольшие части и подберите шаблон для каждой части. Соединяйте фрагменты постепенно и проверяйте результат после каждого изменения.
Как проверить регулярное выражение?
Подготовьте несколько правильных и неправильных строк. Проверяйте не только ожидаемое совпадение, но также слишком короткие значения, неверные разделители, лишний текст и пограничные случаи.
Как написать regex для даты 27.07.2026?
Для поиска даты с точками подходит шаблон
[0-9]{2}.[0-9]{2}.[0-9]{4}. Для проверки всей однострочной строки добавьте^в начале и$в конце.
Почему regex принимает дату 31.02.2026?
Простой шаблон проверяет количество цифр и положение точек, но не календарные правила. Существование даты нужно проверять средствами языка программирования или библиотекой для работы с датами.
Чем поиск даты отличается от проверки даты?
Поиск находит совпадающий фрагмент внутри большого текста. Проверка требует, чтобы вся строка соответствовала формату, поэтому обычно использует границы начала и конца строки.
Почему regex работает в тестере, но не в коде?
Язык программирования может дополнительно обрабатывать обратные слеши и другие символы. В строке JavaScript конструкцию
dприходится записывать какd, тогда как внутри литерала регулярного выражения достаточно одного обратного слеша.
