dot*_*win 5 java simpledateformat
SimpleDateFormat过去几年我一直在成功使用。我使用它构建了一堆时间实用程序类。
由于我遇到了SimpleDateFormat(SDF) 不是线程安全的问题,因此我在过去几天中将这些实用程序类重构为DateTimeFormatter现在内部使用(DTF)。由于两个班级的时间模式几乎相同,因此这种转变在当时似乎是个好主意。
我现在在获取EpochMillis(自 以来的毫秒数1970-01-01T00:00:00Z)时遇到问题:虽然 SDF 会例如解释10:30使用HH:mmas解析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));
Run Code Online (Sandbox Code Playgroud)
但这并不适用于所有模式。它似乎只允许未在pattern. 有没有办法测试ChronoField定义了哪些spattern然后有选择地添加默认值?
或者,我试过
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());
Run Code Online (Sandbox Code Playgroud)
这也没有涵盖所有情况。
启用动态日期/时间/日期时间/区域日期时间解析的最佳策略是什么?
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();
}
Run Code Online (Sandbox Code Playgroud)
原因:
该方法parseDefaulting(...)以一种有趣的方式工作,即像嵌入式解析器。这意味着,如果该字段尚未解析,则此方法将为定义的字段注入默认值。后面的模式指令尝试解析相同的字段(此处:模式“MM/dd”的 MONTH_OF_YEAR 并输入“07/13”),但可能具有不同的值。如果是这样,那么复合解析器将中止,因为它发现同一字段的矛盾值并且无法解决冲突(解析值 7,但默认值 1)。
官方API包含以下通知:
在解析期间,会检查解析的当前状态。如果指定字段没有关联值,因为此时尚未解析成功,则将指定值注入到解析结果中。注入是立即的,因此字段值对对于格式化程序中的任何后续元素都是可见的。因此,该方法通常在构建器末尾调用。
我们应该将其读作:
parseDefaulting(...)不要在同一字段的任何解析指令之前调用。
旁注1:
您的替代方法基于parseBest(...)更糟糕,因为
它不涵盖所有缺少分钟或仅缺少年份(月日?)等的组合。默认值解决方案更加灵活。
性能方面不值得讨论。
旁注2:
我宁愿让整个实现顺序不敏感,因为这个细节对许多用户来说就像一个陷阱。并且可以通过选择基于映射的默认值实现来避免这个陷阱,就像我自己的时间库Time4J中所做的那样,其中默认值指令的顺序根本不重要,因为注入默认值仅在所有字段都已完成之后才发生。被解析了。Time4J 还针对“启用动态日期/时间/日期-时间/区域-日期-时间解析的最佳策略是什么?”提供了专门的答案。通过提供MultiFormatParser。
更新:
在 Java-8 中:使用ChronoField.YEAR_OF_ERAnot ,ChronoField.YEAR因为该模式包含字母“y”(=year-of-era,与 proleptic Gregorian Year 不同)。否则,除了解析的纪元之外,解析引擎还将注入预推的默认年份,并会发现冲突。一个真正的陷阱。就在昨天,我在我的时间库中修复了月份字段的类似陷阱,该陷阱存在两种略有不同的变体。