working_hours 1.3.0 → 1.3.1

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: 4bdb747e39a652368403adb9ad97f3776a8d8ab28f841d70912bcab6141118ea
4
- data.tar.gz: 74e1447b590b829f95e52fe3c5bac18e7a690dcc8b21683402982f5810b94d8c
3
+ metadata.gz: 26d036d577be17378683ab793c1b7331a0f6609fc3c6faf11f46681dd9a3a88b
4
+ data.tar.gz: f037fc32ac6249d4d506a31a75bb69b7fbd5de35022ac39b332a521bae6d9e88
5
5
  SHA512:
6
- metadata.gz: ee953af27c8d6622d8089feec9cd5b966613baff8239c11e488668ad07a48023efbd98d28599f9723c913b7e295ced2ac2b4cb5ef70711c4950ed8afea2aa107
7
- data.tar.gz: 5b632e842d20ff83173c83a3e34c4bf166237f3689ba130db2cfd3031e027078f1efa1acbddac588198d78b07f3775c94e88f6d83fba328a9400629e92fab107
6
+ metadata.gz: 1fa880f28c9d500c1964942ef13c70e5695d0bb5368a24e2bf711ff735de2d96790986f9cc5656e98fe29c623ae560a1bd4930e48f0275e7aa49e00eb6f67f0d
7
+ data.tar.gz: 4c719df9ec920d581fa304c4f9e55d6a97a2484a4f456b62ee5ed89a734f23f7a8c990e57cfce1c3484cd14878c120704b512bd9455aea348cb6933ce127cbba
data/.gitignore CHANGED
@@ -6,6 +6,7 @@
6
6
  .ruby-gemset
7
7
  .ruby-version
8
8
  Gemfile.lock
9
+ gemfiles/*.lock
9
10
  InstalledFiles
10
11
  _yardoc
11
12
  coverage
data/.travis.yml CHANGED
@@ -1,9 +1,10 @@
1
1
  language: ruby
2
+ dist: bionic
2
3
  rvm:
3
- - 2.4.9
4
- - 2.5.7
5
- - 2.6.5
6
- - 2.7.0
4
+ - 2.4.10
5
+ - 2.5.8
6
+ - 2.6.6
7
+ - 2.7.2
7
8
  - 3.0.0
8
9
  - jruby-9.2.14.0
9
10
  gemfile:
@@ -12,17 +13,15 @@ gemfile:
12
13
  - gemfiles/Gemfile.activesupport-6.x
13
14
  jobs:
14
15
  exclude:
15
- - rvm: 2.4.9
16
+ - rvm: 2.4.10
16
17
  gemfile: gemfiles/Gemfile.activesupport-6.x
17
- - rvm: 2.7.0
18
+ - rvm: 2.7.2
18
19
  gemfile: gemfiles/Gemfile.activesupport-4.x
19
20
  - rvm: 3.0.0
20
21
  gemfile: gemfiles/Gemfile.activesupport-4.x
21
22
  include:
22
23
  - rvm: ruby-head
23
24
  gemfile: gemfiles/Gemfile.activesupport-edge
24
- - rvm: jruby-head
25
- gemfile: gemfiles/Gemfile.activesupport-edge
26
25
  allow_failures:
27
26
  - gemfile: gemfiles/Gemfile.activesupport-edge
28
27
  fast_finish: true
data/CHANGELOG.md CHANGED
@@ -1,6 +1,11 @@
1
1
  # Unreleased
2
2
 
3
- [Compare master with v1.3.0](https://github.com/intrepidd/working_hours/compare/v1.3.0...master)
3
+ [Compare master with v1.3.1](https://github.com/intrepidd/working_hours/compare/v1.3.1...master)
4
+
5
+ # v1.3.1
6
+ * 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
+ * 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)
8
+ * Use a Rational number for the midnight value to avoid leaking sub-nanoseconds residue because of floating point accuracy - [#44](https://github.com/Intrepidd/working_hours/pull/44)
4
9
 
5
10
  # v1.3.0
6
11
  * Improve supports for fractional seconds in input times by only rounding results at the end - [#42](https://github.com/Intrepidd/working_hours/issues/42) [#43](https://github.com/Intrepidd/working_hours/pull/43)
@@ -2,4 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec :path => '..'
4
4
 
5
- gem 'activesupport', '~> 6.0'
5
+ gem 'activesupport', '~> 6.1'
@@ -2,4 +2,4 @@ source 'https://rubygems.org'
2
2
 
3
3
  gemspec :path => '..'
4
4
 
5
- gem 'activesupport', github: 'rails/rails'
5
+ gem 'activesupport', github: 'rails/rails', branch: 'main'
@@ -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 + (from - time_in_day) if from >= time_in_day
85
+ return time.beginning_of_day + 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
@@ -101,8 +101,7 @@ module WorkingHours
101
101
  time_in_day = time.seconds_since_midnight
102
102
  time = time.beginning_of_day
103
103
  config[:working_hours][time.wday].each do |from, to|
104
- return time + to if time_in_day >= from and time_in_day < to
105
- return time + to if from >= time_in_day
104
+ return time + to if time_in_day < to
106
105
  end
107
106
  # if none is found, go to next day and loop
108
107
  time = time + 1.day
@@ -183,20 +182,25 @@ module WorkingHours
183
182
  to = in_config_zone(to, config: config)
184
183
  distance = 0
185
184
  while from < to
185
+ from_was = from
186
186
  # look at working ranges
187
187
  time_in_day = from.seconds_since_midnight
188
188
  config[:working_hours][from.wday].each do |begins, ends|
189
189
  if time_in_day >= begins and time_in_day < ends
190
- # take all we can
191
- take = [ends - time_in_day, to - from].min
192
- # advance time
193
- from += take
194
- # increase counter
195
- distance += take
190
+ if (to - from) > (ends - time_in_day)
191
+ # take all the range and continue
192
+ distance += (ends - time_in_day)
193
+ from = from.beginning_of_day + ends
194
+ else
195
+ # take only what's needed and stop
196
+ distance += (to - from)
197
+ from = to
198
+ end
196
199
  end
197
200
  end
198
201
  # roll to next business period
199
202
  from = advance_to_working_time(from, config: config)
203
+ raise "Invalid loop detected in working_time_between (from=#{from.iso8601(12)}, to=#{to.iso8601(12)}, distance=#{distance}, config=#{config}), please open an issue ;)" unless from > from_was
200
204
  end
201
205
  distance.round # round up to supress miliseconds introduced by 24:00 hack
202
206
  end
@@ -7,6 +7,7 @@ module WorkingHours
7
7
 
8
8
  TIME_FORMAT = /\A([0-2][0-9])\:([0-5][0-9])(?:\:([0-5][0-9]))?\z/
9
9
  DAYS_OF_WEEK = [:sun, :mon, :tue, :wed, :thu, :fri, :sat]
10
+ MIDNIGHT = Rational('86399.999999')
10
11
 
11
12
  class << self
12
13
 
@@ -115,7 +116,7 @@ module WorkingHours
115
116
  sec = time[TIME_FORMAT,3].to_i
116
117
  time = hour * 3600 + min * 60 + sec
117
118
  # Converts 24:00 to 23:59:59.999999
118
- return 86399.999999 if time == 86400
119
+ return MIDNIGHT if time == 86400
119
120
  time
120
121
  end
121
122
 
@@ -1,3 +1,3 @@
1
1
  module WorkingHours
2
- VERSION = "1.3.0"
2
+ VERSION = "1.3.1"
3
3
  end
@@ -173,6 +173,10 @@ describe WorkingHours::Computation do
173
173
  WorkingHours::Config.time_zone = 'Tokyo'
174
174
  expect(advance_to_working_time(Time.new(2014, 4, 7, 0, 0, 0)).zone).to eq('JST')
175
175
  end
176
+
177
+ it 'do not leak nanoseconds when advancing' do
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
+ end
176
180
  end
177
181
 
178
182
  describe '#advance_to_closing_time' do
@@ -240,7 +244,7 @@ describe WorkingHours::Computation do
240
244
  end
241
245
 
242
246
  let(:monday_morning) { Time.utc(2014, 4, 7, 0) }
243
- let(:monday_closing) { Time.utc(2014, 4, 7) + 86399.999999 }
247
+ let(:monday_closing) { Time.utc(2014, 4, 7) + WorkingHours::Config::MIDNIGHT }
244
248
  let(:tuesday_closing) { Time.utc(2014, 4, 8, 17) }
245
249
  let(:sunday) { Time.utc(2014, 4, 6, 17) }
246
250
 
@@ -255,6 +259,11 @@ describe WorkingHours::Computation do
255
259
  it 'moves over midnight' do
256
260
  expect(advance_to_closing_time(sunday)).to eq(monday_closing)
257
261
  end
262
+
263
+ it 'give precise computation with nothing other than miliseconds' do
264
+ pending "iso8601 is not precise enough on AS < 4" if ActiveSupport::VERSION::MAJOR <= 4
265
+ expect(advance_to_closing_time(monday_morning).iso8601(25)).to eq("2014-04-07T23:59:59.9999990000000000000000000Z")
266
+ end
258
267
  end
259
268
 
260
269
  it 'works with any input timezone (converts to config)' do
@@ -271,6 +280,10 @@ describe WorkingHours::Computation do
271
280
  WorkingHours::Config.time_zone = 'Tokyo'
272
281
  expect(advance_to_closing_time(Time.new(2014, 4, 7, 0, 0, 0)).zone).to eq('JST')
273
282
  end
283
+
284
+ it 'do not leak nanoseconds when advancing' do
285
+ 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
+ end
274
287
  end
275
288
 
276
289
  describe '#next_working_time' do
@@ -544,6 +557,27 @@ describe WorkingHours::Computation do
544
557
  )).to eq(7.hours)
545
558
  end
546
559
 
560
+ it 'uses precise computation to avoid useless loops' do
561
+ # +200 usec on each time, using floating point would cause
562
+ # precision issues and require several iterations
563
+ expect(self).to receive(:advance_to_working_time).twice.and_call_original
564
+ expect(working_time_between(
565
+ Time.utc(2014, 4, 7, 5, 0, 0, 200),
566
+ Time.utc(2014, 4, 7, 15, 0, 0, 200),
567
+ )).to eq(6.hours)
568
+ end
569
+
570
+ it 'do not cause infinite loop if the time is not advancing properly' do
571
+ # simulate some computation/precision error
572
+ expect(self).to receive(:advance_to_working_time).twice do |time|
573
+ time.change(hour: 9) - 0.0001
574
+ end
575
+ expect { working_time_between(
576
+ Time.utc(2014, 4, 7, 5, 0, 0),
577
+ Time.utc(2014, 4, 7, 15, 0, 0),
578
+ ) }.to raise_error(RuntimeError, /Invalid loop detected in working_time_between \(from=2014-04-07T08:59:59.999/)
579
+ end
580
+
547
581
  # generates two times with +0ms, +250ms, +500ms, +750ms and +1s
548
582
  # then for each combination compare the result with a ruby diff
549
583
  context 'with precise miliseconds timings' do
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.0
4
+ version: 1.3.1
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-01-31 00:00:00.000000000 Z
12
+ date: 2021-02-18 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -152,7 +152,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
152
152
  - !ruby/object:Gem::Version
153
153
  version: '0'
154
154
  requirements: []
155
- rubygems_version: 3.0.3
155
+ rubygems_version: 3.1.2
156
156
  signing_key:
157
157
  specification_version: 4
158
158
  summary: time calculation with working hours