working_hours 1.3.0 → 1.3.1

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: 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