Skip to content

Commit 76146fe

Browse files
author
Fede Fernández
authored
Merge pull request #21 from scala-exercises/issue-16-add-scalacheck-datetime
Issue 16 - Add scalacheck datetime
2 parents e087eb7 + f7022a7 commit 76146fe

File tree

10 files changed

+287
-34
lines changed

10 files changed

+287
-34
lines changed

build.sbt

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,11 @@ lazy val scalacheck = (project in file("."))
1111
Resolver.sonatypeRepo("releases")
1212
),
1313
libraryDependencies ++= Seq(
14-
"org.scalatest" %% "scalatest" % "2.2.4",
15-
"org.scala-exercises" %% "exercise-compiler" % version.value,
16-
"org.scala-exercises" %% "definitions" % version.value,
17-
"org.scalacheck" %% "scalacheck" % "1.12.5",
18-
"com.github.alexarchambault" %% "scalacheck-shapeless_1.12" % "0.3.1",
14+
"org.scalatest" %% "scalatest" % "3.0.1" exclude("org.scalacheck", "scalacheck"),
15+
"org.scala-exercises" %% "exercise-compiler" % version.value excludeAll ExclusionRule("com.github.alexarchambault"),
16+
"org.scala-exercises" %% "definitions" % version.value excludeAll ExclusionRule("com.github.alexarchambault"),
17+
"com.fortysevendeg" %% "scalacheck-datetime" % "0.2.0",
18+
"com.github.alexarchambault" %% "scalacheck-shapeless_1.13" % "1.1.3",
1919
compilerPlugin("org.spire-math" %% "kind-projector" % "0.9.0")
2020
)
2121
)

src/main/scala/scalachecklib/ArbitrarySection.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package scalachecklib
33
import org.scalatest.Matchers
44
import org.scalatest.prop.Checkers
55

6-
/** ==The `arbitrary` Generator
6+
/** ==The `arbitrary` Generator==
77
*
88
* There is a special generator, `org.scalacheck.Arbitrary.arbitrary`, which generates arbitrary values of any
99
* supported type.

src/main/scala/scalachecklib/PropertiesSection.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ object PropertiesSection extends Checkers with Matchers with org.scalaexercises.
185185
*
186186
*/
187187
def groupingProperties(res0: Int, res1: Int, res2: Int) = {
188-
import org.scalacheck.Properties
188+
import org.scalacheck.{Prop, Properties}
189189

190190
class ZeroSpecification extends Properties("Zero") {
191191

@@ -199,6 +199,6 @@ object PropertiesSection extends Checkers with Matchers with org.scalaexercises.
199199

200200
}
201201

202-
check(new ZeroSpecification)
202+
check(Prop.all(new ZeroSpecification().properties.map(_._2): _*))
203203
}
204204
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package scalachecklib
2+
3+
import org.scalatest.Matchers
4+
import org.scalatest.prop.Checkers
5+
6+
/** scalacheck-datetime is a library for helping use datetime libraries with ScalaCheck
7+
*
8+
* The motivation behind this library is to provide a simple, easy way to provide generated date and time instances
9+
* that are useful to your own domain.
10+
*
11+
* For SBT, you can add the dependency to your project’s build file:
12+
*
13+
* {{{
14+
* resolvers += Resolver.sonatypeRepo("releases")
15+
*
16+
* "com.fortysevendeg" %% "scalacheck-datetime" % "0.2.0" % "test"
17+
* }}}
18+
*
19+
* Please, visit the [[https://47deg.github.io/scalacheck-datetime homepage]] for more information
20+
*
21+
* @param name scalacheck-datetime
22+
*/
23+
object ScalacheckDatetimeSection extends Checkers with Matchers with org.scalaexercises.definitions.Section {
24+
25+
/** ==Usage==
26+
*
27+
* To arbitrarily generate dates and times, you need to have the `Arbitrary` in scope for your date/time class.
28+
* Assuming Joda Time:
29+
*/
30+
def usage(res0: Boolean) = {
31+
32+
import com.fortysevendeg.scalacheck.datetime.joda.ArbitraryJoda._
33+
import org.joda.time.DateTime
34+
import org.scalacheck.Prop.forAll
35+
36+
check {
37+
forAll { dt: DateTime =>
38+
(dt.getDayOfMonth >= 1 && dt.getDayOfMonth <= 31) == res0
39+
}
40+
}
41+
}
42+
43+
/** ==A note on imports==
44+
*
45+
* For all of the examples given in this document, you can substitute `jdk8` for `joda` and vice-versa,
46+
* depending on which library you would like to generate instances for.
47+
*
48+
* ==Implementation==
49+
*
50+
* The infrastructure behind the generation of date/time instances for any given date/time library,
51+
* which may take ranges into account, is done using a fairly simple typeclass, which has the type signature
52+
* `ScalaCheckDateTimeInfra[D, R]`. That is to say, as long as there is an implicit `ScalaCheckDateTimeInfra`
53+
* instance in scope for a given date/time type `D` (such as Joda’s `DateTime`) and a range type `R`
54+
* (such as Joda’s `Period`), then the code will compile and be able to provide generated date/time instances.
55+
*
56+
* As stated, currently there are two instances, `ScalaCheckDateTimeInfra[DateTime, Period]` for Joda Time and
57+
* `ScalaCheckDateTimeInfra[ZonedDateTime, Duration]` for Java SE 8’s Date and Time.
58+
*
59+
* ==Granularity==
60+
*
61+
* If you wish to restrict the precision of the generated instances, this library refers to that as <i>granularity</i>.
62+
*
63+
* You can constrain the granularity to:
64+
*
65+
* <ul>
66+
* <li>Seconds</li>
67+
* <li>Minutes</li>
68+
* <li>Hours</li>
69+
* <li>Days</li>
70+
* <li>Years</li>
71+
* </ul>
72+
*
73+
* When a value is constrained, the time fields are set to zero, and the rest to the first day of the month,
74+
* or day of the year. For example, if you constrain a field to be years, the generated instance will be midnight
75+
* exactly, on the first day of January.
76+
*
77+
* To constrain a generated type, you simply need to provide an import for the typeclass for your date/time and
78+
* range, and also an import for the granularity. As an example, this time using Java SE 8's `java.time` package:
79+
*/
80+
def granularity(res0: Int, res1: Int, res2: Int, res3: Int, res4: Int) = {
81+
82+
import java.time._
83+
import com.fortysevendeg.scalacheck.datetime.jdk8.ArbitraryJdk8._
84+
import com.fortysevendeg.scalacheck.datetime.jdk8.granularity.years
85+
import org.scalacheck.Prop.forAll
86+
87+
check {
88+
forAll { zdt: ZonedDateTime =>
89+
zdt.getMonth == Month.JANUARY
90+
(zdt.getDayOfMonth == res0) &&
91+
(zdt.getHour == res1) &&
92+
(zdt.getMinute == res2) &&
93+
(zdt.getSecond == res3) &&
94+
(zdt.getNano == res4)
95+
}
96+
}
97+
98+
}
99+
100+
/** ==Creating Ranges==
101+
*
102+
* You can generate date/time instances only within a certain range, using the `genDateTimeWithinRange` in the
103+
* `GenDateTime` class. The function takes two parameters, the date/time instances as a base from which to generate
104+
* new date/time instances, and a range for the generated instances.
105+
*
106+
* If the range is positive, it will be in the future from the base date/time, negative in the past.
107+
*
108+
* Showing this usage with Joda Time:
109+
*/
110+
def ranges(res0: Int) = {
111+
112+
import org.joda.time._
113+
import com.fortysevendeg.scalacheck.datetime.instances.joda._
114+
import com.fortysevendeg.scalacheck.datetime.GenDateTime.genDateTimeWithinRange
115+
import org.scalacheck.Prop.forAll
116+
117+
val from = new DateTime(2016, 1, 1, 0, 0)
118+
val range = Period.years(1)
119+
120+
check {
121+
forAll(genDateTimeWithinRange(from, range)) { dt =>
122+
dt.getYear == res0
123+
}
124+
}
125+
}
126+
127+
/** ==Using Granularity and Ranges Together==
128+
*
129+
* As you would expect, it is possible to use the granularity and range concepts together.
130+
* This example should not show anything surprising by now:
131+
*/
132+
def granularityAndRanges(res0: Int, res1: Int, res2: Int, res3: Int, res4: Int) = {
133+
134+
import org.joda.time._
135+
import com.fortysevendeg.scalacheck.datetime.instances.joda._
136+
import com.fortysevendeg.scalacheck.datetime.GenDateTime.genDateTimeWithinRange
137+
import com.fortysevendeg.scalacheck.datetime.joda.granularity.days
138+
import org.scalacheck.Prop.forAll
139+
140+
val from = new DateTime(2016, 1, 1, 0, 0)
141+
val range = Period.years(1)
142+
143+
check {
144+
forAll(genDateTimeWithinRange(from, range)) { dt =>
145+
(dt.getYear == res0) &&
146+
(dt.getHourOfDay == res1) &&
147+
(dt.getMinuteOfHour == res2) &&
148+
(dt.getSecondOfMinute == res3) &&
149+
(dt.getMillisOfSecond == res4)
150+
}
151+
}
152+
}
153+
}

src/main/scala/scalachecklib/ScalacheckLibrary.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,8 @@ object ScalacheckLibrary extends org.scalaexercises.definitions.Library {
1212

1313
override def sections = List(
1414
PropertiesSection,
15-
GeneratorsSection
15+
GeneratorsSection,
16+
ScalacheckDatetimeSection
1617
)
1718

1819
override def logoPath = "scalacheck"

src/test/scala/scalachecklib/ArbitrarySpec.scala

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package scalachecklib
22

33
import org.scalacheck.Shapeless._
4-
import org.scalaexercises.Test
5-
import org.scalatest.Spec
4+
import org.scalatest.FunSuite
65
import org.scalatest.prop.Checkers
76
import shapeless.HNil
87

9-
class ArbitrarySpec extends Spec with Checkers {
8+
class ArbitrarySpec extends FunSuite with Checkers {
109

11-
def `implicit arbitrary char` = {
10+
test("implicit arbitrary char") {
1211

1312
check(
1413
Test.testSuccess(
@@ -18,7 +17,7 @@ class ArbitrarySpec extends Spec with Checkers {
1817
)
1918
}
2019

21-
def `implicit arbitrary case class` = {
20+
test("implicit arbitrary case class") {
2221

2322
check(
2423
Test.testSuccess(
@@ -28,7 +27,7 @@ class ArbitrarySpec extends Spec with Checkers {
2827
)
2928
}
3029

31-
def `arbitrary on gen` = {
30+
test("arbitrary on gen") {
3231

3332
check(
3433
Test.testSuccess(

src/test/scala/scalachecklib/GeneratorsSpec.scala

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,13 @@
11
package scalachecklib
22

33
import org.scalacheck.Shapeless._
4-
import org.scalaexercises.Test
5-
import org.scalatest.Spec
4+
import org.scalatest.FunSuite
65
import org.scalatest.prop.Checkers
76
import shapeless.HNil
87

9-
class GeneratorsSpec extends Spec with Checkers {
8+
class GeneratorsSpec extends FunSuite with Checkers {
109

11-
def `for-comprehension generator` = {
10+
test("for-comprehension generator") {
1211

1312
check(
1413
Test.testSuccess(
@@ -19,7 +18,7 @@ class GeneratorsSpec extends Spec with Checkers {
1918

2019
}
2120

22-
def `oneOf method` = {
21+
test("oneOf method") {
2322

2423
check(
2524
Test.testSuccess(
@@ -30,7 +29,7 @@ class GeneratorsSpec extends Spec with Checkers {
3029

3130
}
3231

33-
def `alphaChar, posNum and listOfN` = {
32+
test("alphaChar, posNum and listOfN") {
3433

3534
check(
3635
Test.testSuccess(
@@ -41,7 +40,7 @@ class GeneratorsSpec extends Spec with Checkers {
4140

4241
}
4342

44-
def `suchThat condition` = {
43+
test("suchThat condition") {
4544

4645
check(
4746
Test.testSuccess(
@@ -52,7 +51,7 @@ class GeneratorsSpec extends Spec with Checkers {
5251

5352
}
5453

55-
def `case class generator` = {
54+
test("case class generator") {
5655

5756
check(
5857
Test.testSuccess(
@@ -63,7 +62,7 @@ class GeneratorsSpec extends Spec with Checkers {
6362

6463
}
6564

66-
def `sized generator` = {
65+
test("sized generator") {
6766

6867
check(
6968
Test.testSuccess(
@@ -74,7 +73,7 @@ class GeneratorsSpec extends Spec with Checkers {
7473

7574
}
7675

77-
def `list container` = {
76+
test("list container") {
7877

7978
check(
8079
Test.testSuccess(

src/test/scala/scalachecklib/PropertiesSpec.scala

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,13 @@
11
package scalachecklib
22

33
import org.scalacheck.Shapeless._
4-
import org.scalaexercises.Test
5-
import org.scalatest.Spec
4+
import org.scalatest.FunSuite
65
import org.scalatest.prop.Checkers
76
import shapeless.HNil
87

8+
class PropertiesSpec extends FunSuite with Checkers {
99

10-
class PropertiesSpec extends Spec with Checkers {
11-
12-
def `always ends with the second string` = {
10+
test("always ends with the second string") {
1311

1412
check(
1513
Test.testSuccess(
@@ -19,7 +17,7 @@ class PropertiesSpec extends Spec with Checkers {
1917
)
2018
}
2119

22-
def `all numbers are generated between the desired interval` = {
20+
test("all numbers are generated between the desired interval") {
2321

2422
check(
2523
Test.testSuccess(
@@ -29,7 +27,7 @@ class PropertiesSpec extends Spec with Checkers {
2927
)
3028
}
3129

32-
def `all generated numbers are even` = {
30+
test("all generated numbers are even") {
3331

3432
check(
3533
Test.testSuccess(
@@ -39,7 +37,7 @@ class PropertiesSpec extends Spec with Checkers {
3937
)
4038
}
4139

42-
def `only the second condition is true` = {
40+
test("only the second condition is true") {
4341

4442
check(
4543
Test.testSuccess(
@@ -49,7 +47,7 @@ class PropertiesSpec extends Spec with Checkers {
4947
)
5048
}
5149

52-
def `the zero specification only works for 0` = {
50+
test("the zero specification only works for 0") {
5351

5452
check(
5553
Test.testSuccess(

0 commit comments

Comments
 (0)