Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better ISO-8601 support #1036

Open
paulpdaniels opened this issue Oct 19, 2023 · 1 comment
Open

Better ISO-8601 support #1036

paulpdaniels opened this issue Oct 19, 2023 · 1 comment

Comments

@paulpdaniels
Copy link
Contributor

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.

@plokhotnyuk
Copy link
Contributor

plokhotnyuk commented Oct 19, 2023

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants