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 +4 -4
- data/CHANGELOG.md +3 -0
- data/lib/working_hours/computation.rb +22 -7
- data/lib/working_hours/version.rb +1 -1
- data/spec/working_hours/computation_spec.rb +114 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 102b64fd640e32b4164e32d0323d3575bfd720064d02045ff65f7e320b7dd305
|
4
|
+
data.tar.gz: dea4493b517eda97438e35f3857da9b456ead0e992b6278b33d108f1c38ef7ca
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
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
|
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
|
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
|
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
|
@@ -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.
|
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-
|
12
|
+
date: 2021-02-20 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activesupport
|