在过去的几年里,我成功地使用了SimpleDateFormat
。我使用它构建了一堆时间实用程序类。
当我遇到SimpleDateFormat
(SDF)不是线程安全的问题时,我花了最后几天重构这些实用程序类,现在在内部使用DateTimeFormatter
(DTF)。由于这两个班级的时间模式几乎完全相同,因此这种转变在当时似乎是一个好主意。
我现在遇到问题EpochMillis
(自1970-01-01T00:00:00Z
以来的毫秒数):虽然SDF会例如将使用10:30
解析的HH:mm
解释为1970-01-01T10:30:00Z
,DTF不会这样做。 DTF可以使用10:30
来解析获得LocalTime
所需的ZonedDateTime
,而不是EpochMillis
。
我理解java.time
的对象遵循不同的哲学; Date
,Time
和Zoned
个对象分开保存。但是,为了让我的实用程序类像以前一样解释所有字符串,我需要能够动态地为所有缺少的对象定义默认解析。我试着用
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
builder.parseDefaulting(ChronoField.YEAR, 1970);
builder.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1);
builder.parseDefaulting(ChronoField.DAY_OF_MONTH, 1);
builder.parseDefaulting(ChronoField.HOUR_OF_DAY, 0);
builder.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0);
builder.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0);
builder.append(DateTimeFormatter.ofPattern(pattern));
但这并不适用于所有模式。它似乎只允许pattern
中未定义的参数的默认值。 有没有办法测试ChronoField
中定义了哪些pattern
然后有选择地添加默认值?
或者,我试过
TemporalAccessor temporal = formatter.parseBest(time,
ZonedDateTime::from,
LocalDateTime::from,
LocalDate::from,
LocalTime::from,
YearMonth::from,
Year::from,
Month::from);
if ( temporal instanceof ZonedDateTime )
return (ZonedDateTime)temporal;
if ( temporal instanceof LocalDateTime )
return ((LocalDateTime)temporal).atZone(formatter.getZone());
if ( temporal instanceof LocalDate )
return ((LocalDate)temporal).atStartOfDay().atZone(formatter.getZone());
if ( temporal instanceof LocalTime )
return ((LocalTime)temporal).atDate(LocalDate.of(1970, 1, 1)).atZone(formatter.getZone());
if ( temporal instanceof YearMonth )
return ((YearMonth)temporal).atDay(1).atStartOfDay().atZone(formatter.getZone());
if ( temporal instanceof Year )
return ((Year)temporal).atMonth(1).atDay(1).atStartOfDay().atZone(formatter.getZone());
if ( temporal instanceof Month )
return Year.of(1970).atMonth((Month)temporal).atDay(1).atStartOfDay().atZone(formatter.getZone());
也未涵盖所有案件。
启用动态日期/时间/日期时间/区域日期时间解析的最佳策略是什么?
答案 0 :(得分:3)
<强>的Java-8溶液强>
更改构建器内部解析指令的顺序,使得默认指令全部发生在模式指令之后。
例如使用这个静态代码(好吧,你的方法将使用基于实例的不同模式的组合,根本不具备性能):
private static final DateTimeFormatter FLEXIBLE_FORMATTER;
static {
DateTimeFormatterBuilder builder = new DateTimeFormatterBuilder();
builder.appendPattern("MM/dd");
builder.parseDefaulting(ChronoField.YEAR_OF_ERA, 1970);
builder.parseDefaulting(ChronoField.MONTH_OF_YEAR, 1);
builder.parseDefaulting(ChronoField.DAY_OF_MONTH, 1);
builder.parseDefaulting(ChronoField.HOUR_OF_DAY, 0);
builder.parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0);
builder.parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0);
FLEXIBLE_FORMATTER = builder.toFormatter();
}
<强>原因:强>
方法parseDefaulting(...)
以一种有趣的方式工作,就像嵌入式解析器一样。这意味着,如果尚未解析该字段,则此方法将为定义的字段注入默认值。后面的模式指令试图解析相同的字段(这里:模式“MM / dd”的MONTH_OF_YEAR和输入“07/13”),但可能有不同的值。如果是,则复合解析器将中止,因为它已找到相同字段的矛盾值并且无法解决冲突(解析值7,但默认值为1)。
official API包含以下声明:
在解析期间,将检查解析的当前状态。如果 指定的字段没有关联值,因为它还没有 在该点成功解析,然后指定的值为 注入解析结果。注射是立即的,因此 字段值对将对任何后续元素可见 格式化。因此,这种方法通常在结束时调用 助洗剂。
我们应该把它读作:
在对同一字段的任何解析指令之前,不要调用parseDefaulting(...)
。
旁注1:
基于parseBest(...)
的替代方法更糟糕,因为
它不包括缺少分钟或仅缺少年份的所有组合(MonthDay?)等。默认值解决方案更灵活。
表面上不值得讨论。
旁注2:
我宁愿让整个实现对顺序不敏感,因为这个细节就像许多用户的陷阱。并且可以通过在我自己的时间库Time4J中选择默认值的基于地图的实现来避免此陷阱,其中默认值指令的顺序根本不重要,因为仅注入默认值解析完所有字段后。 Time4J还提供了一个专门的答案:“启用动态日期/时间/日期时间/区域日期时间解析的最佳策略是什么?”提供MultiFormatParser。
<强>更新强>
在Java-8中:使用ChronoField.YEAR_OF_ERA
代替ChronoField.YEAR
,因为该模式包含字母“y”(=年代,与预感格里高利年不同)。否则解析引擎除了解析年代之外还将注入默认年份,并且会发现冲突。一个真正的陷阱。就在昨天,我在我的时间库中为月份字段修复了similar pitfall,该字段存在两种略有不同的变体。
答案 1 :(得分:0)
我使用了新的java.time包,需要时间来适应它。但经过学习曲线后,我不得不说它绝对是非常全面和强大的解决方案,可能会取代Joda时间库和其他先前的解决方案。我编写了自己的实用程序,用于解析字符串到日期。我写了一篇总结文章,解释了我是如何实现一个将未知格式的String解析为Date的功能。它可能会有所帮助。以下是文章的链接:Java 8 java.time package: parsing any string to date