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-120[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-299919/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, поэтому его должно быть легко импортировать и запускать как есть.