Рубрики
Без рубрики

Регулярное выражение для сопоставления шаблона данных в Java

Узнайте, как построить шаблон регулярных выражений для сопоставления дат по григорианскому календарю.

Автор оригинала: baeldung.

1. введение

Регулярные выражения являются мощным инструментом для сопоставления различных типов шаблонов при правильном использовании.

В этой статье мы будем использовать java.util.regex package, чтобы определить, содержит ли данная Строка допустимую дату или нет.

Для ознакомления с регулярными выражениями обратитесь к нашему Руководству по API регулярных выражений Java .

2. Обзор формата даты

Мы собираемся определить действительную дату по отношению к международному григорианскому календарю. Наш формат будет следовать общей схеме: ГГГГ-ММ-ДД.

Давайте также включим понятие високосного года, то есть года, содержащего день 29 февраля. Согласно григорианскому календарю, мы назовем год високосным , если число года можно разделить поровну на 4 за исключением тех, которые делятся на 100 но в том числе и те, которые делятся на 400 .

Во всех остальных случаях , мы будем называть год регулярным .

Примеры действительных дат:

  • 2017-12-31
  • 2020-02-29
  • 2400-02-29

Примеры недопустимых дат:

  • 2017/12/31 : неправильный разделитель токенов
  • 2018-1-1 : отсутствуют ведущие нули
  • 2018-04-31 : неправильный подсчет дней в апреле
  • 2100-02-29 : этот год не является високосным, так как значение делится на 100 , поэтому февраль ограничен 28 днями

3. Реализация решения

Поскольку мы собираемся сопоставить дату с помощью регулярных выражений, давайте сначала набросаем интерфейс DateMatcher , который предоставляет один метод matches :

public interface DateMatcher {
    boolean matches(String date);
}

Ниже мы представим пошаговую реализацию, в конце которой будет завершено решение.

3.1. Соответствие Широкому формату

Мы начнем с создания очень простого прототипа, обрабатывающего ограничения формата наших матчей:

class FormattedDateMatcher implements DateMatcher {

    private static Pattern DATE_PATTERN = Pattern.compile(
      "^\\d{4}-\\d{2}-\\d{2}$");

    @Override
    public boolean matches(String date) {
        return DATE_PATTERN.matcher(date).matches();
    }
}

Здесь мы указываем, что допустимая дата должна состоять из трех групп целых чисел, разделенных тире. Первая группа состоит из четырех целых чисел, а остальные две группы имеют по два целых числа в каждой.

Совпадающие даты: 2017-12-31 , 2018-01-31 , 0000-00-00 , 1029-99-72

Несовпадающие даты: 2018-01 , 2018-01-XX , 2020/02/29

3.2. Соответствие Конкретному Формату даты

Наш второй пример принимает диапазоны маркеров данных, а также наше ограничение форматирования. Для простоты мы ограничили наш интерес 1900-2999 годами.

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

^((19|2[0-9])[0-9]{2})-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])$

Здесь мы ввели три группы целочисленных диапазонов, которые должны совпадать:

  • (19/2[0-9])[0-9]{2} охватывает ограниченный диапазон лет, сопоставляя число, которое начинается с 19 или 2X , за которым следует пара любых цифр.
  • 0[1-9]|1[012] соответствует номеру месяца в диапазоне 01-12
  • 0[1-9]|[12][0-9]|3[01] соответствует номеру дня в диапазоне 01-31

Совпадающие даты: 1900-01-01 , 2205-02-31 , 2999-12-31

Несовпадающие даты: 1899-12-31 , 2018-05-35 , 2018-13-05 , 3000-01-01 , 2018-01-XX

3.3. Совпадение с 29 февраля

Чтобы правильно сопоставить високосные годы , мы должны сначала определить, когда мы столкнулись с високосным годом , а затем убедиться, что мы принимаем 29 февраля в качестве действительной даты для этих лет.

Поскольку количество високосных лет в нашем ограниченном диапазоне достаточно велико, мы должны использовать соответствующие правила делимости для их фильтрации:

  • Если число, образованное последними двумя цифрами в числе, делится на 4, исходное число делится на 4
  • Если последние две цифры числа равны 00, то число делится на 100

Вот решение:

^((2000|2400|2800|(19|2[0-9](0[48]|[2468][048]|[13579][26])))-02-29)$

Шаблон состоит из следующих частей:

  • 2000/2400/2800 соответствует набору високосных лет с делителем 400 в ограниченном диапазоне 1900-2999
  • 19/2[0-9](0[48]|[2468][048]|[13579][26])) соответствует всем белым спискам комбинациям лет, которые имеют делитель 4 и у вас нет делителя 100
  • -02-29 матчи 2 февраля

Совпадающие даты: 2020-02-29 , 2024-02-29 , 2400-02-29

Несоответствие дат: 2019-02-29 , 2100-02-29 , 3200-02-29 , 2020/02/29

3.4. Совпадение общих дней февраля

Помимо сопоставления 29 февраля в високосные годы, нам также необходимо сопоставить все остальные дни февраля (с 1 по 28) во все годы :

^(((19|2[0-9])[0-9]{2})-02-(0[1-9]|1[0-9]|2[0-8]))$

Совпадающие даты: 2018-02-01 , 2019-02-13 , 2020-02-25

Несовпадающие даты: 2000-02-30 , 2400-02-62 , 2018/02/28

3.5. Совпадение 31-Дневных Месяцев

Месяцы Январь, март, Май, Июль, Август, Октябрь и декабрь должны совпадать в течение от 1 до 31 дня:

^(((19|2[0-9])[0-9]{2})-(0[13578]|10|12)-(0[1-9]|[12][0-9]|3[01]))$

Совпадающие даты: 2018-01-31 , 2021-07-31 , 2022-08-31

Несовпадающие даты: 2018-01-32 , 2019-03-64 , 2018/01/31

3.6. Соответствие 30-Дневным Месяцам

Месяцы Апрель, июнь, сентябрь и ноябрь должны совпадать в течение от 1 до 30 дней:

^(((19|2[0-9])[0-9]{2})-(0[469]|11)-(0[1-9]|[12][0-9]|30))$

Совпадающие даты: 2018-04-30 , 2019-06-30 , 2020-09-30

Несовпадающие даты: 2018-04-31 , 2019-06-31 , 2018/04/30

3.7. Совпадения Григорианских Дат

Теперь мы можем объединить все вышеперечисленные шаблоны в один сопоставитель, чтобы получить полное Совпадение григорианской даты удовлетворяющее всем ограничениям:

class GregorianDateMatcher implements DateMatcher {

    private static Pattern DATE_PATTERN = Pattern.compile(
      "^((2000|2400|2800|(19|2[0-9](0[48]|[2468][048]|[13579][26])))-02-29)$" 
      + "|^(((19|2[0-9])[0-9]{2})-02-(0[1-9]|1[0-9]|2[0-8]))$"
      + "|^(((19|2[0-9])[0-9]{2})-(0[13578]|10|12)-(0[1-9]|[12][0-9]|3[01]))$" 
      + "|^(((19|2[0-9])[0-9]{2})-(0[469]|11)-(0[1-9]|[12][0-9]|30))$");

    @Override
    public boolean matches(String date) {
        return DATE_PATTERN.matcher(date).matches();
    }
}

Мы использовали альтернативный символ “|”, чтобы соответствовать хотя бы одной из четырех ветвей. Таким образом, действительная дата февраля либо соответствует первой ветви 29 февраля високосного года, либо второй ветви любого дня из 1 чтобы 28 . Даты оставшихся месяцев соответствуют третьей и четвертой ветвям.

Поскольку мы не оптимизировали этот шаблон в пользу лучшей читаемости, не стесняйтесь экспериментировать с его длиной.

На данный момент мы выполнили все ограничения, которые мы ввели в

На данный момент мы выполнили все ограничения, которые мы ввели в

Анализ сложных регулярных выражений может существенно повлиять на производительность потока выполнения. Основной целью этой статьи не было изучение эффективного способа тестирования строки на предмет ее принадлежности к набору всех возможных дат.

Рассмотрите возможность использования LocalDate.parse () , предоставленного Java 8, если требуется надежный и быстрый подход к проверке даты.

4. Заключение

В этой статье мы узнали, как использовать регулярные выражения для сопоставления строго отформатированной даты григорианского календаря, предоставив правила формата, диапазона и продолжительности месяцев.

Весь код, представленный в этой статье, доступен на Github . Это проект на основе Maven, поэтому его должно быть легко импортировать и запускать как есть.