Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
86eec5d
Add some Schedule constructors for cron-like functionality
heaven-born Sep 2, 2020
91efb34
doc and assertions updates
heaven-born Sep 2, 2020
d0efca0
Add some Schedule constructors for cron-like functionality: bugfixing…
heaven-born Sep 2, 2020
e9e56c8
Add some Schedule constructors for cron-like functionality: test simp…
heaven-born Sep 2, 2020
e16dfcf
Add some Schedule constructors for cron-like functionality: formattin…
heaven-born Sep 2, 2020
a523847
Add some Schedule constructors for cron-like functionality: assert te…
heaven-born Sep 2, 2020
5f1a858
Add some Schedule constructors for cron-like functionality: removed `…
heaven-born Sep 3, 2020
8f9f6bb
Add some Schedule constructors for cron-like functionality: extracted…
heaven-born Sep 3, 2020
8c1953d
Add some Schedule constructors for cron-like functionality: scaladoc fix
heaven-born Sep 3, 2020
cc71714
Add some Schedule constructors for cron-like functionality: removed M…
heaven-born Sep 3, 2020
f6ef1ed
Add some Schedule constructors for cron-like functionality: refactoring
heaven-born Sep 4, 2020
410c362
Add some Schedule constructors for cron-like functionality: moved min…
heaven-born Sep 4, 2020
fbd1c4b
Add some Schedule constructors for cron-like functionality: simplifie…
heaven-born Sep 4, 2020
1a3aada
Add some Schedule constructors for cron-like functionality: better test
heaven-born Sep 4, 2020
25d17ae
Add some Schedule constructors for cron-like functionality: added tes…
heaven-born Sep 4, 2020
3674293
Add some Schedule constructors for cron-like functionality: minor non…
heaven-born Sep 4, 2020
e709343
Add some Schedule constructors for cron-like functionality: added sec…
heaven-born Sep 5, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 93 additions & 2 deletions core-tests/shared/src/test/scala/zio/ScheduleSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import zio.clock.Clock
import zio.duration._
import zio.stream.ZStream
import zio.test.Assertion._
import zio.test.TestAspect.timeout
import zio.test.TestAspect.{ failing, timeout }
import zio.test.environment.{ TestClock, TestRandom }
import zio.test.{ assert, assertM, suite, testM, TestResult }
import zio.test.{ assert, assertM, suite, testM, Assertion, TestFailure, TestResult }

import scala.concurrent.Future

Expand Down Expand Up @@ -301,6 +301,87 @@ object ScheduleSpec extends ZIOBaseSpec {
assertM(failed)(equalTo("OrElseFailed"))
}
) @@ zioTag(errors),
suite("cron-like scheduling. Repeats at point of time (minute of hour, day of week, ...)")(
testM("recur at 01 second of each minute") {
def toOffsetDateTime[T](in: (List[(OffsetDateTime, T)], Option[T])): List[OffsetDateTime] =
in._1.map(t => t._1.withNano(0))

val originOffset = OffsetDateTime.now().withMinute(0).withSecond(0).withNano(0)
val beforeTime = originOffset.withSecond(0)
val afterTime = originOffset.withSecond(3)
val inTimeSecond = originOffset.withSecond(1)
val inTimeSecondNanosec = originOffset.withSecond(1).withNano(1)

val input = List(beforeTime, afterTime, inTimeSecond, inTimeSecondNanosec).map((_, ()))

assertM(runManually(Schedule.secondOfMinute(1), input).map(toOffsetDateTime)) {
val expected = originOffset.withSecond(1)
val afterTimeExpected = expected.withMinute(expected.getMinute + 1)
equalTo(List(expected, afterTimeExpected, expected, expected))
}
},
testM("throw IllegalArgumentException on invalid `second` argument of `secondOfMinute`") {
val input = List(OffsetDateTime.now())
assertM(run(Schedule.secondOfMinute(60))(input)) {
equalTo(Chunk.empty)
}
} @@ failing(diesWith(isSubtype[IllegalArgumentException](anything))),
testM("recur at 01 minute of each hour") {
def toOffsetDateTime[T](in: (List[(OffsetDateTime, T)], Option[T])): List[OffsetDateTime] =
in._1.map(t => t._1.withNano(0))

val originOffset = OffsetDateTime.now().withHour(0).withSecond(0).withNano(0)
val beforeTime = originOffset.withMinute(0)
val afterTime = originOffset.withMinute(3)
val inTimeMinute = originOffset.withMinute(1)
val inTimeMinuteSec = originOffset.withMinute(1).withSecond(1)
val inTimeMinuteNanosec = originOffset.withMinute(1).withNano(1)

val input = List(beforeTime, afterTime, inTimeMinute, inTimeMinuteSec, inTimeMinuteNanosec).map((_, ()))

assertM(runManually(Schedule.minuteOfHour(1), input).map(toOffsetDateTime)) {
val expected = originOffset.withMinute(1)
val afterTimeExpected = expected.withHour(expected.getHour + 1)
equalTo(List(expected, afterTimeExpected, expected, expected, expected))
}
},
testM("throw IllegalArgumentException on invalid `minute` argument of `minuteOfHour`") {
val input = List(OffsetDateTime.now())
assertM(run(Schedule.minuteOfHour(60))(input)) {
equalTo(Chunk.empty)
}
} @@ failing(diesWith(isSubtype[IllegalArgumentException](anything))),
testM("recur at 01 hour of each day") {
def toOffsetDateTime[T](in: (List[(OffsetDateTime, T)], Option[T])): List[OffsetDateTime] =
in._1.map(t => t._1.withNano(0))

val originOffset = OffsetDateTime
.now()
.withMinute(0)
.withSecond(0)
.withNano(0)

val beforeTime = originOffset.withHour(0)
val afterTime = originOffset.withHour(3)
val inTimeHour = originOffset.withHour(1)
val inTimeHourMinute = originOffset.withHour(1).withMinute(1)
val inTimeHourSecond = originOffset.withHour(1).withSecond(1)

val input = List(beforeTime, afterTime, inTimeHour, inTimeHourMinute, inTimeHourSecond).map((_, ()))

assertM(runManually(Schedule.hourOfDay(1), input).map(toOffsetDateTime)) {
val expected = originOffset.withHour(1)
val afterTimeExpected = expected.withDayOfYear(expected.getDayOfYear + 1)
equalTo(List(expected, afterTimeExpected, expected, expected, expected))
}
},
testM("throw IllegalArgumentException on invalid `hour` argument of `hourOfDay`") {
val input = List(OffsetDateTime.now())
assertM(run(Schedule.hourOfDay(24))(input)) {
equalTo(Chunk.empty)
}
} @@ failing(diesWith(isSubtype[IllegalArgumentException](anything)))
),
suite("Return the result after successful retry")(
testM("retry exactly one time for `once` when second time succeeds - retryOrElse") {
for {
Expand Down Expand Up @@ -495,6 +576,16 @@ object ScheduleSpec extends ZIOBaseSpec {
x <- if (i <= 1) IO.fail(s"Error: $i") else IO.succeed(i)
} yield x

def diesWith(assertion: Assertion[Throwable]): Assertion[TestFailure[Any]] =
isCase(
"Runtime",
{
case TestFailure.Runtime(c) => c.dieOption
case _ => None
},
assertion
)

case class ScheduleError(message: String) extends Exception
case class ScheduleFailure(message: String)
case class ScheduleSuccess[O](content: O)
Expand Down
92 changes: 92 additions & 0 deletions core/shared/src/main/scala/zio/Schedule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
package zio

import java.time.OffsetDateTime
import java.time.temporal.ChronoField
import java.util.concurrent.TimeUnit

import zio.duration._
Expand Down Expand Up @@ -1126,6 +1127,97 @@ object Schedule {
Schedule(loop(None, 0L))
}

/**
* Cron-like schedule that recurs every specified `second` of each minute.
* It triggers at zero nanosecond of the second.
* Producing a count of repeats: 0, 1, 2.
*
* NOTE: `second` parameter is validated lazily. Must be in range 0...59.
*/
def secondOfMinute(second: Int): Schedule[Any, Any, Long] = {

def loop(n: Long): StepFunction[Any, Any, Long] =
(now: OffsetDateTime, _: Any) =>
if (second >= 60 || second < 0)
ZIO.die(
new IllegalArgumentException(s"Invalid argument in `secondOfMinute($second)`. Must be in range 0...59")
)
else
ZIO.succeed(
Decision.Continue(
n + 1,
nextFixedOffset(now, second, ChronoField.SECOND_OF_MINUTE).withNano(0),
loop(n + 1L)
)
)

Schedule(loop(0L))

}

/**
* Cron-like schedule that recurs every specified `minute` of each hour.
* It triggers at zero second of the minute.
* Producing a count of repeats: 0, 1, 2.
*
* NOTE: `minute` parameter is validated lazily. Must be in range 0...59.
*/
def minuteOfHour(minute: Int): Schedule[Any, Any, Long] = {

def loop(n: Long): StepFunction[Any, Any, Long] =
(now: OffsetDateTime, _: Any) =>
if (minute >= 60 || minute < 0)
ZIO.die(new IllegalArgumentException(s"Invalid argument in `minuteOfHour($minute)`. Must be in range 0...59"))
else
ZIO.succeed(
Decision.Continue(
n + 1,
nextFixedOffset(now, minute, ChronoField.MINUTE_OF_HOUR)
.withSecond(0)
.withNano(0),
loop(n + 1L)
)
)

Schedule(loop(0L))

}

/**
* Cron-like schedule that recurs every specified `hour` of each day.
* It triggers at zero minute of the hour.
* Producing a count of repeats: 0, 1, 2.
*
* NOTE: `hour` parameter is validated lazily. Must be in range 0...23.
*/
def hourOfDay(hour: Int): Schedule[Any, Any, Long] = {

def loop(n: Long): StepFunction[Any, Any, Long] =
(now: OffsetDateTime, _: Any) =>
if (hour >= 24 || hour < 0)
ZIO.die(new IllegalArgumentException(s"Invalid argument in `hourOfDay($hour)`. Must be in range 0...23"))
else
ZIO.succeed(
Decision.Continue(
n + 1,
nextFixedOffset(now, hour, ChronoField.HOUR_OF_DAY)
.withMinute(0)
.withSecond(0)
.withNano(0),
loop(n + 1L)
)
)

Schedule(loop(0L))

}

private[this] def nextFixedOffset(currentOffset: OffsetDateTime, fixedTimeUnitValue: Int, timeUnit: ChronoField) = {
val fixedSec = currentOffset.`with`(timeUnit, fixedTimeUnitValue.toLong)
if (currentOffset.get(timeUnit) <= fixedTimeUnitValue.toLong) fixedSec
else fixedSec.plus(1, timeUnit.getRangeUnit)
}

type Interval = java.time.OffsetDateTime

def minOffsetDateTime(l: OffsetDateTime, r: OffsetDateTime): OffsetDateTime =
Expand Down