题 如何在Java中检查日期


我发现最明显的创造方式很奇怪 Date Java中的对象已经被弃用,并且似乎已被“替换”了一个不太明显的使用宽松日历。

如何检查日期,月份和年份组合的日期是否为有效日期?

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


57
2017-10-22 18:04


起源


对于有类似问题的任何人,请考虑您是否需要非公历日历支持。 - MSalters
@JasonC我完全赞同你,合并它们看起来是最好的方式。 - acm


答案:


目前的方法是使用日历类。它有 setLenient 如果超出范围,将验证日期和抛出以及异常的方法,如示例中所示。

忘了添加: 如果您获得日历实例并使用日期设置时间,则可以使用此方法进行验证。

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

26
2017-10-22 18:08



不要认为这完全按原样运作。 Calendar.setTime采用java.util.Date,因此当您获得“yourDate”对象时,已经发生了从字符串的转换。 - tardate
示例代码有3个问题:1。获取Calendar实例后,必须调用cal.setLenient(false)。否则,像2007年2月31日这样的日期将被视为有效。 2. cal.setTime()不会抛出异常。您必须在setTime()调用之后调用cal.getTime(),这会在无效日期引发异常。错字:在捕获之前缺少'}'。 - Liron Yahdav
这也比其他方法慢。看到 stackoverflow.com/questions/2149680/... - despot
仅供参考,麻烦的旧日期时间类如 java.util.Date, java.util.Calendar,和 java.text.SimpleDateFormat 现在 遗产,取代了 java.time Java 8及更高版本中内置的类。看到 教程 由Oracle。 - Basil Bourque


关键是 df.setLenient(假);。这对于简单的案例来说已经足够了。如果你正在寻找一个更强大(我怀疑)和/或替代库像joda-time那么看这里(不是接受的答案,但来自用户名为“tardate”的答案): 如何在java中检查日期

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;
        }
}

58
2017-12-24 19:46



试试“09-04-201a”。它将创造一个疯狂的约会。 - ceklock
@ceklock这就是它的工作方式,无论你使用它 setLenient 或不: SimpleDateFormat 将始终解析,直到模式匹配并忽略字符串的其余部分,因此你得到 201 作为一年。 - Daniel Naber
@ceklock我刚刚解决了它 我的解决方案。它可能会为某人节省一两分钟。 - Sufian
合并异常处理会带来很大的性能损失,因此如果您在正常操作中预期输入格式错误(例如验证用户输入),这可能是一个糟糕的设计。但是如果该方法被用作对输入的双重检查,这些输入应该一直有效(除了bug),这很好。 - Aaron
仅供参考,非常麻烦的旧日期时间类如 java.util.Date, java.util.Calendar,和 java.text.SimpleDateFormat 现在 遗产,取代了 java.time Java 8及更高版本中内置的类。看到 教程 由Oracle。 - Basil Bourque


如@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” )

标准库的严格解决方案

因此,完整的字符串到目前为止测试可能如下所示:正则表达式匹配的组合,然后是强制日期转换。正则表达式的技巧是使它对语言环境友好。

  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;
}

43
2018-05-21 10:08



我最终选择了joda替代方案并检查该值是否与模式长度相匹配... - Xtreme Biker
更新:可怕的旧遗产类(Date, SimpleDateFormat现在已经被现代化所取代 java.time 类。同样地, 乔达时间 项目处于维护模式,并建议迁移到 java.time 类。 - Basil Bourque


您可以使用 的SimpleDateFormat

例如:

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

34
2017-10-22 19:35



简单而有效。 - Mukus
这种方法的一个问题是它将接受0003-0002-001。 - despot
另一个问题是,月份中的设置13将返回一个日期,其中月份为次年的01。 - 8bitjunkie
仅供参考,非常麻烦的旧日期时间类如 java.util.Date, java.util.Calendar,和 java.text.SimpleDateFormat 现在 遗产,取代了 java.time Java 8及更高版本中内置的类。看到 教程 由Oracle。 - Basil Bourque


java.time

随着 日期和时间API (java.time 在Java 8及更高版本中内置的类,你可以使用 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;
}

12
2018-05-02 22:55



默认情况下,此代码使用 ResolverStyle.SMART 它将结果值调整为有效日期,而不是抛出异常。所以这段代码不会完成问题的目标。看到 我的答案 例如,使用解决方案 ResolverStyle.STRICT。 - Basil Bourque


TL;博士

使用 严格的模式 上 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 类。

LocalDate & DateTimeFormatter & ResolverStyle

LocalDate class表示没有时间且没有时区的仅日期值。

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 class可以设置为使用中定义的三种宽松模式中的任何一种来解析字符串 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 模式(默认值),作出一个逻辑决定,使日期保持在月份内,并在2月29日的闰年中与该月的最后一天一起,因为该月没有第31天。该 ResolverStyle.STRICT 模式抛出异常,抱怨没有这样的日期。

根据您的业务问题和政策,所有这三项都是合理的。在您的情况下,您希望严格模式拒绝无效日期而不是调整它。


关于 java.time

java.time 框架内置于Java 8及更高版本中。这些课程取代了麻烦的旧课程 遗产 日期时间类如 java.util.DateCalendar,& SimpleDateFormat

乔达时间 项目,现在 维护模式建议迁移到 java.time 类。

要了解更多信息,请参阅 Oracle教程。并搜索Stack Overflow以获取许多示例和解释。规格是 JSR 310

你可以交换 java.time 对象直接与您的数据库。用一个 JDBC驱动程序 符合 JDBC 4.2 或以后。不需要字符串,不需要 java.sql.* 类。

从哪里获取java.time类?

ThreeTen-EXTRA project使用其他类扩展java.time。该项目是未来可能添加到java.time的试验场。您可以在这里找到一些有用的课程,例如 IntervalYearWeekYearQuarter,和 更多


8
2017-09-22 22:25



好吧,假设项目是用java 8+编译的,那么你的答案是正确的。当项目在java 8中时,我也在使用这些类。但有时我必须触摸那些丑陋的古老jsp scriptlet(甚至不支持java 7)。验证日期是痛苦的。因此,我在这里。然而你的答案是最正确的方法......结论:我搞砸了。 - KarelG
@KarelG重读了倒数第二段的内容 后端到Java 6和Java 7。我没有在后端验证这种行为,但我建议你试一试。 - Basil Bourque


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

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

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

3)如果成功,使用相同的日期格式(从(1))重新格式化由(2)得到的日期

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

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


5
2018-04-12 14:00



这绝对是要走的路,另请参阅示例代码 dreamincode.net/forums/topic/... - Victor Ionescu


建立在答案上 @Pangea 解决由此指出的问题 @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+");
}

5
2018-05-29 11:54





我建议你使用 org.apache.commons.validator.GenericValidator 来自apache的课程。

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

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


4
2017-07-17 21:33





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

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;
}

3
2018-02-04 09:49





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

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

正如您所看到的,它可以很好地处理您的两个案例。


2
2017-12-24 19:45



@duffymo第二个声明不是抛出异常...... :( - maximus
@Pangea不是真的....尝试这个日期“31-01-2010”它不会抛出任何异常..........只需在你的机器上运行它,看看......它没有工作.... - sasidhar
我的错。我删除了我的评论。但是第二个声明却为我抛出了异常。 @duffymo - 你用的是哪个jvm? - Aravind R. Yarram
@Pangea我正在使用jdk1.6.0_23我尝试了这个甚至 joda-time.sourceforge.net 但即使那样也行不通.... - sasidhar
我知道这只是一个简单的测试,但为了确保人们不会逐字复制,我想说“DateFormat”并不是安全的。因此,始终将其创建为局部变量或将其与本地线程一起使用。 - Aravind R. Yarram