|
| 1 | +module IceCube |
| 2 | + |
| 3 | + # This validation mixin is used by the various "fixed-time" (e.g. day, |
| 4 | + # day_of_month, hour_of_day) Validation and ScheduleLock::Validation modules. |
| 5 | + # It is not a standalone rule validation like the others. |
| 6 | + # |
| 7 | + # Given the including Validation's defined +type+ field, it will lock |
| 8 | + # to the specified +value+ or else the corresponding time unit from the |
| 9 | + # schedule's start_time |
| 10 | + # |
| 11 | + module Validations::Lock |
| 12 | + |
| 13 | + INTERVALS = {:min => 60, :sec => 60, :hour => 24, :month => 12, :wday => 7} |
| 14 | + |
| 15 | + def validate(time, schedule) |
| 16 | + case type |
| 17 | + when :day then validate_day_lock(time, schedule) |
| 18 | + when :hour then validate_hour_lock(time, schedule) |
| 19 | + else validate_interval_lock(time, schedule) |
| 20 | + end |
| 21 | + end |
| 22 | + |
| 23 | + private |
| 24 | + |
| 25 | + # Validate if the current time unit matches the same unit from the schedule |
| 26 | + # start time, returning the difference to the interval |
| 27 | + # |
| 28 | + def validate_interval_lock(time, schedule) |
| 29 | + t0 = starting_unit(schedule.start_time) |
| 30 | + t1 = time.send(type) |
| 31 | + t0 >= t1 ? t0 - t1 : INTERVALS[type] - t1 + t0 |
| 32 | + end |
| 33 | + |
| 34 | + # Lock the hour if explicitly set by hour_of_day, but allow for the nearest |
| 35 | + # hour during DST start to keep the correct interval. |
| 36 | + # |
| 37 | + def validate_hour_lock(time, schedule) |
| 38 | + h0 = starting_unit(schedule.start_time) |
| 39 | + h1 = time.hour |
| 40 | + if h0 >= h1 |
| 41 | + h0 - h1 |
| 42 | + else |
| 43 | + if dst_offset = TimeUtil.dst_change(time) |
| 44 | + h0 - h1 + dst_offset |
| 45 | + else |
| 46 | + 24 - h1 + h0 |
| 47 | + end |
| 48 | + end |
| 49 | + end |
| 50 | + |
| 51 | + # For monthly rules that have no specified day value, the validation relies |
| 52 | + # on the schedule start time and jumps to include every month even if it |
| 53 | + # has fewer days than the schedule's start day. |
| 54 | + # |
| 55 | + # Negative day values (from month end) also include all months. |
| 56 | + # |
| 57 | + # Positive day values are taken literally so months with fewer days will |
| 58 | + # be skipped. |
| 59 | + # |
| 60 | + def validate_day_lock(time, schedule) |
| 61 | + days_in_month = TimeUtil.days_in_month(time) |
| 62 | + date = Date.new(time.year, time.month, time.day) |
| 63 | + |
| 64 | + if value && value < 0 |
| 65 | + start = TimeUtil.day_of_month(value, date) |
| 66 | + month_overflow = days_in_month - TimeUtil.days_in_next_month(time) |
| 67 | + elsif value && value > 0 |
| 68 | + start = value |
| 69 | + month_overflow = 0 |
| 70 | + else |
| 71 | + start = TimeUtil.day_of_month(schedule.start_time.day, date) |
| 72 | + month_overflow = 0 |
| 73 | + end |
| 74 | + |
| 75 | + sleeps = start - date.day |
| 76 | + |
| 77 | + if value && value > 0 |
| 78 | + until_next_month = days_in_month + sleeps |
| 79 | + else |
| 80 | + until_next_month = start < 28 ? days_in_month : TimeUtil.days_to_next_month(date) |
| 81 | + until_next_month += sleeps - month_overflow |
| 82 | + end |
| 83 | + |
| 84 | + sleeps >= 0 ? sleeps : until_next_month |
| 85 | + end |
| 86 | + |
| 87 | + def starting_unit(start_time) |
| 88 | + start = value || start_time.send(type) |
| 89 | + start += INTERVALS[type] while start < 0 |
| 90 | + start |
| 91 | + end |
| 92 | + |
| 93 | + end |
| 94 | + |
| 95 | +end |
0 commit comments