working_hours 1.3.1 → 1.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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