Как составить регулярное выражение по шагам и проверить regex

2037
Как составить регулярное выражение по шагам и проверить regex

Чтобы составить регулярное выражение, я сначала описываю нужную строку обычными словами, затем делю описание на небольшие части и превращаю каждую часть в отдельный фрагмент regex. Готовый шаблон появляется постепенно. Такой порядок помогает сразу увидеть, после какого изменения выражение перестало работать.

Как составить регулярное выражение

Разберём метод на дате 27.07.2026. Сначала найдём дату с точками, затем добавим строгую проверку всей строки, допустим другие разделители и разберёмся с пробелами. В конце отделим проверку формата от проверки настоящей календарной даты. Один regex не обязан решать обе задачи.

Переводим условие с русского языка на язык шаблонов

Начинать с набора специальных символов неудобно. Гораздо проще сначала записать структуру строки словами.

Дата 27.07.2026 состоит из пяти частей.

  1. Две цифры дня.
  2. Точка.
  3. Две цифры месяца.
  4. Ещё одна точка.
  5. Четыре цифры года.

Теперь каждой части можно сопоставить небольшой фрагмент регулярного выражения.

Часть даты 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, тогда как внутри литерала регулярного выражения достаточно одного обратного слеша.

Alt text
Обращаем внимание, что все материалы в этом блоге представляют личное мнение их авторов. Редакция SecurityLab.ru не несет ответственности за точность, полноту и достоверность опубликованных данных. Вся информация предоставлена «как есть» и может не соответствовать официальной позиции компании.
AI-челлендж по пентесту CyberED × Standoff Hackbase
01
Собери агента
02
Выпусти на полигон
03
Посмотри кто победит
В челлендж
10–17 сентября Публичный рейтинг