Scalacheck-toolbox-datetime

scalacheck-datetime is a library for helping use datetime libraries with ScalaCheck

The motivation behind this library is to provide a simple, easy way to provide generated date and time instances that are useful to your own domain.

For SBT, you can add the dependency to your project’s build file:

resolvers += Resolver.sonatypeRepo("releases")

"com.47deg" %% "scalacheck-toolbox-datetime" % "0.3.1" % "test"

Please, visit the homepage for more information

Usage

To arbitrarily generate dates and times, you need to have the Arbitrary in scope for your date/time class. Assuming Joda Time:

import com.fortysevendeg.scalacheck.datetime.joda.ArbitraryJoda._
import org.joda.time.DateTime
import org.scalacheck.Prop.forAll

check {
  forAll { dt: DateTime => (dt.getDayOfMonth >= 1 && dt.getDayOfMonth <= 31) == res0 }
}

A note on imports

For all of the examples given in this document, you can substitute jdk8 for joda and vice-versa, depending on which library you would like to generate instances for.

Implementation

The infrastructure behind the generation of date/time instances for any given date/time library, which may take ranges into account, is done using a fairly simple typeclass, which has the type signature ScalaCheckDateTimeInfra[D, R]. That is to say, as long as there is an implicit ScalaCheckDateTimeInfra instance in scope for a given date/time type D (such as Joda’s DateTime) and a range type R (such as Joda’s Period), then the code will compile and be able to provide generated date/time instances.

As stated, currently there are two instances, ScalaCheckDateTimeInfra[DateTime, Period] for Joda Time and ScalaCheckDateTimeInfra[ZonedDateTime, Duration] for Java SE 8’s Date and Time.

Granularity

If you wish to restrict the precision of the generated instances, this library refers to that as granularity.

You can constrain the granularity to:

  • Seconds
  • Minutes
  • Hours
  • Days
  • Years

When a value is constrained, the time fields are set to zero, and the rest to the first day of the month, or day of the year. For example, if you constrain a field to be years, the generated instance will be midnight exactly, on the first day of January.

To constrain a generated type, you simply need to provide an import for the typeclass for your date/time and range, and also an import for the granularity. As an example, this time using Java SE 8's java.time package:

import java.time._
import com.fortysevendeg.scalacheck.datetime.jdk8.ArbitraryJdk8._
import com.fortysevendeg.scalacheck.datetime.jdk8.granularity.years
import org.scalacheck.Prop.forAll

check {
  forAll { zdt: ZonedDateTime =>
    (zdt.getMonth == Month.JANUARY) &&
      (zdt.getDayOfMonth == res0) &&
      (zdt.getHour == res1) &&
      (zdt.getMinute == res2) &&
      (zdt.getSecond == res3) &&
      (zdt.getNano == res4)
  }
}

Creating Ranges

You can generate date/time instances only within a certain range, using the genDateTimeWithinRange in the GenDateTime class. The function takes two parameters, the date/time instances as a base from which to generate new date/time instances, and a range for the generated instances.

If the range is positive, it will be in the future from the base date/time, negative in the past.

Showing this usage with Joda Time:

import org.joda.time._
import com.fortysevendeg.scalacheck.datetime.instances.joda._
import com.fortysevendeg.scalacheck.datetime.GenDateTime.genDateTimeWithinRange
import org.scalacheck.Prop.forAll

val from = new DateTime(2016, 1, 1, 0, 0)
val range = Period.years(1)

check {
  forAll(genDateTimeWithinRange(from, range))(dt => dt.getYear == res0)
}

Using Granularity and Ranges Together

As you would expect, it is possible to use the granularity and range concepts together. This example should not show anything surprising by now:

import org.joda.time._
import com.fortysevendeg.scalacheck.datetime.instances.joda._
import com.fortysevendeg.scalacheck.datetime.GenDateTime.genDateTimeWithinRange
import com.fortysevendeg.scalacheck.datetime.joda.granularity.days
import org.scalacheck.Prop.forAll

val from = new DateTime(2016, 1, 1, 0, 0)
val range = Period.years(1)

check {
  forAll(genDateTimeWithinRange(from, range)) { dt =>
    (dt.getYear == res0) &&
      (dt.getHourOfDay == res1) &&
      (dt.getMinuteOfHour == res2) &&
      (dt.getSecondOfMinute == res3) &&
      (dt.getMillisOfSecond == res4)
  }
}