使用不必要的时间和时区解析和格式化LocalDate

时间:2018-12-27 10:19:24

标签: java date datetime java-8 datetime-format

编辑:

我打开了一个错误,并已被Oracle确认。您可以在此处遵循分辨率:https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8216414


我正在与LDAP存储库连接,该存储库存储具有以下时间和时区的人的生日:

  • 如果生日是“ 27-12-2018”,则LDAP字符串是“ 20181227000000 + 0000”。

我找不到一种使用相同的模式解析和格式化出生日期的方法

以下代码适合格式化,但不适用于解析:

LocalDate date = LocalDate.of(2018, 12, 27);
String pattern = "yyyyMMdd'000000+0000'";
DateTimeFormatter birthdateFormat = DateTimeFormatter.ofPattern(pattern);

// Outputs correctly 20181227000000+0000
date.format(birthdateFormat);

// Throw a DatetimeParseException at index 0
date = LocalDate.parse("20181227000000+0000", birthdateFormat);

以下代码可很好地用于解析,但不能用于格式

LocalDate date = LocalDate.of(2018, 12, 27);
String pattern = "yyyyMMddkkmmssxx";
DateTimeFormatter birthdateFormat = DateTimeFormatter.ofPattern(pattern);

// Throws a UnsupportedTemporalTypeException for ClockHourOfDay not supported
// Anyway I would have an unwanted string with non zero hour, minute, second, timezone
date.format(birthdateFormat);

// Parse correctly the date to 27-12-2018
date = LocalDate.parse("20181227000000+0000", birthdateFormat);

哪种模式既可以满足解析又可以满足格式要求?

我被迫使用2种不同的模式吗?

我问是因为模式是在属性文件中配置的。我只想在此属性文件中配置1个模式。我想外部化该模式,因为LDAP不是我的项目的一部分,它是共享资源,并且我不能保证格式不能更改。

3 个答案:

答案 0 :(得分:2)

由于您的LDAP字符串具有区域格式...+0000,因此建议您使用ZonedDateTimeOffsetDateTime

此模式yyyyMMddHHmmssZZZ可以解析和格式化。

LocalDate date =  LocalDate.of(2018, 12, 27);
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmssZZZ");

格式化

  • 首先将您的LocalDate转换为ZonedDateTime / OffsetDateTime

    ZonedDateTime zonedDateTime = date.atStartOfDay(ZoneOffset.UTC);
    // or
    OffsetDateTime offsetDateTime = date.atStartOfDay().atOffset(ZoneOffset.UTC);
    
  • 然后将其格式化:

    // Both output correctly 20181227000000+0000
    System.out.println(zonedDateTime.format(formatter));
    // or 
    System.out.println(offsetDateTime.format(formatter));
    

解析

  • 首先解析ZonedDateTime / OffsetDateTime

    // Both parse correctly
    ZonedDateTime zonedDateTime = ZonedDateTime.parse("20181227000000+0000", formatter);
    // or
    OffsetDateTime offsetDateTime = OffsetDateTime.parse("20181227000000+0000", formatter);
    
  • 拥有ZonedDateTime / OffsetDateTime后,您可以像这样简单地检索LocalDate

    LocalDate date = LocalDate.from(zonedDateTime);
    // or
    LocalDate date = LocalDate.from(offsetDateTime);
    

更新

解析格式都可以简化为单行代码:

LocalDate date = LocalDate.from(formatter.parse(ldapString));

String ldapString = OffsetDateTime.of(date, LocalTime.MIN, ZoneOffset.UTC).format(formatter);

如果您仍然对上面的代码不满意,则可以将逻辑提取到实用程序方法中:

public LocalDate parseLocalDate(String ldapString) {
    return LocalDate.from(formatter.parse(ldapString));
}

public String formatLocalDate(LocalDate date) {
    return OffsetDateTime.of(date, LocalTime.MIN, ZoneOffset.UTC)
                         .format(formatter);
}

答案 1 :(得分:1)

愚蠢的简单解决方案:

    String s1 = "20181227000000+0000";
    DateTimeFormatter yyyyMMdd = DateTimeFormatter.ofPattern("yyyyMMdd");
    LocalDate date = LocalDate.parse(s1.substring(0, 8), yyyyMMdd);
    System.out.println("date = " + date);
    String s2 = date.format(yyyyMMdd) + "000000+0000";
    System.out.println("s2 = " + s2);
    System.out.println(s1.equals(s2));

答案 2 :(得分:1)

我建议:

    LocalDate date = LocalDate.of(2018, Month.DECEMBER, 27);
    String pattern = "yyyyMMddHHmmssxx";
    DateTimeFormatter birthdateFormat = DateTimeFormatter.ofPattern(pattern);

    // Outputs 20181227000000+0000
    String formatted = date.atStartOfDay(ZoneOffset.UTC).format(birthdateFormat);
    System.out.println(formatted);

    // Parses to 2018-12-27T00:00Z
    OffsetDateTime odt = OffsetDateTime.parse("20181227000000+0000", birthdateFormat);
    System.out.println(odt);
    // Validate
    if (! odt.toLocalTime().equals(LocalTime.MIN)) {
        System.out.println("Unexpected time of day: " + odt);
    }
    if (! odt.getOffset().equals(ZoneOffset.UTC)) {
        System.out.println("Unexpected time zone offset: " + odt);
    }
    // Converts to 2018-12-27
    date = odt.toLocalDate();
    System.out.println(date);

LDAP字符串代表日期,时间和UTC偏移量。好的解决方案是尊重这一点,并在格式化(将一天中的时间设置为00:00并将偏移量设置为0)并解析回所有内容时生成所有这些内容(最多也可以验证它们,以防出现任何意外情况)。如果您知道怎么做,LocalDateOffsetDateTime之间的转换就很简单。

编辑3:允许配置模式

  

...模式已在属性文件中配置...我要配置1   模式仅在此属性文件中。

     

...我不能保证格式不能更改。

要考虑到模式有一天可能不包含一天中的时间和/或没有UTC偏移量的可能性,请在上面的代码中使用以下格式化程序:

    DateTimeFormatter birthdateFormat = new DateTimeFormatterBuilder()
            .appendPattern(pattern)
            .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
            .toFormatter()
            .withZone(ZoneOffset.UTC);

这定义默认的一天中的时间(午夜)和默认的偏移量(0)。只要在LDAP字符串中定义了时间和偏移量,就不会使用默认值。

如果您觉得它太复杂了,那么使用两种配置的格式(一种用于格式化和一种用于解析)可能是您的最佳解决方案(最不烦人的解决方案)。

修改:避免类型转换

我认为以上是不错的解决方案。但是,如果您坚持要避免使用LocalDateZonedDateTimeatStartOfDay以及使用OffsetDateTimetoLocalDate DateTimeFormatter birthdateFormat = new DateTimeFormatterBuilder() .appendValue(ChronoField.YEAR, 4, 4, SignStyle.NEVER) .appendValue(ChronoField.MONTH_OF_YEAR, 2, 2, SignStyle.NEVER) .appendValue(ChronoField.DAY_OF_MONTH, 2, 2, SignStyle.NEVER) .appendLiteral("000000+0000") .toFormatter(); // Outputs 20181227000000+0000 String formatted = date.format(birthdateFormat); System.out.println(formatted); // Parses into 2018-12-27 date = LocalDate.parse("20181227000000+0000", birthdateFormat); System.out.println(date); 的转换,则可以通过以下技巧实现: / p>

yyyyMMdd'000000+0000'

我正在指定每个字段的确切宽度,以便格式化程序在解析时可以知道在字符串中将其分隔的位置。

编辑2:这是解析错误吗?

我会立即期望# Database DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'mydb', 'USER': 'myuser', 'PASSWORD': 'mypassword', 'HOST': 'localhost', 'PORT': '', 'OPTIONS': { 'sslmode': 'disable' } } } 可以同时进行格式化和解析。您可以尝试向Oracle提交错误,然后看看他们怎么说,尽管我不太乐观。