Java parse()忽略模式中的字符数
我试图解析一个日期字符串,它可以有不同的格式。 即使字符串不应该与第二个模式匹配,它也会以某种方式匹配,因此返回错误的日期 这是我的密码:Java parse()忽略模式中的字符数,java,parsing,date,simpledateformat,Java,Parsing,Date,Simpledateformat,我试图解析一个日期字符串,它可以有不同的格式。 即使字符串不应该与第二个模式匹配,它也会以某种方式匹配,因此返回错误的日期 这是我的密码: import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; public class Start { public static void main(String[] args) { SimpleDateFor
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class Start {
public static void main(String[] args) {
SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy");
try{
System.out.println(sdf.format(parseDate("2013-01-31")));
} catch(ParseException ex){
System.out.println("Unable to parse");
}
}
public static Date parseDate(String dateString) throws ParseException{
SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy");
SimpleDateFormat sdf2 = new SimpleDateFormat("dd-MM-yyyy");
SimpleDateFormat sdf3 = new SimpleDateFormat("yyyy-MM-dd");
Date parsedDate;
try {
parsedDate = sdf.parse(dateString);
} catch (ParseException ex) {
try{
parsedDate = sdf2.parse(dateString);
} catch (ParseException ex2){
parsedDate = sdf3.parse(dateString);
}
}
return parsedDate;
}
}
通过输入2013-01-31
我得到了输出05.07.0036
如果我试图解析31-01-2013
或31.01.2013
我会得到预期的31.01.2013
我认识到,如果我这样设置模式,程序将给出完全相同的输出:
SimpleDateFormat sdf = new SimpleDateFormat("d.M.y");
SimpleDateFormat sdf2 = new SimpleDateFormat("d-M-y");
SimpleDateFormat sdf3 = new SimpleDateFormat("y-M-d");
为什么它忽略了我的模式中的字符数?它记录在
SimpleDataFormat
javadoc:
对于格式化,图案字母的数量是最小位数,较短的数字将被零填充到此数量对于解析,除非需要分隔两个相邻字段,否则将忽略模式字母的数量。
谢谢@Teetoo。
这帮助我找到了解决问题的方法:
如果希望解析函数与模式完全匹配,则必须将SimpleDataFormat的“lenient”(SimpleDataFormat.setLenient
)设置为false
:
SimpleDateFormat sdf = new SimpleDateFormat("d.M.y");
sdf.setLenient(false);
SimpleDateFormat sdf2 = new SimpleDateFormat("d-M-y");
sdf2.setLenient(false);
SimpleDateFormat sdf3 = new SimpleDateFormat("y-M-d");
sdf3.setLenient(false);
如果我只为每个片段使用一个模式字母,那么它仍然会解析日期,但它会认识到2013年不可能是当天,因此它与第二个模式不匹配。
结合长度检查,我得到了我想要的结果。一个解决方法是使用正则表达式测试yyyy-MM-dd格式:
public static Date parseDate(String dateString) throws ParseException {
SimpleDateFormat sdf = new SimpleDateFormat("dd.MM.yyyy");
SimpleDateFormat sdf2 = new SimpleDateFormat("dd-MM-yyyy");
SimpleDateFormat sdf3 = new SimpleDateFormat("yyyy-MM-dd");
Date parsedDate;
try {
if (dateString.matches("\\d{4}-\\d{2}-\\d{2}")) {
parsedDate = sdf3.parse(dateString);
} else {
throw new ParseException("", 0);
}
} catch (ParseException ex) {
try {
parsedDate = sdf2.parse(dateString);
} catch (ParseException ex2) {
parsedDate = sdf.parse(dateString);
}
}
return parsedDate;
}
SimpleDataFormat存在严重问题。默认的宽大设置会产生垃圾答案,我想不出宽大有什么好处。宽大的设置不是一种可靠的方法,无法对人为输入的日期变化做出合理的解释。这不应该是默认设置 如果可以,请使用DateTimeFormatter,参见Ole V.V.的答案。 这种较新的方法具有优越性,可以生成线程安全且不可变的实例。如果在线程之间共享SimpleDataFormat实例,则它们可以生成无错误或异常的垃圾结果。遗憾的是,我建议的实现继承了这种不良行为 禁用宽容只是解决方案的一部分。您仍然可以得到在测试中很难捕捉到的垃圾结果。有关示例,请参见下面代码中的注释 这里是SimpleDataFormat的一个扩展,它强制进行严格的模式匹配。这应该是该类的默认行为
import java.text.DateFormatSymbols;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
/**
* Extension of SimpleDateFormat that implements strict matching.
* parse(text) will only return a Date if text exactly matches the
* pattern.
*
* This is needed because SimpleDateFormat does not enforce strict
* matching. First there is the lenient setting, which is true
* by default. This allows text that does not match the pattern and
* garbage to be interpreted as valid date/time information. For example,
* parsing "2010-09-01" using the format "yyyyMMdd" yields the date
* 2009/12/09! Is this bizarre interpretation the ninth day of the
* zeroth month of 2010? If you are dealing with inputs that are not
* strictly formatted, you WILL get bad results. You can override lenient
* with setLenient(false), but this strangeness should not be the default.
*
* Second, setLenient(false) still does not strictly interpret the pattern.
* For example "2010/01/5" will match "yyyy/MM/dd". And data disagreement like
* "1999/2011" for the pattern "yyyy/yyyy" is tolerated (yielding 2011).
*
* Third, setLenient(false) still allows garbage after the pattern match.
* For example: "20100901" and "20100901andGarbage" will both match "yyyyMMdd".
*
* This class restricts this undesirable behavior, and makes parse() and
* format() functional inverses, which is what you would expect. Thus
* text.equals(format(parse(text))) when parse returns a non-null result.
*
* @author zobell
*
*/
public class StrictSimpleDateFormat extends SimpleDateFormat {
protected boolean strict = true;
public StrictSimpleDateFormat() {
super();
setStrict(true);
}
public StrictSimpleDateFormat(String pattern) {
super(pattern);
setStrict(true);
}
public StrictSimpleDateFormat(String pattern, DateFormatSymbols formatSymbols) {
super(pattern, formatSymbols);
setStrict(true);
}
public StrictSimpleDateFormat(String pattern, Locale locale) {
super(pattern, locale);
setStrict(true);
}
/**
* Set the strict setting. If strict == true (the default)
* then parsing requires an exact match to the pattern. Setting
* strict = false will tolerate text after the pattern match.
* @param strict
*/
public void setStrict(boolean strict) {
this.strict = strict;
// strict with lenient does not make sense. Really lenient does
// not make sense in any case.
if (strict)
setLenient(false);
}
public boolean getStrict() {
return strict;
}
/**
* Parse text to a Date. Exact match of the pattern is required.
* Parse and format are now inverse functions, so this is
* required to be true for valid text date information:
* text.equals(format(parse(text))
* @param text
* @param pos
* @return
*/
@Override
public Date parse(String text, ParsePosition pos) {
Date d = super.parse(text, pos);
if (strict && d != null) {
String format = this.format(d);
if (pos.getIndex() + format.length() != text.length() ||
!text.endsWith(format)) {
d = null; // Not exact match
}
}
return d;
}
}
java.time
time是现代的java日期和时间API,其行为方式与您预期的相同。因此,这是一个简单的代码翻译问题:
private static final DateTimeFormatter formatter1 = DateTimeFormatter.ofPattern("dd.MM.yyyy");
private static final DateTimeFormatter formatter2 = DateTimeFormatter.ofPattern("dd-MM-yyyy");
private static final DateTimeFormatter formatter3 = DateTimeFormatter.ofPattern("yyyy-MM-dd");
public static LocalDate parseDate(String dateString) {
LocalDate parsedDate;
try {
parsedDate = LocalDate.parse(dateString, formatter1);
} catch (DateTimeParseException dtpe1) {
try {
parsedDate = LocalDate.parse(dateString, formatter2);
} catch (DateTimeParseException dtpe2) {
parsedDate = LocalDate.parse(dateString, formatter3);
}
}
return parsedDate;
}
(我将格式化程序放在您的方法之外,这样就不会为每次调用重新创建它们。如果愿意,您可以将它们放在内部。)
让我们试一下:
LocalDate date = parseDate("2013-01-31");
System.out.println(date);
输出为:
2013-01-31
对于数字DateTimeFormatter.ofPattern
将图案字母的数量作为最小字段宽度。此外,它还假设月份的日期永远不会超过两位数。因此,当尝试格式dd-MM-yyyy
时,它成功地将20
解析为一个月中的一天,然后抛出一个DateTimeParseException
,因为20
后面没有连字符(破折号)。然后该方法继续尝试下一个格式化程序
你的代码出了什么问题
您尝试使用的SimpleDataFormat
类是出了名的麻烦,幸运的是它早已过时。你只遇到了很多问题中的一个。重复文档中关于如何处理Teetoo答案中的数字的重要句子:
对于解析,将忽略模式字母的数量,除非
需要分隔两个相邻字段
因此,new SimpleDateFormat(“dd-MM-yyyy”)
愉快地将2013
解析为月份,01
解析为月份,31
解析为年份。接下来我们应该预料到它会抛出一个例外,因为在第31年1月没有2013天。但是带有默认设置的SimpleDataFormat
不能做到这一点。你观察到的结果是,在接下来的几个月和几年中,它只会持续计算天数,最后在36年7月5日结束,五年半之后
链接
解释如何使用java.time。也谢谢。我在oracle.com类文档中没有看到这一点。这是一个隐藏得很好的陷阱,我们在测试中没有发现。真是个糟糕的设计。另一个不好的特性是默认情况下它们是“宽松”的,所以您需要设置lenient(false)。否则,他们会将各种垃圾解释为有效的日期/时间信息。很好的实现。但我不会用
setStrict
来对抗setLenient
,因为这会产生冗余。最好只使用setLenient
super方法,让构造函数调用setLenient(false)
,解析方法应该调用!IsLenent()
而不是使用strict
。对于冗余,存在不一致的可能性。想象:setStrict(true);宽大(真)代码>现在是严格还是宽松?setStrict(true)覆盖并隐藏宽松设置。设置setLenient(false)不会更改外部行为,但它可以允许内部parse()在奇怪的日期成功,然后我的parse()将拒绝该日期。setStrict(false)返回标准SimpleDataFormat行为,并选择任何宽松的变化。我的目标是对SimpleDataFormat进行真正的更改,使用户可以恢复以前的不良行为。我希望strict=true是默认值,因为它符合人们的理解。如果您拥有Java 8或更高版本,并且不受依赖SimpleDateFormat的遗留代码的限制,DateTimeFormatter是一种很好的方法。较新的类生成不可变的实例,并且是线程安全的。在线程之间共享SimpleDataFormat对象可以