Java中的正则表达式日期格式验证

78

我在想是否有一种方法(可能是用正则表达式),可以验证Java桌面应用程序中输入的内容是否为格式为“YYYY-MM-DD”的字符串。


6
您是否希望允许任何年份,以及月份和日期的无效数字?例如9999-99-99?对于无效日期如2009-02-29呢? - Tim Pietzcker
相关问题,仅限正则表达式:https://dev59.com/2moy5IYBdhLWcg3wScLb - McKay
12个回答

114

请使用以下正则表达式:

^\d{4}-\d{2}-\d{2}$

就像

if (str.matches("\\d{4}-\\d{2}-\\d{2}")) {
    ...
}

使用matches方法时,锚定符号^$(分别表示字符串的开头和结尾)会被隐式地包含在内。

上述模式检查日期的一般“形状”是否符合要求,但它会接受更多无效的日期而不是有效的日期。你可能会惊讶地发现,使用正则表达式检查有效日期(包括闰年!)是 可行的,但不建议这样做。借鉴自Kuldeep在其他答案中的解决方案,我们可以从中发现耐心和勇气。

((18|19|20)[0-9]{2}[\-.](0[13578]|1[02])[\-.](0[1-9]|[12][0-9]|3[01]))|(18|19|20)[0-9]{2}[\-.](0[469]|11)[\-.](0[1-9]|[12][0-9]|30)|(18|19|20)[0-9]{2}[\-.](02)[\-.](0[1-9]|1[0-9]|2[0-8])|(((18|19|20)(04|08|[2468][048]|[13579][26]))|2000)[\-.](02)[\-.]29

在生产环境中,你的同事会更欣赏一种更简单明了的实现方式。记住,优化的第一法则是不要进行优化!


39

你需要的不仅是一个正则表达式,例如"9999-99-00"不是一个有效的日期。有一个名为SimpleDateFormat的类可以完成此任务。它更加重量级,但更加全面。

例如:

SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");

boolean isValidDate(string input) {
     try {
          format.parse(input);
          return true;
     }
     catch(ParseException e){
          return false;
     }
}

遗憾的是,SimpleDateFormat 既笨重又不支持多线程。


19
如果你希望SimpleDateFormat拒绝无效日期,请不要忘记调用setLenient(false)。如果不这样做,你的代码将接受"9999-99-00"并将其解析为Wed Feb 28 00:00:00 CET 10007 - user85421
1
谢谢Carlos,我甚至不知道它会识别像2009-20-20这样的输入。谢谢 :) - Sheldon
1
另外一点:SimpleDateFormat 不会检查格式:"2010-1-8","10-001-002",... 都会被接受。 - user85421
3
我认为应该是“yyyy-MM-dd”。 - maloney
1
@maloney 我同意。SimpleDateFormat中的“Y”表示年的周数。我编辑了格式字符串以匹配您的建议。 - lreeder

23

把所有的东西都放在一起:

  • 正则表达式不能验证值(例如“2010-19-19”)。
  • SimpleDateFormat不会检查格式(“2010-1-2”,“1-0002-003”均被接受)。

使用两者来验证格式和值是必要的:

public static boolean isValid(String text) {
    if (text == null || !text.matches("\\d{4}-[01]\\d-[0-3]\\d"))
        return false;
    SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
    df.setLenient(false);
    try {
        df.parse(text);
        return true;
    } catch (ParseException ex) {
        return false;
    }
}



ThreadLocal可以用来避免为每个调用创建一个新的SimpleDateFormat。
在多线程上下文中需要它,因为SimpleDateFormat不是线程安全的:

private static final ThreadLocal<SimpleDateFormat> format = new ThreadLocal<SimpleDateFormat>() {
    @Override
    protected SimpleDateFormat initialValue() {
        SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd");
        df.setLenient(false);
        System.out.println("created");
        return df;
    }
};

public static boolean isValid(String text) {
    if (text == null || !text.matches("\\d{4}-[01]\\d-[0-3]\\d"))
        return false;
    try {
        format.get().parse(text);
        return true;
    } catch (ParseException ex) {
        return false;
    }
}

(对于 Matcher 也可以执行同样的操作,因为它也不是线程安全的)


正则表达式可以验证值。 - McKay
如果每次需要时都创建一个新的DateFormat对象,您是否应该关注线程安全?我认为您正在过度设计。 - MS13
为了避免在每次调用时创建新的SimpleDateFormat对象 - undefined

16

这个正则表达式可以实现该功能:"^((19|20)\\d\\d)-(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])$"

它可以检查格式和日期是否有效,但无法验证月份的正确天数,例如闰年。

String regex = "^((19|20)\\d\\d)-(0?[1-9]|1[012])-(0?[1-9]|[12][0-9]|3[01])$";

Assert.assertTrue("Date: matched.", Pattern.matches(regex, "2011-1-1"));
Assert.assertFalse("Date (month): not matched.", Pattern.matches(regex, "2011-13-1"));

祝你好运!


15
我建议使用一个简单的正则表达式来检查日期是否超过31天,以及月份是否超过12个月。类似于:
(0?[1-9]|[12][0-9]|3[01])-(0?[1-9]|1[012])-((18|19|20|21)\\d\\d)

这是格式为“dd-MM-yyyy”的模板。您可以根据需要进行调整(例如去掉问号以使前导0为必需项 - 现在它是可选的),然后使用自定义逻辑来缩减到特定规则,如闰年二月天数情况和其他月份天数情况。请参见下面的DateChecker代码。我选择这种方法是因为在考虑性能时测试表明这是最佳方法。(第一)这种方法与验证一个日期的正则表达式的第二种方法和将相同简单正则表达式与SimpleDateFormat.parse(date)组合使用的第三种方法进行了比较。(第一)这种方法比第二种方法快4倍,比第三种方法快8倍。请参见底部的自包含日期检查器和性能测试主类。我没有检查joda时间方法(更有效的日期/时间库)。 日期检查器代码:
class DateChecker {

    private Matcher matcher;
    private Pattern pattern;

    public DateChecker(String regex) {
        pattern = Pattern.compile(regex);
    }

    /**
     * Checks if the date format is a valid.
     * Uses the regex pattern to match the date first. 
     * Than additionally checks are performed on the boundaries of the days taken the month into account (leap years are covered).
     * 
     * @param date the date that needs to be checked.
     * @return if the date is of an valid format or not.
     */
    public boolean check(final String date) {
        matcher = pattern.matcher(date);
        if (matcher.matches()) {
            matcher.reset();
            if (matcher.find()) {
                int day = Integer.parseInt(matcher.group(1));
                int month = Integer.parseInt(matcher.group(2));
                int year = Integer.parseInt(matcher.group(3));

                switch (month) {
                case 1:
                case 3:
                case 5:
                case 7:
                case 8:
                case 10:
                case 12: return day < 32;
                case 4:
                case 6:
                case 9:
                case 11: return day < 31;
                case 2: 
                    int modulo100 = year % 100;
                    //http://science.howstuffworks.com/science-vs-myth/everyday-myths/question50.htm
                    if ((modulo100 == 0 && year % 400 == 0) || (modulo100 != 0 && year % LEAP_STEP == 0)) {
                        //its a leap year
                        return day < 30;
                    } else {
                        return day < 29;
                    }
                default:
                    break;
                }
            }
        }
        return false;
    }

    public String getRegex() {
        return pattern.pattern();
    }
}

日期检查/测试和性能测试:

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class Tester {

    private static final String[] validDateStrings = new String[]{
        "1-1-2000", //leading 0s for day and month optional
        "01-1-2000", //leading 0 for month only optional
        "1-01-2000", //leading 0 for day only optional
        "01-01-1800", //first accepted date
        "31-12-2199", //last accepted date
        "31-01-2000", //January has 31 days
        "31-03-2000", //March has 31 days
        "31-05-2000", //May has 31 days
        "31-07-2000", //July has 31 days
        "31-08-2000", //August has 31 days
        "31-10-2000", //October has 31 days
        "31-12-2000", //December has 31 days
        "30-04-2000", //April has 30 days
        "30-06-2000", //June has 30 days
        "30-09-2000", //September has 30 days
        "30-11-2000", //November has 30 days
    };
    private static final String[] invalidDateStrings = new String[]{
        "00-01-2000", //there is no 0-th day
        "01-00-2000", //there is no 0-th month
        "31-12-1799", //out of lower boundary date
        "01-01-2200", //out of high boundary date
        "32-01-2000", //January doesn't have 32 days
        "32-03-2000", //March doesn't have 32 days
        "32-05-2000", //May doesn't have 32 days
        "32-07-2000", //July doesn't have 32 days
        "32-08-2000", //August doesn't have 32 days
        "32-10-2000", //October doesn't have 32 days
        "32-12-2000", //December doesn't have 32 days
        "31-04-2000", //April doesn't have 31 days
        "31-06-2000", //June doesn't have 31 days
        "31-09-2000", //September doesn't have 31 days
        "31-11-2000", //November doesn't have 31 days
        "001-02-2000", //SimpleDateFormat valid date (day with leading 0s) even with lenient set to false
        "1-0002-2000", //SimpleDateFormat valid date (month with leading 0s) even with lenient set to false
        "01-02-0003", //SimpleDateFormat valid date (year with leading 0s) even with lenient set to false
        "01.01-2000", //. invalid separator between day and month
        "01-01.2000", //. invalid separator between month and year
        "01/01-2000", /// invalid separator between day and month
        "01-01/2000", /// invalid separator between month and year
        "01_01-2000", //_ invalid separator between day and month
        "01-01_2000", //_ invalid separator between month and year
        "01-01-2000-12345", //only whole string should be matched
        "01-13-2000", //month bigger than 13
    };

    /**
     * These constants will be used to generate the valid and invalid boundary dates for the leap years. (For no leap year, Feb. 28 valid and Feb. 29 invalid; for a leap year Feb. 29 valid and Feb. 30 invalid)   
     */
    private static final int LEAP_STEP = 4;
    private static final int YEAR_START = 1800;
    private static final int YEAR_END = 2199;

    /**
     * This date regex will find matches for valid dates between 1800 and 2199 in the format of "dd-MM-yyyy".
     * The leading 0 is optional.
     */
    private static final String DATE_REGEX = "((0?[1-9]|[12][0-9]|3[01])-(0?[13578]|1[02])-(18|19|20|21)[0-9]{2})|((0?[1-9]|[12][0-9]|30)-(0?[469]|11)-(18|19|20|21)[0-9]{2})|((0?[1-9]|1[0-9]|2[0-8])-(0?2)-(18|19|20|21)[0-9]{2})|(29-(0?2)-(((18|19|20|21)(04|08|[2468][048]|[13579][26]))|2000))";

    /**
     * This date regex is similar to the first one, but with the difference of matching only the whole string. So "01-01-2000-12345" won't pass with a match.
     * Keep in mind that String.matches tries to match only the whole string.
     */
    private static final String DATE_REGEX_ONLY_WHOLE_STRING = "^" + DATE_REGEX + "$";

    /**
     * The simple regex (without checking for 31 day months and leap years):
     */
    private static final String DATE_REGEX_SIMPLE = "(0?[1-9]|[12][0-9]|3[01])-(0?[1-9]|1[012])-((18|19|20|21)\\d\\d)";

    /**
     * This date regex is similar to the first one, but with the difference of matching only the whole string. So "01-01-2000-12345" won't pass with a match.
     */
    private static final String DATE_REGEX_SIMPLE_ONLY_WHOLE_STRING = "^" + DATE_REGEX_SIMPLE + "$";

    private static final SimpleDateFormat SDF = new SimpleDateFormat("dd-MM-yyyy");
    static {
        SDF.setLenient(false);
    }

    private static final DateChecker dateValidatorSimple = new DateChecker(DATE_REGEX_SIMPLE);
    private static final DateChecker dateValidatorSimpleOnlyWholeString = new DateChecker(DATE_REGEX_SIMPLE_ONLY_WHOLE_STRING);

    /**
     * @param args
     */
    public static void main(String[] args) {
        DateTimeStatistics dateTimeStatistics = new DateTimeStatistics();
        boolean shouldMatch = true;
        for (int i = 0; i < validDateStrings.length; i++) {
            String validDate = validDateStrings[i];
            matchAssertAndPopulateTimes(
                    dateTimeStatistics,
                    shouldMatch, validDate);
        }

        shouldMatch = false;
        for (int i = 0; i < invalidDateStrings.length; i++) {
            String invalidDate = invalidDateStrings[i];

            matchAssertAndPopulateTimes(dateTimeStatistics,
                    shouldMatch, invalidDate);
        }

        for (int year = YEAR_START; year < (YEAR_END + 1); year++) {
            FebruaryBoundaryDates februaryBoundaryDates = createValidAndInvalidFebruaryBoundaryDateStringsFromYear(year);
            shouldMatch = true;
            matchAssertAndPopulateTimes(dateTimeStatistics,
                    shouldMatch, februaryBoundaryDates.getValidFebruaryBoundaryDateString());
            shouldMatch = false;
            matchAssertAndPopulateTimes(dateTimeStatistics,
                    shouldMatch, februaryBoundaryDates.getInvalidFebruaryBoundaryDateString());
        }

        dateTimeStatistics.calculateAvarageTimesAndPrint();
    }

    private static void matchAssertAndPopulateTimes(
            DateTimeStatistics dateTimeStatistics,
            boolean shouldMatch, String date) {
        dateTimeStatistics.addDate(date);
        matchAndPopulateTimeToMatch(date, DATE_REGEX, shouldMatch, dateTimeStatistics.getTimesTakenWithDateRegex());
        matchAndPopulateTimeToMatch(date, DATE_REGEX_ONLY_WHOLE_STRING, shouldMatch, dateTimeStatistics.getTimesTakenWithDateRegexOnlyWholeString());
        boolean matchesSimpleDateFormat = matchWithSimpleDateFormatAndPopulateTimeToMatchAndReturnMatches(date, dateTimeStatistics.getTimesTakenWithSimpleDateFormatParse());
        matchAndPopulateTimeToMatchAndReturnMatchesAndCheck(
                dateTimeStatistics.getTimesTakenWithDateRegexSimple(), shouldMatch,
                date, matchesSimpleDateFormat, DATE_REGEX_SIMPLE);
        matchAndPopulateTimeToMatchAndReturnMatchesAndCheck(
                dateTimeStatistics.getTimesTakenWithDateRegexSimpleOnlyWholeString(), shouldMatch,
                date, matchesSimpleDateFormat, DATE_REGEX_SIMPLE_ONLY_WHOLE_STRING);

        matchAndPopulateTimeToMatch(date, dateValidatorSimple, shouldMatch, dateTimeStatistics.getTimesTakenWithdateValidatorSimple());
        matchAndPopulateTimeToMatch(date, dateValidatorSimpleOnlyWholeString, shouldMatch, dateTimeStatistics.getTimesTakenWithdateValidatorSimpleOnlyWholeString());
    }

    private static void matchAndPopulateTimeToMatchAndReturnMatchesAndCheck(
            List<Long> times,
            boolean shouldMatch, String date, boolean matchesSimpleDateFormat, String regex) {
        boolean matchesFromRegex = matchAndPopulateTimeToMatchAndReturnMatches(date, regex, times);
        assert !((matchesSimpleDateFormat && matchesFromRegex) ^ shouldMatch) : "Parsing with SimpleDateFormat and date:" + date + "\nregex:" + regex + "\nshouldMatch:" + shouldMatch;
    }

    private static void matchAndPopulateTimeToMatch(String date, String regex, boolean shouldMatch, List<Long> times) {
        boolean matches = matchAndPopulateTimeToMatchAndReturnMatches(date, regex, times);
        assert !(matches ^ shouldMatch) : "date:" + date + "\nregex:" + regex + "\nshouldMatch:" + shouldMatch;
    }

    private static void matchAndPopulateTimeToMatch(String date, DateChecker dateValidator, boolean shouldMatch, List<Long> times) {
        long timestampStart;
        long timestampEnd;
        boolean matches;
        timestampStart = System.nanoTime();
        matches = dateValidator.check(date);
        timestampEnd = System.nanoTime();
        times.add(timestampEnd - timestampStart);
        assert !(matches ^ shouldMatch) : "date:" + date + "\ndateValidator with regex:" + dateValidator.getRegex() + "\nshouldMatch:" + shouldMatch;
    }

    private static boolean matchAndPopulateTimeToMatchAndReturnMatches(String date, String regex, List<Long> times) {
        long timestampStart;
        long timestampEnd;
        boolean matches;
        timestampStart = System.nanoTime();
        matches = date.matches(regex);
        timestampEnd = System.nanoTime();
        times.add(timestampEnd - timestampStart);
        return matches;
    }

    private static boolean matchWithSimpleDateFormatAndPopulateTimeToMatchAndReturnMatches(String date, List<Long> times) {
        long timestampStart;
        long timestampEnd;
        boolean matches = true;
        timestampStart = System.nanoTime();
        try {
            SDF.parse(date);
        } catch (ParseException e) {
            matches = false;
        } finally {
            timestampEnd = System.nanoTime();
            times.add(timestampEnd - timestampStart);
        }
        return matches;
    }

    private static FebruaryBoundaryDates createValidAndInvalidFebruaryBoundaryDateStringsFromYear(int year) {
        FebruaryBoundaryDates februaryBoundaryDates;
        int modulo100 = year % 100;
        //http://science.howstuffworks.com/science-vs-myth/everyday-myths/question50.htm
        if ((modulo100 == 0 && year % 400 == 0) || (modulo100 != 0 && year % LEAP_STEP == 0)) {
            februaryBoundaryDates = new FebruaryBoundaryDates(
                    createFebruaryDateFromDayAndYear(29, year), 
                    createFebruaryDateFromDayAndYear(30, year)
                    );
        } else {
            februaryBoundaryDates = new FebruaryBoundaryDates(
                    createFebruaryDateFromDayAndYear(28, year), 
                    createFebruaryDateFromDayAndYear(29, year)
                    );
        }
        return februaryBoundaryDates;
    }

    private static String createFebruaryDateFromDayAndYear(int day, int year) {
        return String.format("%d-02-%d", day, year);
    }

    static class FebruaryBoundaryDates {
        private String validFebruaryBoundaryDateString;
        String invalidFebruaryBoundaryDateString;
        public FebruaryBoundaryDates(String validFebruaryBoundaryDateString,
                String invalidFebruaryBoundaryDateString) {
            super();
            this.validFebruaryBoundaryDateString = validFebruaryBoundaryDateString;
            this.invalidFebruaryBoundaryDateString = invalidFebruaryBoundaryDateString;
        }
        public String getValidFebruaryBoundaryDateString() {
            return validFebruaryBoundaryDateString;
        }
        public void setValidFebruaryBoundaryDateString(
                String validFebruaryBoundaryDateString) {
            this.validFebruaryBoundaryDateString = validFebruaryBoundaryDateString;
        }
        public String getInvalidFebruaryBoundaryDateString() {
            return invalidFebruaryBoundaryDateString;
        }
        public void setInvalidFebruaryBoundaryDateString(
                String invalidFebruaryBoundaryDateString) {
            this.invalidFebruaryBoundaryDateString = invalidFebruaryBoundaryDateString;
        }
    }

    static class DateTimeStatistics {
        private List<String> dates = new ArrayList<String>();
        private List<Long> timesTakenWithDateRegex = new ArrayList<Long>();
        private List<Long> timesTakenWithDateRegexOnlyWholeString = new ArrayList<Long>();
        private List<Long> timesTakenWithDateRegexSimple = new ArrayList<Long>();
        private List<Long> timesTakenWithDateRegexSimpleOnlyWholeString = new ArrayList<Long>();
        private List<Long> timesTakenWithSimpleDateFormatParse = new ArrayList<Long>();
        private List<Long> timesTakenWithdateValidatorSimple = new ArrayList<Long>();
        private List<Long> timesTakenWithdateValidatorSimpleOnlyWholeString = new ArrayList<Long>();
        public List<String> getDates() {
            return dates;
        }
        public List<Long> getTimesTakenWithDateRegex() {
            return timesTakenWithDateRegex;
        }
        public List<Long> getTimesTakenWithDateRegexOnlyWholeString() {
            return timesTakenWithDateRegexOnlyWholeString;
        }
        public List<Long> getTimesTakenWithDateRegexSimple() {
            return timesTakenWithDateRegexSimple;
        }
        public List<Long> getTimesTakenWithDateRegexSimpleOnlyWholeString() {
            return timesTakenWithDateRegexSimpleOnlyWholeString;
        }
        public List<Long> getTimesTakenWithSimpleDateFormatParse() {
            return timesTakenWithSimpleDateFormatParse;
        }
        public List<Long> getTimesTakenWithdateValidatorSimple() {
            return timesTakenWithdateValidatorSimple;
        }
        public List<Long> getTimesTakenWithdateValidatorSimpleOnlyWholeString() {
            return timesTakenWithdateValidatorSimpleOnlyWholeString;
        }
        public void addDate(String date) {
            dates.add(date);
        }
        public void addTimesTakenWithDateRegex(long time) {
            timesTakenWithDateRegex.add(time);
        }
        public void addTimesTakenWithDateRegexOnlyWholeString(long time) {
            timesTakenWithDateRegexOnlyWholeString.add(time);
        }
        public void addTimesTakenWithDateRegexSimple(long time) {
            timesTakenWithDateRegexSimple.add(time);
        }
        public void addTimesTakenWithDateRegexSimpleOnlyWholeString(long time) {
            timesTakenWithDateRegexSimpleOnlyWholeString.add(time);
        }
        public void addTimesTakenWithSimpleDateFormatParse(long time) {
            timesTakenWithSimpleDateFormatParse.add(time);
        }
        public void addTimesTakenWithdateValidatorSimple(long time) {
            timesTakenWithdateValidatorSimple.add(time);
        }
        public void addTimesTakenWithdateValidatorSimpleOnlyWholeString(long time) {
            timesTakenWithdateValidatorSimpleOnlyWholeString.add(time);
        }

        private void calculateAvarageTimesAndPrint() {
            long[] sumOfTimes = new long[7];
            int timesSize = timesTakenWithDateRegex.size();
            for (int i = 0; i < timesSize; i++) {
                sumOfTimes[0] += timesTakenWithDateRegex.get(i);
                sumOfTimes[1] += timesTakenWithDateRegexOnlyWholeString.get(i);
                sumOfTimes[2] += timesTakenWithDateRegexSimple.get(i);
                sumOfTimes[3] += timesTakenWithDateRegexSimpleOnlyWholeString.get(i);
                sumOfTimes[4] += timesTakenWithSimpleDateFormatParse.get(i);
                sumOfTimes[5] += timesTakenWithdateValidatorSimple.get(i);
                sumOfTimes[6] += timesTakenWithdateValidatorSimpleOnlyWholeString.get(i);
            }
            System.out.println("AVG from timesTakenWithDateRegex (in nanoseconds):" + (double) sumOfTimes[0] / timesSize);
            System.out.println("AVG from timesTakenWithDateRegexOnlyWholeString (in nanoseconds):" + (double) sumOfTimes[1] / timesSize);
            System.out.println("AVG from timesTakenWithDateRegexSimple (in nanoseconds):" + (double) sumOfTimes[2] / timesSize);
            System.out.println("AVG from timesTakenWithDateRegexSimpleOnlyWholeString (in nanoseconds):" + (double) sumOfTimes[3] / timesSize);
            System.out.println("AVG from timesTakenWithSimpleDateFormatParse (in nanoseconds):" + (double) sumOfTimes[4] / timesSize);
            System.out.println("AVG from timesTakenWithDateRegexSimple + timesTakenWithSimpleDateFormatParse (in nanoseconds):" + (double) (sumOfTimes[2] + sumOfTimes[4]) / timesSize);
            System.out.println("AVG from timesTakenWithDateRegexSimpleOnlyWholeString + timesTakenWithSimpleDateFormatParse (in nanoseconds):" + (double) (sumOfTimes[3] + sumOfTimes[4]) / timesSize);
            System.out.println("AVG from timesTakenWithdateValidatorSimple (in nanoseconds):" + (double) sumOfTimes[5] / timesSize);
            System.out.println("AVG from timesTakenWithdateValidatorSimpleOnlyWholeString (in nanoseconds):" + (double) sumOfTimes[6] / timesSize);
        }
    }

    static class DateChecker {

        private Matcher matcher;
        private Pattern pattern;

        public DateChecker(String regex) {
            pattern = Pattern.compile(regex);
        }

        /**
         * Checks if the date format is a valid.
         * Uses the regex pattern to match the date first. 
         * Than additionally checks are performed on the boundaries of the days taken the month into account (leap years are covered).
         * 
         * @param date the date that needs to be checked.
         * @return if the date is of an valid format or not.
         */
        public boolean check(final String date) {
            matcher = pattern.matcher(date);
            if (matcher.matches()) {
                matcher.reset();
                if (matcher.find()) {
                    int day = Integer.parseInt(matcher.group(1));
                    int month = Integer.parseInt(matcher.group(2));
                    int year = Integer.parseInt(matcher.group(3));

                    switch (month) {
                    case 1:
                    case 3:
                    case 5:
                    case 7:
                    case 8:
                    case 10:
                    case 12: return day < 32;
                    case 4:
                    case 6:
                    case 9:
                    case 11: return day < 31;
                    case 2: 
                        int modulo100 = year % 100;
                        //http://science.howstuffworks.com/science-vs-myth/everyday-myths/question50.htm
                        if ((modulo100 == 0 && year % 400 == 0) || (modulo100 != 0 && year % LEAP_STEP == 0)) {
                            //its a leap year
                            return day < 30;
                        } else {
                            return day < 29;
                        }
                    default:
                        break;
                    }
                }
            }
            return false;
        }

        public String getRegex() {
            return pattern.pattern();
        }
    }
}

一些有用的注意事项:
- 要启用断言(assert检查),您需要在运行测试器时使用-ea参数。(在eclipse中,通过编辑Run/Debug配置 -> Arguments选项卡 -> VM Arguments选项卡 -> 插入“-ea”来完成此操作。
- 上述正则表达式仅适用于1800年至2199年。
- 您不需要在开头使用^并在结尾使用$来仅匹配整个日期字符串。String.matches会处理这个问题。
- 确保您检查有效和无效情况,并根据您拥有的规则进行更改。
- 每个正则表达式的“仅整个字符串”版本与“普通”版本(不带^和$的版本)具有相同的速度。如果您看到性能差异,这是因为Java“习惯于”处理相同的指令,因此时间降低了。如果您交换执行“普通”和“仅整个字符串”版本的行,您将看到这一点得到证明。

希望这可以帮助某人!
祝好,
Despot


1
https://dev59.com/D3I-5IYBdhLWcg3wVW3v - BalusC
2
嘿,BalusC - 这是真的:),你可以让SimpleDateFormat.parse覆盖大多数无效日期。我已经在“日期检查/测试和性能测试”部分中测试过这种方法。它不能覆盖像001-0002-00003这样的无效日期。(我想这取决于你认为什么是无效的 - 在我的情况下,这是一个无效的日期)。此外,正如您所看到的,这种方法是最慢的(请查看我讨论第三种方法时的帖子)。您也可以自由地在您的一侧运行Tester并查看时间;) - despot
还有一个提示 - 如果你想让正则表达式覆盖从1900年到9999年的年份,那么这应该是第三个组:(([^01][0-9]|19|[2-9][0-9])\d\d)。 - despot

6

java.time

使用Java 8+的正确(且简单)的日期/时间验证方法是使用java.time.format.DateTimeFormatter类。对于日期来说,使用正则表达式进行验证并不是理想的方式。对于这个问题中的示例情况:

DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");

try {
    LocalDate date = formatter.parse(text, LocalDate::from);
} catch (DateTimeParseException e) {
    // Thrown if text could not be parsed in the specified format
}

此代码将解析文本,验证其是否为有效日期,并返回一个LocalDate对象作为日期。请注意,如果您的用例与任何ISO标准匹配,则DateTimeFormatter类具有多个静态预定义日期格式。

2
很高兴你提到了现代的java.time类。但是你的示例代码可以更简单。(a)该输入字符串的格式是标准的ISO 8601格式。java.time类默认使用标准格式,因此无需指定自定义格式模式。(b)你对DateTimeFormatter对象的调用和传递方法引用是正确的,但有些笨拙。更容易调用LocalDate.parse静态方法并传递格式化程序对象(如果需要,如果输入是非标准的)。(c)综上所述,更简单的做法是:LocalDate ld = LocalDate.parse("2018-01-23"); - Basil Bourque
此外,LocalDate.parse("2018-01-23") 隐式使用的格式化程序是严格的,这比 DateTimeFormatter.ofPattern("yyyy-MM-dd") 提供了更好的验证。 - Ole V.V.

5
以下正则表达式将接受格式为YYYY-MM-DD(在1600-2999年范围内)的日期,并考虑闰年:
 ^((?:(?:1[6-9]|2[0-9])\d{2})(-)(?:(?:(?:0[13578]|1[02])(-)31)|((0[1,3-9]|1[0-2])(-)(29|30))))$|^(?:(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))(-)02(-)29)$|^(?:(?:1[6-9]|2[0-9])\d{2})(-)(?:(?:0[1-9])|(?:1[0-2]))(-)(?:0[1-9]|1\d|2[0-8])$

示例:

日期正则表达式示例

您可以在此处进行测试。

注意:如果您想接受一个数字作为月份或日期,可以使用以下正则表达式:

 ^((?:(?:1[6-9]|2[0-9])\d{2})(-)(?:(?:(?:0?[13578]|1[02])(-)31)|((0?[1,3-9]|1[0-2])(-)(29|30))))$|^(?:(?:(?:(?:1[6-9]|[2-9]\d)?(?:0[48]|[2468][048]|[13579][26])|(?:(?:16|[2468][048]|[3579][26])00)))(-)0?2(-)29)$|^(?:(?:1[6-9]|2[0-9])\d{2})(-)(?:(?:0?[1-9])|(?:1[0-2]))(-)(?:0?[1-9]|1\d|2[0-8])$

我从这个解决方案开始创建了上述正则表达式。


如果您已经使用了其他答案,您可以将此问题标记为重复。 - Toto
\d{4}之前的(?:1[6-9]|[2-9]\d)?的目的是什么? - Toto
从技术上讲,这并不是在问另一种日期格式,我只是想把信用归功于我依赖的答案。 - Yahya Hussein
这几乎是正确的答案,但没有考虑到世纪末闰年的特殊情况。来自维基百科:每个能被4整除的年份都是闰年,但是能被100整除的年份不是闰年,除非它们能被400整除。例如,1700年、1800年和1900年不是闰年,但1600年和2000年是闰年。 - bohemian

3

格式 yyyy-MM-dd默认格式,由 LocalDate#parse 使用。因此,您只需将日期字符串传递给此方法并检查是否成功解析或发生了某些异常。

演示:

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.stream.Stream;

public class Main {
    public static void main(String[] args) {
        String[] arr = { "2022-11-29", "0000-00-00", "2022-1-2", "2022-02-29" };
        for (String s : arr) {
            try {
                System.out.println("================================");
                System.out.println(LocalDate.parse(s) + " is a valid date ");
            } catch (DateTimeParseException e) {
                System.out.println(e.getMessage());

                // Recommended; so that the caller can handle it appropriately
                // throw new IllegalArgumentException("Invalid date");
            }
        }
    }
}

输出:

================================
2022-11-29 is a valid date 
================================
Text '0000-00-00' could not be parsed: Invalid value for MonthOfYear (valid values 1 - 12): 0
================================
Text '2022-1-2' could not be parsed at index 5
================================
Text '2022-02-29' could not be parsed: Invalid date 'February 29' as '2022' is not a leap year

一些重要的注意事项:

  1. java.time 类型遵循 ISO 8601 标准,因此您需要指定一个 DateTimeFormatter 来解析一个符合 ISO 8601 格式的日期时间字符串。
  2. java.time API 是在2014年3月发布的 Java-8 中推出的,取代了 易错的旧日期时间API。自那时以来,强烈建议使用这个现代化的日期时间API。从Trail: Date Time了解更多关于现代日期时间API的信息。

2
使用指定的格式构造一个SimpleDateFormat对象,然后调用SimpleDateFormat.parse(String s, ParsePosition p)方法:

你可以直接调用只有一个参数的 parse(String) 方法,而不是两个参数的方法。同时,如果你想让 SimpleDateFormat 拒绝无效日期,请不要忘记调用 setLenient(false) - user85421
请注意,像java.util.Datejava.util.Calendarjava.text.SimpleDateFormat这样的老旧日期时间类现在已经成为遗留系统,被内置于Java 8及更高版本中的java.time类所取代。请参阅Oracle的教程 - Basil Bourque

1

如果您使用的是格式dd-MM-yyyy,则下面添加的代码适用于我。

public boolean isValidDate(String date) {
        boolean check;
        String date1 = "^(0?[1-9]|[12][0-9]|3[01])-(0?[1-9]|1[012])-([12][0-9]{3})$";
        check = date.matches(date1);

        return check;
    }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接