working_hours 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 4c941c0a9419ac6c9518eb678158e34a1bd7a1c2
4
+ data.tar.gz: 2d67809674b2e66e74a8df2300feba9d6afc0ed4
5
+ SHA512:
6
+ metadata.gz: 22c7e703ea00a9237d6c6050dce76e48e7521c7041358eb2d6ae7d4f1fcf2a79db0557c80001e499e18a7a63af9c2f36fce7cff71204fd3bdad91040c0855300
7
+ data.tar.gz: 45fb3490c70fa7e22d6b92f7eebdc60ef296b185378ab62a56fd0247ca04cfb91fb4b27bf7c95179de0edb6506e5f4944aa3613d60f8d5e6d81f7a4d9a618623
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
4
+ - 2.1.2
5
+ - ruby-head
6
+ gemfile:
7
+ - gemfiles/Gemfile.activesupport-3.2.x
8
+ - gemfiles/Gemfile.activesupport-4.0.x
9
+ - gemfiles/Gemfile.activesupport-4.1.x
10
+ - gemfiles/Gemfile.activesupport-edge
11
+ matrix:
12
+ allow_failures:
13
+ - gemfile: gemfiles/Gemfile.activesupport-edge
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in working_hours.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Intrepidd
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,150 @@
1
+ # WorkingHours
2
+
3
+ [![Build Status](https://travis-ci.org/Intrepidd/working_hours.svg?branch=master)](https://travis-ci.org/Intrepidd/working_hours)
4
+
5
+ A modern ruby gem allowing to do time calculation with working hours.
6
+
7
+ Compatible and tested with:
8
+ - Ruby `2.0`, `2.1`
9
+ - ActiveSupport `3.2.x`, `4.0.x` and `4.1.x`
10
+
11
+ ## Installation
12
+
13
+ Gemfile:
14
+
15
+ ```ruby
16
+ gem 'working_hours'
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```ruby
22
+ require 'working_hours'
23
+
24
+ # Move forward
25
+ 1.working.day.from_now
26
+ 2.working.hours.from_now
27
+ 15.working.minutes.from_now
28
+
29
+ # Move backward
30
+ 1.working.day.ago
31
+ 2.working.hours.ago
32
+ 15.working.minutes.ago
33
+
34
+ # Start from custom Date or Time
35
+ Date.new(2014, 12, 31) + 8.working.days # => Mon, 12 Jan 2015
36
+ Time.utc(2014, 8, 4, 8, 32) - 4.working.hours # => 2014-08-01 13:00:00
37
+
38
+ # Compute working days between two dates
39
+ friday.working_days_until(monday) # => 1
40
+ # Time is considered at end of day, so:
41
+ # - friday to saturday = 0 working days
42
+ # - sunday to monday = 1 working days
43
+
44
+ # Compute working duration (in seconds) between two times
45
+ from = Time.utc(2014, 8, 3, 8, 32) # sunday 8:32am
46
+ to = Time.utc(2014, 8, 4, 10, 32) # monday 10:32am
47
+ from.working_time_until(to) # => 5520 (1.hour + 32.minutes)
48
+
49
+ # Know if a day is worked
50
+ Date.new(2014, 12, 28).working_day? # => false
51
+
52
+ # Know if a time is worked
53
+ Time.utc(2014, 8, 4, 7, 16).in_working_hours? # => false
54
+ ```
55
+
56
+ ## Configuration
57
+
58
+ The working hours configuration is thread local and consists of a hash defining working periods for each day, a time zone and a list of days off.
59
+
60
+ ```ruby
61
+ # Configure working hours
62
+ WorkingHours::Config.working_hours = {
63
+ :tue => {'09:00' => '12:00', '13:00' => '17:00'},
64
+ :wed => {'09:00' => '12:00', '13:00' => '17:00'},
65
+ :thu => {'09:00' => '12:00', '13:00' => '17:00'},
66
+ :fri => {'09:00' => '12:00', '13:00' => '17:00'},
67
+ :sat => {'10:00' => '15:00'}
68
+ }
69
+
70
+ # Configure timezone (uses activesupport, defaults to UTC)
71
+ WorkingHours::Config.time_zone = 'Paris'
72
+
73
+ # Configure holidays
74
+ WorkingHours::Config.holidays = [Date.new(2014, 12, 31)]
75
+ ```
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
+ ## No core extensions / monkey patching
80
+
81
+ 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**:
82
+
83
+ ```ruby
84
+ require 'working_hours/module'
85
+
86
+ # Move forward
87
+ WorkingHours::Duration.new(1, :days).from_now
88
+ WorkingHours::Duration.new(2, :hours).from_now
89
+ WorkingHours::Duration.new(15, :minutes).from_now
90
+
91
+ # Move backward
92
+ WorkingHours::Duration.new(1, :days).ago
93
+ WorkingHours::Duration.new(2, :hours).ago
94
+ WorkingHours::Duration.new(15, :minutes).ago
95
+
96
+ # Start from custom Date or Time
97
+ WorkingHours::Duration.new(8, :days).since(Date.new(2014, 12, 31)) # => Mon, 12 Jan 2015
98
+ WorkingHours::Duration.new(4, :hours).until(Time.utc(2014, 8, 4, 8, 32)) # => 2014-08-01 13:00:00
99
+
100
+ # Compute working days between two dates
101
+ WorkingHours.working_days_between(friday, monday) # => 1
102
+ # Time is considered at end of day, so:
103
+ # - friday to saturday = 0 working days
104
+ # - sunday to monday = 1 working days
105
+
106
+ # Compute working duration (in seconds) between two times
107
+ from = Time.utc(2014, 8, 3, 8, 32) # sunday 8:32am
108
+ to = Time.utc(2014, 8, 4, 10, 32) # monday 10:32am
109
+ WorkingHours.working_time_between(from, to) # => 5520 (1.hour + 32.minutes)
110
+
111
+ # Know if a day is worked
112
+ WorkingHours.working_day?(Date.new(2014, 12, 28)) # => false
113
+
114
+ # Know if a time is worked
115
+ WorkingHours.in_working_hours?(Time.utc(2014, 8, 4, 7, 16)) # => false
116
+ ```
117
+
118
+ ## Use in your class/module
119
+
120
+ If you want to use working hours inside a specific class or module, you can include its computation methods like this:
121
+
122
+ ```ruby
123
+ require 'working_hours'
124
+
125
+ class Order
126
+ include WorkingHours::Computation
127
+
128
+ def shipping_date_estimate
129
+ order_date + 2.working.days
130
+ end
131
+ end
132
+ ```
133
+
134
+ > This also works with zero monkey patch by requiring `working_hours/module`
135
+
136
+ ## Timezones
137
+
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
+
140
+ ## Alternatives
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).
143
+
144
+ ## Contributing
145
+
146
+ 1. Fork it ( http://github.com/<my-github-username>/working_hours/fork )
147
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
148
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
149
+ 4. Push to the branch (`git push origin my-new-feature`)
150
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
7
+
8
+ desc "Open an irb session preloaded with working_hours"
9
+ task :console do
10
+ sh "irb -rubygems -I lib -r working_hours.rb"
11
+ end
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec :path => '..'
4
+
5
+ gem 'activesupport', '~> 3.2'
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec :path => '..'
4
+
5
+ gem 'activesupport', '~> 4.0'
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec :path => '..'
4
+
5
+ gem 'activesupport', '~> 4.1'
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec :path => '..'
4
+
5
+ gem 'activesupport', github: 'rails/rails'
@@ -0,0 +1,186 @@
1
+ require 'active_support/all'
2
+ require 'working_hours/config'
3
+
4
+ module WorkingHours
5
+ module Computation
6
+
7
+ def add_days origin, days
8
+ time = in_config_zone(origin)
9
+ while days > 0
10
+ time += 1.day
11
+ days -= 1 if working_day?(time)
12
+ end
13
+ while days < 0
14
+ time -= 1.day
15
+ days += 1 if working_day?(time)
16
+ end
17
+ convert_to_original_format time, origin
18
+ end
19
+
20
+ def add_hours origin, hours
21
+ add_minutes origin, hours * 60
22
+ end
23
+
24
+ def add_minutes origin, minutes
25
+ add_seconds origin, minutes * 60
26
+ end
27
+
28
+ def add_seconds origin, seconds
29
+ time = in_config_zone(origin).round
30
+ while seconds > 0
31
+ # roll to next business period
32
+ time = advance_to_working_time(time)
33
+ # look at working ranges
34
+ time_in_day = time.seconds_since_midnight
35
+ wh_config[:working_hours][time.wday].each do |from, to|
36
+ if time_in_day >= from and time_in_day < to
37
+ # take all we can
38
+ take = [to - time_in_day, seconds].min
39
+ # advance time
40
+ time += take
41
+ # decrease seconds
42
+ seconds -= take
43
+ end
44
+ end
45
+ end
46
+ while seconds < 0
47
+ # roll to previous business period
48
+ time = return_to_working_time(time)
49
+ # look at working ranges
50
+ time_in_day = time.seconds_since_midnight
51
+ wh_config[:working_hours][time.wday].reverse_each do |from, to|
52
+ if time_in_day > from and time_in_day <= to
53
+ # take all we can
54
+ take = [time_in_day - from, -seconds].min
55
+ # advance time
56
+ time -= take
57
+ # decrease seconds
58
+ seconds += take
59
+ end
60
+ end
61
+ end
62
+ convert_to_original_format time, origin
63
+ end
64
+
65
+ def advance_to_working_time time
66
+ time = in_config_zone(time).round
67
+ loop do
68
+ # skip holidays and weekends
69
+ while not working_day?(time)
70
+ time = (time + 1.day).beginning_of_day
71
+ end
72
+ # find first working range after time
73
+ time_in_day = time.seconds_since_midnight
74
+ (Config.precompiled[:working_hours][time.wday] || {}).each do |from, to|
75
+ return time if time_in_day >= from and time_in_day < to
76
+ return time + (from - time_in_day) if from >= time_in_day
77
+ end
78
+ # if none is found, go to next day and loop
79
+ time = (time + 1.day).beginning_of_day
80
+ end
81
+ end
82
+
83
+ def return_to_working_time time
84
+ time = in_config_zone(time).round
85
+ loop do
86
+ # skip holidays and weekends
87
+ while not working_day?(time)
88
+ time = (time - 1.day).end_of_day
89
+ end
90
+ # find last working range before time
91
+ time_in_day = time.seconds_since_midnight
92
+ (Config.precompiled[:working_hours][time.wday] || {}).reverse_each do |from, to|
93
+ # round is used to suppress miliseconds hack from `end_of_day`
94
+ return time.round if time_in_day > from and time_in_day <= to
95
+ return (time - (time_in_day - to)).round if to <= time_in_day
96
+ end
97
+ # if none is found, go to previous day and loop
98
+ time = (time - 1.day).end_of_day
99
+ end
100
+ end
101
+
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)
105
+ end
106
+
107
+ def in_working_hours? time
108
+ time = in_config_zone(time)
109
+ return false if not working_day?(time)
110
+ time_in_day = time.seconds_since_midnight
111
+ Config.precompiled[:working_hours][time.wday].each do |from, to|
112
+ return true if time_in_day >= from and time_in_day < to
113
+ end
114
+ false
115
+ end
116
+
117
+ def working_days_between from, to
118
+ if to < from
119
+ -working_days_between(to, from)
120
+ else
121
+ from = in_config_zone(from)
122
+ to = in_config_zone(to)
123
+ days = 0
124
+ while from.to_date < to.to_date
125
+ from += 1.day
126
+ days += 1 if working_day?(from)
127
+ end
128
+ days
129
+ end
130
+ end
131
+
132
+ def working_time_between from, to
133
+ if to < from
134
+ -working_time_between(to, from)
135
+ else
136
+ from = advance_to_working_time(in_config_zone(from))
137
+ to = in_config_zone(to).round
138
+ distance = 0
139
+ while from < to
140
+ # look at working ranges
141
+ time_in_day = from.seconds_since_midnight
142
+ wh_config[:working_hours][from.wday].each do |begins, ends|
143
+ if time_in_day >= begins and time_in_day < ends
144
+ # take all we can
145
+ take = [ends - time_in_day, to - from].min
146
+ # advance time
147
+ from += take
148
+ # increase counter
149
+ distance += take
150
+ end
151
+ end
152
+ # roll to next business period
153
+ from = advance_to_working_time(from)
154
+ end
155
+ distance
156
+ end
157
+ end
158
+
159
+ private
160
+
161
+ def wh_config
162
+ WorkingHours::Config.precompiled
163
+ end
164
+
165
+ # fix for ActiveRecord < 4, doesn't implement in_time_zone for Date
166
+ def in_config_zone time
167
+ if time.respond_to? :in_time_zone
168
+ time.in_time_zone(wh_config[:time_zone])
169
+ elsif time.is_a? Date
170
+ wh_config[:time_zone].local(time.year, time.month, time.day)
171
+ else
172
+ raise TypeError.new("Can't convert #{time.class} to a Time")
173
+ end
174
+ end
175
+
176
+ def convert_to_original_format time, original
177
+ case original
178
+ when Date then time.to_date
179
+ when DateTime then time.to_datetime
180
+ when Time then time.to_time
181
+ else time
182
+ end
183
+ end
184
+
185
+ end
186
+ end
@@ -0,0 +1,152 @@
1
+ require 'set'
2
+ require 'working_hours/deep_freeze'
3
+
4
+ module WorkingHours
5
+ InvalidConfiguration = Class.new StandardError
6
+
7
+ class Config
8
+ extend WorkingHours::DeepFreeze
9
+
10
+ TIME_FORMAT = /\A([0-1][0-9]|2[0-3]):([0-5][0-9])\z/
11
+ DAYS_OF_WEEK = [:sun, :mon, :tue, :wed, :thu, :fri, :sat]
12
+
13
+ class << self
14
+
15
+ def working_hours
16
+ config[:working_hours]
17
+ end
18
+
19
+ def working_hours=(val)
20
+ validate_working_hours! val
21
+ config[:working_hours] = deep_freeze(val)
22
+ config.delete :precompiled
23
+ end
24
+
25
+ def holidays
26
+ config[:holidays]
27
+ end
28
+
29
+ def holidays=(val)
30
+ validate_holidays! val
31
+ config[:holidays] = deep_freeze(val)
32
+ config.delete :precompiled
33
+ end
34
+
35
+ # Returns an optimized for computing version
36
+ def precompiled
37
+ config[:precompiled] ||= begin
38
+ compiled = {working_hours: []}
39
+ working_hours.each do |day, hours|
40
+ compiled[:working_hours][DAYS_OF_WEEK.index(day)] = {}
41
+ hours.each do |start, finish|
42
+ compiled[:working_hours][DAYS_OF_WEEK.index(day)][compile_time(start)] = compile_time(finish)
43
+ end
44
+ end
45
+ compiled[:holidays] = Set.new(holidays)
46
+ compiled[:time_zone] = time_zone
47
+ compiled
48
+ end
49
+ end
50
+
51
+ def time_zone
52
+ config[:time_zone]
53
+ end
54
+
55
+ def time_zone=(val)
56
+ zone = validate_time_zone! val
57
+ config[:time_zone] = zone.freeze
58
+ config.delete :precompiled
59
+ end
60
+
61
+ def reset!
62
+ Thread.current[:working_hours] = default_config
63
+ end
64
+
65
+ private
66
+
67
+ def config
68
+ Thread.current[:working_hours] ||= default_config
69
+ end
70
+
71
+ def default_config
72
+ {
73
+ working_hours: {
74
+ mon: {'09:00' => '17:00'},
75
+ tue: {'09:00' => '17:00'},
76
+ wed: {'09:00' => '17:00'},
77
+ thu: {'09:00' => '17:00'},
78
+ fri: {'09:00' => '17:00'}
79
+ }.freeze,
80
+ holidays: [].freeze,
81
+ time_zone: ActiveSupport::TimeZone['UTC'].freeze
82
+ }
83
+ end
84
+
85
+ def compile_time time
86
+ hour = time[TIME_FORMAT,1].to_i
87
+ min = time[TIME_FORMAT,2].to_i
88
+ hour * 3600 + min * 60
89
+ end
90
+
91
+ def validate_working_hours! week
92
+ if week.empty?
93
+ raise InvalidConfiguration.new "No working hours given"
94
+ end
95
+ if (invalid_keys = (week.keys - DAYS_OF_WEEK)).any?
96
+ raise InvalidConfiguration.new "Invalid day identifier(s): #{invalid_keys.join(', ')} - must be 3 letter symbols"
97
+ end
98
+ week.each do |day, hours|
99
+ if not hours.is_a? Hash
100
+ raise InvalidConfiguration.new "Invalid type for `#{day}`: #{hours.class} - must be Hash"
101
+ elsif hours.empty?
102
+ raise InvalidConfiguration.new "No working hours given for day `#{day}`"
103
+ end
104
+ last_time = nil
105
+ hours.each do |start, finish|
106
+ if not start =~ TIME_FORMAT
107
+ raise InvalidConfiguration.new "Invalid time: #{start} - must be 'HH:MM'"
108
+ elsif not finish =~ TIME_FORMAT
109
+ raise InvalidConfiguration.new "Invalid time: #{finish} - must be 'HH:MM'"
110
+ elsif start >= finish
111
+ raise InvalidConfiguration.new "Invalid range: #{start} => #{finish} - ends before it starts"
112
+ elsif last_time and start < last_time
113
+ raise InvalidConfiguration.new "Invalid range: #{start} => #{finish} - overlaps previous range"
114
+ end
115
+ last_time = finish
116
+ end
117
+ end
118
+ end
119
+
120
+ def validate_holidays! holidays
121
+ if not holidays.is_a? Array
122
+ raise InvalidConfiguration.new "Invalid type for holidays: #{holidays.class} - must be Array"
123
+ end
124
+ holidays.each do |day|
125
+ if not day.is_a? Date
126
+ raise InvalidConfiguration.new "Invalid holiday: #{day} - must be Date"
127
+ end
128
+ end
129
+ end
130
+
131
+ def validate_time_zone! zone
132
+ if zone.is_a? String
133
+ res = ActiveSupport::TimeZone[zone]
134
+ if res.nil?
135
+ raise InvalidConfiguration.new "Unknown time zone: #{zone}"
136
+ end
137
+ elsif zone.is_a? ActiveSupport::TimeZone
138
+ res = zone
139
+ else
140
+ raise InvalidConfiguration.new "Invalid time zone: #{zone.inspect} - must be String or ActiveSupport::TimeZone"
141
+ end
142
+ res
143
+ end
144
+
145
+ end
146
+
147
+ private
148
+
149
+ def initialize
150
+ end
151
+ end
152
+ end
@@ -0,0 +1,57 @@
1
+ require 'active_support/time_with_zone'
2
+ require 'working_hours/module'
3
+
4
+ module WorkingHours
5
+ module CoreExt
6
+ module DateAndTime
7
+
8
+ def +(other)
9
+ if (other.is_a?(WorkingHours::Duration))
10
+ other.since(self)
11
+ else
12
+ super(other)
13
+ end
14
+ end
15
+
16
+ def -(other)
17
+ if (other.is_a?(WorkingHours::Duration))
18
+ other.until(self)
19
+ else
20
+ super(other)
21
+ end
22
+ end
23
+
24
+ def working_days_until(other)
25
+ WorkingHours.working_days_between(self, other)
26
+ end
27
+
28
+ def working_time_until(other)
29
+ WorkingHours.working_time_between(self, other)
30
+ end
31
+
32
+ def working_day?
33
+ WorkingHours.working_day?(self)
34
+ end
35
+
36
+ def in_working_hours?
37
+ WorkingHours.in_working_hours?(self)
38
+ end
39
+ end
40
+ end
41
+ end
42
+
43
+ class Date
44
+ prepend WorkingHours::CoreExt::DateAndTime
45
+ end
46
+
47
+ class DateTime
48
+ prepend WorkingHours::CoreExt::DateAndTime
49
+ end
50
+
51
+ class Time
52
+ prepend WorkingHours::CoreExt::DateAndTime
53
+ end
54
+
55
+ class ActiveSupport::TimeWithZone
56
+ prepend WorkingHours::CoreExt::DateAndTime
57
+ end
@@ -0,0 +1,15 @@
1
+ require "working_hours/duration_proxy"
2
+
3
+ module WorkingHours
4
+ module CoreExt
5
+ module Fixnum
6
+
7
+ def working
8
+ WorkingHours::DurationProxy.new(self)
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+
15
+ Fixnum.send(:include, WorkingHours::CoreExt::Fixnum)
@@ -0,0 +1,12 @@
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