使用新的Java8DateTimeFormatter进行严格的日期解析
我有一个简单的问题:我想严格地解析格式为使用新的Java8DateTimeFormatter进行严格的日期解析,java,parsing,date,datetime,java-8,Java,Parsing,Date,Datetime,Java 8,我有一个简单的问题:我想严格地解析格式为“yyyyMMdd”的Java字符串,以便“19800229”是有效日期,但“19820229”不是。假设这些是正常公历的广告日期 我试图使用JDK 8中新的java.time包来解决这个问题,但事实证明它比预期的更复杂。我目前的代码是: private static final DateTimeFormatter FORMAT = DateTimeFormatter .ofPattern("yyyyMMdd").withChronolog
“yyyyMMdd”
的Java字符串,以便“19800229”
是有效日期,但“19820229”
不是。假设这些是正常公历的广告日期
我试图使用JDK 8中新的java.time
包来解决这个问题,但事实证明它比预期的更复杂。我目前的代码是:
private static final DateTimeFormatter FORMAT = DateTimeFormatter
.ofPattern("yyyyMMdd").withChronology(IsoChronology.INSTANCE)
.withResolverStyle(STRICT);
public static LocalDate parse(String yyyyMMdd) {
return LocalDate.parse(yyyyMMdd, FORMAT);
}
但是,解析有效日期(如“19800228”)会产生我认为不可理解的错误:
java.time.format.DateTimeParseException:无法分析文本“19820228”:无法从TemporalAccessor获取LocalDate:{MonthOfYear=2,DayOfMonth=28,YearOfEra=1982},ISO类型为java.time.format.parsed
如何使用
java.time.format.DateTimeFormatter
来解决我的简单用例?我正在编辑,以通过使用使用DateTimeFormatterBuilder创建的自定义格式化程序来限制哪种字符串被认为是有效的
public class DateFormmaterTest {
static DateTimeFormatter CUSTOM_BASIC_ISO_DATE = new DateTimeFormatterBuilder()
.parseCaseInsensitive().appendValue(YEAR, 4)
.appendValue(MONTH_OF_YEAR, 2).appendValue(DAY_OF_MONTH, 2)
.optionalStart().toFormatter()
.withResolverStyle(ResolverStyle.STRICT)
.withChronology(IsoChronology.INSTANCE);
public static void main(String[] args) {
LocalDate date1 = LocalDate.parse("19800228-5000",
CUSTOM_BASIC_ISO_DATE);
System.out.println(date1);
}
}
1982年2月29日无效,将引发以下事件:
Caused by: java.time.DateTimeException: Invalid date 'February 29' as '1982' is not a leap year
at java.time.LocalDate.create(LocalDate.java:429)
Exception in thread "main" java.time.format.DateTimeParseException: Text '19800228-5000' could not be parsed, unparsed text found at index 8.
日期19800228-5000将与基本ISO日期一起使用,因为它允许您不希望允许的可选偏移量。我的自定义_BASIC_ISO_日期格式化程序将不允许这样做,并抛出以下内容:
Caused by: java.time.DateTimeException: Invalid date 'February 29' as '1982' is not a leap year
at java.time.LocalDate.create(LocalDate.java:429)
Exception in thread "main" java.time.format.DateTimeParseException: Text '19800228-5000' could not be parsed, unparsed text found at index 8.
注意,如果您确定字符串长度yyyyMMdd,那么您可以始终使用前8个字符的子字符串来消除对解析器的需求。然而,这是两件不同的事情。解析程序将在输入时标记无效的日期格式,子字符串当然会去掉多余的字符。尝试使用格式“uummdd”代替。Java8使用
uuu
表示年份,而不是yyyy
。在Java 8中,yyyy
表示“纪年”(公元前或公元后),错误消息抱怨MonthOfYear、DayOfMonth和YearOfEra没有足够的信息来构造日期,因为纪年未知
要解决此问题,请在格式字符串中使用uuu
,例如模式(“uummdd”)的日期时间格式
或者,如果要继续使用yyyy
,可以设置默认纪元,例如:
private static final DateTimeFormatter FORMAT = new DateTimeFormatterBuilder()
.appendPattern("yyyyMMdd")
.parseDefaulting(ChronoField.ERA, 1 /* era is AD */)
.toFormatter()
.withChronology(IsoChronology.INSTANCE)
.withResolverStyle(ResolverStyle.STRICT);
我最近也有类似的要求。我正在检修一个遗留应用程序,该应用程序是在Java5成为最新版本时编写的。我需要继续解析特定的日期和日期/时间格式,但我想要旧的
SimpleDateFormat
类所提供的“严格”行为。我还需要在java.util.Date
之间进行转换,以与旧的现有代码兼容
下面是我的实用程序类和单元测试:
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.ResolverStyle;
import java.time.temporal.ChronoField;
import java.time.temporal.TemporalAccessor;
import java.util.Date;
/**
* Static utility class for converting from java.util.Date to modern Java time classes,
* and some predefined DateTimeFormatter instances to handle formats used in data files
* that this application processes.
*/
public class DateTimeUtils {
private DateTimeUtils() {}
// Jim Tough - 2020-09-14
// These two date/time formats are part of the data file specification.
// We are stuck with these formats now.
private static final String DTF_YYYYMMDDHHMMSS_FORMAT_STRING = "uuuuMMdd HH:mm:ss";
private static final String DTF_YYYYMMDDHHMM_FORMAT_STRING = "uuuuMMddHHmm";
private static final String DTF_YYYYMMDD_FORMAT_STRING = "uuuuMMdd";
public static final DateTimeFormatter DTF_YYYYMMDDHHMMSS =
DateTimeFormatter.ofPattern(DTF_YYYYMMDDHHMMSS_FORMAT_STRING)
.withResolverStyle(ResolverStyle.STRICT);
public static final DateTimeFormatter DTF_YYYYMMDDHHMM =
DateTimeFormatter.ofPattern(DTF_YYYYMMDDHHMM_FORMAT_STRING)
.withResolverStyle(ResolverStyle.STRICT);
public static final DateTimeFormatter DTF_YYYYMMDD =
DateTimeFormatter.ofPattern(DTF_YYYYMMDD_FORMAT_STRING)
.withResolverFields(ChronoField.YEAR, ChronoField.MONTH_OF_YEAR, ChronoField.DAY_OF_MONTH)
.withResolverStyle(ResolverStyle.STRICT);
//-------------------------------------------------------------------------
public static LocalDate convertToLocalDateViaInstant(Date dateToConvert) {
if (dateToConvert == null) {
return null;
}
return dateToConvert.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDate();
}
public static LocalDateTime convertToLocalDateTimeViaInstant(Date dateToConvert) {
if (dateToConvert == null) {
return null;
}
return dateToConvert
.toInstant()
.atZone(ZoneId.systemDefault())
.toLocalDateTime();
}
public static Date convertToDateViaInstant(LocalDate dateToConvert) {
if (dateToConvert == null) {
return null;
}
return java.util.Date.from(dateToConvert.atStartOfDay()
.atZone(ZoneId.systemDefault())
.toInstant());
}
public static Date convertToDateViaInstant(LocalDateTime dateToConvert) {
if (dateToConvert == null) {
return null;
}
return java.util.Date.from(
dateToConvert.atZone(ZoneId.systemDefault()).toInstant()
);
}
//-------------------------------------------------------------------------
/**
* Parse the supplied string using the supplied {@code DateTimeFormatter} and return the result
* as a {@code LocalDate}
* @param dtf Non-null
* @param s String, or null value
* @return {@code LocalDate}, or null if the supplied string parameter is null
* @throws java.time.DateTimeException when supplied string cannot be parsed by the
* {@code DateTimeFormatter} and converted to a {@code LocalDate}
*/
public static LocalDate parseAsLocalDate(DateTimeFormatter dtf, String s) {
if (s == null) {
return null;
}
TemporalAccessor ta = dtf.parse(s);
return LocalDate.from(ta);
}
/**
* Parse the supplied string using the supplied {@code DateTimeFormatter} and return the result
* as a {@code LocalDateTime}
* @param dtf Non-null
* @param s String, or null value
* @return {@code LocalDateTime}, or null if the supplied string parameter is null
* @throws java.time.DateTimeException when supplied string cannot be parsed by the
* {@code DateTimeFormatter} and converted to a {@code LocalDateTime}
*/
public static LocalDateTime parseAsLocalDateTime(DateTimeFormatter dtf, String s) {
if (s == null) {
return null;
}
TemporalAccessor ta = dtf.parse(s);
return LocalDateTime.from(ta);
}
}
单元测试
import org.junit.jupiter.api.Test;
import java.time.DateTimeException;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.Month;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author JTough
*/
public class DateTimeUtilsTest {
@Test
void testConvertToLocalDateViaInstant_nominal() {
LocalDate ld = DateTimeUtils.convertToLocalDateViaInstant(new java.util.Date());
assertNotNull(ld);
}
@Test
void testConvertToLocalDateViaInstantWithNullValue() {
assertNull(DateTimeUtils.convertToLocalDateViaInstant(null));
}
//-------------------------------------------------------------------------
@Test
void testConvertToLocalDateTimeViaInstant_nominal() {
LocalDateTime ldt = DateTimeUtils.convertToLocalDateTimeViaInstant(new java.util.Date());
assertNotNull(ldt);
}
@Test
void testConvertToLocalDateTimeViaInstantWithNullValue() {
assertNull(DateTimeUtils.convertToLocalDateTimeViaInstant(null));
}
//-------------------------------------------------------------------------
@Test
void testConvertToDateViaInstant_nominal_A() {
java.util.Date d = DateTimeUtils.convertToDateViaInstant(LocalDate.now());
assertNotNull(d);
}
@Test
void testConvertToDateViaInstantWithNullValueA() {
assertNull(DateTimeUtils.convertToDateViaInstant((LocalDate)null));
}
//-------------------------------------------------------------------------
@Test
void testConvertToDateViaInstant_nominal_B() {
java.util.Date d = DateTimeUtils.convertToDateViaInstant(LocalDateTime.now());
assertNotNull(d);
}
@Test
void testConvertToDateViaInstantWithNullValueB() {
assertNull(DateTimeUtils.convertToDateViaInstant((LocalDate)null));
assertNull(DateTimeUtils.convertToDateViaInstant((LocalDateTime)null));
}
//-------------------------------------------------------------------------
@Test
void testParseAsLocalDate_nominal_A() {
LocalDate ld = DateTimeUtils.parseAsLocalDate(DateTimeUtils.DTF_YYYYMMDD, "20201225");
assertNotNull(ld);
assertEquals(2020, ld.getYear());
assertEquals(Month.DECEMBER, ld.getMonth());
assertEquals(25, ld.getDayOfMonth());
}
@Test
void testParseAsLocalDate_nominal_B() {
LocalDate ld = DateTimeUtils.parseAsLocalDate(DateTimeUtils.DTF_YYYYMMDD, "20200229");
assertNotNull(ld);
assertEquals(2020, ld.getYear());
assertEquals(Month.FEBRUARY, ld.getMonth());
assertEquals(29, ld.getDayOfMonth());
}
@Test
void testParseAsLocalDateWithInvalidDayOfMonth() {
assertThrows(DateTimeException.class, ()->DateTimeUtils.parseAsLocalDate(DateTimeUtils.DTF_YYYYMMDD, "20200230"));
}
@Test
void testParseAsLocalDateWithGarbageInputString() {
assertThrows(DateTimeException.class, ()->DateTimeUtils.parseAsLocalDate(DateTimeUtils.DTF_YYYYMMDD, "garbage"));
}
//-------------------------------------------------------------------------
@Test
void testParseAsLocalDateTime_withoutseconds_nominal_A() {
LocalDateTime ldt = DateTimeUtils.parseAsLocalDateTime(DateTimeUtils.DTF_YYYYMMDDHHMM, "202012251359");
assertNotNull(ldt);
assertEquals(2020, ldt.getYear());
assertEquals(Month.DECEMBER, ldt.getMonth());
assertEquals(25, ldt.getDayOfMonth());
assertEquals(13, ldt.getHour());
assertEquals(59, ldt.getMinute());
}
@Test
void testParseAsLocalDateTime_withseconds_nominal_A() {
LocalDateTime ldt = DateTimeUtils.parseAsLocalDateTime(DateTimeUtils.DTF_YYYYMMDDHHMMSS, "20201225 13:59:33");
assertNotNull(ldt);
assertEquals(2020, ldt.getYear());
assertEquals(Month.DECEMBER, ldt.getMonth());
assertEquals(25, ldt.getDayOfMonth());
assertEquals(13, ldt.getHour());
assertEquals(59, ldt.getMinute());
assertEquals(33, ldt.getSecond());
}
@Test
void testParseAsLocalDateTime_withoutseconds_nominal_B() {
LocalDateTime ldt = DateTimeUtils.parseAsLocalDateTime(DateTimeUtils.DTF_YYYYMMDDHHMM, "202002291234");
assertNotNull(ldt);
assertEquals(2020, ldt.getYear());
assertEquals(Month.FEBRUARY, ldt.getMonth());
assertEquals(29, ldt.getDayOfMonth());
assertEquals(12, ldt.getHour());
assertEquals(34, ldt.getMinute());
}
@Test
void testParseAsLocalDateTime_withseconds_nominal_B() {
LocalDateTime ldt = DateTimeUtils.parseAsLocalDateTime(DateTimeUtils.DTF_YYYYMMDDHHMMSS, "20200229 12:34:56");
assertNotNull(ldt);
assertEquals(2020, ldt.getYear());
assertEquals(Month.FEBRUARY, ldt.getMonth());
assertEquals(29, ldt.getDayOfMonth());
assertEquals(12, ldt.getHour());
assertEquals(34, ldt.getMinute());
assertEquals(56, ldt.getSecond());
}
@Test
void testParseAsLocalDateTimeWithInvalidDayOfMonth() {
assertThrows(DateTimeException.class, ()->DateTimeUtils.parseAsLocalDateTime(DateTimeUtils.DTF_YYYYMMDDHHMM, "202002301234"));
}
@Test
void testParseAsLocalDateTimeWithInvalidHour() {
assertThrows(DateTimeException.class, ()->DateTimeUtils.parseAsLocalDateTime(DateTimeUtils.DTF_YYYYMMDDHHMM, "202012252500"));
}
@Test
void testParseAsLocalDateTimeWithInvalidMinute() {
assertThrows(DateTimeException.class, ()->DateTimeUtils.parseAsLocalDateTime(DateTimeUtils.DTF_YYYYMMDDHHMM, "202012251366"));
}
@Test
void testParseAsLocalDateTimeWithGarbageInputString() {
assertThrows(DateTimeException.class, ()->DateTimeUtils.parseAsLocalDateTime(DateTimeUtils.DTF_YYYYMMDD, "garbage"));
}
}
谢谢@techtrip。对不起,正如你所指出的,在这两种情况下,我的意思都是2/29。我之所以避免使用
DateTimeFormatter.BASIC\u ISO\u DATE
,是因为它有一个我不想解析的可选部分。我可以做一个没有可选部分的等效格式化程序吗?我修改了答案以考虑到你的最后评论。请看一看!够好了,@techtrip。这也是我发现的。注意:您可以去掉parsecaseminsensitive()
——不需要,因为yyyyMMdd没有大小写问题;startopical()
,因为您没有开始任何可选的操作。太棒了,谢谢!这一切都是全新的,文档当然可以稍微好一点,特别是解析器样式和解析器之间的区别。这让我有点糊涂了。而不是TemporalAccessor ta=dtf.parse(s)
然后返回LocalDate.from(ta)
您只需执行返回LocalDate.parse(s,dtf)代码>。多解释一下需要注意的问题可能会有所帮助。你的代码很好。