Skip to content

Commit

Permalink
Merge pull request #598 from commercetools/java-time-json-compat
Browse files Browse the repository at this point in the history
Be more lenient in accepted instants
  • Loading branch information
satabin authored Jul 25, 2024
2 parents 7b6169d + 846099d commit 501224d
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 1 deletion.
26 changes: 25 additions & 1 deletion json/json-core/src/main/scala/io/sphere/json/FromJSON.scala
Original file line number Diff line number Diff line change
Expand Up @@ -380,8 +380,32 @@ object FromJSON extends FromJSONInstances {
}

// java.time
// this formatter is used to parse instant in an extra lenient way
// similar to what the joda `DateTime` constructor accepts
// the accepted grammar for joda is described here: https://www.joda.org/joda-time/apidocs/org/joda/time/format/ISODateTimeFormat.html#dateTimeParser--
// this only supports the part where the date is specified
private val lenientInstantParser =
new time.format.DateTimeFormatterBuilder()
.appendPattern("uuuu[-MM[-dd]]")
.optionalStart()
.appendPattern("'T'[HH[:mm[:ss]]]")
.appendFraction(time.temporal.ChronoField.NANO_OF_SECOND, 0, 9, true)
.optionalStart()
.appendZoneOrOffsetId()
.optionalEnd()
.optionalEnd()
.parseDefaulting(time.temporal.ChronoField.MONTH_OF_YEAR, 1L)
.parseDefaulting(time.temporal.ChronoField.DAY_OF_MONTH, 1L)
.parseDefaulting(time.temporal.ChronoField.HOUR_OF_DAY, 0L)
.parseDefaulting(time.temporal.ChronoField.MINUTE_OF_HOUR, 0L)
.parseDefaulting(time.temporal.ChronoField.SECOND_OF_MINUTE, 0L)
.parseDefaulting(time.temporal.ChronoField.NANO_OF_SECOND, 0L)
.toFormatter()
.withZone(time.ZoneOffset.UTC)

implicit val javaInstantReader: FromJSON[time.Instant] =
jsonStringReader("Failed to parse date/time: %s")(time.Instant.parse(_))
jsonStringReader("Failed to parse date/time: %s")(s =>
time.Instant.from(lenientInstantParser.parse(s)))

implicit val javaLocalTimeReader: FromJSON[time.LocalTime] =
jsonStringReader("Failed to parse time: %s")(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package io.sphere.json
import org.json4s.JString
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import java.time.Instant
import cats.data.Validated.Valid

class DateTimeParsingSpec extends AnyWordSpec with Matchers {

Expand Down Expand Up @@ -107,4 +109,65 @@ class DateTimeParsingSpec extends AnyWordSpec with Matchers {
javaInstantReader.read(jsonDateStringWith(secondOfTheMinute = "87")) shouldNot beValid
}
}

// ported from https://github.com/JodaOrg/joda-time/blob/4a1402a47cab4636bf4c73d42a62bfa80c1535ca/src/test/java/org/joda/time/convert/TestStringConverter.java#L114-L156
// ensures that we accept similar patterns as joda when parsing instants
"parsing a Java instant" should {
"accept a full instant with milliseconds and offset" in {
javaInstantReader.read(JString("2004-06-09T12:24:48.501+08:00")) shouldBe Valid(
Instant.parse("2004-06-09T04:24:48.501Z"))
}

"accept a year with offset" in {
javaInstantReader.read(JString("2004T+08:00")) shouldBe Valid(
Instant.parse("2004-01-01T00:00:00+08:00"))
}

"accept a year month with offset" in {
javaInstantReader.read(JString("2004-06T+08:00")) shouldBe Valid(
Instant.parse("2004-06-01T00:00:00+08:00"))
}

"accept a year month day with offset" in {
javaInstantReader.read(JString("2004-06-09T+08:00")) shouldBe Valid(
Instant.parse("2004-06-09T00:00:00+08:00"))
}

"accept a year month day with hour and offset" in {
javaInstantReader.read(JString("2004-06-09T12+08:00")) shouldBe Valid(
Instant.parse("2004-06-09T04:00:00Z"))
}

"accept a year month day with hour, minute, and offset" in {
javaInstantReader.read(JString("2004-06-09T12:24+08:00")) shouldBe Valid(
Instant.parse("2004-06-09T04:24:00Z"))
}

"accept a year month day with hour, minute, second, and offset" in {
javaInstantReader.read(JString("2004-06-09T12:24:48+08:00")) shouldBe Valid(
Instant.parse("2004-06-09T04:24:48Z"))
}

"accept a year month day with hour, fraction, and offset" in {
javaInstantReader.read(JString("2004-06-09T12.5+08:00")) shouldBe Valid(
Instant.parse("2004-06-09T04:00:00.5Z"))
}

"accept a year month day with hour, minute, fraction, and offset" in {
javaInstantReader.read(JString("2004-06-09T12:24.5+08:00")) shouldBe Valid(
Instant.parse("2004-06-09T04:24:00.5Z"))
}

"accept a year month day with hour, minute, second, fraction, and offset" in {
javaInstantReader.read(JString("2004-06-09T12:24:48.5+08:00")) shouldBe Valid(
Instant.parse("2004-06-09T04:24:48.5Z"))
}

"accept a year month day with hour, minute, second, fraction, but no offset" in {
javaInstantReader.read(JString("2004-06-09T12:24:48.501")) shouldBe Valid(
Instant.parse("2004-06-09T12:24:48.501Z"))
}

}

}

0 comments on commit 501224d

Please sign in to comment.