我有一个简单的问题:我想严格解析格式为"yyyyMMdd"
的Java字符串,因此"19800229"
是有效日期,但"19820229"
不是。{1}}。假设这些是正常公历的AD日期。
我正在尝试使用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:Text' 19820228'无法解析:无法从TemporalAccessor获取LocalDate:{MonthOfYear = 2,DayOfMonth = 28,YearOfEra = 1982},ISO类型为java.time.format.Parsed
如何使用java.time.format.DateTimeFormatter
来解决我的简单用例?
答案 0 :(得分:17)
Java 8使用uuuu
年,而不是yyyy
。在Java 8中,yyyy
意味着"年代#34; (BC或AD)和错误消息抱怨MonthOfYear,DayOfMonth和YearOfEra不足以构建日期,因为时代未知。
要解决此问题,请在格式字符串中使用uuuu
,例如DateTimeFormatter.ofPattern("uuuuMMdd")
或者,如果您想继续使用yyyy
,您可以设置默认时代,例如
private static final DateTimeFormatter FORMAT = new DateTimeFormatterBuilder()
.appendPattern("yyyyMMdd")
.parseDefaulting(ChronoField.ERA, 1 /* era is AD */)
.toFormatter()
.withChronology(IsoChronology.INSTANCE)
.withResolverStyle(ResolverStyle.STRICT);
答案 1 :(得分:2)
我正在编辑以使用使用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);
}
}
2/29/1982无效,会抛出以下内容:
Caused by: java.time.DateTimeException: Invalid date 'February 29' as '1982' is not a leap year
at java.time.LocalDate.create(LocalDate.java:429)
日期19800228-5000可以使用BASIC_ISO_DATE,因为它允许您不希望允许的可选偏移量。我的CUSTOM_BASIC_ISO_DATE格式化程序不允许这样做并抛出以下内容:
Exception in thread "main" java.time.format.DateTimeParseException: Text '19800228-5000' could not be parsed, unparsed text found at index 8.
注意,如果您确定字符串长度yyyyMMdd,则可以始终使用前8个字符的子字符串来消除对解析器的需要。然而,这是两件不同的事情。解析器将在输入上标记无效的日期格式,子字符串当然只会删除额外的字符。
答案 2 :(得分:1)
请尝试使用格式“uuuuMMdd”。
答案 3 :(得分:1)
我最近有类似的要求。我正在检查Java 5是最新版本时回写的旧应用程序。我需要继续解析特定的日期和日期/时间格式,但我希望使用与旧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"));
}
}