timeplan_calculation_spec.rb 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. # Copyright (C) 2012-2022 Zammad Foundation, https://zammad-foundation.org/
  2. require 'rails_helper'
  3. RSpec.describe TimeplanCalculation do
  4. subject(:instance) { described_class.new(timeplan, timezone) }
  5. let(:timezone) { nil }
  6. describe '#contains?' do
  7. context 'without a valid timeplan' do
  8. let(:timeplan) { {} }
  9. it { is_expected.not_to be_contains(Time.zone.now) }
  10. end
  11. context 'with monday 09:20' do
  12. let(:timeplan) { { 'days' => { 'Mon' => true }, 'hours' => { '9' => true }, 'minutes' => { '20' => true } } }
  13. it { is_expected.to be_contains(Time.zone.parse('2020-12-28 09:20')) }
  14. it { is_expected.to be_contains(Time.zone.parse('2020-12-21 09:20')) }
  15. it { is_expected.not_to be_contains(Time.zone.parse('2020-12-21 10:20')) }
  16. it { is_expected.not_to be_contains(Time.zone.parse('2020-12-21 9:10')) }
  17. it { is_expected.not_to be_contains(Time.zone.parse('2020-12-22 9:20')) }
  18. it { is_expected.not_to be_contains(Time.zone.parse('2020-12-20 9:20')) }
  19. end
  20. context 'with monday and tuesday 09:20' do
  21. let(:timeplan) { { 'days' => { 'Mon' => true, 'Tue' => true }, 'hours' => { '9' => true }, 'minutes' => { '20' => true } } }
  22. it { is_expected.to be_contains(Time.zone.parse('2020-12-28 09:20')) }
  23. it { is_expected.to be_contains(Time.zone.parse('2020-12-21 09:20')) }
  24. it { is_expected.not_to be_contains(Time.zone.parse('2020-12-21 10:20')) }
  25. it { is_expected.not_to be_contains(Time.zone.parse('2020-12-21 9:10')) }
  26. it { is_expected.to be_contains(Time.zone.parse('2020-12-22 9:20')) }
  27. it { is_expected.not_to be_contains(Time.zone.parse('2020-12-20 9:20')) }
  28. end
  29. context 'with monday 09:20 and 10:20' do
  30. let(:timeplan) { { 'days' => { 'Mon' => true }, 'hours' => { '9' => true, '10' => true }, 'minutes' => { '20' => true } } }
  31. it { is_expected.to be_contains(Time.zone.parse('2020-12-28 09:20')) }
  32. it { is_expected.to be_contains(Time.zone.parse('2020-12-21 09:20')) }
  33. it { is_expected.to be_contains(Time.zone.parse('2020-12-21 10:20')) }
  34. it { is_expected.not_to be_contains(Time.zone.parse('2020-12-21 9:10')) }
  35. it { is_expected.not_to be_contains(Time.zone.parse('2020-12-22 9:20')) }
  36. it { is_expected.not_to be_contains(Time.zone.parse('2020-12-20 9:20')) }
  37. end
  38. context 'with monday 09:20 and 9:10' do
  39. let(:timeplan) { { 'days' => { 'Mon' => true }, 'hours' => { '9' => true }, 'minutes' => { '20' => true, '10' => true } } }
  40. it { is_expected.to be_contains(Time.zone.parse('2020-12-28 09:20')) }
  41. it { is_expected.to be_contains(Time.zone.parse('2020-12-21 09:20')) }
  42. it { is_expected.not_to be_contains(Time.zone.parse('2020-12-21 10:20')) }
  43. it { is_expected.to be_contains(Time.zone.parse('2020-12-21 9:10')) }
  44. it { is_expected.not_to be_contains(Time.zone.parse('2020-12-22 9:20')) }
  45. it { is_expected.not_to be_contains(Time.zone.parse('2020-12-20 9:20')) }
  46. end
  47. context 'with monday 09:00 and time zone' do
  48. let(:timeplan) { { 'days' => { 'Mon' => true }, 'hours' => { '1' => true }, 'minutes' => { '0' => true } } }
  49. let(:timezone) { 'Europe/Vilnius' }
  50. it { is_expected.to be_contains(Time.use_zone('Europe/Vilnius') { Time.zone.parse('2020-12-21 01:00') }) }
  51. it { is_expected.to be_contains(Time.zone.parse('2020-12-20 23:00')) }
  52. end
  53. end
  54. describe '#next_at' do
  55. context 'without a valid timeplan' do
  56. let(:timeplan) { {} }
  57. it { expect(instance.next_at(Time.zone.now)).to be_nil }
  58. end
  59. context 'with monday 09:20' do
  60. let(:timeplan) { { 'days' => { 'Mon' => true }, 'hours' => { '9' => true }, 'minutes' => { '20' => true } } }
  61. it { expect(instance.next_at(Time.zone.parse('2020-12-28 09:31'))).to eq(Time.zone.parse('2021-01-04 09:20')) }
  62. it { expect(instance.next_at(Time.zone.parse('2020-12-28 09:30'))).to eq(Time.zone.parse('2021-01-04 09:20')) }
  63. it { expect(instance.next_at(Time.zone.parse('2020-12-28 09:20'))).to eq(Time.zone.parse('2020-12-28 09:20')) }
  64. it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:21'))).to eq(Time.zone.parse('2020-12-21 09:20')) }
  65. it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:35'))).to eq(Time.zone.parse('2020-12-28 09:20')) }
  66. it { expect(instance.next_at(Time.zone.parse('2020-12-21 10:20'))).to eq(Time.zone.parse('2020-12-28 09:20')) }
  67. it { expect(instance.next_at(Time.zone.parse('2020-12-21 10:21'))).to eq(Time.zone.parse('2020-12-28 09:20')) }
  68. it { expect(instance.next_at(Time.zone.parse('2020-12-21 9:10'))).to eq(Time.zone.parse('2020-12-21 09:20')) }
  69. it { expect(instance.next_at(Time.zone.parse('2020-12-22 9:20'))).to eq(Time.zone.parse('2020-12-28 09:20')) }
  70. it { expect(instance.next_at(Time.zone.parse('2020-12-20 9:20'))).to eq(Time.zone.parse('2020-12-21 09:20')) }
  71. end
  72. context 'with monday and tuesday 09:20' do
  73. let(:timeplan) { { 'days' => { 'Mon' => true, 'Tue' => true }, 'hours' => { '9' => true }, 'minutes' => { '20' => true } } }
  74. it { expect(instance.next_at(Time.zone.parse('2020-12-28 09:30'))).to eq(Time.zone.parse('2020-12-29 09:20')) }
  75. it { expect(instance.next_at(Time.zone.parse('2020-12-29 09:30'))).to eq(Time.zone.parse('2021-01-04 09:20')) }
  76. it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:25'))).to eq(Time.zone.parse('2020-12-21 09:20')) }
  77. it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:35'))).to eq(Time.zone.parse('2020-12-22 09:20')) }
  78. it { expect(instance.next_at(Time.zone.parse('2020-12-21 10:20'))).to eq(Time.zone.parse('2020-12-22 09:20')) }
  79. it { expect(instance.next_at(Time.zone.parse('2020-12-21 10:21'))).to eq(Time.zone.parse('2020-12-22 09:20')) }
  80. it { expect(instance.next_at(Time.zone.parse('2020-12-21 9:10'))).to eq(Time.zone.parse('2020-12-21 09:20')) }
  81. it { expect(instance.next_at(Time.zone.parse('2020-12-22 9:30'))).to eq(Time.zone.parse('2020-12-28 09:20')) }
  82. it { expect(instance.next_at(Time.zone.parse('2020-12-20 9:20'))).to eq(Time.zone.parse('2020-12-21 09:20')) }
  83. end
  84. context 'with monday 09:20 and 10:20' do
  85. let(:timeplan) { { 'days' => { 'Mon' => true }, 'hours' => { '9' => true, '10' => true }, 'minutes' => { '20' => true } } }
  86. it { expect(instance.next_at(Time.zone.parse('2020-12-28 09:30'))).to eq(Time.zone.parse('2020-12-28 10:20')) }
  87. it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:30'))).to eq(Time.zone.parse('2020-12-21 10:20')) }
  88. it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:35'))).to eq(Time.zone.parse('2020-12-21 10:20')) }
  89. it { expect(instance.next_at(Time.zone.parse('2020-12-21 08:07'))).to eq(Time.zone.parse('2020-12-21 09:20')) }
  90. it { expect(instance.next_at(Time.zone.parse('2020-12-21 10:30'))).to eq(Time.zone.parse('2020-12-28 09:20')) }
  91. it { expect(instance.next_at(Time.zone.parse('2020-12-21 10:31'))).to eq(Time.zone.parse('2020-12-28 09:20')) }
  92. it { expect(instance.next_at(Time.zone.parse('2020-12-21 9:10'))).to eq(Time.zone.parse('2020-12-21 09:20')) }
  93. it { expect(instance.next_at(Time.zone.parse('2020-12-21 9:47'))).to eq(Time.zone.parse('2020-12-21 10:20')) }
  94. it { expect(instance.next_at(Time.zone.parse('2020-12-22 9:20'))).to eq(Time.zone.parse('2020-12-28 09:20')) }
  95. it { expect(instance.next_at(Time.zone.parse('2020-12-20 9:30'))).to eq(Time.zone.parse('2020-12-21 09:20')) }
  96. end
  97. context 'with monday 09:30 and 9:10' do
  98. let(:timeplan) { { 'days' => { 'Mon' => true }, 'hours' => { '9' => true }, 'minutes' => { '30' => true, '10' => true } } }
  99. it { expect(instance.next_at(Time.zone.parse('2020-12-28 09:40'))).to eq(Time.zone.parse('2021-01-04 09:10')) }
  100. it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:40'))).to eq(Time.zone.parse('2020-12-28 09:10')) }
  101. it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:30'))).to eq(Time.zone.parse('2020-12-21 09:30')) }
  102. it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:25'))).to eq(Time.zone.parse('2020-12-21 09:30')) }
  103. it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:35'))).to eq(Time.zone.parse('2020-12-21 09:30')) }
  104. it { expect(instance.next_at(Time.zone.parse('2020-12-21 09:45'))).to eq(Time.zone.parse('2020-12-28 09:10')) }
  105. it { expect(instance.next_at(Time.zone.parse('2020-12-21 08:07'))).to eq(Time.zone.parse('2020-12-21 09:10')) }
  106. it { expect(instance.next_at(Time.zone.parse('2020-12-21 10:20'))).to eq(Time.zone.parse('2020-12-28 09:10')) }
  107. it { expect(instance.next_at(Time.zone.parse('2020-12-21 10:21'))).to eq(Time.zone.parse('2020-12-28 09:10')) }
  108. it { expect(instance.next_at(Time.zone.parse('2020-12-21 9:10'))).to eq(Time.zone.parse('2020-12-21 09:10')) }
  109. it { expect(instance.next_at(Time.zone.parse('2020-12-21 9:20'))).to eq(Time.zone.parse('2020-12-21 09:30')) }
  110. it { expect(instance.next_at(Time.zone.parse('2020-12-22 9:20'))).to eq(Time.zone.parse('2020-12-28 09:10')) }
  111. it { expect(instance.next_at(Time.zone.parse('2020-12-20 9:20'))).to eq(Time.zone.parse('2020-12-21 09:10')) }
  112. end
  113. context 'with monday 09:00 and time zone' do
  114. let(:timezone) { 'Europe/Vilnius' }
  115. let(:timeplan) { { 'days' => { 'Mon' => true }, 'hours' => { '1' => true }, 'minutes' => { '0' => true } } }
  116. it 'calculates time respecting time zone' do
  117. from = Time.use_zone('Europe/Vilnius') { Time.zone.parse('2020-12-21 01:15') }
  118. to = Time.use_zone('Europe/Vilnius') { Time.zone.parse('2020-12-28 01:00') }
  119. expect(instance.next_at(from)).to eq(to)
  120. end
  121. it 'calculates time converting to time zone' do
  122. from = Time.zone.parse('2020-12-20 23:10')
  123. to = Time.use_zone('Europe/Vilnius') { Time.zone.parse('2020-12-28 01:00') }
  124. expect(instance.next_at(from)).to eq(to)
  125. end
  126. end
  127. end
  128. describe 'legacy tests moved from Job model' do
  129. let(:job) { create(:job, :never_on) }
  130. let(:timeplan) { job.timeplan }
  131. let(:time) { Time.current }
  132. context 'when the current day, hour, and minute all match true values in #timeplan' do
  133. it 'for Symbol/Integer keys returns true' do
  134. timeplan[:days].transform_keys!(&:to_sym)[time.strftime('%a').to_sym] = true
  135. timeplan[:hours].transform_keys!(&:to_i)[time.hour] = true
  136. timeplan[:minutes].transform_keys!(&:to_i)[time.min.floor(-1)] = true
  137. expect(instance.contains?(time)).to be(true)
  138. end
  139. it 'for String keys returns true' do
  140. timeplan[:days].transform_keys!(&:to_s)[time.strftime('%a')] = true
  141. timeplan[:hours].transform_keys!(&:to_s)[time.hour.to_s] = true
  142. timeplan[:minutes].transform_keys!(&:to_s)[time.min.floor(-1).to_s] = true
  143. expect(instance.contains?(time)).to be(true)
  144. end
  145. end
  146. context 'when the current day does not match a true value in #timeplan' do
  147. it 'for Symbol/Integer keys returns false' do
  148. timeplan[:days].transform_keys!(&:to_sym).transform_values! { true }[time.strftime('%a').to_sym] = false
  149. timeplan[:hours].transform_keys!(&:to_i)[time.hour] = true
  150. timeplan[:minutes].transform_keys!(&:to_i)[time.min.floor(-1)] = true
  151. expect(instance.contains?(time)).to be(false)
  152. end
  153. it 'for String keys returns false' do
  154. timeplan[:days].transform_keys!(&:to_s).transform_values! { true }[time.strftime('%a')] = false
  155. timeplan[:hours].transform_keys!(&:to_s)[time.hour.to_s] = true
  156. timeplan[:minutes].transform_keys!(&:to_s)[time.min.floor(-1).to_s] = true
  157. expect(instance.contains?(time)).to be(false)
  158. end
  159. end
  160. context 'when the current hour does not match a true value in #timeplan' do
  161. it 'for Symbol/Integer keys returns false' do
  162. timeplan[:days].transform_keys!(&:to_sym)[time.strftime('%a').to_sym] = true
  163. timeplan[:hours].transform_keys!(&:to_i).transform_values! { true }[time.hour] = false
  164. timeplan[:minutes].transform_keys!(&:to_i)[time.min.floor(-1)] = true
  165. expect(instance.contains?(time)).to be(false)
  166. end
  167. it 'for String keys returns false' do
  168. timeplan[:days].transform_keys!(&:to_s)[time.strftime('%a')] = true
  169. timeplan[:hours].transform_keys!(&:to_s).transform_values! { true }[time.hour.to_s] = false
  170. timeplan[:minutes].transform_keys!(&:to_s)[time.min.floor(-1).to_s] = true
  171. expect(instance.contains?(time)).to be(false)
  172. end
  173. end
  174. context 'when the current minute does not match a true value in #timeplan' do
  175. it 'for Symbol/Integer keys returns false' do
  176. timeplan[:days].transform_keys!(&:to_sym)[time.strftime('%a').to_sym] = true
  177. timeplan[:hours].transform_keys!(&:to_i)[time.hour] = true
  178. timeplan[:minutes].transform_keys!(&:to_i).transform_values! { true }[time.min.floor(-1)] = false
  179. expect(instance.contains?(time)).to be(false)
  180. end
  181. it 'for String keys returns false' do
  182. timeplan[:days].transform_keys!(&:to_s)[time.strftime('%a')] = true
  183. timeplan[:hours].transform_keys!(&:to_s)[time.hour.to_s] = true
  184. timeplan[:minutes].transform_keys!(&:to_s).transform_values! { true }[time.min.floor(-1).to_s] = false
  185. expect(instance.contains?(time)).to be(false)
  186. end
  187. end
  188. end
  189. end