如何理智地检查Java中的日期


83

我感到奇怪的是,Date在Java中创建对象的最明显的方法已被弃用,并且似乎已被使用宽大日历的不太明显的方法所“替代”。

如何检查以日,月和年的组合形式给出的日期是否为有效日期?

例如,2008-02-31(如yyyy-mm-dd)将是无效日期。


对于有类似问题的任何人,请考虑是否需要非格雷戈里日历支持。
MSalters 2010年

@JasonC我完全同意您的看法,将它们合并似乎是最好的方法。
acm 2016年

Answers:


37

当前的方法是使用日历类。它具有setLenient方法,该方法将验证日期和引发异常,如果超出示例,则抛出异常。

忘记添加:如果您获得日历实例并使用日期设置时间,那么这就是获得验证的方式。

Calendar cal = Calendar.getInstance();
cal.setLenient(false);
cal.setTime(yourDate);
try {
    cal.getTime();
}
catch (Exception e) {
  System.out.println("Invalid date");
}

2
不要以为这完全按原样工作。Calendar.setTime采用java.util.Date,因此在您获得“ yourDate”对象时,已经完成了从字符串的转换。
tardate

32
示例代码存在3个问题:1.获取Calendar实例后,必须调用cal.setLenient(false)。否则,像2007年2月31日这样的日期将被视为有效。2. cal.setTime()不会引发异常。您必须在setTime()调用之后调用cal.getTime(),这会在无效日期引发异常。3.错别字:在捕获之前缺少“}”。
Liron Yahdav 2010年

这也比其他方法慢。参见stackoverflow.com/questions/2149680/…–
专卖店

5
仅供参考,麻烦的旧日期时间类(如java.util.Datejava.util.Calendarjava.text.SimpleDateFormat现在已被遗留)由Java 8及更高版本中内置的java.time类取代。请参见Oracle教程
罗勒·布尔克

3
无法验证日期,例如2月31日
shikha singh

78

密钥是df.setLenient(false); 。对于简单的情况,这已绰绰有余。如果您正在寻找更健壮的(我怀疑)和/或替代的库(例如joda-time),请查看用户“ tardate”答案

final static String DATE_FORMAT = "dd-MM-yyyy";

public static boolean isDateValid(String date) 
{
        try {
            DateFormat df = new SimpleDateFormat(DATE_FORMAT);
            df.setLenient(false);
            df.parse(date);
            return true;
        } catch (ParseException e) {
            return false;
        }
}

2
尝试使用“ 09-04-201a”。它将创造一个疯狂的约会。
ceklock 2014年

@ceklock就是它的工作方式,无论您是否使用setLenientSimpleDateFormat始终解析直到匹配模式,然后忽略字符串的其余部分,从而得到201年份。
Daniel Naber 2014年

@ceklock我刚刚在解决方案中解决这个问题。为某人节省一两分钟。
苏菲安2015年

1
合并异常处理会带来很大的性能损失,因此,如果您期望正常操作中输入的格式错误(例如,验证用户输入),则这可能是一个糟糕的设计。但是,如果该方法被用作对应该一直有效的输入(错误除外)的双重检查,那很好。
亚伦2015年

2
仅供参考,非常麻烦旧日期,时间类,如java.util.Datejava.util.Calendarjava.text.SimpleDateFormat现在的遗产,由取代java.time内置到Java 8和更高等级。请参见Oracle教程
罗勒·布尔克

48

如@Maglob所示,基本方法是使用SimpleDateFormat.parse测试从字符串到日期的转换。这将捕获无效的日/月组合,例如2008-02-31。

但是,实际上,这几乎是不够的,因为SimpleDateFormat.parse非常自由。您可能会关注两种行为:

日期字符串中的无效字符 令人惊讶的是,例如,2008-02-2x将以区域设置格式=“ yyyy-MM-dd”作为有效日期“传递”。即使isLenient == false。

年:2、3或4位数字? 您可能还希望强制使用4位数字的年份,而不是允许默认的SimpleDateFormat行为(根据您的格式是“ yyyy-MM-dd”还是“ yy-MM-dd”,它对“ 12-02-31”的解释不同) )

标准库的严格解决方案

因此,完整的日期字符串测试可能看起来像这样:regex匹配的组合,然后是强制日期转换。正则表达式的诀窍是使其对语言环境友好。

  Date parseDate(String maybeDate, String format, boolean lenient) {
    Date date = null;

    // test date string matches format structure using regex
    // - weed out illegal characters and enforce 4-digit year
    // - create the regex based on the local format string
    String reFormat = Pattern.compile("d+|M+").matcher(Matcher.quoteReplacement(format)).replaceAll("\\\\d{1,2}");
    reFormat = Pattern.compile("y+").matcher(reFormat).replaceAll("\\\\d{4}");
    if ( Pattern.compile(reFormat).matcher(maybeDate).matches() ) {

      // date string matches format structure, 
      // - now test it can be converted to a valid date
      SimpleDateFormat sdf = (SimpleDateFormat)DateFormat.getDateInstance();
      sdf.applyPattern(format);
      sdf.setLenient(lenient);
      try { date = sdf.parse(maybeDate); } catch (ParseException e) { }
    } 
    return date;
  } 

  // used like this:
  Date date = parseDate( "21/5/2009", "d/M/yyyy", false);

请注意,正则表达式假定格式字符串仅包含日,月,年和分隔符。除此之外,格式可以是任何区域设置格式:“ d / MM / yy”,“ yyyy-MM-dd”,依此类推。可以通过以下方式获取当前语言环境的格式字符串:

Locale locale = Locale.getDefault();
SimpleDateFormat sdf = (SimpleDateFormat)DateFormat.getDateInstance(DateFormat.SHORT, locale );
String format = sdf.toPattern();

乔达时间-更好的选择?

我最近听说乔达时间,以为我可以比较一下。两点:

  1. 与SimpleDateFormat不同,似乎更严格地要求严格日期字符串中的无效字符
  2. 尚无办法强制使用4位数字年份(但我想您可以为此创建自己的DateTimeFormatter

使用起来非常简单:

import org.joda.time.format.*;
import org.joda.time.DateTime;

org.joda.time.DateTime parseDate(String maybeDate, String format) {
  org.joda.time.DateTime date = null;
  try {
    DateTimeFormatter fmt = DateTimeFormat.forPattern(format);
    date =  fmt.parseDateTime(maybeDate);
  } catch (Exception e) { }
  return date;
}

我最终选择了joda选项,并自己检查了值是否与模式长度匹配...
Xtreme Biker

更新:可怕的旧遗留类(DateSimpleDateFormat等)现在被现代的java.time类所取代。同样,Joda-Time项目处于维护模式,建议迁移到java.time类。
罗勒·布尔克

38

您可以使用SimpleDateFormat

例如:

boolean isLegalDate(String s) {
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    sdf.setLenient(false);
    return sdf.parse(s, new ParsePosition(0)) != null;
}

2
这种方法的问题之一是它将接受0003-0002-001。
霸主

1
另一个问题是,在月份中设置13会返回一个日期,其中月份是下一年的01。
8bitjunkie 2013年

仅供参考,非常麻烦旧日期,时间类,如java.util.Datejava.util.Calendarjava.text.SimpleDateFormat现在的遗产,由取代java.time内置到Java 8和更高等级。请参见Oracle教程
罗勒·布尔克

28

tl; dr

使用严格模式java.time.DateTimeFormatter分析一个LocalDate。陷阱DateTimeParseException

LocalDate.parse(                   // Represent a date-only value, without time-of-day and without time zone.
    "31/02/2000" ,                 // Input string.
    DateTimeFormatter              // Define a formatting pattern to match your input string.
    .ofPattern ( "dd/MM/uuuu" )
    .withResolverStyle ( ResolverStyle.STRICT )  // Specify leniency in tolerating questionable inputs.
)

解析后,您可能会检查合理的值。例如,最近一百年内的出生日期。

birthDate.isAfter( LocalDate.now().minusYears( 100 ) )

避免使用旧的日期时间类

避免使用Java最早版本附带的麻烦的旧日期时间类。现在由java.time类取代。

LocalDateDateTimeFormatterResolverStyle

LocalDate级表示没有时间一天和不同时区的日期,唯一的价值。

String input = "31/02/2000";
DateTimeFormatter f = DateTimeFormatter.ofPattern ( "dd/MM/uuuu" );
try {
    LocalDate ld = LocalDate.parse ( input , f );
    System.out.println ( "ld: " + ld );
} catch ( DateTimeParseException e ) {
    System.out.println ( "ERROR: " + e );
}

java.time.DateTimeFormatter类可以被设置与任何在定义了三种模式从宽解析字符串ResolverStyle枚举。我们在上面的代码中插入一行以尝试每种模式。

f = f.withResolverStyle ( ResolverStyle.LENIENT );

结果:

  • ResolverStyle.LENIENT
    ld:2000-03-02
  • ResolverStyle.SMART
    ld:2000-02-29
  • ResolverStyle.STRICT
    错误:java.time.format.DateTimeParseException:无法解析文本“ 31/02/2000”:无效的日期“ FEBRUARY 31”

我们可以看到,在ResolverStyle.LENIENT模式下,无效日期被向前移动了相等的天数。在ResolverStyle.SMART模式(默认)下,做出合理的决定是将日期保留在一个月之内,并与a年的2月29日进行匹配,因为该月没有第31天。该ResolverStyle.STRICT模式引发异常,抱怨没有这样的日期。

所有这三个都是合理的,具体取决于您的业务问题和策略。听起来您的情况是您希望严格模式拒绝无效日期而不是对其进行调整。


Java中所有日期和时间类型的列表,包括现代的和传统的。


关于java.time

java.time框架是建立在Java 8和更高版本。这些类取代麻烦的老传统日期时间类,如java.util.DateCalendar,和SimpleDateFormat

要了解更多信息,请参见Oracle教程。并在Stack Overflow中搜索许多示例和说明。规格为JSR 310

现在处于维护模式Joda-Time项目建议迁移到java.time类。

您可以直接与数据库交换java.time对象。使用与JDBC 4.2或更高版本兼容的JDBC驱动程序。不需要字符串,不需要类。java.sql.*

在哪里获取java.time类?

哪个版本的Java或Android使用哪个java.time库的表

ThreeTen-额外项目与其他类扩展java.time。该项目是将来可能向java.time添加内容的试验场。你可能在这里找到一些有用的类,比如IntervalYearWeekYearQuarter,和更多


好吧,假设该项目正在使用Java 8+进行编译,那么您的答案是正确的。当项目在Java 8中时,我也在使用这些类。但是有时我不得不触摸那些难看的古老的jsp scriptlet(甚至不支持Java 7)。验证日期很痛苦。因此,我在这里。但是,您的答案是最正确的方法。结论:我很困惑。
KarelG

@KarelG重读关于倒入Java 6和Java 7的倒数第二段。我尚未在后端验证此行为,但建议您尝试一下。
罗勒·布尔克

13

java.time

使用Java 8和更高版本中内置的Date and Time APIjava.time类),您可以使用LocalDate该类。

public static boolean isDateValid(int year, int month, int day) {
    boolean dateIsValid = true;
    try {
        LocalDate.of(year, month, day);
    } catch (DateTimeException e) {
        dateIsValid = false;
    }
    return dateIsValid;
}

2
默认情况下,此代码使用ResolverStyle.SMART它将结果值调整为有效日期,而不是引发异常。因此,此代码将无法完成课题的目标。有关示例以及使用的解决方案,请参见我的答案ResolverStyle.STRICT
罗勒·布尔克

7

使用标准库的另一种严格解决方案是执行以下操作:

1)使用您的模式创建严格的SimpleDateFormat

2)尝试使用格式对象解析用户输入的值

3)如果成功,则使用相同的日期格式(来自(1))将由(2)生成的日期重新格式化。

4)将重新格式化的日期与用户输入的原始值进行比较。如果它们相等,则输入的值将严格匹配您的模式。

这样,您不需要创建复杂的正则表达式-就我而言,我需要支持所有SimpleDateFormat的模式语法,而不是仅限于某些类型,例如几天,几个月和几年。


这绝对是必经之路,另请参见dreamincode.net/forums/topic/…
Victor Ionescu

7

Aravind的答案为基础,以解决ceklock在其评论中指出的问题,我添加了一种方法来验证,该方法dateString不包含任何无效字符。

这是我的方法:

private boolean isDateCorrect(String dateString) {
    try {
        Date date = mDateFormatter.parse(dateString);
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(date);
        return matchesOurDatePattern(dateString);    //added my method
    }
    catch (ParseException e) {
        return false;
    }
}

/**
 * This will check if the provided string matches our date format
 * @param dateString
 * @return true if the passed string matches format 2014-1-15 (YYYY-MM-dd)
 */
private boolean matchesDatePattern(String dateString) {
    return dateString.matches("^\\d+\\-\\d+\\-\\d+");
}

4

我建议您使用org.apache.commons.validator.GenericValidatorapache的class。

GenericValidator.isDate(String value, String datePattern, boolean strict);

注意:严格-是否与datePattern完全匹配。


4

我认为最简单的方法是将字符串转换为日期对象,然后将其转换回字符串。如果两个字符串仍然匹配,则给定的日期字符串会很好。

public boolean isDateValid(String dateString, String pattern)
{   
    try
    {
        SimpleDateFormat sdf = new SimpleDateFormat(pattern);
        if (sdf.format(sdf.parse(dateString)).equals(dateString))
            return true;
    }
    catch (ParseException pe) {}

    return false;
}

2

假设这两个都是字符串(否则它们已经是有效的日期),这是一种方法:

package cruft;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class DateValidator
{
    private static final DateFormat DEFAULT_FORMATTER;

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

    public static void main(String[] args)
    {
        for (String dateString : args)
        {
            try
            {
                System.out.println("arg: " + dateString + " date: " + convertDateString(dateString));
            }
            catch (ParseException e)
            {
                System.out.println("could not parse " + dateString);
            }
        }
    }

    public static Date convertDateString(String dateString) throws ParseException
    {
        return DEFAULT_FORMATTER.parse(dateString);
    }
}

这是我得到的输出:

java cruft.DateValidator 32-11-2010 31-02-2010 04-01-2011
could not parse 32-11-2010
could not parse 31-02-2010
arg: 04-01-2011 date: Tue Jan 04 00:00:00 EST 2011

Process finished with exit code 0

如您所见,它确实很好地处理了两种情况。


@duffymo第二条语句没有引发异常... :(
maximus 2010年

@Pangea不是真的。...尝试在“ 31-01-2010”这个日期上不会抛出任何异常…………。只需在您的计算机上运行它,看看……它不会工作....
sasidhar

我的错。我删除了我的评论。但是第二个陈述虽然给我抛出了例外。@duffymo-您正在使用哪个jvm?
Aravind Yarram 2010年

@Pangea我正在使用jdk1.6.0_23我甚在joda-time.sourceforge.net上都尝试过此操作,但即使这样也不起作用...
sasidhar 2010年

1
我知道这只是一个简单的测试,但是为了确保人们不会原样抄袭,我想说“ DateFormat”并不安全。因此,始终将其创建为局部变量或将其与线程局部变量一起使用。
Aravind Yarram '12

2

这对我来说很棒。本建议的方法。

private static boolean isDateValid(String s) {
    SimpleDateFormat sdf = new SimpleDateFormat("dd/MM/yyyy");
    try {
        Date d = asDate(s);
        if (sdf.format(d).equals(s)) {
            return true;
        } else {
            return false;
        }
    } catch (ParseException e) {
        return false;
    }
}

1
无法识别asDate。那是什么?
Susheel

2

看起来SimpleDateFormat即使在setLenient(false)之后也没有严格检查模式方法应用于其上,因此我已使用以下方法来验证输入的日期是否为有效日期(不是按照提供的模式)。

import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
public boolean isValidFormat(String dateString, String pattern) {
    boolean valid = true;
    DateTimeFormatter formatter = DateTimeFormatter.ofPattern(pattern);
    try {
        formatter.parse(dateString);
    } catch (DateTimeParseException e) {
        valid = false;
    }
    return valid;
}

1

关于使用SimpleDateFormat的两个评论。

如果声明为静态访问,则应将其声明为静态实例,因为它不是线程安全的,因此应进行同步

IME最好为每个日期的解析实例化一个实例。


汤姆好点。我更新了提供的示例,以确保始终使用静态实例。
tardate

对于某些项目,使用同步功能可能无法扩展。我会建议将SimpleDateFormat放在ThreadLocal变量中,而不是通过同步函数访问的静态变量中。
user327961 2014年

0

上面的日期解析方法很好,我只是在现有方法中添加了新检查,使用格式化程序仔细检查了转换后的日期和原始日期,因此在我验证的情况下,它几乎适用于每种情况。例如02/29/2013是无效日期。给定函数根据当前可接受的日期格式解析日期。如果未成功解析日期,则返回true。

 public final boolean validateDateFormat(final String date) {
        String[] formatStrings = {"MM/dd/yyyy"};
        boolean isInvalidFormat = false;
        Date dateObj;
        for (String formatString : formatStrings) {
            try {
                SimpleDateFormat sdf = (SimpleDateFormat) DateFormat.getDateInstance();
                sdf.applyPattern(formatString);
                sdf.setLenient(false);
                dateObj = sdf.parse(date);
                System.out.println(dateObj);
                if (date.equals(sdf.format(dateObj))) {
                    isInvalidFormat = false;
                    break;
                }
            } catch (ParseException e) {
                isInvalidFormat = true;
            }
        }
        return isInvalidFormat;
    }

0

这是我在不使用外部库的情况下对Node环境所做的工作:

Date.prototype.yyyymmdd = function() {
   var yyyy = this.getFullYear().toString();
   var mm = (this.getMonth()+1).toString(); // getMonth() is zero-based
   var dd  = this.getDate().toString();
   return zeroPad([yyyy, mm, dd].join('-'));  
};

function zeroPad(date_string) {
   var dt = date_string.split('-');
   return dt[0] + '-' + (dt[1][1]?dt[1]:"0"+dt[1][0]) + '-' + (dt[2][1]?dt[2]:"0"+dt[2][0]);
}

function isDateCorrect(in_string) {
   if (!matchesDatePattern) return false;
   in_string = zeroPad(in_string);
   try {
      var idate = new Date(in_string);
      var out_string = idate.yyyymmdd();
      return in_string == out_string;
   } catch(err) {
      return false;
   }

   function matchesDatePattern(date_string) {
      var dateFormat = /[0-9]+-[0-9]+-[0-9]+/;
      return dateFormat.test(date_string); 
   }
}

这是如何使用它:

isDateCorrect('2014-02-23')
true

0
// to return valid days of month, according to month and year
int returnDaysofMonth(int month, int year) {
    int daysInMonth;
    boolean leapYear;
    leapYear = checkLeap(year);
    if (month == 4 || month == 6 || month == 9 || month == 11)
        daysInMonth = 30;
    else if (month == 2)
        daysInMonth = (leapYear) ? 29 : 28;
    else
        daysInMonth = 31;
    return daysInMonth;
}

// to check a year is leap or not
private boolean checkLeap(int year) {
    Calendar cal = Calendar.getInstance();
    cal.set(Calendar.YEAR, year);
    return cal.getActualMaximum(Calendar.DAY_OF_YEAR) > 365;
}

0

这是我要检查的日期格式:

 public static boolean checkFormat(String dateTimeString) {
    return dateTimeString.matches("^\\d{4}-\\d{2}-\\d{2}") || dateTimeString.matches("^\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}")
            || dateTimeString.matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}") || dateTimeString
            .matches("^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}Z") ||
            dateTimeString.matches("^\\d{4}-\\d{2}-\\d{2}\\s\\d{2}:\\d{2}:\\d{2}Z");
}

现在,我们有了现代的java.time来完成这项工作。编写复杂的正则表达式未必能最好地利用您的时间。请参阅我的答案中的单线
罗勒·布尔克

0
        public static String detectDateFormat(String inputDate, String requiredFormat) {
        String tempDate = inputDate.replace("/", "").replace("-", "").replace(" ", "");
        String dateFormat;

        if (tempDate.matches("([0-12]{2})([0-31]{2})([0-9]{4})")) {
            dateFormat = "MMddyyyy";
        } else if (tempDate.matches("([0-31]{2})([0-12]{2})([0-9]{4})")) {
            dateFormat = "ddMMyyyy";
        } else if (tempDate.matches("([0-9]{4})([0-12]{2})([0-31]{2})")) {
            dateFormat = "yyyyMMdd";
        } else if (tempDate.matches("([0-9]{4})([0-31]{2})([0-12]{2})")) {
            dateFormat = "yyyyddMM";
        } else if (tempDate.matches("([0-31]{2})([a-z]{3})([0-9]{4})")) {
            dateFormat = "ddMMMyyyy";
        } else if (tempDate.matches("([a-z]{3})([0-31]{2})([0-9]{4})")) {
            dateFormat = "MMMddyyyy";
        } else if (tempDate.matches("([0-9]{4})([a-z]{3})([0-31]{2})")) {
            dateFormat = "yyyyMMMdd";
        } else if (tempDate.matches("([0-9]{4})([0-31]{2})([a-z]{3})")) {
            dateFormat = "yyyyddMMM";
        } else {
            return "Pattern Not Added";
//add your required regex
        }
        try {
            String formattedDate = new SimpleDateFormat(requiredFormat, Locale.ENGLISH).format(new SimpleDateFormat(dateFormat).parse(tempDate));

            return formattedDate;
        } catch (Exception e) {
            //
            return "";
        }

    }

0

使用“旧式”日期格式,我们可以格式化结果并将其与源进行比较。

    public boolean isValidFormat(String source, String pattern) {
    SimpleDateFormat sd = new SimpleDateFormat(pattern);
    sd.setLenient(false);
    try {
        Date date = sd.parse(source);
        return date != null && sd.format(date).equals(source);
    } catch (Exception e) {
        return false;
    }
}

该执行对模式为'01 .01.2004'的source = 0.1.0.04说'false'


-1

如果您需要严格的验证,则将setLenient设置为false

public boolean isThisDateValid(String dateToValidate, String dateFromat){

    if(dateToValidate == null){
        return false;
    }

    SimpleDateFormat sdf = new SimpleDateFormat(dateFromat);
    sdf.setLenient(false);

    try {

        //if not valid, it will throw ParseException
        Date date = sdf.parse(dateToValidate);
        System.out.println(date);

    } catch (ParseException e) {

        e.printStackTrace();
        return false;
    }

    return true;
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.