Java中如何校验一个日期是否正确且合法


=Start=

缘由:

整理一下Java中校验一个日期是否正确且合法的方法,网上关于这类功能的代码很多,但质量参差不齐,且不保证正确性,所以自己在此整理一下自己验证过的内容,方便需要的时候参考和使用。

正文:

参考解答:

可行的方法:

  1. 自己按照原理进行编写
  2. SimpleDateFormat + setLenient(false)
  3. LocalDate (需要Java 8及以上版本)
  4. Apache Commons Validator 包
package com.example.learn;

import org.apache.commons.validator.GenericValidator;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.time.format.ResolverStyle;

public class DateUtils {

    public static String dateArray[] = {"2020-02-28","2020-02-29","2020-02-30","2020-02-31","2020-01-31","2019-02-29"};
    public static String datePattern = "yyyy-MM-dd";

    // Key is setLenient(false);
    public static boolean isValidDate(String dateString, String datePattern) {
        try {
            DateFormat df = new SimpleDateFormat(datePattern);
            df.setLenient(false);
            df.parse(dateString);
            return true;
        } catch (ParseException e) {
            return false;
        }
    }

    // The java.time framework is built into Java 8 and later.
    public static boolean isValidDate2(String dateString) {
        boolean dateIsValid = true;
        try {
            int year = Integer.parseInt(dateString.substring(0,4));
            int month = Integer.parseInt(dateString.substring(5,7));
            int day = Integer.parseInt(dateString.substring(8,10));
            // System.out.println(String.format("%d\t%d\t%d", year, month, day));
            LocalDate.of(year, month, day);
        } catch (DateTimeException e) {
            dateIsValid = false;
        }
        return dateIsValid;
    }

    // The java.time framework is built into Java 8 and later.
    // Error case:
    // 2020-02-30: true
    // 2020-02-31: true
    // 2019-02-29: true
    public static boolean isValidDateFormat(String dateString, String pattern) {
        boolean valid = true;
        // DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
        DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern).withResolverStyle(ResolverStyle.STRICT);
        try {
            formatter.parse(dateString);
        } catch (DateTimeParseException e) {
            valid = false;
        }
        return valid;
    }


    static int MAX_VALID_YR = 9999;
    static int MIN_VALID_YR = 1900;

    // Judge is 闰年(leap year) or not ?
    static boolean isLeap(int year) {
        // Return true if year is a multiple of 4 and not multiple of 100.
        // OR year is multiple of 400.
        return (((year % 4 == 0) &&
                (year % 100 != 0)) ||
                (year % 400 == 0));
    }

    // Returns true if given year+month+day is valid, or false(not valid).
    static boolean isValidDate3(int y, int m, int d) {
        // If year, month and day // are not in given range
        if (y > MAX_VALID_YR || y < MIN_VALID_YR)
            return false;
        if (m < 1 || m > 12)
            return false;
        if (d < 1 || d > 31)
            return false;

        // Handle February month with leap year
        if (m == 2) {
            if (isLeap(y))
                return (d <= 29);
            else
                return (d <= 28);
        }

        // Months of April, June, Sept and Nov must have
        // number of days less than or equal to 30.
        if (m == 4 || m == 6 || m == 9 || m == 11)
            return (d <= 30);

        return true;
    }


    public static void main(String[] args) {
        System.out.println("SimpleDateFormat-setLenient");
        for (String dateStr: dateArray) {
            System.out.println(String.format("%s: %b", dateStr, isValidDate(dateStr, datePattern)));
        }

        System.out.println("\njava.time.LocalDate");
        for (String dateStr: dateArray) {
            System.out.println(String.format("%s: %b", dateStr, isValidDate2(dateStr)));
        }

        System.out.println("\ncommons-validator");
        for (String dateStr: dateArray) {
            System.out.println(String.format("%s: %b", dateStr, GenericValidator.isDate(dateStr, datePattern, true)));
        }

        System.out.println("\nvia default judgement");
        for (String dateStr: dateArray) {
            int year = Integer.parseInt(dateStr.substring(0,4));
            int month = Integer.parseInt(dateStr.substring(5,7));
            int day = Integer.parseInt(dateStr.substring(8,10));
            System.out.println(String.format("%s: %b", dateStr, isValidDate3(year, month, day)));
        }

        System.out.println("\nDateTimeFormatter");
        for (String dateStr: dateArray) {
            System.out.println(String.format("%s: %b", dateStr, isValidDateFormat(dateStr, datePattern)));
        }

    }
}

参考链接:

Program to check if a date is valid or not
https://www.geeksforgeeks.org/program-check-date-valid-not/

Check If a String Is a Valid Date in Java
https://www.baeldung.com/java-string-valid-date

https://github.com/eugenp/tutorials/tree/master/core-java-modules/core-java-datetime-string

https://mvnrepository.com/artifact/commons-validator/commons-validator/1.6

Java中如何校验一个日期是否正确?
https://stackoverflow.com/questions/226910/how-to-sanity-check-a-date-in-java
https://stackoverflow.com/a/4528094
https://stackoverflow.com/a/23438158

https://docs.oracle.com/javase/7/docs/api/java/util/Calendar.html#setLenient(boolean)

How to check if date is valid in Java
https://mkyong.com/java/how-to-check-if-date-is-valid-in-java/

=END=


《 “Java中如何校验一个日期是否正确且合法” 》 有 3 条评论

  1. 面试官一步一步的套路你,为什么SimpleDateFormat不是线程安全的
    https://mp.weixin.qq.com/s/m74S_AZ6fBO9Z-abCo334g
    `
    面试官:SimpleDateFormat用过吗?

    面试官:能写一下你是如何使用的吗?

    面试官:为什么要使用ThreadLocal包装SimpleDateFormat?
    小小白:如果不使用ThreadLocal包装一下,直接创建一个SimpleDateFormat共享实例对象,在多线程并发的情况下使用这个对象的方法是线程不安全的,可能会抛出NumberFormatException或其它异常。使用ThreadLocal包装一下,每个线程都有自己的SimpleDateFormat实例对象,这样多线程并发的情况下就不会出现线程不安全的问题了。

    面试官:那为什么SimpleDateFormat作为共享变量会出现线程不安全的问题?

    面试官:那既然是因为SimpleDateFormat类中的calendar是共享变量导致的,可以将SimpleDateFormat类型的变量设置成方法私有的,每次要用的时候new一个不就完了,或者使用同步锁控制一下并发访问,为什么还要使用ThreadLocal这么麻烦?
    小小白:方法私有和同步锁并发控制确实可以解决问题,但是他们有各自的缺点。方法私有,每次都需要创建一个新对象,这样占用内存不说,还需要不断回收。同步锁并发控制,多线程高并发的情况下,性能会严重下降。使用ThreadLocal包装,每个线程只需要创建一个SimpleDateFormat对象实例,既解决不断创建新对象的问题,又解决了并发的问题。

    面试官:那为什么最开始你写的代码中,在finally方法中使用了df.remove(),这是为什么?
    小小白:线程用完了SimpleDateFormat,如果不调用remove方法将其清除,可能会引发因使用ThreadLocal而导致的内存泄漏。

    面试官:使用ThreadLocal为什么可能会导致内存泄漏?
    `

  2. Java Time Zone When Parsing DateFormat
    https://stackoverflow.com/questions/4542679/java-time-zone-when-parsing-dateformat
    `
    OffsetDateTime odt = OffsetDateTime.parse( “2010-12-27T10:50:44.000-08:00” );
    Instant instant = odt.toInstant();

    # ISO 8601
    The input string format is defined in the ISO 8601 standard, a family of date-time formats.
    `

    Converting ISO 8601-compliant String to java.util.Date
    https://stackoverflow.com/questions/2201925/converting-iso-8601-compliant-string-to-java-util-date

  3. 日期和时间格式符号
    https://docs.oracle.com/cloud/help/zh_CN/enterprise-data-management-cloud/DMCAA/date_time_formatting_reference_102x5fccd953.htm
    `
    符号 日期或时间组成部分 示例
    G 纪元标志符 AD
    y 年份 1996; 96
    Y 基于周的年份 (week year) 2009; 09
    M 年中的月份 七月;7 月;07
    w 年中的第几周 27
    W 月中的第几周 2
    D 年中的第几天 10
    d 月中的第几天 2
    E 星期几(名称) 星期二;周二
    u 星期几(编号)(1 = 星期一,7 = 星期天) 1
    a AM/PM 标记 PM
    H 一天中的小时 (0-23) 0
    k 一天中的小时 (1-24) 24
    K AM/PM 中的小时 (0-11) 0
    h AM/PM 中的小时 (1-12) 12
    m 小时中的分钟 30
    s 分钟中的秒 55
    S 毫秒 978
    z 时区 太平洋标准时间;PST;GMT-08:00
    Z 时区 -800
    X 时区 -08; -0800; -08:00
    `

    Java 日期时间
    https://www.runoob.com/java/java-date-time.html

    Java时间格式化原来这么多玩法
    https://juejin.cn/post/7050329304189648926

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注