11package 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
36import kotlinx.datetime.LocalDateTime
47import kotlinx.datetime.Month
58import 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
3534private 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
6465private 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
9295private 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
114119private 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
138145private 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
0 commit comments