Skip to content

Commit

Permalink
Merge pull request #37 from alonsodomin/custom_datetime_docs
Browse files Browse the repository at this point in the history
Document how to give support for custom datetime impls.
  • Loading branch information
A. Alonso Dominguez authored Feb 4, 2017
2 parents c733896 + 605cd9b commit 37c1605
Show file tree
Hide file tree
Showing 3 changed files with 133 additions and 0 deletions.
4 changes: 4 additions & 0 deletions docs/src/main/resources/microsite/data/menu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ options:
url: docs/builtin_libs.html
section: intro

- title: Custom DateTime
url: docs/custom_datetime.html
section: intro

- title: API Docs
url: api/index.html
section: intro
Expand Down
127 changes: 127 additions & 0 deletions docs/src/main/tut/docs/custom_datetime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
---
layout: docs
title: "Custom DateTime"
---

## Custom DateTime

As already mentioned in [Built-in libs](builtin_libs.html), **cron4s** comes with support for some DateTime libraries
out of the box. But implementations of DateTime are many, both in the JVM and in JavaScript and adding support for all
of them as a _batteries included_ is impossible. Also, asking all the potential users to stick to a very limited range
of libraries to be able to use **cron4s** is quite unrealistic.

To solve this, **cron4s** has a means to allow users to provide with ability to _plug-in_ their favorite library and
get all the profit that the library provides with very little effort. This is due to **cron4s**'s typeclass-driven
design and, in fact, there is nothing in the internals of the library tied to the _built-in_ lib support, all of them
are implemented the same way as we are going to see right now.

### `IsDateTime` typeclass

All comes down to one single (and very simple) typeclass: `cron4s.datetime.IsDateTime`. Providing an implicit instance
of this trait is enough to be able to use your favourite library.

As an example, we are going to implement support for a very dummy version of a _time_ object:

```tut:silent
case class MyTime(seconds: Int, minutes: Int, hour: Int)
```

Now that we have a way of representing time, we need to provide an instance for the `IsDateTime` typeclass:

```tut:silent
import cron4s._
import cron4s.datetime.IsDateTime
implicit object MyDateInstance extends IsDateTime[MyTime] {
def supportedFields(myTime: MyTime): List[CronField] =
List(CronField.Second, CronField.Minute, CronField.Hour)
def get[F <: CronField](myTime: MyTime, field: F): Option[Int] = field match {
case CronField.Second => Some(myTime.seconds)
case CronField.Minute => Some(myTime.minutes)
case CronField.Hour => Some(myTime.hour)
case _ => None
}
def set[F <: CronField](myTime: MyTime, field: F, value: Int): Option[MyTime] = field match {
case CronField.Second => Some(myTime.copy(seconds = value))
case CronField.Minute => Some(myTime.copy(minutes = value))
case CronField.Hour => Some(myTime.copy(hour = value))
case _ => None
}
}
```

That should be enough. To be sure that all works we are going to use it with a CRON expression:

```tut
val Right(cron) = Cron("* 5,15,30 6-12 * * *")
val myTime = MyTime(59, 15, 10)
cron.next(myTime)
cron.timePart.next(myTime)
```

### Testing

Running a series of expressions in a REPL is not enough to be 100% confident that our implementation is correct.
**cron4s** provides with the `cron4s-testkit` module which includes laws and base test classes to help in this task.
To start using it, just include it in your dependencies:

```scala
libraryDependencies += "com.github.alonsodomin.cron4s" %% "cron4s-testkit" % "x.y.z" % Test
```

The first thing to to is to provide an implementation of the `cron4s.testkit.DateTimeTestKitBase` trait, this
is meant to instruct **cron4s** on how to create arbitrary instances of your date time object:

```tut:silent
import cron4s.CronUnit
import cron4s.testkit.DateTimeTestKitBase
import org.scalacheck.{Arbitrary, Gen}
trait MyTimeTestBase extends DateTimeTestKitBase[MyTime] {
import CronUnit._
override implicit lazy val arbitraryDateTime: Arbitrary[MyTime] = Arbitrary {
for {
second <- Gen.choose(Seconds.min, Seconds.max)
minute <- Gen.choose(Minutes.min, Minutes.max)
hour <- Gen.choose(Hours.min, Hours.max)
} yield MyTime(second, minute, hour)
}
def createDateTime(seconds: Int, minutes: Int, hours: Int, dayOfMonth: Int, month: Int, dayOfWeek: Int): MyTime =
MyTime(seconds, minutes, hours)
}
```

Also, to be able to perform assertions, **cron4s** needs an instance of the `scalaz.Equal` type class:

```tut:silent
import scalaz.Equal
implicit val myTimeEq: Equal[MyTime] = Equal.equalA[MyTime]
```

Now define a spec for your `IsDateTime` instance (along with your other test sources):

```tut:silent
import cron4s.testkit.IsDateTimeTestKit
class MyTimeSpec extends IsDateTimeTestKit[MyTime]("MyTime") with MyTimeTestBase
```

This will test that your implementation of the `IsDateTime` typeclass abides by its laws. To be fully covered is also
recommended to verify that the different CRON expressions work correctly with your date time library. In this case
we will be defining a class extending from `cron4s.testkit.DateTimeCronTestKit`:

```tut:silent
import cron4s.testkit.DateTimeCronTestKit
class MyTimeCronSpec extends DateTimeCronTestKit[MyTime] with MyTimeTestBase
```

And that's all, now those tests will be run along any other test sources in your project.
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,9 @@ package cron4s.lib.threetenbp

import cron4s.CronUnit
import cron4s.testkit.DateTimeTestKitBase

import org.scalacheck.{Arbitrary, Gen}

import org.threeten.bp._

/**
Expand Down

0 comments on commit 37c1605

Please sign in to comment.