working_hours 0.9.0 → 1.0.0

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
  SHA1:
3
- metadata.gz: 4c941c0a9419ac6c9518eb678158e34a1bd7a1c2
4
- data.tar.gz: 2d67809674b2e66e74a8df2300feba9d6afc0ed4
3
+ metadata.gz: 2504840e341046d817df5de9c4e7d8271e192177
4
+ data.tar.gz: 2a97bd89965b04bd6b68c1bdcaf90a27ca0ae2ec
5
5
  SHA512:
6
- metadata.gz: 22c7e703ea00a9237d6c6050dce76e48e7521c7041358eb2d6ae7d4f1fcf2a79db0557c80001e499e18a7a63af9c2f36fce7cff71204fd3bdad91040c0855300
7
- data.tar.gz: 45fb3490c70fa7e22d6b92f7eebdc60ef296b185378ab62a56fd0247ca04cfb91fb4b27bf7c95179de0edb6506e5f4944aa3613d60f8d5e6d81f7a4d9a618623
6
+ metadata.gz: 213aed1c4be42b0ff98a9e926253941da1cc4e05bfc85be8d3ca77e51082b87da00da0169410e16c3045366eaf0c202fd7c352d000df19d0edeee3c4b89fa976
7
+ data.tar.gz: 016639b14b4a5ec24be13ea3415223fef1c7743c04654e23c2346ecaaef4d83c9985c3a63f4786c4430e4b087a3b95b0a04b5ae80ab538dfe186ce4ac26d294f
data/.gitignore CHANGED
File without changes
data/.rspec CHANGED
File without changes
data/.travis.yml CHANGED
File without changes
data/CHANGELOG.md ADDED
@@ -0,0 +1,15 @@
1
+ # Unreleased
2
+
3
+ [Compare master with v1.0.0](https://github.com/intrepidd/working_hours/compare/v1.0.0...master)
4
+
5
+ # v1.0.0
6
+
7
+ * Replace config freeze by hash based caching (config is recompiled when changed), this avoids freezing unwanted objects (nil, timezones, integers, etc..)
8
+
9
+ _15/09/2014_
10
+
11
+ # v0.9.0
12
+
13
+ * First beta release
14
+
15
+ _24/08/2014_
data/Gemfile CHANGED
File without changes
data/LICENSE.txt CHANGED
File without changes
data/README.md CHANGED
@@ -74,8 +74,6 @@ WorkingHours::Config.time_zone = 'Paris'
74
74
  WorkingHours::Config.holidays = [Date.new(2014, 12, 31)]
75
75
  ```
76
76
 
77
- > Once the config has been set, internal objects are frozen and you **can't** modify them (ex: `holidays << Date.today`). This is because the configuration is precompiled to a computing friendly form and changes would not be taken into account. To change the config you **must use** one of the 3 setters shown above.
78
-
79
77
  ## No core extensions / monkey patching
80
78
 
81
79
  Core extensions (monkey patching to add methods on Time, Date, Numbers, etc.) are handy but not appreciated by everyone. WorkingHours can also be used **without any monkey patching**:
@@ -117,33 +115,35 @@ WorkingHours.in_working_hours?(Time.utc(2014, 8, 4, 7, 16)) # => false
117
115
 
118
116
  ## Use in your class/module
119
117
 
120
- If you want to use working hours inside a specific class or module, you can include its computation methods like this:
118
+ If you want to use working hours only inside a specific class or module, you can include its computation methods like this:
121
119
 
122
120
  ```ruby
123
- require 'working_hours'
121
+ require 'working_hours/module'
124
122
 
125
123
  class Order
126
- include WorkingHours::Computation
124
+ include WorkingHours
127
125
 
128
126
  def shipping_date_estimate
129
- order_date + 2.working.days
127
+ Duration.new(2, :days).since(paiement_received_at)
128
+ end
129
+
130
+ def paiement_delay
131
+ working_days_between(created_at, paiement_received_at)
130
132
  end
131
133
  end
132
134
  ```
133
135
 
134
- > This also works with zero monkey patch by requiring `working_hours/module`
135
-
136
136
  ## Timezones
137
137
 
138
138
  This gem uses a simple but efficient approach in dealing with timezones. When you define your working hours **you have to choose** a timezome associated with it (in the config example, the working hours are in Paris time). Then, any time used in calcultation will be converted to this timezone first, so you don't have to worry if your times are local or UTC as long as they are correct :)
139
139
 
140
140
  ## Alternatives
141
141
 
142
- There is a gem called [business_time](https://github.com/bokmann/business_time) already available to do this kind of computation and it was of great help to us. But we decided to start another one because business_time is suffering from a [few](https://github.com/bokmann/business_time/issues/50) [bugs](https://github.com/bokmann/business_time/pull/84) and inconsistencies regarding timezones. It also lacks essential features to us (like working minutes computation).
142
+ There is a gem called [business_time](https://github.com/bokmann/business_time) already available to do this kind of computation and it was of great help to us. But we decided to start another one because business_time is suffering from a few [bugs](https://github.com/bokmann/business_time/pull/84) and [inconsistencies](https://github.com/bokmann/business_time/issues/50). It also lacks essential features to us (like working minutes computation).
143
143
 
144
144
  ## Contributing
145
145
 
146
- 1. Fork it ( http://github.com/<my-github-username>/working_hours/fork )
146
+ 1. Fork it ( http://github.com/intrepidd/working_hours/fork )
147
147
  2. Create your feature branch (`git checkout -b my-new-feature`)
148
148
  3. Commit your changes (`git commit -am 'Add some feature'`)
149
149
  4. Push to the branch (`git push origin my-new-feature`)
data/Rakefile CHANGED
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -4,35 +4,39 @@ require 'working_hours/config'
4
4
  module WorkingHours
5
5
  module Computation
6
6
 
7
- def add_days origin, days
8
- time = in_config_zone(origin)
7
+ def add_days origin, days, config: nil
8
+ config ||= wh_config
9
+ time = in_config_zone(origin, config: config)
9
10
  while days > 0
10
11
  time += 1.day
11
- days -= 1 if working_day?(time)
12
+ days -= 1 if working_day?(time, config: config)
12
13
  end
13
14
  while days < 0
14
15
  time -= 1.day
15
- days += 1 if working_day?(time)
16
+ days += 1 if working_day?(time, config: config)
16
17
  end
17
18
  convert_to_original_format time, origin
18
19
  end
19
20
 
20
- def add_hours origin, hours
21
- add_minutes origin, hours * 60
21
+ def add_hours origin, hours, config: nil
22
+ config ||= wh_config
23
+ add_minutes origin, hours * 60, config: config
22
24
  end
23
25
 
24
- def add_minutes origin, minutes
25
- add_seconds origin, minutes * 60
26
+ def add_minutes origin, minutes, config: nil
27
+ config ||= wh_config
28
+ add_seconds origin, minutes * 60, config: config
26
29
  end
27
30
 
28
- def add_seconds origin, seconds
29
- time = in_config_zone(origin).round
31
+ def add_seconds origin, seconds, config: nil
32
+ config ||= wh_config
33
+ time = in_config_zone(origin, config: config).round
30
34
  while seconds > 0
31
35
  # roll to next business period
32
- time = advance_to_working_time(time)
36
+ time = advance_to_working_time(time, config: config)
33
37
  # look at working ranges
34
38
  time_in_day = time.seconds_since_midnight
35
- wh_config[:working_hours][time.wday].each do |from, to|
39
+ config[:working_hours][time.wday].each do |from, to|
36
40
  if time_in_day >= from and time_in_day < to
37
41
  # take all we can
38
42
  take = [to - time_in_day, seconds].min
@@ -45,10 +49,10 @@ module WorkingHours
45
49
  end
46
50
  while seconds < 0
47
51
  # roll to previous business period
48
- time = return_to_working_time(time)
52
+ time = return_to_working_time(time, config: config)
49
53
  # look at working ranges
50
54
  time_in_day = time.seconds_since_midnight
51
- wh_config[:working_hours][time.wday].reverse_each do |from, to|
55
+ config[:working_hours][time.wday].reverse_each do |from, to|
52
56
  if time_in_day > from and time_in_day <= to
53
57
  # take all we can
54
58
  take = [time_in_day - from, -seconds].min
@@ -62,16 +66,17 @@ module WorkingHours
62
66
  convert_to_original_format time, origin
63
67
  end
64
68
 
65
- def advance_to_working_time time
66
- time = in_config_zone(time).round
69
+ def advance_to_working_time time, config: nil
70
+ config ||= wh_config
71
+ time = in_config_zone(time, config: config).round
67
72
  loop do
68
73
  # skip holidays and weekends
69
- while not working_day?(time)
74
+ while not working_day?(time, config: config)
70
75
  time = (time + 1.day).beginning_of_day
71
76
  end
72
77
  # find first working range after time
73
78
  time_in_day = time.seconds_since_midnight
74
- (Config.precompiled[:working_hours][time.wday] || {}).each do |from, to|
79
+ (config[:working_hours][time.wday] || {}).each do |from, to|
75
80
  return time if time_in_day >= from and time_in_day < to
76
81
  return time + (from - time_in_day) if from >= time_in_day
77
82
  end
@@ -80,16 +85,17 @@ module WorkingHours
80
85
  end
81
86
  end
82
87
 
83
- def return_to_working_time time
84
- time = in_config_zone(time).round
88
+ def return_to_working_time time, config: nil
89
+ config ||= wh_config
90
+ time = in_config_zone(time, config: config).round
85
91
  loop do
86
92
  # skip holidays and weekends
87
- while not working_day?(time)
93
+ while not working_day?(time, config: config)
88
94
  time = (time - 1.day).end_of_day
89
95
  end
90
96
  # find last working range before time
91
97
  time_in_day = time.seconds_since_midnight
92
- (Config.precompiled[:working_hours][time.wday] || {}).reverse_each do |from, to|
98
+ (config[:working_hours][time.wday] || {}).reverse_each do |from, to|
93
99
  # round is used to suppress miliseconds hack from `end_of_day`
94
100
  return time.round if time_in_day > from and time_in_day <= to
95
101
  return (time - (time_in_day - to)).round if to <= time_in_day
@@ -99,47 +105,51 @@ module WorkingHours
99
105
  end
100
106
  end
101
107
 
102
- def working_day? time
103
- time = in_config_zone(time)
104
- Config.precompiled[:working_hours][time.wday].present? and not Config.precompiled[:holidays].include?(time.to_date)
108
+ def working_day? time, config: nil
109
+ config ||= wh_config
110
+ time = in_config_zone(time, config: config)
111
+ config[:working_hours][time.wday].present? and not config[:holidays].include?(time.to_date)
105
112
  end
106
113
 
107
- def in_working_hours? time
108
- time = in_config_zone(time)
109
- return false if not working_day?(time)
114
+ def in_working_hours? time, config: nil
115
+ config ||= wh_config
116
+ time = in_config_zone(time, config: config)
117
+ return false if not working_day?(time, config: config)
110
118
  time_in_day = time.seconds_since_midnight
111
- Config.precompiled[:working_hours][time.wday].each do |from, to|
119
+ config[:working_hours][time.wday].each do |from, to|
112
120
  return true if time_in_day >= from and time_in_day < to
113
121
  end
114
122
  false
115
123
  end
116
124
 
117
- def working_days_between from, to
125
+ def working_days_between from, to, config: nil
126
+ config ||= wh_config
118
127
  if to < from
119
- -working_days_between(to, from)
128
+ -working_days_between(to, from, config: config)
120
129
  else
121
- from = in_config_zone(from)
122
- to = in_config_zone(to)
130
+ from = in_config_zone(from, config: config)
131
+ to = in_config_zone(to, config: config)
123
132
  days = 0
124
133
  while from.to_date < to.to_date
125
134
  from += 1.day
126
- days += 1 if working_day?(from)
135
+ days += 1 if working_day?(from, config: config)
127
136
  end
128
137
  days
129
138
  end
130
139
  end
131
140
 
132
- def working_time_between from, to
141
+ def working_time_between from, to, config: nil
142
+ config ||= wh_config
133
143
  if to < from
134
- -working_time_between(to, from)
144
+ -working_time_between(to, from, config: config)
135
145
  else
136
- from = advance_to_working_time(in_config_zone(from))
137
- to = in_config_zone(to).round
146
+ from = advance_to_working_time(in_config_zone(from, config: config))
147
+ to = in_config_zone(to, config: config).round
138
148
  distance = 0
139
149
  while from < to
140
150
  # look at working ranges
141
151
  time_in_day = from.seconds_since_midnight
142
- wh_config[:working_hours][from.wday].each do |begins, ends|
152
+ config[:working_hours][from.wday].each do |begins, ends|
143
153
  if time_in_day >= begins and time_in_day < ends
144
154
  # take all we can
145
155
  take = [ends - time_in_day, to - from].min
@@ -150,7 +160,7 @@ module WorkingHours
150
160
  end
151
161
  end
152
162
  # roll to next business period
153
- from = advance_to_working_time(from)
163
+ from = advance_to_working_time(from, config: config)
154
164
  end
155
165
  distance
156
166
  end
@@ -163,11 +173,11 @@ module WorkingHours
163
173
  end
164
174
 
165
175
  # fix for ActiveRecord < 4, doesn't implement in_time_zone for Date
166
- def in_config_zone time
176
+ def in_config_zone time, config: nil
167
177
  if time.respond_to? :in_time_zone
168
- time.in_time_zone(wh_config[:time_zone])
178
+ time.in_time_zone(config[:time_zone])
169
179
  elsif time.is_a? Date
170
- wh_config[:time_zone].local(time.year, time.month, time.day)
180
+ config[:time_zone].local(time.year, time.month, time.day)
171
181
  else
172
182
  raise TypeError.new("Can't convert #{time.class} to a Time")
173
183
  end
@@ -1,11 +1,9 @@
1
1
  require 'set'
2
- require 'working_hours/deep_freeze'
3
2
 
4
3
  module WorkingHours
5
4
  InvalidConfiguration = Class.new StandardError
6
5
 
7
6
  class Config
8
- extend WorkingHours::DeepFreeze
9
7
 
10
8
  TIME_FORMAT = /\A([0-1][0-9]|2[0-3]):([0-5][0-9])\z/
11
9
  DAYS_OF_WEEK = [:sun, :mon, :tue, :wed, :thu, :fri, :sat]
@@ -18,7 +16,7 @@ module WorkingHours
18
16
 
19
17
  def working_hours=(val)
20
18
  validate_working_hours! val
21
- config[:working_hours] = deep_freeze(val)
19
+ config[:working_hours] = val
22
20
  config.delete :precompiled
23
21
  end
24
22
 
@@ -28,12 +26,18 @@ module WorkingHours
28
26
 
29
27
  def holidays=(val)
30
28
  validate_holidays! val
31
- config[:holidays] = deep_freeze(val)
29
+ config[:holidays] = val
32
30
  config.delete :precompiled
33
31
  end
34
32
 
35
33
  # Returns an optimized for computing version
36
34
  def precompiled
35
+ config_hash = [config[:working_hours], config[:holidays], config[:time_zone]].hash
36
+ if config_hash != config[:config_hash]
37
+ config[:config_hash] = config_hash
38
+ config.delete :precompiled
39
+ end
40
+
37
41
  config[:precompiled] ||= begin
38
42
  compiled = {working_hours: []}
39
43
  working_hours.each do |day, hours|
@@ -54,7 +58,7 @@ module WorkingHours
54
58
 
55
59
  def time_zone=(val)
56
60
  zone = validate_time_zone! val
57
- config[:time_zone] = zone.freeze
61
+ config[:time_zone] = zone
58
62
  config.delete :precompiled
59
63
  end
60
64
 
@@ -76,9 +80,9 @@ module WorkingHours
76
80
  wed: {'09:00' => '17:00'},
77
81
  thu: {'09:00' => '17:00'},
78
82
  fri: {'09:00' => '17:00'}
79
- }.freeze,
80
- holidays: [].freeze,
81
- time_zone: ActiveSupport::TimeZone['UTC'].freeze
83
+ },
84
+ holidays: [],
85
+ time_zone: ActiveSupport::TimeZone['UTC']
82
86
  }
83
87
  end
84
88
 
File without changes
File without changes
File without changes
File without changes
File without changes
@@ -1,3 +1,3 @@
1
1
  module WorkingHours
2
- VERSION = "0.9.0"
2
+ VERSION = "1.0.0"
3
3
  end
data/lib/working_hours.rb CHANGED
File without changes
data/spec/spec_helper.rb CHANGED
File without changes
@@ -90,6 +90,14 @@ describe WorkingHours::Computation do
90
90
  time = Time.utc(1991, 11, 15, 16, 59, 42) # Friday
91
91
  expect(add_seconds(time, 120)).to eq(Time.utc(1991, 11, 18, 9, 1, 42))
92
92
  end
93
+
94
+ it 'Calls precompiled only once' do
95
+ precompiled = WorkingHours::Config.precompiled
96
+ expect(WorkingHours::Config).to receive(:precompiled).once.and_return(precompiled) # in_config_zone and add_seconds
97
+ time = Time.utc(1991, 11, 15, 16, 59, 42) # Friday
98
+ add_seconds(time, 120)
99
+ end
100
+
93
101
  end
94
102
 
95
103
  describe '#advance_to_working_time' do
@@ -313,4 +321,4 @@ describe WorkingHours::Computation do
313
321
  )).to eq(7.hours)
314
322
  end
315
323
  end
316
- end
324
+ end
@@ -44,15 +44,15 @@ describe WorkingHours::Config do
44
44
  expect(config).to eq(time_sheet)
45
45
  end
46
46
 
47
- it "can't be modified once precompiled" do
47
+ it "recomputes precompiled when modified" do
48
48
  time_sheet = {:mon => {'08:00' => '14:00'}}
49
49
  WorkingHours::Config.working_hours = time_sheet
50
50
  expect {
51
51
  WorkingHours::Config.working_hours[:tue] = {'08:00' => '14:00'}
52
- }.to raise_error(RuntimeError, "can't modify frozen Hash")
52
+ }.to change { WorkingHours::Config.precompiled[:working_hours][2] }
53
53
  expect {
54
54
  WorkingHours::Config.working_hours[:mon]['08:00'] = '15:00'
55
- }.to raise_error(RuntimeError, "can't modify frozen Hash")
55
+ }.to change { WorkingHours::Config.precompiled[:working_hours][1] }
56
56
  end
57
57
 
58
58
  describe 'validations' do
@@ -116,10 +116,10 @@ describe WorkingHours::Config do
116
116
  expect(config).to eq([Date.today])
117
117
  end
118
118
 
119
- it "can't be modified once precompiled" do
119
+ it "recomputes precompiled when modified" do
120
120
  expect {
121
121
  WorkingHours::Config.holidays << Date.today
122
- }.to raise_error(RuntimeError, "can't modify frozen Array")
122
+ }.to change { WorkingHours::Config.precompiled[:holidays] }.by(Set.new([Date.today]))
123
123
  end
124
124
 
125
125
  describe 'validation' do
@@ -154,10 +154,10 @@ describe WorkingHours::Config do
154
154
  expect(config).to eq(ActiveSupport::TimeZone['Tokyo'])
155
155
  end
156
156
 
157
- it "can't be modified once precompiled" do
157
+ it "recomputes precompiled when modified" do
158
158
  expect {
159
159
  WorkingHours::Config.time_zone.instance_variable_set(:@name, 'Bordeaux')
160
- }.to raise_error(RuntimeError, "can't modify frozen ActiveSupport::TimeZone")
160
+ }.to change { WorkingHours::Config.time_zone.name }.from('UTC').to('Bordeaux')
161
161
  end
162
162
 
163
163
  describe 'validation' do
@@ -224,8 +224,9 @@ describe WorkingHours::Config do
224
224
  end
225
225
 
226
226
  it 'is computed only once' do
227
- expect(WorkingHours::Config).to receive(:compile_time).exactly(10).times
227
+ precompiled = WorkingHours::Config.precompiled
228
228
  3.times { WorkingHours::Config.precompiled }
229
+ expect(WorkingHours::Config.precompiled).to be(precompiled)
229
230
  end
230
231
  end
231
232
  end
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
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: 0.9.0
4
+ version: 1.0.0
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: 2014-08-24 00:00:00.000000000 Z
12
+ date: 2014-09-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activesupport
@@ -106,6 +106,7 @@ files:
106
106
  - ".gitignore"
107
107
  - ".rspec"
108
108
  - ".travis.yml"
109
+ - CHANGELOG.md
109
110
  - Gemfile
110
111
  - LICENSE.txt
111
112
  - README.md
@@ -119,7 +120,6 @@ files:
119
120
  - lib/working_hours/config.rb
120
121
  - lib/working_hours/core_ext/date_and_time.rb
121
122
  - lib/working_hours/core_ext/fixnum.rb
122
- - lib/working_hours/deep_freeze.rb
123
123
  - lib/working_hours/duration.rb
124
124
  - lib/working_hours/duration_proxy.rb
125
125
  - lib/working_hours/module.rb
@@ -1,12 +0,0 @@
1
- module WorkingHours
2
- module DeepFreeze
3
- def deep_freeze object
4
- if object.is_a? Array
5
- object.replace(object.dup.each { |_, value| deep_freeze(value) })
6
- elsif object.is_a? Hash
7
- object.replace(object.dup.each { |_, value| deep_freeze(value) })
8
- end
9
- object.freeze
10
- end
11
- end
12
- end