使用不必要的时间和时区解析和格式化 LocalDate
Parsing and formatting LocalDate with unnecessary time and timezone
编辑:
我开了一个bug,已经被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 不是我项目的一部分,它是一个共享资源,我不能保证格式不会改变。
愚蠢的简单解决方案:
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));
由于您的 LDAP 字符串具有分区格式 ...+0000
,我建议使用 ZonedDateTime
或 OffsetDateTime
。
此模式 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);
}
我建议:
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)并解析所有这些(最好也验证它们以捕捉是否出现任何意外) .如果您知道如何进行 LocalDate
和 OffsetDateTime
之间的转换,那就很简单了。
编辑 3:允许配置模式
… the pattern is configured in a property file… I want to configure 1
pattern only in this property file.
… I have no guarantee that the format cannot change.
考虑到模式可能某一天不包含一天中的时间的可能性and/or没有UTC偏移量在上面的代码中使用这个格式化程序:
DateTimeFormatter birthdateFormat = new DateTimeFormatterBuilder()
.appendPattern(pattern)
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.toFormatter()
.withZone(ZoneOffset.UTC);
这定义了一天中的默认时间(午夜)和默认偏移量 (0)。只要在来自 LDAP 的字符串中定义了时间和偏移量,就不会使用默认值。
如果你觉得它变得太复杂,使用两种配置的格式,一种用于格式化,一种用于解析,可能是最适合你的解决方案(最不烦人的解决方案)。
编辑:避免类型转换
我认为上面的解决方案很好。但是,如果您坚持使用 atStartOfDay
避免从 LocalDate
到 ZonedDateTime
的转换以及使用 toLocalDate
从 OffsetDateTime
的转换,则可以通过以下 hack:
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);
我正在指定每个字段的确切宽度,以便格式化程序在解析时可以知道在字符串中的何处分隔它们。
编辑 2:这是解析中的错误吗?
我会立即期望 yyyyMMdd'000000+0000'
可以用于格式化和解析。您可以尝试向 Oracle 提交错误,看看他们怎么说,但我不会太乐观。
编辑:
我开了一个bug,已经被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 不是我项目的一部分,它是一个共享资源,我不能保证格式不会改变。
愚蠢的简单解决方案:
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));
由于您的 LDAP 字符串具有分区格式 ...+0000
,我建议使用 ZonedDateTime
或 OffsetDateTime
。
此模式 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);
}
我建议:
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)并解析所有这些(最好也验证它们以捕捉是否出现任何意外) .如果您知道如何进行 LocalDate
和 OffsetDateTime
之间的转换,那就很简单了。
编辑 3:允许配置模式
… the pattern is configured in a property file… I want to configure 1 pattern only in this property file.
… I have no guarantee that the format cannot change.
考虑到模式可能某一天不包含一天中的时间的可能性and/or没有UTC偏移量在上面的代码中使用这个格式化程序:
DateTimeFormatter birthdateFormat = new DateTimeFormatterBuilder()
.appendPattern(pattern)
.parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
.toFormatter()
.withZone(ZoneOffset.UTC);
这定义了一天中的默认时间(午夜)和默认偏移量 (0)。只要在来自 LDAP 的字符串中定义了时间和偏移量,就不会使用默认值。
如果你觉得它变得太复杂,使用两种配置的格式,一种用于格式化,一种用于解析,可能是最适合你的解决方案(最不烦人的解决方案)。
编辑:避免类型转换
我认为上面的解决方案很好。但是,如果您坚持使用 atStartOfDay
避免从 LocalDate
到 ZonedDateTime
的转换以及使用 toLocalDate
从 OffsetDateTime
的转换,则可以通过以下 hack:
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);
我正在指定每个字段的确切宽度,以便格式化程序在解析时可以知道在字符串中的何处分隔它们。
编辑 2:这是解析中的错误吗?
我会立即期望 yyyyMMdd'000000+0000'
可以用于格式化和解析。您可以尝试向 Oracle 提交错误,看看他们怎么说,但我不会太乐观。