working_hours 1.3.1 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 26d036d577be17378683ab793c1b7331a0f6609fc3c6faf11f46681dd9a3a88b
4
- data.tar.gz: f037fc32ac6249d4d506a31a75bb69b7fbd5de35022ac39b332a521bae6d9e88
3
+ metadata.gz: 102b64fd640e32b4164e32d0323d3575bfd720064d02045ff65f7e320b7dd305
4
+ data.tar.gz: dea4493b517eda97438e35f3857da9b456ead0e992b6278b33d108f1c38ef7ca
5
5
  SHA512:
6
- metadata.gz: 1fa880f28c9d500c1964942ef13c70e5695d0bb5368a24e2bf711ff735de2d96790986f9cc5656e98fe29c623ae560a1bd4930e48f0275e7aa49e00eb6f67f0d
7
- data.tar.gz: 4c719df9ec920d581fa304c4f9e55d6a97a2484a4f456b62ee5ed89a734f23f7a8c990e57cfce1c3484cd14878c120704b512bd9455aea348cb6933ce127cbba
6
+ metadata.gz: 88e9102b1a8111aab7b0c5017c93f2eda8c99821499dfe9aa91e89572925b090a95840059a91de0e1114340ae566973a4d67844ebabee4a8f93dd6f11ae94786
7
+ data.tar.gz: 5dcdc183274c968c8cf61880870642ec1045092f9f579fb0f14c9ad1e45ffd232e37aa544c0a1c2f4c58dca4a676d1c6856fdd7cf95171e8702201ec44b550bc
data/CHANGELOG.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  [Compare master with v1.3.1](https://github.com/intrepidd/working_hours/compare/v1.3.1...master)
4
4
 
5
+ # v1.3.2
6
+ * Improve support for time shifts - [#46](https://github.com/Intrepidd/working_hours/pull/46)
7
+
5
8
  # v1.3.1
6
9
  * Improve computation accuracy in `advance_to_working_time` and `working_time_between` by using more exact (integer-based) time operations instead of floating point numbers - [#44](https://github.com/Intrepidd/working_hours/pull/44)
7
10
  * Raise an exception when we detect an infinite loops in `advance_to_working_time` to improve resilience and make debugging easier - [#44](https://github.com/Intrepidd/working_hours/pull/44)
@@ -82,7 +82,7 @@ module WorkingHours
82
82
  time_in_day = time.seconds_since_midnight
83
83
  config[:working_hours][time.wday].each do |from, to|
84
84
  return time if time_in_day >= from and time_in_day < to
85
- return time.beginning_of_day + from if from >= time_in_day
85
+ return move_time_of_day(time, from) if from >= time_in_day
86
86
  end
87
87
  # if none is found, go to next day and loop
88
88
  time = (time + 1.day).beginning_of_day
@@ -99,12 +99,11 @@ module WorkingHours
99
99
  end
100
100
  # find next working range after time
101
101
  time_in_day = time.seconds_since_midnight
102
- time = time.beginning_of_day
103
102
  config[:working_hours][time.wday].each do |from, to|
104
- return time + to if time_in_day < to
103
+ return move_time_of_day(time, to) if time_in_day < to
105
104
  end
106
105
  # if none is found, go to next day and loop
107
- time = time + 1.day
106
+ time = (time + 1.day).beginning_of_day
108
107
  end
109
108
  end
110
109
 
@@ -131,9 +130,8 @@ module WorkingHours
131
130
  # find last working range before time
132
131
  time_in_day = time.seconds_since_midnight
133
132
  config[:working_hours][time.wday].reverse_each do |from, to|
134
- # round is used to suppress miliseconds hack from `end_of_day`
135
133
  return time if time_in_day > from and time_in_day <= to
136
- return (time - (time_in_day - to)) if to <= time_in_day
134
+ return move_time_of_day(time, to) if to <= time_in_day
137
135
  end
138
136
  # if none is found, go to previous day and loop
139
137
  time = (time - 1.day).end_of_day
@@ -190,7 +188,7 @@ module WorkingHours
190
188
  if (to - from) > (ends - time_in_day)
191
189
  # take all the range and continue
192
190
  distance += (ends - time_in_day)
193
- from = from.beginning_of_day + ends
191
+ from = move_time_of_day(from, ends)
194
192
  else
195
193
  # take only what's needed and stop
196
194
  distance += (to - from)
@@ -208,6 +206,23 @@ module WorkingHours
208
206
 
209
207
  private
210
208
 
209
+ # Changes the time of the day to match given time (in seconds since midnight)
210
+ # preserving nanosecond prevision (rational number) and honoring time shifts
211
+ #
212
+ # This replaces the previous implementation which was:
213
+ # time.beginning_of_day + seconds
214
+ # (because this one would shift hours during time shifts days)
215
+ def move_time_of_day time, seconds
216
+ # return time.beginning_of_day + seconds
217
+ hour = (seconds / 3600).to_i
218
+ seconds %= 3600
219
+ minutes = (seconds / 60).to_i
220
+ seconds %= 60
221
+ # sec/usec separation is required for ActiveSupport <= 5.1
222
+ usec = ((seconds % 1) * 10**6)
223
+ time.change(hour: hour, min: minutes, sec: seconds.to_i, usec: usec)
224
+ end
225
+
211
226
  def wh_config
212
227
  WorkingHours::Config.precompiled
213
228
  end
@@ -1,3 +1,3 @@
1
1
  module WorkingHours
2
- VERSION = "1.3.1"
2
+ VERSION = "1.3.2"
3
3
  end
@@ -177,6 +177,32 @@ describe WorkingHours::Computation do
177
177
  it 'do not leak nanoseconds when advancing' do
178
178
  expect(advance_to_working_time(Time.utc(2014, 4, 7, 5, 0, 0, 123456.789))).to eq(Time.utc(2014, 4, 7, 9, 0, 0, 0))
179
179
  end
180
+
181
+ it 'returns correct hour during positive time shifts' do
182
+ WorkingHours::Config.working_hours = {sun: {'09:00' => '17:00'}}
183
+ WorkingHours::Config.time_zone = 'Paris'
184
+ from = Time.new(2020, 3, 29, 0, 0, 0, "+01:00")
185
+ expect(from.utc_offset).to eq(3600)
186
+ res = advance_to_working_time(from)
187
+ expect(res).to eq(Time.new(2020, 3, 29, 9, 0, 0, "+02:00"))
188
+ expect(res.utc_offset).to eq(7200)
189
+ # starting from wrong time-zone
190
+ expect(advance_to_working_time(Time.new(2020, 3, 29, 5, 0, 0, "+01:00"))).to eq(Time.new(2020, 3, 29, 9, 0, 0, "+02:00"))
191
+ expect(advance_to_working_time(Time.new(2020, 3, 29, 1, 0, 0, "+02:00"))).to eq(Time.new(2020, 3, 29, 9, 0, 0, "+02:00"))
192
+ end
193
+
194
+ it 'returns correct hour during negative time shifts' do
195
+ WorkingHours::Config.working_hours = {sun: {'09:00' => '17:00'}}
196
+ WorkingHours::Config.time_zone = 'Paris'
197
+ from = Time.new(2020, 10, 25, 0, 0, 0, "+02:00")
198
+ expect(from.utc_offset).to eq(7200)
199
+ res = advance_to_working_time(from)
200
+ expect(res).to eq(Time.new(2020, 10, 25, 9, 0, 0, "+01:00"))
201
+ expect(res.utc_offset).to eq(3600)
202
+ # starting from wrong time-zone
203
+ expect(advance_to_working_time(Time.new(2020, 10, 25, 4, 0, 0, "+02:00"))).to eq(Time.new(2020, 10, 25, 9, 0, 0, "+01:00"))
204
+ expect(advance_to_working_time(Time.new(2020, 10, 25, 1, 0, 0, "+01:00"))).to eq(Time.new(2020, 10, 25, 9, 0, 0, "+01:00"))
205
+ end
180
206
  end
181
207
 
182
208
  describe '#advance_to_closing_time' do
@@ -284,6 +310,32 @@ describe WorkingHours::Computation do
284
310
  it 'do not leak nanoseconds when advancing' do
285
311
  expect(advance_to_closing_time(Time.utc(2014, 4, 7, 5, 0, 0, 123456.789))).to eq(Time.utc(2014, 4, 7, 17, 0, 0, 0))
286
312
  end
313
+
314
+ it 'returns correct hour during positive time shifts' do
315
+ WorkingHours::Config.working_hours = {sun: {'09:00' => '17:00'}}
316
+ WorkingHours::Config.time_zone = 'Paris'
317
+ from = Time.new(2020, 3, 29, 0, 0, 0, "+01:00")
318
+ expect(from.utc_offset).to eq(3600)
319
+ res = advance_to_closing_time(from)
320
+ expect(res).to eq(Time.new(2020, 3, 29, 17, 0, 0, "+02:00"))
321
+ expect(res.utc_offset).to eq(7200)
322
+ # starting from wrong time-zone
323
+ expect(advance_to_closing_time(Time.new(2020, 3, 29, 5, 0, 0, "+01:00"))).to eq(Time.new(2020, 3, 29, 17, 0, 0, "+02:00"))
324
+ expect(advance_to_closing_time(Time.new(2020, 3, 29, 1, 0, 0, "+02:00"))).to eq(Time.new(2020, 3, 29, 17, 0, 0, "+02:00"))
325
+ end
326
+
327
+ it 'returns correct hour during negative time shifts' do
328
+ WorkingHours::Config.working_hours = {sun: {'09:00' => '17:00'}}
329
+ WorkingHours::Config.time_zone = 'Paris'
330
+ from = Time.new(2020, 10, 25, 0, 0, 0, "+02:00")
331
+ expect(from.utc_offset).to eq(7200)
332
+ res = advance_to_closing_time(from)
333
+ expect(res).to eq(Time.new(2020, 10, 25, 17, 0, 0, "+01:00"))
334
+ expect(res.utc_offset).to eq(3600)
335
+ # starting from wrong time-zone
336
+ expect(advance_to_closing_time(Time.new(2020, 10, 25, 4, 0, 0, "+02:00"))).to eq(Time.new(2020, 10, 25, 17, 0, 0, "+01:00"))
337
+ expect(advance_to_closing_time(Time.new(2020, 10, 25, 1, 0, 0, "+01:00"))).to eq(Time.new(2020, 10, 25, 17, 0, 0, "+01:00"))
338
+ end
287
339
  end
288
340
 
289
341
  describe '#next_working_time' do
@@ -390,6 +442,32 @@ describe WorkingHours::Computation do
390
442
  WorkingHours::Config.time_zone = 'Tokyo'
391
443
  expect(return_to_working_time(Time.new(2014, 4, 7, 1, 0, 0)).zone).to eq('JST')
392
444
  end
445
+
446
+ it 'returns correct hour during positive time shifts' do
447
+ WorkingHours::Config.working_hours = {sun: {'00:00' => '01:00'}}
448
+ WorkingHours::Config.time_zone = 'Paris'
449
+ from = Time.new(2020, 3, 29, 9, 0, 0, "+02:00")
450
+ expect(from.utc_offset).to eq(7200)
451
+ res = return_to_working_time(from)
452
+ expect(res).to eq(Time.new(2020, 3, 29, 1, 0, 0, "+01:00"))
453
+ expect(res.utc_offset).to eq(3600)
454
+ # starting from wrong time-zone
455
+ expect(return_to_working_time(Time.new(2020, 3, 29, 2, 0, 0, "+02:00"))).to eq(Time.new(2020, 3, 29, 1, 0, 0, "+01:00"))
456
+ expect(return_to_working_time(Time.new(2020, 3, 29, 9, 0, 0, "+01:00"))).to eq(Time.new(2020, 3, 29, 1, 0, 0, "+01:00"))
457
+ end
458
+
459
+ it 'returns correct hour during negative time shifts' do
460
+ WorkingHours::Config.working_hours = {sun: {'00:00' => '01:00'}}
461
+ WorkingHours::Config.time_zone = 'Paris'
462
+ from = Time.new(2020, 10, 25, 9, 0, 0, "+01:00")
463
+ expect(from.utc_offset).to eq(3600)
464
+ res = return_to_working_time(from)
465
+ expect(res).to eq(Time.new(2020, 10, 25, 1, 0, 0, "+02:00"))
466
+ expect(res.utc_offset).to eq(7200)
467
+ # starting from wrong time-zone
468
+ expect(return_to_working_time(Time.new(2020, 10, 25, 9, 0, 0, "+02:00"))).to eq(Time.new(2020, 10, 25, 1, 0, 0, "+02:00"))
469
+ expect(return_to_working_time(Time.new(2020, 10, 25, 1, 0, 0, "+01:00"))).to eq(Time.new(2020, 10, 25, 1, 0, 0, "+02:00"))
470
+ end
393
471
  end
394
472
 
395
473
  describe '#working_day?' do
@@ -567,6 +645,42 @@ describe WorkingHours::Computation do
567
645
  )).to eq(6.hours)
568
646
  end
569
647
 
648
+ it 'works across positive time shifts' do
649
+ WorkingHours::Config.working_hours = {sun: {'08:00' => '21:00'}}
650
+ WorkingHours::Config.time_zone = 'Paris'
651
+ expect(working_time_between(
652
+ Time.utc(2020, 3, 29, 1, 0),
653
+ Time.utc(2020, 3, 30, 0, 0),
654
+ )).to eq(13.hours)
655
+ end
656
+
657
+ it 'works across negative time shifts' do
658
+ WorkingHours::Config.working_hours = {sun: {'08:00' => '21:00'}}
659
+ WorkingHours::Config.time_zone = 'Paris'
660
+ expect(working_time_between(
661
+ Time.utc(2019, 10, 27, 1, 0),
662
+ Time.utc(2019, 10, 28, 0, 0),
663
+ )).to eq(13.hours)
664
+ end
665
+
666
+ it 'works across time shifts + midnight' do
667
+ WorkingHours::Config.working_hours = {sun: {'00:00' => '24:00'}}
668
+ WorkingHours::Config.time_zone = 'Paris'
669
+ expect(working_time_between(
670
+ Time.utc(2020, 10, 24, 22, 0),
671
+ Time.utc(2020, 10, 25, 23, 0),
672
+ )).to eq(24.hours)
673
+ end
674
+
675
+ it 'works across multiple time shifts' do
676
+ WorkingHours::Config.working_hours = {sun: {'08:00' => '21:00'}}
677
+ WorkingHours::Config.time_zone = 'Paris'
678
+ expect(working_time_between(
679
+ Time.utc(2002, 10, 27, 6, 0),
680
+ Time.utc(2021, 10, 30, 0, 0),
681
+ )).to eq(12896.hours)
682
+ end
683
+
570
684
  it 'do not cause infinite loop if the time is not advancing properly' do
571
685
  # simulate some computation/precision error
572
686
  expect(self).to receive(:advance_to_working_time).twice do |time|
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: working_hours
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.1
4
+ version: 1.3.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Adrien Jarthon
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2021-02-18 00:00:00.000000000 Z
12
+ date: 2021-02-20 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport