如何在 Java 中检查日期的完整性
-
03-07-2019 - |
题
我觉得奇怪的是,最明显的创建方式 Date
Java 中的对象已被弃用,并且似乎已被使用不太明显的宽松日历“替代”。
如何检查以日、月、年组合形式给出的日期是否有效?
例如,2008-02-31(如 yyyy-mm-dd)将是无效日期。
解决方案
目前的方法是使用日历类。它有 setLenient 如果超出范围,将验证日期和抛出以及异常的方法,如示例中所示。
忘记添加: 如果您获得日历实例并使用日期设置时间,则可通过此方式进行验证。
Calendar cal = Calendar.getInstance();
cal.setLenient(false);
cal.setTime(yourDate);
try {
cal.getTime();
}
catch (Exception e) {
System.out.println("Invalid date");
}
其他提示
键是 df.setLenient(false); 。这对于简单的案例来说已经足够了。如果您正在寻找更强大(我怀疑)和/或替代库,如joda-time,那么请查看用户的回答< !> QUOT;!tardate <> QUOT;
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;
}
}
如@Maglob所示,基本方法是使用 SimpleDateFormat.parse 。这将捕获无效的日/月组合,如2008-02-31。
然而,在实践中,由于SimpleDateFormat.parse非常自由,因此很少。您可能会关注两种行为:
日期字符串中的字符无效 令人惊讶的是,2008-02-2x将<!>“传递<!>”;作为语言环境格式= <!>的有效日期; yyyy-MM-dd <!> quot;例如。即使isLenient == false。
年份:2,3或4位? 您可能还希望强制执行4位数年,而不是允许默认的SimpleDateFormat行为(将根据您的格式是否为<!>来解释<!>“12-02-31 <!>”; yyyy- MM-dd <!>“或<!>”; yy-MM-dd <!>;)
标准库的严格解决方案
因此,完整的字符串到目前为止测试可能如下所示:正则表达式匹配的组合,然后是强制日期转换。正则表达式的技巧是使其对语言环境友好。
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();
Joda时间 - 更好的选择?
我最近听说过 joda time ,并认为我会比较。两点:
- 与SimpleDateFormat 不同,似乎更擅长对日期字符串中的无效字符进行严格处理
- 看不到用它来强制执行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;
}
您可以使用 SimpleDateFormat
例如:
boolean isLegalDate(String s) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
sdf.setLenient(false);
return sdf.parse(s, new ParsePosition(0)) != null;
}
太长了;博士
使用 严格模式 在 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
类表示仅日期值,没有时间和时区。
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-02ResolverStyle.SMART
LD:2000-02-29ResolverStyle.STRICT
错误:java.time.format.DateTimeParseException:无法解析文本“31/02/2000”:日期“2 月 31 日”无效
我们可以看到,在 ResolverStyle.LENIENT
模式下,无效日期会向前移动相同的天数。在 ResolverStyle.SMART
模式(默认),会做出合理的决定,将日期保留在月份内,并使用该月的最后一天(闰年的 2 月 29 日),因为该月没有第 31 天。这 ResolverStyle.STRICT
mode 抛出异常,抱怨没有这样的日期。
根据您的业务问题和政策,这三者都是合理的。听起来在您的情况下您希望严格模式拒绝无效日期而不是调整它。
关于 java.time
这 java.time 框架内置于 Java 8 及更高版本中。这些课程取代了麻烦的旧课程 遗产 日期时间类,例如 java.util.Date
, Calendar
, & SimpleDateFormat
.
这 乔达时间 项目,现在在 维护模式, ,建议迁移到 java.time 类。
要了解更多信息,请参阅 甲骨文教程. 。并在 Stack Overflow 上搜索许多示例和解释。规格为 JSR 310.
您可以交换 java.time 对象直接与您的数据库。用一个 JDBC驱动程序 符合 数据库连接4.2 或稍后。不需要字符串,不需要 java.sql.*
类。
从哪里获取 java.time 类?
- Java SE 8, Java SE 9, , 然后
- 内置。
- 具有捆绑实现的标准 Java API 的一部分。
- Java 9 添加了一些小功能和修复。
- Java SE 6 和 Java SE 7
- 许多 java.time 功能都向后移植到 Java 6 和 7 三十后端口.
- 安卓
- 更高版本的 Android 捆绑了 java.time 类的实现。
- 对于早期的 Android (<26), 三十ABP 项目适应 三十后端口 (上文提到的)。看 如何使用 ThreeTenABP....
这 三十额外 项目通过附加类扩展了 java.time。该项目是 java.time 未来可能添加的内容的试验场。您可能会在这里找到一些有用的类,例如 Interval
, YearWeek
, YearQuarter
, , 和 更多的.
使用标准库的替代严格解决方案是执行以下操作:
1)使用您的模式
创建一个严格的SimpleDateFormat2)尝试使用格式对象
解析用户输入的值3)如果成功,使用相同的日期格式(从(1))重新格式化由(2)得到的日期
4)将重新格式化的日期与用户输入的原始值进行比较。如果它们相等,则输入的值严格匹配您的模式。
这样,您不需要创建复杂的正则表达式 - 在我的情况下,我需要支持所有SimpleDateFormat的模式语法,而不是仅限于某些类型,例如几天,几个月和几年。
根据 @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+");
}
我建议您使用apache中的org.apache.commons.validator.GenericValidator
类。
GenericValidator.isDate(String value, String datePattern, boolean strict);
注意:strict - 是否与datePattern完全匹配。
我认为最简单的方法是将字符串转换为日期对象并将其转换回字符串。如果两个字符串仍然匹配,则给定的日期字符串就可以了。
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;
}
假设这两个都是字符串(否则它们已经是有效的日期),这是一种方式:
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
正如您所看到的,它可以很好地处理您的两个案例。
这对我很有用。 Ben提出的方法。
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;
}
}
关于使用SimpleDateFormat的两条评论。
它应该声明为静态实例 如果声明为静态访问应该同步,因为它不是线程安全的
对于每个日期解析实例化实例更好的IME。
上面的日期解析方法很好,我只是添加了新的检查现有方法,使用formater将原始日期仔细检查转换日期,因此它几乎适用于我验证的每种情况。例如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;
}
这是我使用没有外部库的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
// 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;
}
我会检查日期格式:
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");
}