Question
Maybe I am misusing the API here or missing some piece of information. Is this a bug or a design flaw of the API?
Follow up
Lately I was using java.time
(in scala's REPL but it is not really of interest) to parse
and format
a date which also contains an offset but no time like this: "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
So far so good, when trying to create an Instant.from(t)
this will lead to an exception:
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
This is expected or at least plausible to me since the required ChronoFields
are missing. A fix to this particular scenario could be to provide a sane default time like LocalDate.MIDNIGHT
and construct an Instant
manually after calling parse
on 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
Well, this fixes my current problem but feels unsatisfying. Then I wondered if the API would be able to do round trips in general when using the formatters that are time preserving. Therefore I came up with the list of available java.time.format.DateTimeFormatter
instances and threw them on a small test function to verify this:
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)
This produces the following output:
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)
Thus only the ISO_INSTANT
formatter is currently working as at least I would expect it to work and is thus able to work in a round-trip scenario.