Alpha version cron 'next' function
parent
e3da3d0d7f
commit
135b6c3d7f
|
@ -27,6 +27,29 @@ pub fn cron(conf Config) ? {
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// println(queue)
|
// println(queue)
|
||||||
exp := '10/2 5 *'
|
// exp := '10/2 5 *'
|
||||||
println(parse_expression(exp) ?)
|
// println(parse_expression(exp) ?)
|
||||||
|
ce := parse_expression('0 3 */2') ?
|
||||||
|
println(ce)
|
||||||
|
// ce := CronExpression{
|
||||||
|
// minutes: [0]
|
||||||
|
// hours: [3]
|
||||||
|
// days: [1, 2, 3, 4, 5, 6]
|
||||||
|
// months: [1, 2]
|
||||||
|
// }
|
||||||
|
mut t := time.Time{
|
||||||
|
year: 2022
|
||||||
|
month: 2
|
||||||
|
minute: 9
|
||||||
|
hour: 13
|
||||||
|
day: 12
|
||||||
|
}
|
||||||
|
|
||||||
|
// mut t := time.now()
|
||||||
|
println(t)
|
||||||
|
|
||||||
|
for _ in 1..25 {
|
||||||
|
t = ce.next(t) ?
|
||||||
|
println(t)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,50 +3,117 @@ module cron
|
||||||
import math
|
import math
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
const days_in_month = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31]
|
||||||
|
|
||||||
struct CronExpression {
|
struct CronExpression {
|
||||||
minutes []u32
|
minutes []int
|
||||||
hours []u32
|
hours []int
|
||||||
days []u32
|
days []int
|
||||||
|
months []int
|
||||||
}
|
}
|
||||||
|
|
||||||
// next calculates the earliest time this cron expression is valid.
|
// next calculates the earliest time this cron expression is valid.
|
||||||
pub fn (ce &CronExpression) next(ref &time.Time) time.Time {
|
pub fn (ce &CronExpression) next(ref time.Time) ?time.Time {
|
||||||
res := time.Time{}
|
mut minute_index := 0
|
||||||
|
mut hour_index := 0
|
||||||
|
mut day_index := 0
|
||||||
|
mut month_index := 0
|
||||||
|
|
||||||
mut day := 0
|
for month_index < ce.months.len && ref.month > ce.months[month_index] {
|
||||||
mut hour := 0
|
month_index++
|
||||||
mut minute := 0
|
}
|
||||||
|
|
||||||
// Find the next minute
|
if month_index < ce.months.len {
|
||||||
// If ref.minute is greater than
|
for day_index < ce.days.len && ref.day > ce.days[day_index] {
|
||||||
if ref.minute >= ce.minutes[ce.minutes.len - 1] || ref.minute < ce.minutes[0] {
|
day_index++
|
||||||
minute = ce.minutes[0]
|
}
|
||||||
}else{
|
|
||||||
for i in 0..ce.minutes.len {
|
if day_index < ce.days.len {
|
||||||
if ce.minutes[i] > ref.minute {
|
for hour_index < ce.hours.len && ref.hour > ce.hours[hour_index] {
|
||||||
minute = ce.minutes[i]
|
hour_index++
|
||||||
break
|
}
|
||||||
|
|
||||||
|
if hour_index < ce.hours.len {
|
||||||
|
// For each unit, we calculate what the next value is
|
||||||
|
for minute_index < ce.minutes.len && ref.minute >= ce.minutes[minute_index] {
|
||||||
|
minute_index++
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res
|
|
||||||
|
// Sometime we have to shift values one more
|
||||||
|
if minute_index == ce.minutes.len && hour_index < ce.hours.len {
|
||||||
|
hour_index += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if hour_index == ce.hours.len && day_index < ce.days.len {
|
||||||
|
day_index += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if day_index == ce.days.len && month_index < ce.months.len {
|
||||||
|
month_index += 1
|
||||||
|
}
|
||||||
|
|
||||||
|
mut minute := ce.minutes[minute_index % ce.minutes.len]
|
||||||
|
mut hour := ce.hours[hour_index % ce.hours.len]
|
||||||
|
mut day := ce.days[day_index % ce.days.len]
|
||||||
|
|
||||||
|
mut reset := false
|
||||||
|
|
||||||
|
// If the day can't be planned in the current month, we go to the next one
|
||||||
|
// and go back to day one
|
||||||
|
if day > days_in_month[ce.months[month_index % ce.months.len] - 1] {
|
||||||
|
month_index += 1
|
||||||
|
day = ce.days[0]
|
||||||
|
|
||||||
|
// Make sure we only plan in a month that the day occurs in
|
||||||
|
for day > days_in_month[ce.months[month_index & ce.months.len] - 1] {
|
||||||
|
month_index += 1
|
||||||
|
|
||||||
|
// Prevent scenario where there are no months that can be scheduled.
|
||||||
|
if month_index == 2 * ce.months.len {
|
||||||
|
return error('No schedulable moment.')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
month := ce.months[month_index % ce.months.len]
|
||||||
|
mut year := ref.year
|
||||||
|
|
||||||
|
if month_index >= ce.months.len {
|
||||||
|
year++
|
||||||
|
}
|
||||||
|
|
||||||
|
return time.Time{
|
||||||
|
year: year
|
||||||
|
month: month
|
||||||
|
day: day
|
||||||
|
minute: minute
|
||||||
|
hour: hour
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn (ce &CronExpression) next_from_now() ?time.Time {
|
||||||
|
return ce.next(time.now())
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse_range parses a given string into a range of sorted integers, if
|
// parse_range parses a given string into a range of sorted integers, if
|
||||||
// possible.
|
// possible.
|
||||||
fn parse_range(s string, min u32, max u32) ?[]u32 {
|
fn parse_range(s string, min int, max int) ?[]int {
|
||||||
mut out := []u32{}
|
mut out := []int{}
|
||||||
mut start := min
|
mut start := min
|
||||||
mut interval := u32(1)
|
mut interval := 1
|
||||||
|
|
||||||
if s != '*' {
|
if s != '*' {
|
||||||
exps := s.split('/')
|
exps := s.split('/')
|
||||||
|
|
||||||
start = math.min(max, math.max(exps[0].u32(), min))
|
start = math.min(max, math.max(exps[0].int(), min))
|
||||||
|
|
||||||
if exps.len > 1 {
|
if exps.len > 1 {
|
||||||
interval = exps[1].u32()
|
interval = exps[1].int()
|
||||||
}
|
}
|
||||||
// Here, s solely consists of a number, so that's the only value we
|
// Here, s solely consists of a number, so that's the only value we
|
||||||
// should return.
|
// should return.
|
||||||
|
@ -69,15 +136,22 @@ fn parse_range(s string, min u32, max u32) ?[]u32 {
|
||||||
|
|
||||||
// min hour day month day-of-week
|
// min hour day month day-of-week
|
||||||
fn parse_expression(exp string) ?CronExpression {
|
fn parse_expression(exp string) ?CronExpression {
|
||||||
parts := exp.split(' ')
|
mut parts := exp.split(' ')
|
||||||
|
|
||||||
if parts.len != 3 {
|
if parts.len < 2 || parts.len > 4 {
|
||||||
return error('Expression must contain 5 space-separated parts.')
|
return error('Expression must contain between 2 and 4 space-separated parts.')
|
||||||
|
}
|
||||||
|
|
||||||
|
// For ease of use, we allow the user to only specify as many parts as they
|
||||||
|
// need.
|
||||||
|
for parts.len < 4 {
|
||||||
|
parts << '*'
|
||||||
}
|
}
|
||||||
|
|
||||||
return CronExpression{
|
return CronExpression{
|
||||||
minutes: parse_range(parts[0], 0, 59) ?
|
minutes: parse_range(parts[0], 0, 59) ?
|
||||||
hours: parse_range(parts[1], 0, 23) ?
|
hours: parse_range(parts[1], 0, 23) ?
|
||||||
days: parse_range(parts[2], 0, 31) ?
|
days: parse_range(parts[2], 1, 31) ?
|
||||||
|
months: parse_range(parts[3], 1, 12) ?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,27 +2,27 @@ module cron
|
||||||
|
|
||||||
// =====parse_range=====
|
// =====parse_range=====
|
||||||
fn test_parse_star_range() ? {
|
fn test_parse_star_range() ? {
|
||||||
assert parse_range('*', 0, 5) ? == [u32(0), 1, 2, 3, 4, 5]
|
assert parse_range('*', 0, 5) ? == [0, 1, 2, 3, 4, 5]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_parse_number() ? {
|
fn test_parse_number() ? {
|
||||||
assert parse_range('4', 0, 5) ? == [u32(4)]
|
assert parse_range('4', 0, 5) ? == [4]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_parse_number_too_large() ? {
|
fn test_parse_number_too_large() ? {
|
||||||
assert parse_range('10', 0, 6) ? == [u32(6)]
|
assert parse_range('10', 0, 6) ? == [6]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_parse_number_too_small() ? {
|
fn test_parse_number_too_small() ? {
|
||||||
assert parse_range('0', 2, 6) ? == [u32(2)]
|
assert parse_range('0', 2, 6) ? == [2]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_parse_step_star() ? {
|
fn test_parse_step_star() ? {
|
||||||
assert parse_range('*/4', 0, 20) ? == [u32(0), 4, 8, 12, 16, 20]
|
assert parse_range('*/4', 0, 20) ? == [0, 4, 8, 12, 16, 20]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_parse_step_star_too_large() ? {
|
fn test_parse_step_star_too_large() ? {
|
||||||
assert parse_range('*/21', 0, 20) ? == [u32(0)]
|
assert parse_range('*/21', 0, 20) ? == [0]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_parse_step_zero() ? {
|
fn test_parse_step_zero() ? {
|
||||||
|
@ -30,15 +30,15 @@ fn test_parse_step_zero() ? {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_parse_step_number() ? {
|
fn test_parse_step_number() ? {
|
||||||
assert parse_range('5/4', 0, 20) ? == [u32(5), 9, 13, 17]
|
assert parse_range('5/4', 0, 20) ? == [5, 9, 13, 17]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_parse_step_number_too_large() ? {
|
fn test_parse_step_number_too_large() ? {
|
||||||
assert parse_range('10/4', 0, 5) ? == [u32(5)]
|
assert parse_range('10/4', 0, 5) ? == [5]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_parse_step_number_too_small() ? {
|
fn test_parse_step_number_too_small() ? {
|
||||||
assert parse_range('2/4', 5, 10) ? == [u32(5), 9]
|
assert parse_range('2/4', 5, 10) ? == [5, 9]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue