Валидация даты с помощью регулярного выражения в Java

В статье рассматривается один из способов валидации даты с помощью регулярного выражения в среде Java. Правильность работы регулярного выражения проверяется средствами библиотеки TestNG.

Регулярное выражение для валидации даты

(0?[1-9]|[12][0-9]|3[01]).(0?[1-9]|1[012]).((19|20)\d\d)

Объяснение регулярного выражения:

(          # Начало группы 1
    0?[1-9]         # 01-09 или 1-9
    |         # Или
    [12][0-9]       # 10-19 или 20-29
    |         # Или
    3[01]           # 30 или 31
)          # Конец группы 1
.         # Дальше должна быть точка "."
(          # Начало группы 2
    0?[1-9]         # 01-09 или 1-9
    |         # Или
    1[012]          # 10,11 или 12
)          # Конец группы 2
.         # Дальше должна быть точка "."
(          # Начало группы 2
    (19|20)\d\d   # Число от 1900 до 2099
)          # Конец группы 3

Таким образом, корректными с точки зрения данного регулярного выражения форматами даты будут являться даты, записанные в формате DD.MM.YYYY, где DD — 2 цифры дня (с ведущими нулями или без них), MM — 2 цифры месяца (с ведущими нулями или без них), YYYY — четыре цифры года в интервале от 1900 до 2099.

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

Валидация даты

Если соблюдение правильного форматирования (могут быть только цифры, разделенные точками, не должно быть больше трех цифр в дне и месяце и так далее) мы можем отдать на откуп регулярному выражению, то соблюдение правил формирования даты (28, 30 или 31 дней в месяце, високосные и невисокосные года) мы должны производить вручную. При этом следует отметить, что високосный год — это не просто год, кратный 4-м. Полное правило определения високосного года выглядит следующим образом:


Год високосный, если он делится на четыре без остатка, но если он делится на 100 без остатка, это не високосный год. Однако, если он делится без остатка на 400, это високосный год. Таким образом, 2000 г. является особым високосным годом, который бывает лишь раз в 400 лет.
package ru.j4web.examples.java.regex.datevalidatorexample;

import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DateValidator {

    private static final String DATE_PATTERN
            = "(0?[1-9]|[12][0-9]|3[01])\.(0?[1-9]|1[012])"
            + "\.((19|20)\d\d)";

    private final Pattern pattern;
    private Matcher matcher;

    public DateValidator() {
        pattern = Pattern.compile(DATE_PATTERN);
    }

    public boolean validate(String date) {

        matcher = pattern.matcher(date);

        if (matcher.matches()) {

            matcher.reset();

            if (matcher.find()) {
                String day = matcher.group(1);
                String month = matcher.group(2);
                int year = Integer.parseInt(matcher.group(3));

                if ("31".equals(day)) {
                    // 31 день может быть только в месяцах
                    // 1, 3, 5, 7, 8, 10, 12
                    return Arrays.asList(new String[]{"1", "01", "3", "03",
                        "5", "05", "7", "07", "8", "08", "10", "12"})
                            .contains(month);

                } else if ("2".equals(month) || "02".equals(month)) {
                    // Проверяем февраль
                    if (year % 4 == 0) {
                        if (year % 100 == 0) {
                            if (year % 400 == 0) {
                                // Високосный год
                                return Integer.parseInt(day) <= 29;
                            }
                            // Невисокосный год
                            return Integer.parseInt(day) <= 28;
                        }
                        // Високосный год
                        return Integer.parseInt(day) <= 29;
                    } else {
                        // Невисокосный год
                        return Integer.parseInt(day) <= 28;
                    }

                } else {
                    return true;
                }
            }
        }

        return false;
    }
}

Замечание.

При возникновении реальной практической задачи валидации даты никаких регулярных выражений и подобных классов писать самостоятельно не следует. С этой задачей отлично справляется класс SimpleDateFormat (JavaDoc здесь). Рассмотренный здесь пример носит демонстрационный характер.

Корректные форматы даты

Исходя из определенного выше регулярного выражения корректными форматами даты будут являться:

  • 1.1.2010, 01.01.2010;
  • 31.1.2010, 31.01.2010;
  • 29.02.2008, 29.02.2008;
  • 28.2.2009, 28.02.2009;
  • 31.3.2010, 31.03.2010;
  • 30.4.2010, 30.4.2010;
  • 31.5.2010, 31.05.2010;
  • 30.6.2010, 30.06.2010;
  • 31.7.2010, 31.07.2010;
  • 31.8.2010, 31.08.2010;
  • 30.9.2010, 30.09.2010;
  • 31.10.2010, 30.10.2010;
  • 30.11.2010, 29.11.2010;
  • 29.2.2000, 31.12.2010.

Некорректные форматы даты

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

  • 32.1.2010, 32.01.2010 — максимальное количество дней в году — 31;
  • 01.13.2010, 31.01.1860 — в году 12 месяцев, даты мы считаем валидными только начиная с 1900 года;
  • 29.2.2007, 29.02.2007 — в 2007-м невисокосном году в феврале 28 дней;
  • 30.2.2008, 31.02.2008 — в феврале не может быть больше 29-ти дней;
  • 28.a.2010, a.01.2010 — и месяц и день могут быть только числами;
  • 333.1.2010, 31.01.201a — в дне может быть не больше 2-х цифр, год не может содержать ничего, кроме цифр;
  • 31.4.2010, 31.04.2010 — в апреле 30 дней;
  • 31.6.2010, 31.06.2010 — в июне 30 дней;
  • 31.9.2010, 31.09.2010 — в августе 30 дней;
  • 31.11.2010 — в ноябре 30 дней.

Тестирование регулярного выражения

package ru.j4web.examples.java.regex.datevalidatorexample;

import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

public class DateValidatorTest {
    
    private static DateValidator validator;
    
    @BeforeClass
    public static void setUpClass() throws Exception {
        validator = new DateValidator();
        
    }
    
    @DataProvider
    public Object[][] validDateProvider() {
        return new Object[][] {
            new Object[] {"1.1.2010"}, new Object[] {"01.01.2010"},
            new Object[] {"31.1.2010"}, new Object[] {"31.01.2010"},
            new Object[] {"29.02.2008"}, new Object[] {"29.02.2008"},
            new Object[] {"28.2.2009"}, new Object[] {"28.02.2009"},
            new Object[] {"31.3.2010"}, new Object[] {"31.03.2010"},
            new Object[] {"30.4.2010"}, new Object[] {"30.4.2010"},
            new Object[] {"31.5.2010"}, new Object[] {"31.05.2010"},
            new Object[] {"30.6.2010"}, new Object[] {"30.06.2010"},
            new Object[] {"31.7.2010"}, new Object[] {"31.07.2010"},
            new Object[] {"31.8.2010"}, new Object[] {"31.08.2010"},
            new Object[] {"30.9.2010"}, new Object[] {"30.09.2010"},
            new Object[] {"31.10.2010"}, new Object[] {"30.10.2010"},
            new Object[] {"30.11.2010"}, new Object[] {"29.11.2010"},
            new Object[] {"29.2.2000"}, new Object[] {"31.12.2010"},
        };
    }
    
    @DataProvider
    public Object[][] invalidDateProvider() {
        return new Object[][] {
            new Object[] {"32.1.2010"}, new Object[] {"32.01.2010"},
            new Object[] {"01.13.2010"}, new Object[] {"31.01.1860"},
            new Object[] {"29.2.2007"}, new Object[] {"29.02.2007"},
            new Object[] {"30.2.2008"}, new Object[] {"31.02.2008"},
            new Object[] {"28.a.2010"}, new Object[] {"a.01.2010"},
            new Object[] {"333.1.2010"}, new Object[] {"31.01.201a"},
            new Object[] {"31.4.2010"}, new Object[] {"31.04.2010"},
            new Object[] {"31.6.2010"}, new Object[] {"31.06.2010"},
            new Object[] {"31.9.2010"}, new Object[] {"31.09.2010"},
            new Object[] {"31.11.2010"}
        };
    }
    
    @Test(dataProvider="validDateProvider")
    public void validDateTest(String date) {
        boolean valid = validator.validate(date);
        System.out.println("Date "" + date + "" is valid: " + valid);
        Assert.assertTrue(valid);
    }

    @Test(dataProvider="invalidDateProvider", dependsOnMethods="validDateTest")
    public void invalidDateTest(String date) {
        boolean valid = validator.validate(date);
        System.out.println("Date "" + date + "" is valid: " + valid);
        Assert.assertFalse(valid);
    }
}
-------------------------------------------------------
&amp;nbsp;T E S T S
-------------------------------------------------------
Running ru.j4web.examples.java.regex.datevalidatorexample.DateValidatorTest
Date "1.1.2010" is valid: true
Date "01.01.2010" is valid: true
Date "31.1.2010" is valid: true
Date "31.01.2010" is valid: true
Date "29.02.2008" is valid: true
Date "29.02.2008" is valid: true
Date "28.2.2009" is valid: true
Date "28.02.2009" is valid: true
Date "31.3.2010" is valid: true
Date "31.03.2010" is valid: true
Date "30.4.2010" is valid: true
Date "30.4.2010" is valid: true
Date "31.5.2010" is valid: true
Date "31.05.2010" is valid: true
Date "30.6.2010" is valid: true
Date "30.06.2010" is valid: true
Date "31.7.2010" is valid: true
Date "31.07.2010" is valid: true
Date "31.8.2010" is valid: true
Date "31.08.2010" is valid: true
Date "30.9.2010" is valid: true
Date "30.09.2010" is valid: true
Date "31.10.2010" is valid: true
Date "31.10.2010" is valid: true
Date "30.11.2010" is valid: true
Date "30.11.2010" is valid: true
Date "29.2.2000" is valid: true
Date "31.12.2010" is valid: true
Date "32.1.2010" is valid: false
Date "32.01.2010" is valid: false
Date "01.13.2010" is valid: false
Date "31.01.1860" is valid: false
Date "29.2.2007" is valid: false
Date "29.02.2007" is valid: false
Date "30.2.2008" is valid: false
Date "31.02.2008" is valid: false
Date "28.a.2010" is valid: false
Date "a.01.2010" is valid: false
Date "333.1.2010" is valid: false
Date "31.01.201a" is valid: false
Date "31.4.2010" is valid: false
Date "31.04.2010" is valid: false
Date "31.6.2010" is valid: false
Date "31.06.2010" is valid: false
Date "31.9.2010" is valid: false
Date "31.09.2010" is valid: false
Date "31.11.2010" is valid: false
Tests run: 47, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.549 sec

Results :

Tests run: 47, Failures: 0, Errors: 0, Skipped: 0

------------------------------------------------------------------------
BUILD SUCCESS
------------------------------------------------------------------------

Оставить комментарий

Ваш email нигде не будет показанОбязательные для заполнения поля помечены *

*