Skip to content

Commit

Permalink
feat: lenient local date parser
Browse files Browse the repository at this point in the history
in java.time parsing a LocalDate is stricter than in joda
we want to guaranteebackwards compatibility

BEC-254
  • Loading branch information
nolledge committed Sep 30, 2024
1 parent a83904c commit 3b05fac
Show file tree
Hide file tree
Showing 2 changed files with 65 additions and 2 deletions.
19 changes: 17 additions & 2 deletions json/json-core/src/main/scala/io/sphere/json/FromJSON.scala
Original file line number Diff line number Diff line change
Expand Up @@ -403,6 +403,21 @@ object FromJSON extends FromJSONInstances {
.parseDefaulting(time.temporal.ChronoField.OFFSET_SECONDS, 0L)
.toFormatter()

private val lenientLocalDateParser =
new time.format.DateTimeFormatterBuilder()
.appendValue(time.temporal.ChronoField.YEAR, 1, 9, java.time.format.SignStyle.NORMAL)
.optionalStart()
.appendLiteral('-')
.appendValue(time.temporal.ChronoField.MONTH_OF_YEAR, 1, 2, java.time.format.SignStyle.NORMAL)
.optionalStart()
.appendLiteral('-')
.appendValue(time.temporal.ChronoField.DAY_OF_MONTH, 1, 2, java.time.format.SignStyle.NORMAL)
.optionalEnd()
.optionalEnd()
.parseDefaulting(time.temporal.ChronoField.MONTH_OF_YEAR, 1L)
.parseDefaulting(time.temporal.ChronoField.DAY_OF_MONTH, 1L)
.toFormatter()

implicit val javaInstantReader: FromJSON[time.Instant] =
jsonStringReader("Failed to parse date/time: %s")(s =>
time.Instant.from(lenientInstantParser.parse(s)))
Expand All @@ -412,8 +427,8 @@ object FromJSON extends FromJSONInstances {
time.LocalTime.parse(_, time.format.DateTimeFormatter.ISO_LOCAL_TIME))

implicit val javaLocalDateReader: FromJSON[time.LocalDate] =
jsonStringReader("Failed to parse date: %s")(
time.LocalDate.parse(_, time.format.DateTimeFormatter.ISO_LOCAL_DATE))
jsonStringReader("Failed to parse date: %s")(s =>
time.LocalDate.from(lenientLocalDateParser.parse(s)))

implicit val javaYearMonthReader: FromJSON[time.YearMonth] =
jsonStringReader("Failed to parse year/month: %s")(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
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 JodaJavaLocalDateCompatSpec extends AnyWordSpec with Matchers {

val jodaReader = FromJSON.dateReader
val javaReader = FromJSON.javaLocalDateReader
def jsonDateStringWith(
year: String = "2035",
dayOfTheMonth: String = "23",
monthOfTheYear: String = "11"): JString = JString(s"$year-$monthOfTheYear-${dayOfTheMonth}")

private def test(value: JString) =
(jodaReader.read(value), javaReader.read(value)) match {
case (Valid(jodaDate), Valid(javaDate)) =>
jodaDate.getYear shouldBe javaDate.getYear
jodaDate.getMonthOfYear shouldBe javaDate.getMonthValue
jodaDate.getDayOfMonth shouldBe javaDate.getDayOfMonth
case (jodaDate, javaDate) =>
fail(s"invalid date. joda: $jodaDate, java: $javaDate")
}

"parsing a LocalDate" should {
"accept two digit years" in {
test(jsonDateStringWith(year = "50"))
}
"accept year zero" in {
test(JString("0-10-31"))
}
"accept no day set" in {
test(JString("2024-09"))
}
"accept up to nine digit years" in {
(1 to 9).foreach { l =>
val year = List.fill(l)("1").mkString("")
test(jsonDateStringWith(year = year))
}
}
"accept a year with leading zero" in {
test(jsonDateStringWith(year = "02020"))
}
}
}

0 comments on commit 3b05fac

Please sign in to comment.