使用新的Java 8 DateTimeFormatter进行严格的日期解析

时间:2014-10-15 23:07:15

标签: java parsing date datetime java-8

我有一个简单的问题:我想严格解析格式为"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来解决我的简单用例?

4 个答案:

答案 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"));
    }

}