2

问题

也许我在这里误用了 API 或遗漏了一些信息。这是 API 的错误还是设计缺陷?

跟进

最近我正在使用java.time(在 scala 的 REPL 中,但它并不真正感兴趣)parseformat一个日期,它也包含一个偏移量但没有这样的时间:"2018-03-24+01:00"

scala> :paste
// Entering paste mode (ctrl-D to finish)

import java.time._
import java.time.temporal._
import java.time.format._
import java.util._

val f = DateTimeFormatter.ISO_OFFSET_DATE
  .withLocale(Locale.GERMAN)
  .withZone(ZoneId.of("GMT"))

val t = f.parse("2018-03-24+01:00")

// Exiting paste mode, now interpreting.

import java.time._
import java.time.temporal._
import java.time.format._
import java.util._
f: java.time.format.DateTimeFormatter = ParseCaseSensitive(false)(Value(Year,4,10,EXCEEDS_PAD)'-'Value(MonthOfYear,2)'-'Value(DayOfMonth,2))Offset(+HH:MM:ss,'Z')
t: java.time.temporal.TemporalAccessor = {OffsetSeconds=3600},ISO,GMT resolved to 2018-03-24

到目前为止一切顺利,当尝试创建Instant.from(t)this 时会导致异常:

scala> Instant.from(t)
java.time.DateTimeException: Unable to obtain Instant from TemporalAccessor: {OffsetSeconds=3600},ISO,GMT resolved to 2018-03-24 of type java.time.format.Parsed
  at java.time.Instant.from(Instant.java:378)
  ... 28 elided
Caused by: java.time.temporal.UnsupportedTemporalTypeException: Unsupported field: InstantSeconds
  at java.time.format.Parsed.getLong(Parsed.java:203)
  at java.time.Instant.from(Instant.java:373)
  ... 28 more

ChronoFields由于缺少所需的内容,因此这是可以预期的或至少对我来说是合理的。对此特定场景的修复可能是提供一个健全的默认时间,例如在调用后手动LocalDate.MIDNIGHT构建一个:Instantparsef

scala> :paste
// Entering paste mode (ctrl-D to finish)

val i = OffsetDateTime.of(
  LocalDate.from(t),
  LocalTime.MIDNIGHT,
  ZoneOffset.from(t)
).toInstant


// Exiting paste mode, now interpreting.

i: java.time.Instant = 2018-03-23T23:00:00Z

好吧,这解决了我当前的问题,但感觉不满意。然后我想知道在使用保存时间的格式化程序时,API 是否能够进行往返。因此,我想出了可用java.time.format.DateTimeFormatter实例的列表,并将它们放在一个小测试函数上来验证这一点:

val dfts = scala.collection.immutable.Map(
  "BASIC_ISO_DATE"       -> DateTimeFormatter.BASIC_ISO_DATE,
  "ISO_INSTANT"          -> DateTimeFormatter.ISO_INSTANT,
  "ISO_LOCAL_TIME"       -> DateTimeFormatter.ISO_LOCAL_TIME,
  "ISO_OFFSET_TIME"      -> DateTimeFormatter.ISO_OFFSET_TIME,
  "ISO_WEEK_DATE"        -> DateTimeFormatter.ISO_WEEK_DATE,
  "ISO_DATE"             -> DateTimeFormatter.ISO_DATE,
  "ISO_LOCAL_DATE"       -> DateTimeFormatter.ISO_LOCAL_DATE,
  "ISO_OFFSET_DATE"      -> DateTimeFormatter.ISO_OFFSET_DATE,
  "ISO_ORDINAL_DATE"     -> DateTimeFormatter.ISO_ORDINAL_DATE,
  "ISO_ZONED_DATE_TIME"  -> DateTimeFormatter.ISO_ZONED_DATE_TIME,
  "ISO_DATE_TIME"        -> DateTimeFormatter.ISO_DATE_TIME,
  "ISO_LOCAL_DATE_TIME"  -> DateTimeFormatter.ISO_LOCAL_DATE_TIME,
  "ISO_OFFSET_DATE_TIME" -> DateTimeFormatter.ISO_OFFSET_DATE_TIME,
  "ISO_TIME"             -> DateTimeFormatter.ISO_TIME,
  "RFC_1123_DATE_TIME"   -> DateTimeFormatter.RFC_1123_DATE_TIME
)

def test(f: DateTimeFormatter) = scala.util.Try {
  Instant.from(f.parse(f.format(Instant.now)))
}

dfts.mapValues(test)
  .mapValues(_.toString)
  .mapValues(_.replace("java.time.temporal.UnsupportedTemporalTypeException", "j.t.t.UTTE"))
  .map{ case (k,v) => f"$k%20s : $v" }
  .foreach(println)

这会产生以下输出:

            ISO_DATE : Failure(j.t.t.UTTE: Unsupported field: Year)
 ISO_ZONED_DATE_TIME : Failure(j.t.t.UTTE: Unsupported field: Year)
      BASIC_ISO_DATE : Failure(j.t.t.UTTE: Unsupported field: Year)
      ISO_LOCAL_TIME : Failure(j.t.t.UTTE: Unsupported field: HourOfDay)
    ISO_ORDINAL_DATE : Failure(j.t.t.UTTE: Unsupported field: Year)
      ISO_LOCAL_DATE : Failure(j.t.t.UTTE: Unsupported field: Year)
       ISO_DATE_TIME : Failure(j.t.t.UTTE: Unsupported field: Year)
         ISO_INSTANT : Success(2018-03-25T07:48:48.360Z)
 ISO_LOCAL_DATE_TIME : Failure(j.t.t.UTTE: Unsupported field: Year)
     ISO_OFFSET_TIME : Failure(j.t.t.UTTE: Unsupported field: HourOfDay)
ISO_OFFSET_DATE_TIME : Failure(j.t.t.UTTE: Unsupported field: Year)
  RFC_1123_DATE_TIME : Failure(j.t.t.UTTE: Unsupported field: DayOfMonth)
     ISO_OFFSET_DATE : Failure(j.t.t.UTTE: Unsupported field: Year)
            ISO_TIME : Failure(j.t.t.UTTE: Unsupported field: HourOfDay)
       ISO_WEEK_DATE : Failure(j.t.t.UTTE: Unsupported field: WeekBasedYear)

因此,目前只有ISO_INSTANT格式化程序正在工作,至少我希望它能够工作,因此能够在往返场景中工作。

4

1 回答 1

4

您可以使用 aDateTimeFormatterBuilder并为时间字段定义默认值。代码是用 Java 编写的,但应该很容易将其移植到 Scala:

DateTimeFormatter fmt = new DateTimeFormatterBuilder()
    // date and offset
    .append(DateTimeFormatter.ISO_OFFSET_DATE)
    // default value for hour
    .parseDefaulting(ChronoField.HOUR_OF_DAY, 0)
    // default value for minute
    .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0)
    // create formatter
    .toFormatter(Locale.GERMAN);

Instant instant = Instant.from(fmt.parse("2018-03-24+01:00")); // 2018-03-23T23:00:00Z

输入没有任何与时间相关的字段(小时、分钟等),因此在解析它时,您需要为自己设置时间,或者使用特定的LocalTime(如您所做的),或者通过将默认值定义为以上。

没有“时间保存”格式化程序之类的东西。当您将日期格式化为字符串时,它只打印格式化程序中配置的字段。将此字符串解析回日期/时间类时,您需要以某种方式添加缺少的字段。

而且您尝试所有内置格式化程序的代码实际上无法格式化Instant而不是解析它。这是因为 anInstant仅代表 Unix 纪元的计数,并且任何内置格式化程序(除了ISO_INSTANT)都尝试从中获取与日期/时间相关的字段(例如年、月、日、小时、分钟等),但是Instant没有它的概念,因为它仅代表从纪元开始的纳秒计数,并且需要将其附加到时区以获取与日期/时间相关的字段-此处有更多详细信息:https ://stackoverflow.com/a /27483371/9552515

如果您认为这是一个错误,请继续在 Oracle 进行注册。但我相信这是设计使然。我在上面链接的答案来自 java.time API 的创建者并强化了这种行为,并且还有这个评论,他解释了这一点。如果这是一个设计缺陷,那是一个见仁见智的问题(而 IMO,它不是)。话虽如此,我认为这不太可能改变。

于 2018-03-26T13:28:27.140 回答