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"


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._

// res1: Option[CronExpr] = Some(CronExpr(10-35, 2,4,6, *, ?, *, *))
// 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(
// 	at repl.Session$App$$anonfun$8.apply(

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") }