-
Notifications
You must be signed in to change notification settings - Fork 7
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
Java 8 Date Time support: typeclasses #6
Changes from all commits
8665c50
f7ebf65
ee189fe
a6c8929
6bf2a7c
d9e9980
e9d2707
1c340b0
3b5a9f8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,4 @@ | ||
*~ | ||
target/* | ||
project/target/* | ||
project/project/target/* |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,13 @@ | ||
language: scala | ||
scala: | ||
- 2.11.7 | ||
|
||
script: | ||
- sbt ++$TRAVIS_SCALA_VERSION clean coverage test | ||
|
||
jdk: | ||
- oraclejdk8 | ||
|
||
after_success: | ||
- sbt coverageReport | ||
- bash <(curl -s https://codecov.io/bash) -t d8c6d7cf-f05c-4953-9f6e-ff046716a0f0 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
scalacheck-datetime | ||
==== | ||
|
||
[![Build Status](https://travis-ci.org/47deg/scalacheck-datetime.svg?branch=master)](https://travis-ci.org/47deg/scalacheck-datetime) | ||
|
||
A helper library for using datetime libraries with ScalaCheck |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
addSbtPlugin("org.scoverage" % "sbt-scoverage" % "1.3.5") |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package com.fortysevendeg.scalacheck.datetime.instances | ||
|
||
import com.fortysevendeg.scalacheck.datetime.typeclasses._ | ||
import java.time._ | ||
import java.time.temporal.ChronoUnit.MILLIS | ||
|
||
trait J8Instances { | ||
|
||
implicit val j8ForDuration: ScalaCheckDateTimeInfra[ZonedDateTime, Duration] = new ScalaCheckDateTimeInfra[ZonedDateTime, Duration] { | ||
def addRange(zonedDateTime: ZonedDateTime, duration: Duration): ZonedDateTime = zonedDateTime.plus(duration) | ||
def addMillis(zonedDateTime: ZonedDateTime, millis: Long): ZonedDateTime = zonedDateTime.plus(millis, MILLIS) | ||
def getMillis(zonedDateTime: ZonedDateTime): Long = zonedDateTime.toInstant.toEpochMilli | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package com.fortysevendeg.scalacheck.datetime.instances | ||
|
||
import com.fortysevendeg.scalacheck.datetime.typeclasses._ | ||
import org.joda.time._ | ||
|
||
trait JodaInstances { | ||
|
||
// todo have another instance with Duration rather than period? | ||
implicit val jodaForPeriod: ScalaCheckDateTimeInfra[DateTime, Period] = new ScalaCheckDateTimeInfra[DateTime, Period] { | ||
def addRange(dateTime: DateTime, period: Period): DateTime = dateTime.plus(period) | ||
def addMillis(dateTime: DateTime, millis: Long): DateTime = dateTime.plus(millis) | ||
def getMillis(dateTime: DateTime): Long = dateTime.getMillis | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
package com.fortysevendeg.scalacheck.datetime | ||
|
||
package object instances { | ||
object joda extends JodaInstances | ||
object j8 extends J8Instances | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JDK8 may be a better known acronym There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package com.fortysevendeg.scalacheck.datetime.j8 | ||
|
||
import collection.JavaConverters._ | ||
|
||
import org.scalacheck.Gen | ||
import org.scalacheck.Arbitrary.arbitrary | ||
|
||
import java.time._ | ||
import java.time.temporal.ChronoUnit.MILLIS | ||
|
||
object GenJ8 { | ||
|
||
val genZonedDateTime: Gen[ZonedDateTime] = for { | ||
year <- Gen.choose(-292278994, 292278994) | ||
month <- Gen.choose(1, 12) | ||
maxDaysInMonth = Month.of(month).length(Year.of(year).isLeap) | ||
dayOfMonth <- Gen.choose(1, maxDaysInMonth) | ||
hour <- Gen.choose(0, 23) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The documentation says that
I don't know if the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Great comment, thanks. This is something I did think about, and corner-cases like this are really the motivation for the library. I've just tried to create a time with The It looks like |
||
minute <- Gen.choose(0, 59) | ||
second <- Gen.choose(0, 59) | ||
nanoOfSecond <- Gen.choose(0, 999999999) | ||
zoneId <- Gen.oneOf(ZoneId.getAvailableZoneIds.asScala.toList) | ||
} yield ZonedDateTime.of(year, month, dayOfMonth, hour, minute, second, nanoOfSecond, ZoneId.of(zoneId)) | ||
|
||
val genDuration: Gen[Duration] = Gen.choose(Long.MinValue, Long.MaxValue / 1000).map(l => Duration.of(l, MILLIS)) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package com.fortysevendeg.scalacheck.datetime.joda | ||
|
||
import org.scalacheck.Gen | ||
import org.joda.time._ | ||
|
||
/** | ||
* Generators specific for Joda time. | ||
*/ | ||
object GenJoda { | ||
|
||
/** A <code>Years</code> period generator. */ | ||
val genYearsPeriod: Gen[Years] = Gen.choose(-292275054, 292278993).map(Years.ZERO.plus(_)) // Years.MIN_VALUE produces exception-throwing results | ||
|
||
/** A <code>Months</code> period generator. */ | ||
val genMonthsPeriod: Gen[Months] = Gen.choose(Months.MIN_VALUE.getMonths, Months.MAX_VALUE.getMonths).map(Months.ZERO.plus(_)) | ||
|
||
/** A <code>Weeks</code> period generator. */ | ||
val genWeeksPeriod: Gen[Weeks] = Gen.choose(Weeks.MIN_VALUE.getWeeks, Weeks.MAX_VALUE.getWeeks).map(Weeks.ZERO.plus(_)) | ||
|
||
/** A <code>Days</code> period generator. */ | ||
val genDaysPeriod: Gen[Days] = Gen.choose(Days.MIN_VALUE.getDays, Days.MAX_VALUE.getDays).map(Days.ZERO.plus(_)) | ||
|
||
/** A <code>Hours</code> period generator. */ | ||
val genHoursPeriod: Gen[Hours] = Gen.choose(Hours.MIN_VALUE.getHours, Hours.MAX_VALUE.getHours).map(Hours.ZERO.plus(_)) | ||
|
||
/** A <code>Minutes</code> period generator. */ | ||
val genMinutesPeriod: Gen[Minutes] = Gen.choose(Minutes.MIN_VALUE.getMinutes, Minutes.MAX_VALUE.getMinutes).map(Minutes.ZERO.plus(_)) | ||
|
||
/** A <code>Seconds</code> period generator. */ | ||
val genSecondsPeriod: Gen[Seconds] = Gen.choose(Seconds.MIN_VALUE.getSeconds, Seconds.MAX_VALUE.getSeconds).map(Seconds.ZERO.plus(_)) | ||
|
||
/** | ||
* A <code>Period</code> generator consisting of years, days, hours, minutes, seconds and millis. | ||
*/ | ||
val genPeriod: Gen[Period] = for { | ||
years <- genYearsPeriod | ||
days <- Gen.choose(1, 365) | ||
hours <- Gen.choose(0, 23) | ||
minutes <- Gen.choose(0, 59) | ||
seconds <- Gen.choose(0, 59) | ||
millis <- Gen.choose(0, 999) | ||
} yield Period.years(years.getYears).withDays(days).withHours(hours).withMinutes(minutes).withSeconds(seconds).withMillis(millis) | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package com.fortysevendeg.scalacheck.datetime.typeclasses | ||
|
||
/* | ||
* TODO: | ||
* - Use simulacrum? | ||
* - Rename this | ||
* - Try the Aux pattern to remove the range type param? | ||
*/ | ||
trait ScalaCheckDateTimeInfra[D, R] { | ||
def addRange(dateTime: D, range: R): D | ||
def addMillis(dateTime: D, millis: Long): D | ||
def getMillis(dateTime: D): Long | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package com.fortysevendeg.scalacheck.datetime.j8 | ||
|
||
import scala.util.Try | ||
|
||
import org.scalacheck._ | ||
import org.scalacheck.Prop._ | ||
|
||
import java.time._ | ||
|
||
import com.fortysevendeg.scalacheck.datetime.GenDateTime._ | ||
import com.fortysevendeg.scalacheck.datetime.instances.j8._ | ||
|
||
import GenJ8._ | ||
|
||
object GenJ8Properties extends Properties("Java 8 Generators") { | ||
|
||
property("genDuration creates valid durations") = forAll(genDuration) { _ => passed } | ||
|
||
property("genZonedDateTime created valid times") = forAll(genZonedDateTime) { _ => passed } | ||
|
||
// Guards against adding a duration to a datetime which cannot represent millis in a long, causing an exception. | ||
private[this] def tooLargeForAddingRanges(dateTime: ZonedDateTime, d: Duration): Boolean = { | ||
Try(dateTime.plus(d).toInstant().toEpochMilli()).isFailure | ||
} | ||
|
||
property("genDuration can be added to any date") = forAll(genZonedDateTime, genDuration) { (dt, dur) => | ||
!tooLargeForAddingRanges(dt, dur) ==> { | ||
val attempted = Try(dt.plus(dur).toInstant().toEpochMilli()) | ||
attempted.isSuccess :| attempted.toString | ||
} | ||
} | ||
|
||
property("genDateTimeWithinRange for Java 8 should generate ZonedDateTimes between the given date and the end of the specified Duration") = forAll(genZonedDateTime, genDuration) { (now, d) => | ||
!tooLargeForAddingRanges(now, d) ==> { | ||
forAll(genDateTimeWithinRange(now, d)) { generated => | ||
val durationBoundary = now.plus(d) | ||
|
||
val resultText = s"""Duration: $d | ||
|Now: $now | ||
|Generated: $generated | ||
|Period Boundary: $durationBoundary""".stripMargin | ||
|
||
val (lowerBound, upperBound) = if(durationBoundary.isAfter(now)) (now, durationBoundary) else (durationBoundary, now) | ||
|
||
val check = (lowerBound.isBefore(generated) || lowerBound.isEqual(generated)) && | ||
(upperBound.isAfter(generated) || upperBound.isEqual(generated)) | ||
|
||
check :| resultText | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be a good idea to replace it for the following ?
implicit object j8ForDuration extends ScalaCheckDateTimeInfre[ZonedDateTime, Duration] {
I have done this before, but I do not know if they are better than a
val
. It could save an anonymous class.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've had issues in the past with type inference when doing things that way (admittedly with more complex types than this). I seem to recall that
implicit val...
is the suggested way to do it, though I can't find any evidence to back that up. Doing it my way allows explicit setting of the desired type, which I like, so I'll probably stick with that for now.