也许我在这里滥用API或遗漏了一些信息。这是API的错误还是设计缺陷?
最近我使用java.time
(在scala&#39的REPL中但并不感兴趣)parse
和format
日期也包含偏移但没有时间像这样:"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)
时,这将导致异常:
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
,并在Instant
上调用parse
后手动构建f
:
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
格式化程序当前正在工作,至少我希望它能够工作,因此能够在往返场景中工作。
答案 0 :(得分:4)
您可以使用DateTimeFormatterBuilder
和define default values作为时间字段。代码是用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
,而不是解析它。这是因为Instant
只表示来自Unix纪元的计数,并且任何内置格式化程序(ISO_INSTANT
除外)都试图从中获取与日期/时间相关的字段(例如年,月,日,小时,分钟等,但Instant
没有它的概念,因为它只代表了一个纳秒的时间,它需要附加到一个时区来获得日期/时间相关字段 - 更多详细信息:https://stackoverflow.com/a/27483371/9552515
如果您认为这是一个错误,go on and register it at Oracle。 但我相信这是设计的。我上面链接的答案来自java.time API的创建者,并加强了这种行为,并且他还解释了这一点this comment。如果这是一个设计缺陷,这是一个意见问题(和IMO,它不是)。话虽如此,我不认为这可能会改变。