timeplan_calculation.rb 2.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  1. # Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
  2. class TimeplanCalculation
  3. DAY_MAP = {
  4. 0 => 'Sun',
  5. 1 => 'Mon',
  6. 2 => 'Tue',
  7. 3 => 'Wed',
  8. 4 => 'Thu',
  9. 5 => 'Fri',
  10. 6 => 'Sat'
  11. }.freeze
  12. attr_reader :timeplan
  13. def initialize(timeplan, timezone)
  14. @timeplan = timeplan.deep_transform_keys(&:to_s)
  15. @timezone = timezone
  16. end
  17. def contains?(time)
  18. return false if !valid?
  19. time_in_zone = ensure_matching_time(time)
  20. day?(time_in_zone) && hour?(time_in_zone) && minute?(time_in_zone)
  21. end
  22. def next_at(time)
  23. return nil if !valid?
  24. time_in_zone = ensure_matching_time(time)
  25. next_run_at_same_day(time_in_zone) || next_run_at_coming_week(time_in_zone)
  26. end
  27. private
  28. def ensure_matching_time(time)
  29. time.in_time_zone @timezone
  30. end
  31. def valid?
  32. timeplan.key?('days') && timeplan.key?('hours') && timeplan.key?('minutes')
  33. end
  34. def match_minutes(minutes)
  35. minutes / 10 * 10
  36. end
  37. def day?(time)
  38. timeplan['days'][DAY_MAP[time.wday]]
  39. end
  40. def hour?(time)
  41. timeplan.dig 'hours', time.hour.to_s
  42. end
  43. def minute?(time)
  44. timeplan.dig 'minutes', match_minutes(time.min).to_s
  45. end
  46. def loop_minutes(base_time)
  47. return if !hour?(base_time)
  48. 0
  49. .step(50, 10)
  50. .lazy
  51. .map { |minute| base_time.change min: minute }
  52. .find { |time| minute?(time) }
  53. end
  54. def loop_hours(base_time)
  55. return if !day?(base_time)
  56. (base_time.hour..23)
  57. .lazy
  58. .map { |hour| loop_minutes base_time.change hour: hour }
  59. .find(&:present?)
  60. end
  61. def loop_partial_hour(base_time)
  62. return if !day?(base_time)
  63. base_time
  64. .min
  65. .step(50, 10)
  66. .lazy
  67. .map { |minute| base_time.change(min: minute) }
  68. .find { |time| hour?(time) && minute?(time) }
  69. end
  70. def next_run_at_same_day(time)
  71. day_to_check = time.change min: match_minutes(time.min)
  72. if day_to_check.min.nonzero?
  73. date = loop_partial_hour(day_to_check)
  74. return date if date
  75. day_to_check = day_to_check.change(min: 0)
  76. day_to_check += 1.hour
  77. end
  78. loop_hours(day_to_check)
  79. end
  80. def next_run_at_coming_week(time)
  81. (1..7)
  82. .lazy
  83. .map { |day| loop_hours (time + day.day).midnight }
  84. .find(&:present?)
  85. end
  86. end