To start using cron4s in your project just include the library as part of your dependencies:
libraryDependencies += "com.github.alonsodomin.cron4s" %% "cron4s-core" % "0.6.1"
Or in ScalaJS:
libraryDependencies += "com.github.alonsodomin.cron4s" %%% "cron4s-core" % "0.6.1"
Parsing
cron4s uses cron expressions that go from seconds to day of week in the following order:
- Seconds
- Minutes
- Hour Of Day
- Day Of Month
- Month
- Day Of Week
To parse a cron expression into a type that we can work with we will use the Cron
smart constructor:
import cron4s._
val parsed = Cron("10-35 2,4,6 * ? * *")
// parsed: Either[Error, CronExpr] = Right(CronExpr(10-35, 2,4,6, *, ?, *, *))
We will get an Either[Error, CronExpr]
, the left side giving us an error description if the parsing
has failed. In the above example the expression parsed successfully but if we pass a wrong or invalid expression
we will get the actual reason for the failure in the left side of the Either
:
val invalid = Cron("10-65 * * * * *")
// invalid: Either[Error, CronExpr] = Left(
// ParseFailed("blank expected", 3, Some("-"))
// )
If we are not interested in the left side of the result (the error), we can easily convert it into an Option[CronExpr]
:
import cats.syntax.either._
parsed.toOption
// res1: Option[CronExpr] = Some(CronExpr(10-35, 2,4,6, *, ?, *, *))
invalid.toOption
// res2: Option[CronExpr] = None
Note: In Scala 2.12 you can avoid importing cats.syntax.either._
to be able to make the conversion.
Cron(expr)
is just a short-hand for Cron.parse(expr)
. This object provides also with additional methods for parsing
that return different types. In the first place we have Cron.tryParse(expr)
which will return a Try[CronExpr]
instead:
val invalidTry = Cron.tryParse("10-65 * * * * *")
// invalidTry: util.Try[CronExpr] = Failure(
// ParseFailed("blank expected", 3, Some("-"))
// )
And also Cron.unsafeParse(expr)
, which will return a naked CronExpr
or happily blow-up with an exception
interrupting the execution. This is the most Java-friendly version of all of them and you should try to avoid using it
unless you are aware of the consequences (well, it also comes handy during a REPL session or to write this tutorial):
Cron.unsafeParse("10-65 * * * * *")
// cron4s.ParseFailed: blank expected at position 3 but found '-'
// at cron4s.ParseFailed$.apply(errors.scala:39)
// at cron4s.parsing.BaseParser.handleError(base.scala:33)
// at cron4s.parsing.BaseParser.handleError$(base.scala:24)
// at cron4s.parsing.CronParser$.handleError(parser.scala:32)
// at cron4s.parsing.CronParser$.read(parser.scala:184)
// at cron4s.parsing.package$.$anonfun$parse$1(package.scala:25)
// at scala.util.Either.flatMap(Either.scala:352)
// at cron4s.parsing.package$.parse(package.scala:24)
// at cron4s.Cron$.parse(Cron.scala:50)
// at cron4s.Cron$.unsafeParse(Cron.scala:73)
// at repl.Session$App$$anonfun$8.apply(index.md:54)
// at repl.Session$App$$anonfun$8.apply(index.md:54)
Validation errors
The CRON expression will be validated right after parsing. Any error found during this stage will be returned
as well in the left side of the Either
returned by the Cron
constructor. For instance, the following expression
has a sequence that mixes elements that self-imply each other, which is invalid:
val failsValidation = Cron("12,10-20 * * * * ?")
// failsValidation: Either[Error, CronExpr] = Left(
// InvalidCron(
// NonEmptyList(
// InvalidField(Second, "Value '12' is implied by '10-20'"),
// List()
// )
// )
// )
In this case, the error type in the left side of the Either
can be narrowed down to InvalidCron
, which will give
a NonEmptyList
with all the validation errors that the expression had. To demosrate this, here is an example:
failsValidation.swap.foreach { err => err.asInstanceOf[InvalidCron].reason.toList.mkString("\n") }