business_time 0.10.0 → 0.13.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
  SHA256:
3
- metadata.gz: cb1621dd19416fb58fba1a2dcaff32e65542e29e18d09fdea0a9d7dca621f132
4
- data.tar.gz: ef8f7a0816e6e1df588a3e14424894b397df07e4217718594dd109c6c2bc4aaf
3
+ metadata.gz: 3006a4d1708551ad12693457ba051e16b0b8f94439cc8cca54999519d20a5a63
4
+ data.tar.gz: 785c54ba3652f861532d1963dff4afbcb8194b78ffb7a46de3932778aafb6703
5
5
  SHA512:
6
- metadata.gz: fa4d68c497aa80d701692dc5182837603e8cd66cdef810298fb727f29b65d5c92b22da9b3ce5a6f1c5e4533551b3de1b4a7591931c1f68dc3c0e1dde8b62dd66
7
- data.tar.gz: a62611ad7ac045fa795a98eaca9fed77a86725bc09a87e4941290ad2f7bda3a3851756fc22d609b802fadfc122440945f58f6bc240140bcd6bc19c0b21820ecf
6
+ metadata.gz: dac78848ef4878c0891b30f3eb8842578047eb3ae559681e25f7f5dbac885643719fa28ebab37af36daa26d5278cc88f9e40c8919bfbb9b26aa8d38e625c25b2
7
+ data.tar.gz: 73529a47adeb15c99c0cc06143ee70bb5f2eeb6ee4f0da6c2cff79970082f742aeb412f4e0d201643bb60b9c024dea51c96b0db67ff4220f9a0ca51049264ca8
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009-2016 bokmann
1
+ Copyright (c) 2009-2022 bokmann
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
@@ -5,7 +5,7 @@ module BusinessTime
5
5
  include Comparable
6
6
  attr_reader :days
7
7
 
8
- def initialize(days)
8
+ def initialize(days, options={})
9
9
  @days = days
10
10
  end
11
11
 
@@ -16,15 +16,15 @@ module BusinessTime
16
16
  self.days <=> other.days
17
17
  end
18
18
 
19
- def after(time = Time.current)
20
- non_negative_days? ? calculate_after(time, @days) : calculate_before(time, -@days)
19
+ def after(time = Time.current, options={})
20
+ non_negative_days? ? calculate_after(time, @days, options) : calculate_before(time, -@days, options)
21
21
  end
22
22
 
23
23
  alias_method :from_now, :after
24
24
  alias_method :since, :after
25
25
 
26
- def before(time = Time.current)
27
- non_negative_days? ? calculate_before(time, @days) : calculate_after(time, -@days)
26
+ def before(time = Time.current, options={})
27
+ non_negative_days? ? calculate_before(time, @days, options) : calculate_after(time, -@days, options)
28
28
  end
29
29
 
30
30
  alias_method :ago, :before
@@ -36,35 +36,35 @@ module BusinessTime
36
36
  @days >= 0
37
37
  end
38
38
 
39
- def calculate_after(time, days)
40
- if (time.is_a?(Time) || time.is_a?(DateTime)) && !time.workday?
39
+ def calculate_after(time, days, options={})
40
+ if (time.is_a?(Time) || time.is_a?(DateTime)) && !time.workday?(options)
41
41
  time = Time.beginning_of_workday(time)
42
42
  end
43
- while days > 0 || !time.workday?
44
- days -= 1 if time.workday?
43
+ while days > 0 || !time.workday?(options)
44
+ days -= 1 if time.workday?(options)
45
45
  time += 1.day
46
46
  end
47
47
  # If we have a Time or DateTime object, we can roll_forward to the
48
48
  # beginning of the next business day
49
49
  if time.is_a?(Time) || time.is_a?(DateTime)
50
- time = Time.roll_forward(time) unless time.during_business_hours?
50
+ time = Time.roll_forward(time, options) unless time.during_business_hours?
51
51
  end
52
52
  time
53
53
  end
54
54
 
55
- def calculate_before(time, days)
56
- if (time.is_a?(Time) || time.is_a?(DateTime)) && !time.workday?
55
+ def calculate_before(time, days, options={})
56
+ if (time.is_a?(Time) || time.is_a?(DateTime)) && !time.workday?(options)
57
57
  time = Time.beginning_of_workday(time)
58
58
  end
59
- while days > 0 || !time.workday?
60
- days -= 1 if time.workday?
59
+ while days > 0 || !time.workday?(options)
60
+ days -= 1 if time.workday?(options)
61
61
  time -= 1.day
62
62
  end
63
63
  # If we have a Time or DateTime object, we can roll_backward to the
64
64
  # beginning of the previous business day
65
65
  if time.is_a?(Time) || time.is_a?(DateTime)
66
66
  unless time.during_business_hours?
67
- time = Time.beginning_of_workday(Time.roll_backward(time))
67
+ time = Time.beginning_of_workday(Time.roll_backward(time, options))
68
68
  end
69
69
  end
70
70
  time
@@ -3,8 +3,8 @@ module BusinessTime
3
3
  class BusinessHours
4
4
  include Comparable
5
5
  attr_reader :hours
6
-
7
- def initialize(hours)
6
+
7
+ def initialize(hours, options={})
8
8
  @hours = hours
9
9
  end
10
10
 
@@ -15,21 +15,21 @@ module BusinessTime
15
15
  self.hours <=> other.hours
16
16
  end
17
17
 
18
- def ago
19
- Time.zone ? before(Time.zone.now) : before(Time.now)
18
+ def ago(options={})
19
+ Time.zone ? before(Time.zone.now, options) : before(Time.now, options)
20
20
  end
21
21
 
22
- def from_now
23
- Time.zone ? after(Time.zone.now) : after(Time.now)
22
+ def from_now(options={})
23
+ Time.zone ? after(Time.zone.now, options) : after(Time.now, options)
24
24
  end
25
25
 
26
- def after(time)
27
- non_negative_hours? ? calculate_after(time, @hours) : calculate_before(time, -@hours)
26
+ def after(time, options={})
27
+ non_negative_hours? ? calculate_after(time, @hours, options) : calculate_before(time, -@hours, options)
28
28
  end
29
29
  alias_method :since, :after
30
30
 
31
- def before(time)
32
- non_negative_hours? ? calculate_before(time, @hours) : calculate_after(time, -@hours)
31
+ def before(time, options={})
32
+ non_negative_hours? ? calculate_before(time, @hours, options) : calculate_after(time, -@hours, options)
33
33
  end
34
34
 
35
35
  private
@@ -38,18 +38,18 @@ module BusinessTime
38
38
  @hours >= 0
39
39
  end
40
40
 
41
- def calculate_after(time, hours)
42
- after_time = Time.roll_forward(time)
41
+ def calculate_after(time, hours, options={})
42
+ after_time = Time.roll_forward(time, options)
43
43
  # Step through the hours, skipping over non-business hours
44
44
  hours.times do
45
45
  after_time = after_time + 1.hour
46
46
 
47
47
  if after_time.hour == 0 && after_time.min == 0 && after_time.sec == 0
48
- after_time = Time.roll_forward(after_time)
48
+ after_time = Time.roll_forward(after_time, options)
49
49
  elsif (after_time > Time.end_of_workday(after_time))
50
50
  # Ignore hours before opening and after closing
51
51
  delta = after_time - Time.end_of_workday(after_time)
52
- after_time = Time.roll_forward(after_time) + delta
52
+ after_time = Time.roll_forward(after_time, options) + delta
53
53
  end
54
54
 
55
55
  # Ignore weekends and holidays
@@ -60,20 +60,20 @@ module BusinessTime
60
60
  after_time
61
61
  end
62
62
 
63
- def calculate_before(time, hours)
63
+ def calculate_before(time, hours, options={})
64
64
  before_time = Time.roll_backward(time)
65
65
  # Step through the hours, skipping over non-business hours
66
66
  hours.times do
67
67
  before_time = before_time - 1.hour
68
68
 
69
69
  if before_time.hour == 0 && before_time.min == 0 && before_time.sec == 0
70
- before_time = Time.roll_backward(before_time - 1.second)
70
+ before_time = Time.roll_backward(before_time - 1.second, options)
71
71
  elsif (before_time <= Time.beginning_of_workday(before_time))
72
72
  # Ignore hours before opening and after closing
73
73
  delta = Time.beginning_of_workday(before_time) - before_time
74
74
 
75
75
  # Due to the 23:59:59 end-of-workday exception
76
- time_roll_backward = Time.roll_backward(before_time)
76
+ time_roll_backward = Time.roll_backward(before_time, options)
77
77
  time_roll_backward += 1.second if time_roll_backward.iso8601 =~ /23:59:59/
78
78
 
79
79
  before_time = time_roll_backward - delta
@@ -7,7 +7,7 @@ module BusinessTime
7
7
  # manually, or with a yaml file and the load method.
8
8
  class Config
9
9
  DEFAULT_CONFIG = {
10
- holidays: SortedSet.new,
10
+ holidays: Set.new,
11
11
  beginning_of_workday: ParsedTime.parse('9:00 am'),
12
12
  end_of_workday: ParsedTime.parse('5:00 pm'),
13
13
  work_week: %w(mon tue wed thu fri),
@@ -144,7 +144,7 @@ module BusinessTime
144
144
  wday_to_int(day_name)
145
145
  end.compact
146
146
 
147
- self._weekdays = SortedSet.new(days)
147
+ self._weekdays = days.sort.to_set
148
148
  end
149
149
 
150
150
  # loads the config data from a yaml file written as:
@@ -186,13 +186,18 @@ module BusinessTime
186
186
 
187
187
  private
188
188
 
189
+ DAY_NAMES = [
190
+ 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'
191
+ ]
192
+ private_constant :DAY_NAMES
193
+
189
194
  def wday_to_int day_name
190
- lowercase_day_names = ::Time::RFC2822_DAY_NAME.map(&:downcase)
195
+ lowercase_day_names = DAY_NAMES.map(&:downcase)
191
196
  lowercase_day_names.find_index(day_name.to_s.downcase)
192
197
  end
193
198
 
194
199
  def int_to_wday num
195
- ::Time::RFC2822_DAY_NAME.map(&:downcase).map(&:to_sym)[num]
200
+ DAY_NAMES.map(&:downcase).map(&:to_sym)[num]
196
201
  end
197
202
 
198
203
  def reset
@@ -2,15 +2,15 @@
2
2
  class Date
3
3
  include BusinessTime::TimeExtensions
4
4
 
5
- def business_days_until(to_date, inclusive = false)
6
- business_dates_until(to_date, inclusive).size
5
+ def business_days_until(to_date, inclusive = false, options={})
6
+ business_dates_until(to_date, inclusive, options).size
7
7
  end
8
8
 
9
- def business_dates_until(to_date, inclusive = false)
9
+ def business_dates_until(to_date, inclusive = false, options={})
10
10
  if inclusive
11
- (self..to_date).select(&:workday?)
11
+ (self..to_date).select{|this_date| this_date.workday?(options)}
12
12
  else
13
- (self...to_date).select(&:workday?)
13
+ (self...to_date).select{|this_date| this_date.workday?(options)}
14
14
  end
15
15
  end
16
16
 
@@ -4,13 +4,13 @@
4
4
  # 3.business_days.after(some_date)
5
5
  # 4.business_hours.before(some_date_time)
6
6
  class Integer
7
- def business_hours
8
- BusinessTime::BusinessHours.new(self)
7
+ def business_hours(options={})
8
+ BusinessTime::BusinessHours.new(self, options)
9
9
  end
10
10
  alias_method :business_hour, :business_hours
11
11
 
12
- def business_days
13
- BusinessTime::BusinessDays.new(self)
12
+ def business_days(options={})
13
+ BusinessTime::BusinessDays.new(self, options)
14
14
  end
15
15
  alias_method :business_day, :business_days
16
16
  end
@@ -2,8 +2,12 @@ module BusinessTime
2
2
  module TimeExtensions
3
3
  # True if this time is on a workday (between 00:00:00 and 23:59:59), even if
4
4
  # this time falls outside of normal business hours.
5
- def workday?
6
- weekday? && !BusinessTime::Config.holidays.include?(to_date)
5
+ # holidays option allows you to pass in a different Array of holiday dates on
6
+ # each call vs the BusinessTime::Config.holidays which is always static.
7
+ def workday?(options={})
8
+ weekday? &&
9
+ !BusinessTime::Config.holidays.include?(to_date) &&
10
+ !to_array_of_dates(options[:holidays]).include?(to_date)
7
11
  end
8
12
 
9
13
  # True if this time falls on a weekday.
@@ -32,9 +36,9 @@ module BusinessTime
32
36
 
33
37
  # True if this time is on a workday (between 00:00:00 and 23:59:59), even if
34
38
  # this time falls outside of normal business hours.
35
- def workday?(day)
39
+ def workday?(day, options={})
36
40
  ActiveSupport::Deprecation.warn("`Time.workday?(time)` is deprecated. Please use `time.workday?`")
37
- day.workday?
41
+ day.workday?(options)
38
42
  end
39
43
 
40
44
  # True if this time falls on a weekday.
@@ -53,9 +57,8 @@ module BusinessTime
53
57
 
54
58
  # Rolls forward to the next beginning_of_workday
55
59
  # when the time is outside of business hours
56
- def roll_forward(time)
57
-
58
- if Time.before_business_hours?(time) || !time.workday?
60
+ def roll_forward(time, options={})
61
+ if Time.before_business_hours?(time) || !time.workday?(options)
59
62
  next_business_time = Time.beginning_of_workday(time)
60
63
  elsif Time.after_business_hours?(time) || Time.end_of_workday(time) == time
61
64
  next_business_time = Time.beginning_of_workday(time + 1.day)
@@ -63,7 +66,7 @@ module BusinessTime
63
66
  next_business_time = time.clone
64
67
  end
65
68
 
66
- while !next_business_time.workday?
69
+ while !next_business_time.workday?(options)
67
70
  next_business_time = Time.beginning_of_workday(next_business_time + 1.day)
68
71
  end
69
72
 
@@ -72,8 +75,8 @@ module BusinessTime
72
75
 
73
76
  # Returns the time parameter itself if it is a business day
74
77
  # or else returns the next business day
75
- def first_business_day(time)
76
- while !time.workday?
78
+ def first_business_day(time, options={})
79
+ while !time.workday?(options)
77
80
  time = time + 1.day
78
81
  end
79
82
 
@@ -82,8 +85,8 @@ module BusinessTime
82
85
 
83
86
  # Rolls backwards to the previous end_of_workday when the time is outside
84
87
  # of business hours
85
- def roll_backward(time)
86
- prev_business_time = if (Time.before_business_hours?(time) || !time.workday?)
88
+ def roll_backward(time, options={})
89
+ prev_business_time = if (Time.before_business_hours?(time) || !time.workday?(options))
87
90
  Time.end_of_workday(time - 1.day)
88
91
  elsif Time.after_business_hours?(time)
89
92
  Time.end_of_workday(time)
@@ -91,7 +94,7 @@ module BusinessTime
91
94
  time.clone
92
95
  end
93
96
 
94
- while !prev_business_time.workday?
97
+ while !prev_business_time.workday?(options)
95
98
  prev_business_time = Time.end_of_workday(prev_business_time - 1.day)
96
99
  end
97
100
 
@@ -100,16 +103,16 @@ module BusinessTime
100
103
 
101
104
  # Returns the time parameter itself if it is a business day
102
105
  # or else returns the previous business day
103
- def previous_business_day(time)
104
- while !time.workday?
106
+ def previous_business_day(time, options={})
107
+ while !time.workday?(options)
105
108
  time = time - 1.day
106
109
  end
107
110
 
108
111
  time
109
112
  end
110
113
 
111
- def work_hours_total(day)
112
- return 0 unless day.workday?
114
+ def work_hours_total(day, options={})
115
+ return 0 unless day.workday?(options)
113
116
 
114
117
  day = day.strftime('%a').downcase.to_sym
115
118
 
@@ -136,7 +139,7 @@ module BusinessTime
136
139
  end
137
140
  end
138
141
 
139
- def business_time_until(to_time)
142
+ def business_time_until(to_time, options={})
140
143
  # Make sure that we will calculate time from A to B "clockwise"
141
144
  if self < to_time
142
145
  time_a = self
@@ -149,8 +152,8 @@ module BusinessTime
149
152
  end
150
153
 
151
154
  # Align both times to the closest business hours
152
- time_a = Time::roll_forward(time_a)
153
- time_b = Time::roll_forward(time_b)
155
+ time_a = Time::roll_forward(time_a, options)
156
+ time_b = Time::roll_forward(time_b, options)
154
157
 
155
158
  if time_a.to_date == time_b.to_date
156
159
  time_b - time_a
@@ -166,16 +169,16 @@ module BusinessTime
166
169
  end * direction
167
170
  end
168
171
 
169
- def during_business_hours?
170
- self.workday? && self.to_i.between?(Time.beginning_of_workday(self).to_i, Time.end_of_workday(self).to_i)
172
+ def during_business_hours?(options={})
173
+ self.workday?(options) && self.to_i.between?(Time.beginning_of_workday(self).to_i, Time.end_of_workday(self).to_i)
171
174
  end
172
175
 
173
- def consecutive_workdays
174
- workday? ? consecutive_days { |date| date.workday? } : []
176
+ def consecutive_workdays(options={})
177
+ workday?(options) ? consecutive_days { |date| date.workday?(options) } : []
175
178
  end
176
179
 
177
- def consecutive_non_working_days
178
- !workday? ? consecutive_days { |date| !date.workday? } : []
180
+ def consecutive_non_working_days(options={})
181
+ !workday?(options) ? consecutive_days { |date| !date.workday?(options) } : []
179
182
  end
180
183
 
181
184
  private
@@ -194,5 +197,9 @@ module BusinessTime
194
197
  end
195
198
  (days << self).sort
196
199
  end
200
+
201
+ def to_array_of_dates(values)
202
+ Array.wrap(values).map(&:to_date)
203
+ end
197
204
  end
198
205
  end
@@ -1,3 +1,3 @@
1
1
  module BusinessTime
2
- VERSION = "0.10.0"
2
+ VERSION = "0.13.0"
3
3
  end
data/lib/business_time.rb CHANGED
@@ -1,9 +1,10 @@
1
- require 'thread'
2
- require 'active_support'
3
- require 'active_support/time'
1
+ require 'set'
4
2
  require 'time'
5
3
  require 'yaml'
6
4
 
5
+ require 'active_support'
6
+ require 'active_support/time'
7
+
7
8
  require 'business_time/parsed_time'
8
9
  require 'business_time/version'
9
10
  require 'business_time/config'
@@ -1,21 +1,21 @@
1
1
  module BusinessTime
2
2
  module Generators
3
3
  class ConfigGenerator < Rails::Generators::Base # :nodoc:
4
-
4
+
5
5
  def self.gem_root
6
- File.expand_path("../../../..", __FILE__)
6
+ File.expand_path("../../..", __dir__)
7
7
  end
8
8
 
9
9
  def self.source_root
10
10
  # Use the templates from the 2.3.x generator
11
11
  File.join(gem_root, 'rails_generators', 'business_time_config', 'templates')
12
12
  end
13
-
13
+
14
14
  def generate
15
15
  template 'business_time.rb', File.join('config', 'initializers', 'business_time.rb')
16
16
  template 'business_time.yml', File.join('config', 'business_time.yml')
17
17
  end
18
-
18
+
19
19
  end
20
20
  end
21
- end
21
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: business_time
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.10.0
4
+ version: 0.13.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - bokmann
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-02-24 00:00:00.000000000 Z
11
+ date: 2022-06-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -38,20 +38,6 @@ dependencies:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '0'
41
- - !ruby/object:Gem::Dependency
42
- name: sorted_set
43
- requirement: !ruby/object:Gem::Requirement
44
- requirements:
45
- - - ">="
46
- - !ruby/object:Gem::Version
47
- version: '0'
48
- type: :runtime
49
- prerelease: false
50
- version_requirements: !ruby/object:Gem::Requirement
51
- requirements:
52
- - - ">="
53
- - !ruby/object:Gem::Version
54
- version: '0'
55
41
  - !ruby/object:Gem::Dependency
56
42
  name: rake
57
43
  requirement: !ruby/object:Gem::Requirement
@@ -116,7 +102,6 @@ extensions: []
116
102
  extra_rdoc_files: []
117
103
  files:
118
104
  - LICENSE
119
- - README.rdoc
120
105
  - lib/business_time.rb
121
106
  - lib/business_time/business_days.rb
122
107
  - lib/business_time/business_hours.rb
@@ -151,7 +136,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
151
136
  - !ruby/object:Gem::Version
152
137
  version: '0'
153
138
  requirements: []
154
- rubygems_version: 3.2.11
139
+ rubygems_version: 3.3.12
155
140
  signing_key:
156
141
  specification_version: 4
157
142
  summary: Support for doing time math in business hours and days
data/README.rdoc DELETED
@@ -1,223 +0,0 @@
1
- = business_time
2
-
3
- {<img src="https://github.com/bokmann/business_time/workflows/CI/badge.svg" />}[https://github.com/bokmann/business_time/actions?query=workflow%3ACI]
4
-
5
- ActiveSupport gives us some great helpers so we can do things like:
6
-
7
- 5.days.ago
8
-
9
- and
10
-
11
- 8.hours.from_now
12
-
13
- as well as helpers to do that from any provided date or time.
14
-
15
- I needed this, but taking into account business hours/days and holidays.
16
-
17
- == Usage
18
- === install the gem
19
-
20
- gem install business_time
21
-
22
- === open up your console
23
-
24
- # if in irb, add these lines:
25
-
26
- require 'business_time'
27
-
28
- # try these examples, using the current time:
29
-
30
- 1.business_hour.from_now
31
- 4.business_hours.from_now
32
- 8.business_hours.from_now
33
-
34
- 1.business_hour.ago
35
- 4.business_hours.ago
36
- 8.business_hours.ago
37
-
38
- 1.business_day.from_now
39
- 4.business_days.from_now
40
- 8.business_days.from_now
41
-
42
- 1.business_day.ago
43
- 4.business_days.ago
44
- 8.business_days.ago
45
-
46
- Date.today.workday?
47
- Date.parse("2015-12-09").workday?
48
- Date.parse("2015-12-12").workday?
49
-
50
- And we can do it from any Date or Time object.
51
- my_birthday = Date.parse("August 4th, 1969")
52
- 8.business_days.after(my_birthday)
53
- 8.business_days.before(my_birthday)
54
-
55
- my_birthday = Time.parse("August 4th, 1969, 8:32 am")
56
- 8.business_days.after(my_birthday)
57
- 8.business_days.before(my_birthday)
58
-
59
-
60
- We can adjust the start and end time of our business hours
61
- BusinessTime::Config.beginning_of_workday = "8:30 am"
62
- BusinessTime::Config.end_of_workday = "5:30 pm"
63
-
64
- and we can add holidays that don't count as business days
65
- July 5 in 2010 is a monday that the U.S. takes off because our independence day falls on that Sunday.
66
- three_day_weekend = Date.parse("July 5th, 2010")
67
- BusinessTime::Config.holidays << three_day_weekend
68
- friday_afternoon = Time.parse("July 2nd, 2010, 4:50 pm")
69
- tuesday_morning = 1.business_hour.after(friday_afternoon)
70
-
71
- plus, we can change the work week:
72
- # July 9th in 2010 is a Friday.
73
- BusinessTime::Config.work_week = [:sun, :mon, :tue, :wed, :thu]
74
- thursday_afternoon = Time.parse("July 8th, 2010, 4:50 pm")
75
- sunday_morning = 1.business_hour.after(thursday_afternoon)
76
-
77
- As alternative we also can change the business hours for each work day:
78
- BusinessTime::Config.work_hours = {
79
- :mon=>["9:00","17:00"],
80
- :fri=>["9:00","17:00"],
81
- :sat=>["10:00","15:00"]
82
- }
83
- friday = Time.parse("December 24, 2010 15:00")
84
- monday = Time.parse("December 27, 2010 11:00")
85
- working_hours = friday.business_time_until(monday) # 9.hours
86
-
87
- You can also calculate business duration between two dates
88
- friday = Date.parse("December 24, 2010")
89
- monday = Date.parse("December 27, 2010")
90
- friday.business_days_until(monday) #=> 1
91
-
92
- Or you can calculate business duration between two Time objects
93
- ticket_reported = Time.parse("February 3, 2012, 10:40 am")
94
- ticket_resolved = Time.parse("February 4, 2012, 10:50 am")
95
- ticket_reported.business_time_until(ticket_resolved) #=> 8.hours + 10.minutes
96
-
97
- You can also determine if a given time is within business hours
98
- Time.parse("February 3, 2012, 10:00 am").during_business_hours?
99
-
100
- Note that counterintuitively, durations might not be quite what you expect when involving weekends.
101
- Consider the following example:
102
- ticket_reported = Time.parse("February 3, 2012, 10:40 am")
103
- ticket_resolved = Time.parse("February 4, 2012, 10:40 am")
104
- ticket_reported.business_time_until(ticket_resolved) # will equal 6 hours and 20 minutes!
105
-
106
- Why does this happen? Feb 4 2012 is a Saturday. That time will roll over to
107
- Monday, Feb 6th 2012, 9:00am. The business time between 10:40am friday and 9am monday is
108
- 6 hours and 20 minutes. From a quick inspection of the code, it looks like it should be 8 hours.
109
-
110
- Or you can calculate business dates between two dates
111
- monday = Date.parse("December 20, 2010")
112
- wednesday = Date.parse("December 22, 2010")
113
- monday.business_dates_until(wednesday) #=> [Mon, 20 Dec 2010, Tue, 21 Dec 2010]
114
-
115
- You can get the first workday after a time or return itself if it is a workday
116
- saturday = Time.parse("Sat Aug 9, 18:00:00, 2014")
117
- monday = Time.parse("Mon Aug 11, 18:00:00, 2014")
118
- Time.first_business_day(saturday) #=> "Mon Aug 11, 18:00:00, 2014"
119
- Time.first_business_day(monday) #=> "Mon Aug 11, 18:00:00, 2014"
120
-
121
- # similar to Time#first_business_day Time#previous_business_day only cares about
122
- # workdays:
123
- saturday = Time.parse("Sat Aug 9, 18:00:00, 2014")
124
- monday = Time.parse("Mon Aug 11, 18:00:00, 2014")
125
- Time.previous_business_day(saturday) #=> "Fri Aug 8, 18:00:00, 2014"
126
- Time.previous_business_day(monday) #=> "Mon Aug 11, 18:00:00, 2014"
127
- == Rails generator
128
-
129
- rails generate business_time:config
130
-
131
- The generator will add a ./config/business_time.yml and a ./config/initializers/business_time.rb
132
- file that will cause the start of business day, the end of business day, and your holidays to be loaded from the yaml file.
133
-
134
- You might want to programatically load your holidays from a database table,
135
- but you will want to pay attention to how the initializer works -
136
- you will want to make sure that the initializer sets stuff up appropriately so
137
- rails instances on mongrels or passenger will have the appropriate data as they come up and down.
138
-
139
- == Timezone support
140
- This gem strives to be timezone-agnostic.
141
- Due to some complications in the handling of timezones in the built in Time class,
142
- and some complexities (bugs?) in the timeWithZone class, this was harder than expected... but here's the idea:
143
-
144
- * When you configure the gem with something like 9:00am as the start time,
145
- this is agnostic of time zone.
146
- * When you are dealing with a Time or TimeWithZone class, the timezone is
147
- preserved and the beginning and end of times for the business day are
148
- referenced in that time zone.
149
-
150
- This can lead to some weird looking effects if, say, you are in the Eastern time zone but doing everything in UTC times...
151
- Your business day will appear to start and end at 9:00 and 5:00 UTC.
152
- If this seems perplexing to you, I can almost guarantee you are in over your head with timezones in other ways too,
153
- this is just the first place you encountered it.
154
- Timezone relative date handling gets more and more complicated every time you look at it and takes a long time before it starts to seem simple again.
155
-
156
- == Integration with the Holidays gem
157
-
158
- Chris Wise wrote up a great article[http://murmurinfo.wordpress.com/2012/01/11/handling-holidays-and-business-hours/]
159
- on using the business_time gem with the holidays[https://github.com/alexdunae/holidays] gem. It boils down to this:
160
-
161
- Holidays.between(Date.civil(2013, 1, 1), 2.years.from_now, :ca_on, :observed).map do |holiday|
162
- BusinessTime::Config.holidays << holiday[:date]
163
- # Implement long weekends if they apply to the region, eg:
164
- # BusinessTime::Config.holidays << holiday[:date].next_week if !holiday[:date].weekday?
165
- end
166
-
167
- == Contributors
168
- * David Bock http://github.com/bokmann
169
- * Enrico Bianco http://github.com/enricob
170
- * Arild Shirazi http://github.com/ashirazi
171
- * Piotr Jakubowski http://github.com/piotrj
172
- * Glenn Vanderburg http://github.com/glv
173
- * Michael Grosser http://github.com/grosser
174
- * Michael Curtis http://github.com/mcurtis
175
- * Brian Ewins http://github.com/bazzargh
176
-
177
- (Special thanks for Arild on the complexities of dealing with TimeWithZone)
178
-
179
- == Note on Patches/Pull Requests
180
-
181
- * Fork the project.
182
- * Make your feature addition or bug fix.
183
- * Add tests for it. This is important so I don't break it in a
184
- future version unintentionally.
185
- * Commit, do not mess with rakefile, version, or history.
186
- (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
187
- * Send me a pull request. Bonus points for topic branches.
188
-
189
- == TODO
190
-
191
- * Arild has pointed out that there may be some logical inconsistencies
192
- regarding the beginning_of_workday and end_of workday times not actually
193
- being considered inside of the workday. I'd like to make sure that they
194
- work as if the beginning_of_workday is included and the end_of_workday is
195
- not included, just like the '...' range operator in Ruby.
196
-
197
- == NOT TODO
198
-
199
- * I spent way too much time in my previous java-programmer life building frameworks that worshipped complexity,
200
- always trying to give the developer-user ultimate flexibility at the expense of the 'surface area' of the api.
201
- Never again - I will sooner limit functionality to 80% so that something stays usable and let people fork.
202
- * While there have been requests to add 'business minutes' and even 'business seconds' to this gem, I won't
203
- entertain a pull request with such things. If you find it useful, great. Most users won't, and they don't
204
- need the baggage.
205
-
206
-
207
- == A note on stability and change
208
-
209
- Sometimes people ask me why this gem doesn't release more often. My opinions on that are best discussed in person in a friendly discussion, but I'll attempt some of that here.
210
-
211
- First, a big part of the reason is that the projects I do use this gem on are happy with it's current functionality. It is 'suitable for the purpose' for which I released it, and as such, maintenance I do on this gem is a gift to the community.
212
-
213
- Second, out of the ~1.3 million downloads (according to rubygems.org), the number of real 'issues' with this gem have been minimal. Most of the issues that are opened are really people with slightly different requirements than I have regarding whether 'off hours' work counts as the previous or the next business day, a disagreement on the semantics of days vs. hours, etc. I take care to try to explain these choices in the open issues, but to my mind, they aren't true issues if it's just a difference of opinion. Even so, I'll gladly accept pull requests that resolve this difference of opinion as a configuration option... just don't expect me to do your job for you. I've already given you 90% of what you need.
214
-
215
- Third, a business time gem is, well, relevant to businesses. Many businesses don't move quickly. My government clients move even more slowly. Stability is favored in these environments.
216
-
217
- Fourth, new features can wait. To the person that adds them they can be mission critical, but with modern packaging processes, they can use their version without waiting for their changes to be included in the upstream version. Their changes don't break your code.
218
-
219
- I'm proud of the work in this gem; the stability is a big part of that. This gem has lived longer than many others that have attempted to do the same thing. I expect it to be here chugging away when Ruby has become the next COBOL.
220
-
221
- == Copyright
222
-
223
- Copyright (c) 2010-2021 bokmann. See LICENSE for details.