GitHub - tmccarthy/intime: Scala integration for the java.time classes
Libraries for integration between the java.time
classes and common Scala libraries.
intime-coreprovides integration with the core Scala libraryintime-catsprovides instances for the Cats FP libraryintime-argonautprovides encoders and decoders for the Argonaut JSON libraryintime-scalacheckprovides instances for the Scalacheck property-based-testing library
Add the following to your build.sbt file:
val intimeVersion = "2.2.0" libraryDependencies += "au.id.tmm.intime" %% "intime-core" % intimeVersion libraryDependencies += "au.id.tmm.intime" %% "intime-cats" % intimeVersion // Cats integration libraryDependencies += "au.id.tmm.intime" %% "intime-argonaut" % intimeVersion // Argonaut integration libraryDependencies += "au.id.tmm.intime" %% "intime-scalacheck" % intimeVersion % Test // Scalacheck integration
intime-core
intime-core adds integrations with the Scala standard library. Add it to your project with:
libraryDependencies += "au.id.tmm.intime" %% "intime-core" % "1.0.2"
Ordering instances for ordered classes
intime-core provides Ordering instances for all classes in the java.time package for which an ordering can be
defined. This includes the most common classes like Instant and LocalDate.
import java.time._ import au.id.tmm.intime.std.implicits._ val dates: List[LocalDate] = List( LocalDate.of(2003, 3, 23), LocalDate.of(2015, 3, 29), LocalDate.of(2007, 4, 28), ) dates.sorted // Sorts list of dates
PartialOrdering instances for Period
Period presents some problems when it comes to defining an Ordering instance, as any two instances cannot
necessarily be compared (is 1 month longer or shorter than 30 days?). intime-core provides a PartialOrdering for
each, handling those cases where an ordering can be computed.
Overloaded operators
intime-core provides overloaded operators for arithmetic and comparison operations on java.time classes:
import java.time._ import au.id.tmm.intime.std.implicits._ LocalDate.of(1999, 6, 20) + Period.ofDays(3) // 1999-06-23 Instant.EPOCH - Duration.ofSeconds(5) // 1969-12-31T23:59:55Z Period.ofDays(5) * 3 // P15D - Duration.ofHours(42) // PT-42H Duration.ofDays(30) / 10 // P3D Instant.MAX > Instant.EPOCH // true
intime-cats
intime-cats adds integrations with Cats. Add it to your project with:
libraryDependencies += "au.id.tmm.intime" %% "intime-cats" % "1.0.2"
All instances are tested with discipline.
Hash and Show instances
intime-cats provides Hash and Show instances for all classes in java.time.
import java.time._ import au.id.tmm.intime.cats.implicits._ import cats.syntax.show._ import cats.syntax.eq._ LocalDate.of(1999, 6, 20).show // 1999-06-20 Instant.EPOCH === Instant.EPOCH // true
Order and PartialOrder instances
intime-cats uses the orderings in intime-core to define Cats Order instances (PartialOrder for Period).
import java.time._ import au.id.tmm.intime.cats.implicits._ import cats.syntax.partialOrder._ LocalDate.of(2003, 3, 23) < LocalDate.of(2015, 3, 29) // true Period.ofYears(1) < Period.ofMonths(13) // true Period.ofDays(30) partialCompare Period.ofMonths(1) // NaN
CommutativeGroup instances for Period and Duration
import java.time._ import au.id.tmm.intime.cats.implicits._ import cats.syntax.group._ Duration.ofDays(1) |+| Duration.ofHours(2) // P1DT2H Duration.ofDays(1) |-| Duration.ofHours(2) // PT22H
intime-scalacheck
intime-scalacheck adds integrations with Scalacheck. Add it to your project
with:
libraryDependencies += "au.id.tmm.intime" %% "intime-scalacheck" % "1.0.2" % "test"
Arbitrary instances
intime-scalacheck provides instances of Arbitrary for all classes in java.time. These can be used to generate
arbitrary instances for property-based testing.
import java.time._ import au.id.tmm.intime.scalacheck._ import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks._ forAll { localDate: LocalDate => assert(localDate.plusDays(1) isAfter localDate) }
Generators for "sensible" datetime values
intime-scalacheck provides generators for "sensible" values of java.time classes. The generated values are all
between 1900 and 2100, allowing property-based-tests that don't have to worry about peculiarities like
year zero or durations that overflow.
import java.time._ import au.id.tmm.intime.scalacheck._ import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks._ forAll(genSensibleLocalDate) { localDate: LocalDate => assert(localDate.getYear >= 1900) }
Choose instances
intime-scalacheck provides instances of Choose, which let you define your own generators that produce values within
a range.
import java.time._ import au.id.tmm.intime.scalacheck._ import org.scalacheck.Gen import org.scalatestplus.scalacheck.ScalaCheckDrivenPropertyChecks._ val rangeGenerator: Gen[LocalDate] = Gen.choose( min = LocalDate.of(2019, 5, 30), max = LocalDate.of(2019, 7, 14), ) forAll(rangeGenerator) { localDate: LocalDate => assert(localDate.getYear == 2019) }
Shrink instances
intime-scalacheck provides instances for Shrink, which Scalacheck will use to try to identify the smallest value for
which a test fails. These are used automatically as long as you have the import:
import au.id.tmm.intime.scalacheck._
intime-argonaut
intime-argonaut provides integration with the Argonaut library for JSON
handling. Add it to your project with:
libraryDependencies += "au.id.tmm.intime" %% "intime-argonaut" % "1.0.2"
Standard encoders and decoders
intime-argonaut defines EncodeJson and DecodeJson instances for all classes in the java.time package. They are
encoded and decoded to JSON strings according to the most obvious format (see
StandardCodecs.
import java.time._ import au.id.tmm.intime.argonaut._ import argonaut.Argonaut._ LocalDate.of(2019, 7, 14).asJson // jString("2019-07-14") jString("2019-07-14").as[LocalDate] // DecodeResult.ok(LocalDate.of(2019, 7, 14))
Custom encoders and decoders
intime-argonaut allows for the definition of custom EncodeJson and DecodeJson instances using instances of
DateTimeFormatter.
import java.time._ import au.id.tmm.intime.argonaut._ import argonaut.Argonaut._ val formatter = DateTimeFormatter.ofPattern("MM-dd-uuuu") implicit val customCodec = DateTimeFormatterCodecs.localDateCodecFrom(formatter) LocalDate.of(2019, 7, 14).asJson // jString("07-14-2019") jString("07-14-2019").as[LocalDate] // DecodeResult.ok(LocalDate.of(2019, 7, 14))
Known issues
- In Java 8, the standard codec for
ZonedDateTimewill fail to decode when the zone isGMT. This is fixed in Java 11. - In Java 8, the standard codec for
Durationwill drop the negative sign for durations between 0 and -1 seconds. This is fixed in Java 11.