java - 从 SimpleDateFormat 移动到 DateTimeFormatter 时出现的问题

标签 java simpledateformat

在过去的几年里,我一直在成功地使用 SimpleDateFormat。我使用它构建了一堆时间实用程序类。

当我遇到 SimpleDateFormat (SDF) 不是线程安全的问题时,我花了最近几天重构这些实用程序类以在内部使用 DateTimeFormatter (DTF)现在。由于两个类(class)的时间模式几乎相同,这种转变在当时看来是个好主意。

我现在在获取 EpochMillis(自 1970-01-01T00:00:00Z 以来的毫秒数)时遇到问题:将使用 HH:mm 解析的 10:30 解释为 1970-01-01T10:30:00Z,DTF 不这样做。 DTF 可以使用 10:30 来解析 LocalTime,但不能使用 ZonedDateTime 来获取 EpochMillis

我理解 java.time 的对象遵循不同的哲学; DateTimeZoned 对象分开保存。但是,为了让我的实用程序类像以前一样解释所有字符串,我需要能够动态地为所有丢失的对象定义默认解析。我试着用

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 中定义的参数使用默认值。 有没有办法测试 pattern 中定义了哪些 ChronoField,然后有选择地添加默认值?

或者,我试过

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());

也没有涵盖所有情况。

启用动态日期/时间/日期时间/区域日期时间解析的最佳策略是什么?

最佳答案

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(...) 方法以一种有趣的方式工作,即类似于嵌入式解析器。这意味着,如果尚未解析该字段,则此方法将为定义的字段注入(inject)默认值。后面的模式指令尝试解析相同的字段(此处:模式“MM/dd”和输入“07/13”的 MONTH_OF_YEAR)但可能具有不同的值。如果是这样,那么复合解析器将中止,因为它发现相同字段的矛盾值并且无法解决冲突(解析值 7,但默认值 1)。

official API包含以下通知:

During parsing, the current state of the parse is inspected. If the specified field has no associated value, because it has not been parsed successfully at that point, then the specified value is injected into the parse result. Injection is immediate, thus the field-value pair will be visible to any subsequent elements in the formatter. As such, this method is normally called at the end of the builder.

我们应该这样理解:

不要在同一字段的任何解析指令之前调用 parseDefaulting(...)

旁注 1:

您基于 parseBest(...) 的替代方法更糟糕,因为

  • 它不涵盖所有缺少分钟或仅缺少年份(MonthDay?)等的组合。默认值解决方案更灵活。

  • 性能方面不值得讨论。

旁注 2:

我宁愿让整个实现顺序不敏感,因为这个细节对许多用户来说就像一个陷阱。并且可以通过为默认值选择基于映射的实现来避免此陷阱,就像在我自己​​的时间库中所做的那样 Time4J default-value-instructions 的顺序根本不重要,因为注入(inject)默认值只发生在所有字段都被解析之后。 Time4J 还专门回答了“启用动态日期/时间/日期-时间/区域-日期-时间解析的最佳策略是什么?”通过提供 MultiFormatParser .

更新:

在 Java-8 中:使用 ChronoField.YEAR_OF_ERA 而不是 ChronoField.YEAR 因为模式包含字母“y”(=year-of-era,不是与 proleptic 公历年相同)。否则,解析引擎除了解析的时代年份之外,还会注入(inject)预期的默认年份,并会发现冲突。一个真正的陷阱。就在昨天,我修好了一个 similar pitfall在我的时间库中,月份字段存在两种略有不同的变化。

关于java - 从 SimpleDateFormat 移动到 DateTimeFormatter 时出现的问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40557933/

相关文章:

java - 用于 PDF 到文本转换的开源 Java 库

java - 不使用 SimpleDateFormat 将字符串解析为日期?

Java DateFormat 未正确解析我的 SimpleDateFormat

java - 将 java.time.LocalDate 转换为 java.util.Date

java - SimpleDateFormat 在解析 "YYYY-MM-dd HH:mm"时产生错误的日​​期时间

Java:将日期值保存到数据库时出错(Simpledateformat)

java - Logstash JDBC插件 - 并非所有字段都从oracle进入elasticsearch

java - Java 7 中集成测试文件系统相关代码

java - 使用 asm java 时出现验证错误

java - 从java中的sql文件将模式加载到h2中