Skip to content

Commit d71e2dc

Browse files
Match sets of values (#128)
Closes #92.
1 parent fd544bc commit d71e2dc

File tree

3 files changed

+320
-165
lines changed

3 files changed

+320
-165
lines changed

src/commonMain/kotlin/io/github/kevincianfarini/cardiologist/impl/LocalDateTime.kt

Lines changed: 73 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,75 @@
11
package io.github.kevincianfarini.cardiologist.impl
22

3+
import io.github.kevincianfarini.cardiologist.PulseSchedule
4+
import io.github.kevincianfarini.cardiologist.PulseScheduleBuilder
5+
import io.github.kevincianfarini.cardiologist.buildPulseSchedule
36
import kotlinx.datetime.LocalDateTime
47
import kotlinx.datetime.Month
58
import kotlinx.datetime.number
69

7-
internal fun LocalDateTime.nextMatch(
8-
atSeconds: IntRange = 0..59,
9-
atMinutes: IntRange = 0..59,
10-
atHours: IntRange = 0..23,
11-
onDaysOfMonth: IntRange = 1..31,
12-
inMonths: ClosedRange<Month> = Month.JANUARY..Month.DECEMBER,
13-
): LocalDateTime {
10+
internal fun LocalDateTime.nextMatch(scheduleBuilder: PulseScheduleBuilder.() -> Unit = {}): LocalDateTime {
11+
return nextMatch(buildPulseSchedule(scheduleBuilder))
12+
}
13+
14+
internal fun LocalDateTime.nextMatch(schedule: PulseSchedule): LocalDateTime {
1415
// Ensure that the nextMatch of this LocalDateTime doesn't produce itself. If it does then increment the
1516
// nanosecond component by one to ensure that we produce a match that's distinct from this value.
16-
val time = if (matches(atSeconds, atMinutes, atHours, onDaysOfMonth, inMonths)) copy(nanosecond = 1) else this
17-
return time.nextMonth(inMonths)
18-
.nextDay(onDaysOfMonth, inMonths)
19-
.nextHour(atHours, onDaysOfMonth, inMonths)
20-
.nextMinute(atMinutes, atHours, onDaysOfMonth, inMonths)
21-
.nextSecond(atSeconds, atMinutes, atHours, onDaysOfMonth, inMonths)
17+
val time = if (matches(schedule)) copy(nanosecond = 1) else this
18+
return with(schedule) {
19+
time.nextMonth(inMonths)
20+
.nextDay(onDaysOfMonth, inMonths)
21+
.nextHour(atHours, onDaysOfMonth, inMonths)
22+
.nextMinute(atMinutes, atHours, onDaysOfMonth, inMonths)
23+
.nextSecond(atSeconds, atMinutes, atHours, onDaysOfMonth, inMonths)
24+
}
2225
}
2326

24-
private fun LocalDateTime.matches(
25-
atSeconds: IntRange = 0..59,
26-
atMinutes: IntRange = 0..59,
27-
atHours: IntRange = 0..23,
28-
onDaysOfMonth: IntRange = 1..31,
29-
inMonths: ClosedRange<Month> = Month.JANUARY..Month.DECEMBER,
30-
): Boolean {
31-
return nanosecond == 0 && second in atSeconds && minute in atMinutes && hour in atHours
32-
&& dayOfMonth in onDaysOfMonth && month in inMonths
33-
}
27+
private fun LocalDateTime.matches(schedule: PulseSchedule): Boolean = nanosecond == 0 &&
28+
second in schedule.atSeconds &&
29+
minute in schedule.atMinutes &&
30+
hour in schedule.atHours &&
31+
dayOfMonth in schedule.onDaysOfMonth &&
32+
month in schedule.inMonths
3433

3534
private fun LocalDateTime.nextMonth(
36-
inMonths: ClosedRange<Month>,
35+
inMonths: Set<Month>,
3736
increment: Boolean = false,
3837
): LocalDateTime {
3938
val incrementedMonth = if (increment) month.inc() else month
39+
val minMonth = inMonths.minOrNull()!!
40+
val maxMonth = inMonths.maxOrNull()!!
4041
return when {
4142
incrementedMonth < month -> copy(year = year + 1, monthNumber = incrementedMonth.number)
4243
incrementedMonth in inMonths -> copy(monthNumber = incrementedMonth.number)
43-
incrementedMonth < inMonths.start -> copy(
44-
monthNumber = inMonths.start.number,
44+
incrementedMonth < minMonth -> copy(
45+
monthNumber = minMonth.number,
4546
dayOfMonth = 1,
4647
hour = 0,
4748
minute = 0,
4849
second = 0,
4950
nanosecond = 0
5051
)
51-
incrementedMonth > inMonths.endInclusive -> copy(
52+
incrementedMonth > maxMonth -> copy(
5253
year = year + 1,
53-
monthNumber = inMonths.start.number,
54+
monthNumber = minMonth.number,
5455
dayOfMonth = 1,
5556
hour = 0,
5657
minute = 0,
5758
second = 0,
5859
nanosecond = 0
5960
)
60-
else -> error("This should be impossible")
61+
else -> copy(monthNumber = inMonths.sorted().first { it > incrementedMonth }.number)
6162
}
6263
}
6364

6465
private fun LocalDateTime.nextDay(
65-
onDaysOfMonth: IntRange,
66-
inMonths: ClosedRange<Month>,
66+
onDaysOfMonth: Set<Int>,
67+
inMonths: Set<Month>,
6768
increment: Boolean = false,
6869
): LocalDateTime {
69-
require(onDaysOfMonth.first >= 1 && onDaysOfMonth.last <= 31) { "onDaysOfMonth $onDaysOfMonth not in range 1..31." }
70+
val minDayOfMonth = onDaysOfMonth.minOrNull()!!
71+
val maxDayOfMonth = onDaysOfMonth.maxOrNull()!!
72+
require(minDayOfMonth >= 1 && maxDayOfMonth <= 31) { "onDaysOfMonth $onDaysOfMonth not in range 1..31." }
7073
val incrementedDay = when {
7174
increment && dayOfMonth + 1 <= month.numberOfDays(year) -> dayOfMonth + 1
7275
increment -> 1
@@ -75,89 +78,95 @@ private fun LocalDateTime.nextDay(
7578
return when {
7679
incrementedDay < dayOfMonth -> copy(dayOfMonth = incrementedDay).nextMonth(inMonths, increment = true)
7780
incrementedDay in onDaysOfMonth -> copy(dayOfMonth = incrementedDay)
78-
incrementedDay < onDaysOfMonth.first -> {
79-
if (onDaysOfMonth.first <= month.numberOfDays(year)) {
80-
copy(dayOfMonth = onDaysOfMonth.first)
81+
incrementedDay < minDayOfMonth -> {
82+
if (minDayOfMonth <= month.numberOfDays(year)) {
83+
copy(dayOfMonth = minDayOfMonth)
8184
} else {
8285
nextMonth(inMonths, increment = true).copy(dayOfMonth = 1)
8386
}
8487
}
85-
incrementedDay > onDaysOfMonth.last -> {
88+
incrementedDay > maxDayOfMonth -> {
8689
nextMonth(inMonths, increment = true).copy(dayOfMonth = 1)
8790
}
88-
else -> error("This should be impossible.")
91+
else -> copy(dayOfMonth = onDaysOfMonth.sorted().first { it > incrementedDay })
8992
}
9093
}
9194

9295
private fun LocalDateTime.nextHour(
93-
atHours: IntRange,
94-
onDaysOfMonth: IntRange,
95-
inMonths: ClosedRange<Month>,
96+
atHours: Set<Int>,
97+
onDaysOfMonth: Set<Int>,
98+
inMonths: Set<Month>,
9699
increment: Boolean = false,
97100
): LocalDateTime {
98-
require(atHours.first >= 0 && atHours.last <= 23) { "atHours $atHours not in range 0..23." }
101+
val minHour = atHours.minOrNull()!!
102+
val maxHour = atHours.maxOrNull()!!
103+
require(minHour >= 0 && maxHour <= 23) { "atHours $atHours not in range 0..23." }
99104
val incrementedHour = if (increment) (hour + 1) % 24 else hour
100105
return when {
101106
incrementedHour < hour -> nextDay(onDaysOfMonth, inMonths, increment = true).copy(hour = incrementedHour)
102107
incrementedHour in atHours -> copy(hour = incrementedHour)
103-
incrementedHour < atHours.first -> copy(hour = atHours.first, minute = 0, second = 0, nanosecond = 0)
104-
incrementedHour > atHours.last -> nextDay(onDaysOfMonth, inMonths, increment = true).copy(
105-
hour = atHours.first,
108+
incrementedHour < minHour -> copy(hour = minHour, minute = 0, second = 0, nanosecond = 0)
109+
incrementedHour > maxHour -> nextDay(onDaysOfMonth, inMonths, increment = true).copy(
110+
hour = minHour,
106111
minute = 0,
107112
second = 0,
108113
nanosecond = 0,
109114
)
110-
else -> error("This should be impossible.")
115+
else -> copy(hour = atHours.sorted().first { it > incrementedHour })
111116
}
112117
}
113118

114119
private fun LocalDateTime.nextMinute(
115-
atMinutes: IntRange,
116-
atHours: IntRange,
117-
onDaysOfMonth: IntRange,
118-
inMonths: ClosedRange<Month>,
120+
atMinutes: Set<Int>,
121+
atHours: Set<Int>,
122+
onDaysOfMonth: Set<Int>,
123+
inMonths: Set<Month>,
119124
increment: Boolean = false,
120125
): LocalDateTime {
121-
require(atMinutes.first >= 0 && atMinutes.last <= 59) { "atMinutes $atMinutes not in range 0..59." }
126+
val minMinute = atMinutes.minOrNull()!!
127+
val maxMinute = atMinutes.maxOrNull()!!
128+
require(minMinute >= 0 && maxMinute <= 59) { "atMinutes $atMinutes not in range 0..59." }
122129
val incrementedMinute = if (increment) (minute + 1) % 60 else minute
123130
return when {
124131
incrementedMinute < minute -> nextHour(atHours, onDaysOfMonth, inMonths, increment = true).copy(
125132
minute = incrementedMinute
126133
)
127134
incrementedMinute in atMinutes -> copy(minute = incrementedMinute)
128-
incrementedMinute < atMinutes.first -> copy(minute = atMinutes.first, second = 0, nanosecond = 0)
129-
incrementedMinute > atMinutes.last -> nextHour(atHours, onDaysOfMonth, inMonths, increment = true).copy(
130-
minute = atMinutes.first,
135+
incrementedMinute < minMinute -> copy(minute = minMinute, second = 0, nanosecond = 0)
136+
incrementedMinute > maxMinute -> nextHour(atHours, onDaysOfMonth, inMonths, increment = true).copy(
137+
minute = minMinute,
131138
second = 0,
132139
nanosecond = 0,
133140
)
134-
else -> error("This should be impossible.")
141+
else -> copy(minute = atMinutes.sorted().first { it > incrementedMinute })
135142
}
136143
}
137144

138145
private fun LocalDateTime.nextSecond(
139-
atSeconds: IntRange,
140-
atMinutes: IntRange,
141-
atHours: IntRange,
142-
onDaysOfMonth: IntRange,
143-
inMonths: ClosedRange<Month>,
146+
atSeconds: Set<Int>,
147+
atMinutes: Set<Int>,
148+
atHours: Set<Int>,
149+
onDaysOfMonth: Set<Int>,
150+
inMonths: Set<Month>,
144151
): LocalDateTime {
145-
require(atSeconds.first >= 0 && atSeconds.last <= 59) { " atSeconds $atSeconds not in range 0..59." }
152+
val minSecond = atSeconds.minOrNull()!!
153+
val maxSeconds = atSeconds.maxOrNull()!!
154+
require(minSecond >= 0 && maxSeconds <= 59) { " atSeconds $atSeconds not in range 0..59." }
146155
val incrementedSecond = if (nanosecond > 0) (second + 1) % 60 else second
147156
return when {
148157
incrementedSecond < second -> nextMinute(atMinutes, atHours, onDaysOfMonth, inMonths, increment = true).copy(
149158
second = incrementedSecond
150159
)
151160
incrementedSecond in atSeconds -> copy(second = incrementedSecond)
152-
incrementedSecond < atSeconds.first -> copy(second = atSeconds.first)
153-
incrementedSecond > atSeconds.last -> nextMinute(
161+
incrementedSecond < minSecond -> copy(second = minSecond)
162+
incrementedSecond > maxSeconds -> nextMinute(
154163
atMinutes,
155164
atHours,
156165
onDaysOfMonth,
157166
inMonths,
158167
increment = true,
159-
).copy(second = atSeconds.first)
160-
else -> error("This should be impossible.")
168+
).copy(second = minSecond)
169+
else -> copy(second = atSeconds.sorted().first { it > incrementedSecond })
161170
}.copy(nanosecond = 0)
162171
}
163172

src/commonMain/kotlin/io/github/kevincianfarini/cardiologist/schedule.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,13 +90,13 @@ public fun Clock.schedulePulse(
9090
val flow = flow {
9191
var lastPulse: LocalDateTime = now().toLocalDateTime(timeZone)
9292
while (true) {
93-
val nextPulse = lastPulse.nextMatch(
94-
atSeconds = atSecond?.let { it..it } ?: 0..59,
95-
atMinutes = atMinute?.let { it..it } ?: 0..59,
96-
atHours = atHour?.let { it..it } ?: 0..23,
97-
onDaysOfMonth = onDayOfMonth?.let { it..it } ?: 1..31,
98-
inMonths = inMonth?.let { it..it } ?: Month.JANUARY..Month.DECEMBER,
99-
)
93+
val nextPulse = lastPulse.nextMatch {
94+
atSecond?.run(this::atSeconds)
95+
atMinute?.run(this::atMinutes)
96+
atHour?.run(this::atHours)
97+
onDayOfMonth?.run(this::onDaysOfMonth)
98+
inMonth?.run(this::inMonths)
99+
}
100100
delayUntil(nextPulse, timeZone)
101101
emit(nextPulse.toInstant(timeZone))
102102
lastPulse = nextPulse

0 commit comments

Comments
 (0)