Better ISO-8601 support
paulpdaniels opened this issue · 1 comments
According to the section on timezone designators: https://en.wikipedia.org/wiki/ISO_8601
The following should be valid:
+hh
+hhmm
+hh:mm
However only (1) and (3) are actually supported. I tried the base java.time
and circe
and they had the same failure cases, so it's at least consistent but I don't think it is fully correct.
In fact, zio-json implements none of them, because currently supported format is Z
or +hh:mm[:ss]
or -hh:mm[:ss]
.
So it will not reject seconds in time-zone values, and that is how the default parser for time-zones works in Java and corresponding implementations for Scala.js and Scala Native:
scala> import java.time.*
scala> import java.time.format.*
scala> val fmtXXX = DateTimeFormatter.ofPattern("XXX")
val fmtXXX: java.time.format.DateTimeFormatter = Offset(+HH:MM,'Z')
scala> val fmtX = DateTimeFormatter.ofPattern("X")
val fmtX: java.time.format.DateTimeFormatter = Offset(+HHmm,'Z')
scala> val fmtZ = DateTimeFormatter.ofPattern("Z")
val fmtZ: java.time.format.DateTimeFormatter = Offset(+HHMM,'+0000')
scala> val tz = ZoneOffset.ofHoursMinutesSeconds(12, 34, 56)
val tz: java.time.ZoneOffset = +12:34:56
scala> tz.toString
val res0: String = +12:34:56
scala> fmtXXX.format(tz)
val res1: String = +12:34
scala> fmtX.format(tz)
val res2: String = +1234
scala> fmtZ.format(tz)
val res3: String = +1234
scala> ZoneOffset.of("+12:34:56")
val res4: java.time.ZoneOffset = +12:34:56
scala> fmtXXX.parse("+12:34:56")
java.time.format.DateTimeParseException: Text '+12:34:56' could not be parsed, unparsed text found at index 6
at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2055)
at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1880)
... 32 elided
scala> fmtX.parse("+12:34:56")
java.time.format.DateTimeParseException: Text '+12:34:56' could not be parsed, unparsed text found at index 3
at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2055)
at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1880)
... 32 elided
scala> fmtZ.parse("+12:34:56")
java.time.format.DateTimeParseException: Text '+12:34:56' could not be parsed at index 0
at java.base/java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:2052)
at java.base/java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1880)
... 32 elided
But you can override default decoders/encoders for any type (including java.time.*
types), and try to parse by fmtX
and fmtXXX
formatters sequentially in case of error:
scala> def iso8601_timezone_parser(s: String): Try[ZoneOffset] = Try(fmtX.parse(s)).orElse(Try(fmtXXX.parse(s))).flatMap(x => Try(ZoneOffset.from(x))).filter(_ => s != "Z")
def iso8601_timezone_parser(s: String): util.Try[java.time.ZoneOffset]
scala> iso8601_timezone_parser("Z")
val res5: util.Try[java.time.ZoneOffset] = Failure(java.util.NoSuchElementException: Predicate does not hold for Z)
scala> iso8601_timezone_parser("+12")
val res6: util.Try[java.time.ZoneOffset] = Success(+12:00)
scala> iso8601_timezone_parser("+12:34")
val res7: util.Try[java.time.ZoneOffset] = Success(+12:34)
scala> iso8601_timezone_parser("+1234")
val res8: util.Try[java.time.ZoneOffset] = Success(+12:34)
scala> iso8601_timezone_parser("+123456")
val res9: util.Try[java.time.ZoneOffset] = Failure(java.time.format.DateTimeParseException: Text '+123456' could not be parsed at index 0)
scala> iso8601_timezone_parser("+12:34:56")
val res10: util.Try[java.time.ZoneOffset] = Failure(java.time.format.DateTimeParseException: Text '+12:34:56' could not be parsed, unparsed text found at index 6)