123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174 |
- # Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/
- class TimeplanCalculation
- DAY_MAP = {
- 0 => 'Sun',
- 1 => 'Mon',
- 2 => 'Tue',
- 3 => 'Wed',
- 4 => 'Thu',
- 5 => 'Fri',
- 6 => 'Sat'
- }.freeze
- attr_reader :timeplan
- def initialize(timeplan, timezone)
- @timeplan = timeplan.deep_transform_keys(&:to_s)
- @timezone = timezone
- end
- # Checks if given time matches timeplan
- # @param [Time]
- # @return [Boolean]
- def contains?(time)
- return false if !valid?
- time_in_zone = ensure_matching_time(time)
- day?(time_in_zone) && hour?(time_in_zone) && minute?(time_in_zone)
- end
- # Calculates next time in timeplan after the given time
- # @param [Time]
- # @return [Time, nil]
- def next_at(time)
- return nil if !valid?
- time_in_zone = ensure_matching_time(time)
- next_run_at_same_day(time_in_zone) || next_run_at_coming_week(time_in_zone)
- end
- # Calculates previous time in timeplan before the given time
- # @param [Time]
- # @return [Time, nil]
- def previous_at(time)
- return nil if !valid?
- time_in_zone = ensure_matching_time(time)
- previous_run_at_same_day(time_in_zone) || previous_run_at_past_week(time_in_zone)
- end
- private
- def ensure_matching_time(time)
- time.in_time_zone @timezone
- end
- def valid?
- timeplan.key?('days') && timeplan.key?('hours') && timeplan.key?('minutes')
- end
- def match_minutes(minutes)
- minutes / 10 * 10
- end
- def day?(time)
- timeplan['days'][DAY_MAP[time.wday]]
- end
- def hour?(time)
- timeplan.dig 'hours', time.hour.to_s
- end
- def minute?(time)
- timeplan.dig 'minutes', match_minutes(time.min).to_s
- end
- def next_loop_minutes(base_time)
- loop_minutes base_time, range: 0.step(50, 10)
- end
- def previous_loop_minutes(base_time)
- loop_minutes base_time, range: 50.step(0, -10)
- end
- def loop_minutes(base_time, range:)
- return if !hour?(base_time)
- range
- .lazy
- .map { |minute| base_time.change min: minute }
- .find { |time| minute?(time) }
- end
- def next_loop_hours(base_time)
- loop_hours base_time, range: (base_time.hour..23), minutes_symbol: :next_loop_minutes
- end
- def previous_loop_hours(base_time)
- loop_hours base_time, range: (0..base_time.hour).entries.reverse, minutes_symbol: :previous_loop_minutes
- end
- def loop_hours(base_time, range:, minutes_symbol:)
- return if !day?(base_time)
- range
- .lazy
- .map { |hour| send(minutes_symbol, base_time.change(hour: hour)) }
- .find(&:present?)
- end
- def next_loop_partial_hour(base_time)
- loop_partial_hour base_time, range: base_time.min.step(50, 10)
- end
- def previous_loop_partial_hour(base_time)
- loop_partial_hour base_time, range: base_time.min.step(0, -10)
- end
- def loop_partial_hour(base_time, range:)
- return if !day?(base_time)
- range
- .lazy
- .map { |minute| base_time.change(min: minute) }
- .find { |time| hour?(time) && minute?(time) }
- end
- def next_run_at_same_day(time)
- day_to_check = time.change min: match_minutes(time.min)
- if day_to_check.min.nonzero?
- date = next_loop_partial_hour(day_to_check)
- return date if date
- day_to_check = day_to_check.change(min: 0)
- day_to_check += 1.hour
- end
- next_loop_hours(day_to_check)
- end
- def next_run_at_coming_week(time)
- (1..7)
- .lazy
- .map { |day| next_loop_hours (time + day.day).beginning_of_day }
- .find(&:present?)
- end
- def previous_run_at_same_day(time)
- day_to_check = time.change min: match_minutes(time.min)
- if day_to_check.min.nonzero?
- date = previous_loop_partial_hour(day_to_check)
- return date if date
- day_to_check = day_to_check.change(min: 0)
- day_to_check -= 1.second
- end
- previous_loop_hours(day_to_check)
- end
- def previous_run_at_past_week(time)
- (1..7)
- .lazy
- .map { |day| previous_loop_hours (time - day.day).end_of_day }
- .find(&:present?)
- end
- end
|