business_time 0.7.4 → 0.9.3

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: 5bdd88444abb3662202edb5a58a7966198095d0c
4
- data.tar.gz: d3bb972b85a2f229c34a88511bcb6eb9595f8589
3
+ metadata.gz: 5fe356eb4f4e620e3f3db2aa9242df1c63213500
4
+ data.tar.gz: d1f662a4f576662e3f6d140970d5b2191016fda8
5
5
  SHA512:
6
- metadata.gz: b4d19b1491299ca47b3a765f701ba6ba909a5af84925005c75262679b4d193c70520d7938bc23c694fc03d5ee509c742f472285d82da75050aa457513d2f5f5f
7
- data.tar.gz: c959f75f0cc63e165e7a55bceaddbc9dceefb0db9491cbeec38bf75944359c0113e20aa9a22460ee650071e6a7af515b7de6e3c3ceb4fa71004bda8f76f5e0fd
6
+ metadata.gz: 86434d31fbf26aa3372510db883266643431a449fa2980cee43e9d34198c140a560d0606d41911aa0a47e030b373eeba0442f38dad07c36a5b5113a811c97213
7
+ data.tar.gz: 5c1ae7866418a21538030ba2129698eaafb2016b5d715cd2c13e31e1739b8732ff625af6f87e48e98bbe697ea06005d74c35bfab7c8610b306f7ba7c9d0121b5
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2009,2010,2011,2012 bokmann
1
+ Copyright (c) 2009-2016 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
data/README.rdoc CHANGED
@@ -15,11 +15,11 @@ as well as helpers to do that from any provided date or time.
15
15
  I needed this, but taking into account business hours/days and holidays.
16
16
 
17
17
  == Usage
18
- * install the gem
18
+ === install the gem
19
19
 
20
20
  gem install business_time
21
21
 
22
- * open up your console
22
+ === open up your console
23
23
 
24
24
  # if in irb, add these lines:
25
25
 
@@ -43,7 +43,11 @@ I needed this, but taking into account business hours/days and holidays.
43
43
  4.business_days.ago
44
44
  8.business_days.ago
45
45
 
46
- # and we can do it from any Date or Time object.
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.
47
51
  my_birthday = Date.parse("August 4th, 1969")
48
52
  8.business_days.after(my_birthday)
49
53
  8.business_days.before(my_birthday)
@@ -53,24 +57,24 @@ I needed this, but taking into account business hours/days and holidays.
53
57
  8.business_days.before(my_birthday)
54
58
 
55
59
 
56
- # We can adjust the start and end time of our business hours
60
+ We can adjust the start and end time of our business hours
57
61
  BusinessTime::Config.beginning_of_workday = "8:30 am"
58
62
  BusinessTime::Config.end_of_workday = "5:30 pm"
59
63
 
60
- # and we can add holidays that don't count as business days
61
- # July 5 in 2010 is a monday that the U.S. takes off because our independence day falls on that Sunday.
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.
62
66
  three_day_weekend = Date.parse("July 5th, 2010")
63
67
  BusinessTime::Config.holidays << three_day_weekend
64
68
  friday_afternoon = Time.parse("July 2nd, 2010, 4:50 pm")
65
69
  tuesday_morning = 1.business_hour.after(friday_afternoon)
66
70
 
67
- # plus, we can change the work week:
71
+ plus, we can change the work week:
68
72
  # July 9th in 2010 is a Friday.
69
73
  BusinessTime::Config.work_week = [:sun, :mon, :tue, :wed, :thu]
70
74
  thursday_afternoon = Time.parse("July 8th, 2010, 4:50 pm")
71
75
  sunday_morning = 1.business_hour.after(thursday_afternoon)
72
76
 
73
- # as alternative we also can change the business hours for each work day:
77
+ As alternative we also can change the business hours for each work day:
74
78
  BusinessTime::Config.work_hours = {
75
79
  :mon=>["9:00","17:00"],
76
80
  :fri=>["9:00","17:00"],
@@ -80,39 +84,46 @@ I needed this, but taking into account business hours/days and holidays.
80
84
  monday = Time.parse("December 27, 2010 11:00")
81
85
  working_hours = friday.business_time_until(monday) # 9.hours
82
86
 
83
- # you can also calculate business duration between two dates
87
+ You can also calculate business duration between two dates
84
88
  friday = Date.parse("December 24, 2010")
85
89
  monday = Date.parse("December 27, 2010")
86
90
  friday.business_days_until(monday) #=> 1
87
91
 
88
- # or you can calculate business duration between two Time objects
92
+ Or you can calculate business duration between two Time objects
89
93
  ticket_reported = Time.parse("February 3, 2012, 10:40 am")
90
94
  ticket_resolved = Time.parse("February 4, 2012, 10:50 am")
91
95
  ticket_reported.business_time_until(ticket_resolved) #=> 8.hours + 10.minutes
92
96
 
93
- # you can also determine if a given time is within business hours
97
+ You can also determine if a given time is within business hours
94
98
  Time.parse("February 3, 2012, 10:00 am").during_business_hours?
95
99
 
96
- # note that counterintuitively, durations might not be quite what you expect when involving weekends.
97
- # Consider the following example:
100
+ Note that counterintuitively, durations might not be quite what you expect when involving weekends.
101
+ Consider the following example:
98
102
  ticket_reported = Time.parse("February 3, 2012, 10:40 am")
99
103
  ticket_resolved = Time.parse("February 4, 2012, 10:40 am")
100
104
  ticket_reported.business_time_until(ticket_resolved) # will equal 6 hours and 20 minutes!
101
105
 
102
- # why does this happen? Feb 4 2012 is a Saturday. That time will roll over to
103
- # Monday, Feb 6th 2012, 9:00am. The business time between 10:40am friday and 9am monday is
104
- # 6 hours and 20 minutes. From a quick inspection of the code, it looks like it should be 8 hours.
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.
105
109
 
106
- # or you can calculate business dates between two dates
110
+ Or you can calculate business dates between two dates
107
111
  monday = Date.parse("December 20, 2010")
108
112
  wednesday = Date.parse("December 22, 2010")
109
113
  monday.business_dates_until(wednesday) #=> [Mon, 20 Dec 2010, Tue, 21 Dec 2010]
110
114
 
111
- # you can get the first workday after a time or return itself if it is a workday
115
+ You can get the first workday after a time or return itself if it is a workday
112
116
  saturday = Time.parse("Sat Aug 9, 18:00:00, 2014")
113
117
  monday = Time.parse("Mon Aug 11, 18:00:00, 2014")
114
118
  Time.first_business_day(saturday) #=> "Mon Aug 11, 18:00:00, 2014"
115
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"
116
127
  == Rails generator
117
128
 
118
129
  rails generate business_time:config
@@ -136,12 +147,11 @@ and some complexities (bugs?) in the timeWithZone class, this was harder than ex
136
147
  preserved and the beginning and end of times for the business day are
137
148
  referenced in that time zone.
138
149
 
139
- This can lead to some wierd looking effects if, say, you are in the Eastern time zone but doing everything in UTC times...
150
+ This can lead to some weird looking effects if, say, you are in the Eastern time zone but doing everything in UTC times...
140
151
  Your business day will appear to start and end at 9:00 and 5:00 UTC.
141
152
  If this seems perplexing to you, I can almost guarantee you are in over your head with timezones in other ways too,
142
153
  this is just the first place you encountered it.
143
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.
144
- I'm hoping Arild and I write some good blog entries on the subject at http://blog.codesherpas.com.
145
155
 
146
156
  == Integration with the Holidays gem
147
157
 
@@ -154,16 +164,6 @@ I'm hoping Arild and I write some good blog entries on the subject at http://blo
154
164
  # BusinessTime::Config.holidays << holiday[:date].next_week if !holiday[:date].weekday?
155
165
  end
156
166
 
157
- == Releases
158
-
159
- 0.7.1 - fixing a multithreaded issue, upgrading some dependencies, loosening the dependency on TZInfo
160
-
161
- 0.7.0 - major maintenance upgrade on the process of constructing the gem, testing the gem, and updating dependencies.
162
- the api has not changed.
163
-
164
-
165
- 0.6.2 - rchady pointed out that issue #14 didn't appear to be released. This fixes that, as well as confirms that all tests run as expected on Ruby 2.0.0p195
166
-
167
167
  == Contributors
168
168
  * David Bock http://github.com/bokmann
169
169
  * Enrico Bianco http://github.com/enricob
@@ -172,6 +172,7 @@ I'm hoping Arild and I write some good blog entries on the subject at http://blo
172
172
  * Glenn Vanderburg http://github.com/glv
173
173
  * Michael Grosser http://github.com/grosser
174
174
  * Michael Curtis http://github.com/mcurtis
175
+ * Brian Ewins http://github.com/bazzargh
175
176
 
176
177
  (Special thanks for Arild on the complexities of dealing with TimeWithZone)
177
178
 
@@ -203,6 +204,20 @@ I'm hoping Arild and I write some good blog entries on the subject at http://blo
203
204
  need the baggage.
204
205
 
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
+
206
221
  == Copyright
207
222
 
208
- Copyright (c) 2010,2011,2012,2013 bokmann. See LICENSE for details.
223
+ Copyright (c) 2010-2017 bokmann. See LICENSE for details.
@@ -2,32 +2,66 @@ require 'active_support/time'
2
2
 
3
3
  module BusinessTime
4
4
  class BusinessDays
5
+ include Comparable
6
+ attr_reader :days
7
+
5
8
  def initialize(days)
6
9
  @days = days
7
10
  end
8
11
 
9
- def after(time = Time.current)
10
- days = @days
11
- while days > 0 || !time.workday?
12
- days -= 1 if time.workday?
13
- time = time + 1.day
12
+ def <=>(other)
13
+ if other.class != self.class
14
+ raise ArgumentError.new("#{self.class} can't be compared with #{other.class}")
14
15
  end
15
- time
16
+ self.days <=> other.days
17
+ end
18
+
19
+ def after(time = Time.current)
20
+ non_negative_days? ? calculate_after(time, @days) : calculate_before(time, -@days)
16
21
  end
17
22
 
18
23
  alias_method :from_now, :after
19
24
  alias_method :since, :after
20
25
 
21
26
  def before(time = Time.current)
22
- days = @days
27
+ non_negative_days? ? calculate_before(time, @days) : calculate_after(time, -@days)
28
+ end
29
+
30
+ alias_method :ago, :before
31
+ alias_method :until, :before
32
+
33
+ private
34
+
35
+ def non_negative_days?
36
+ @days >= 0
37
+ end
38
+
39
+ def calculate_after(time, days)
23
40
  while days > 0 || !time.workday?
24
41
  days -= 1 if time.workday?
25
- time = time - 1.day
42
+ time += 1.day
43
+ end
44
+ # If we have a Time or DateTime object, we can roll_forward to the
45
+ # beginning of the next business day
46
+ if time.is_a?(Time) || time.is_a?(DateTime)
47
+ time = Time.roll_forward(time) unless time.during_business_hours?
26
48
  end
27
49
  time
28
50
  end
29
51
 
30
- alias_method :ago, :before
31
- alias_method :until, :before
52
+ def calculate_before(time, days)
53
+ while days > 0 || !time.workday?
54
+ days -= 1 if time.workday?
55
+ time -= 1.day
56
+ end
57
+ # If we have a Time or DateTime object, we can roll_backward to the
58
+ # beginning of the previous business day
59
+ if time.is_a?(Time) || time.is_a?(DateTime)
60
+ unless time.during_business_hours?
61
+ time = Time.beginning_of_workday(Time.roll_backward(time))
62
+ end
63
+ end
64
+ time
65
+ end
32
66
  end
33
67
  end
@@ -1,10 +1,20 @@
1
1
  module BusinessTime
2
2
 
3
3
  class BusinessHours
4
+ include Comparable
5
+ attr_reader :hours
6
+
4
7
  def initialize(hours)
5
8
  @hours = hours
6
9
  end
7
10
 
11
+ def <=>(other)
12
+ if other.class != self.class
13
+ raise ArgumentError.new("#{self.class.to_s} can't be compared with #{other.class.to_s}")
14
+ end
15
+ self.hours <=> other.hours
16
+ end
17
+
8
18
  def ago
9
19
  Time.zone ? before(Time.zone.now) : before(Time.now)
10
20
  end
@@ -14,9 +24,24 @@ module BusinessTime
14
24
  end
15
25
 
16
26
  def after(time)
27
+ non_negative_hours? ? calculate_after(time, @hours) : calculate_before(time, -@hours)
28
+ end
29
+ alias_method :since, :after
30
+
31
+ def before(time)
32
+ non_negative_hours? ? calculate_before(time, @hours) : calculate_after(time, -@hours)
33
+ end
34
+
35
+ private
36
+
37
+ def non_negative_hours?
38
+ @hours >= 0
39
+ end
40
+
41
+ def calculate_after(time, hours)
17
42
  after_time = Time.roll_forward(time)
18
43
  # Step through the hours, skipping over non-business hours
19
- @hours.times do
44
+ hours.times do
20
45
  after_time = after_time + 1.hour
21
46
 
22
47
  if after_time.hour == 0 && after_time.min == 0 && after_time.sec == 0
@@ -34,12 +59,11 @@ module BusinessTime
34
59
  end
35
60
  after_time
36
61
  end
37
- alias_method :since, :after
38
62
 
39
- def before(time)
63
+ def calculate_before(time, hours)
40
64
  before_time = Time.roll_backward(time)
41
65
  # Step through the hours, skipping over non-business hours
42
- @hours.times do
66
+ hours.times do
43
67
  before_time = before_time - 1.hour
44
68
 
45
69
  if before_time.hour == 0 && before_time.min == 0 && before_time.sec == 0
@@ -50,7 +74,7 @@ module BusinessTime
50
74
 
51
75
  # Due to the 23:59:59 end-of-workday exception
52
76
  time_roll_backward = Time.roll_backward(before_time)
53
- time_roll_backward += 1.second if time_roll_backward.to_s =~ /23:59:59/
77
+ time_roll_backward += 1.second if time_roll_backward.iso8601 =~ /23:59:59/
54
78
 
55
79
  before_time = time_roll_backward - delta
56
80
  end
@@ -7,30 +7,73 @@ module BusinessTime
7
7
  # manually, or with a yaml file and the load method.
8
8
  class Config
9
9
  DEFAULT_CONFIG = {
10
- holidays: [],
11
- beginning_of_workday: '9:00 am',
12
- end_of_workday: '5:00 pm',
10
+ holidays: SortedSet.new,
11
+ beginning_of_workday: ParsedTime.parse('9:00 am'),
12
+ end_of_workday: ParsedTime.parse('5:00 pm'),
13
13
  work_week: %w(mon tue wed thu fri),
14
14
  work_hours: {},
15
15
  work_hours_total: {},
16
16
  _weekdays: nil,
17
+ fiscal_month_offset: 10,
17
18
  }
18
19
 
19
20
  class << self
21
+ def beginning_of_workday=(time)
22
+ config[:beginning_of_workday] = ParsedTime.parse(time)
23
+ end
24
+
25
+ def end_of_workday=(time)
26
+ config[:end_of_workday] = ParsedTime.parse(time)
27
+ end
28
+
29
+ def work_hours=(work_hours)
30
+ work_hours.each_with_object(config[:work_hours] = {}) do |(day, hours), c|
31
+ c[day] = hours.map do |time|
32
+ ParsedTime.parse(time)
33
+ end
34
+ end
35
+ end
36
+
20
37
  private
21
38
 
22
39
  def config
40
+ return local_config if local_config?
23
41
  Thread.main[:business_time_config] ||= default_config
24
42
  end
25
43
 
26
44
  def config=(config)
45
+ return self.local_config = config if local_config?
27
46
  Thread.main[:business_time_config] = config
28
47
  end
29
48
 
49
+ def local_config
50
+ local_config_stack.last
51
+ end
52
+
53
+ def local_config=(config)
54
+ local_config_stack.last.replace(config)
55
+ end
56
+
57
+ def local_config_stack
58
+ Thread.current[:business_time_local_config] ||= []
59
+ end
60
+
61
+ def local_config?
62
+ !local_config_stack.empty?
63
+ end
64
+
30
65
  def threadsafe_cattr_accessor(name)
66
+ threadsafe_cattr_reader(name)
67
+ threadsafe_cattr_setter(name)
68
+ end
69
+
70
+ def threadsafe_cattr_reader(name)
31
71
  define_singleton_method name do
32
72
  config[name]
33
73
  end
74
+ end
75
+
76
+ def threadsafe_cattr_setter(name)
34
77
  define_singleton_method "#{name}=" do |value|
35
78
  config[name] = value
36
79
  end
@@ -41,13 +84,13 @@ module BusinessTime
41
84
  # by saying
42
85
  # BusinessTime::Config.beginning_of_workday = "8:30 am"
43
86
  # someplace in the initializers of your application.
44
- threadsafe_cattr_accessor :beginning_of_workday
87
+ threadsafe_cattr_reader :beginning_of_workday
45
88
 
46
89
  # You can set this yourself, either by the load method below, or
47
90
  # by saying
48
91
  # BusinessTime::Config.end_of_workday = "5:30 pm"
49
92
  # someplace in the initializers of your application.
50
- threadsafe_cattr_accessor :end_of_workday
93
+ threadsafe_cattr_reader :end_of_workday
51
94
 
52
95
  # You can set this yourself, either by the load method below, or
53
96
  # by saying
@@ -65,18 +108,20 @@ module BusinessTime
65
108
  # and end_of_workday. Keys will be added ad weekdays.
66
109
  # Example:
67
110
  # {:mon => ["9:00","17:00"],:tue => ["9:00","17:00"].....}
68
- threadsafe_cattr_accessor :work_hours
111
+ threadsafe_cattr_reader :work_hours
69
112
 
70
113
  # total work hours for a day. Never set, always calculated.
71
114
  threadsafe_cattr_accessor :work_hours_total
72
115
 
73
116
  threadsafe_cattr_accessor :_weekdays # internal
74
117
 
118
+ threadsafe_cattr_accessor :fiscal_month_offset
119
+
75
120
  class << self
76
121
  def end_of_workday(day=nil)
77
122
  if day
78
123
  wday = work_hours[int_to_wday(day.wday)]
79
- wday ? (wday.last =~ /^0{1,2}\:0{1,2}$/ ? "23:59:59" : wday.last) : config[:end_of_workday]
124
+ wday ? (wday.last == ParsedTime.new(0, 0) ? ParsedTime.new(23, 59, 59) : wday.last) : config[:end_of_workday]
80
125
  else
81
126
  config[:end_of_workday]
82
127
  end
@@ -99,10 +144,11 @@ module BusinessTime
99
144
  def weekdays
100
145
  return _weekdays unless _weekdays.nil?
101
146
 
102
- self._weekdays = (!work_hours.empty? ? work_hours.keys : work_week).each_with_object([]) do |day_name, days|
103
- day_num = wday_to_int(day_name)
104
- days << day_num unless day_num.nil?
105
- end
147
+ days = (!work_hours.empty? ? work_hours.keys : work_week).map do |day_name|
148
+ wday_to_int(day_name)
149
+ end.compact
150
+
151
+ self._weekdays = SortedSet.new(days)
106
152
  end
107
153
 
108
154
  # loads the config data from a yaml file written as:
@@ -131,11 +177,11 @@ module BusinessTime
131
177
  end
132
178
 
133
179
  def with(config)
134
- old = config().dup
180
+ local_config_stack.push(config().dup)
135
181
  config.each { |k,v| send("#{k}=", v) } # calculations are done on setting
136
182
  yield
137
183
  ensure
138
- self.config = old
184
+ local_config_stack.pop
139
185
  end
140
186
 
141
187
  def default_config
@@ -154,6 +200,7 @@ module BusinessTime
154
200
  end
155
201
 
156
202
  def reset
203
+ local_config_stack.clear
157
204
  self.config = default_config
158
205
  end
159
206
 
@@ -2,11 +2,60 @@
2
2
  class Date
3
3
  include BusinessTime::TimeExtensions
4
4
 
5
- def business_days_until(to_date)
6
- business_dates_until(to_date).size
5
+ def business_days_until(to_date, inclusive = false)
6
+ business_dates_until(to_date, inclusive).size
7
7
  end
8
8
 
9
- def business_dates_until(to_date)
10
- (self...to_date).select { |day| day.workday? }
9
+ def business_dates_until(to_date, inclusive = false)
10
+ if inclusive
11
+ (self..to_date).select(&:workday?)
12
+ else
13
+ (self...to_date).select(&:workday?)
14
+ end
11
15
  end
12
- end
16
+
17
+ # Adapted from:
18
+ # https://github.com/activewarehouse/activewarehouse/blob/master/lib/active_warehouse/core_ext/time/calculations.rb
19
+
20
+ def week
21
+ cyw = ((yday - 1) / 7) + 1
22
+ cyw = 52 if cyw == 53
23
+ cyw
24
+ end
25
+
26
+ def quarter
27
+ ((month - 1) / 3) + 1
28
+ end
29
+
30
+ def fiscal_month_offset
31
+ BusinessTime::Config.fiscal_month_offset
32
+ end
33
+
34
+ def fiscal_year_week
35
+ fyw = ((fiscal_year_yday - 1) / 7) + 1
36
+ fyw = 52 if fyw == 53
37
+ fyw
38
+ end
39
+
40
+ def fiscal_year_month
41
+ shifted_month = month - (fiscal_month_offset - 1)
42
+ shifted_month += 12 if shifted_month <= 0
43
+ shifted_month
44
+ end
45
+
46
+ def fiscal_year_quarter
47
+ ((fiscal_year_month - 1) / 3) + 1
48
+ end
49
+
50
+ def fiscal_year
51
+ month >= fiscal_month_offset ? year + 1 : year
52
+ end
53
+
54
+ def fiscal_year_yday
55
+ offset_days = 0
56
+ 1.upto(fiscal_month_offset - 1) { |m| offset_days += ::Time.days_in_month(m, year) }
57
+ shifted_year_day = yday - offset_days
58
+ shifted_year_day += 365 if shifted_year_day <= 0
59
+ shifted_year_day
60
+ end
61
+ end
@@ -1,9 +1,9 @@
1
- # hook into fixnum so we can say things like:
1
+ # hook into Integer so we can say things like:
2
2
  # 5.business_hours.from_now
3
3
  # 7.business_days.ago
4
4
  # 3.business_days.after(some_date)
5
5
  # 4.business_hours.before(some_date_time)
6
- class Fixnum
6
+ class Integer
7
7
  def business_hours
8
8
  BusinessTime::BusinessHours.new(self)
9
9
  end
@@ -0,0 +1,34 @@
1
+ module BusinessTime
2
+ class ParsedTime
3
+ include Comparable
4
+
5
+ attr_reader :hour, :min, :sec
6
+
7
+ def initialize(hour, min = 0, sec = 0)
8
+ @hour = hour
9
+ @min = min
10
+ @sec = sec
11
+ end
12
+
13
+ def self.parse(time_or_string)
14
+ if time_or_string.is_a?(String)
15
+ time = Time.parse(time_or_string)
16
+ else
17
+ time = time_or_string
18
+ end
19
+ new(time.hour, time.min, time.sec)
20
+ end
21
+
22
+ def to_s
23
+ "#{hour}:#{min}:#{sec}"
24
+ end
25
+
26
+ def -(other)
27
+ (hour - other.hour) * 3600 + (min - other.min) * 60 + sec - other.sec
28
+ end
29
+
30
+ def <=>(other)
31
+ [hour, min, sec] <=> [other.hour, other.min, other.sec]
32
+ end
33
+ end
34
+ end
@@ -17,7 +17,7 @@ module BusinessTime
17
17
  # Note: It pretends that this day is a workday whether or not it really is a
18
18
  # workday.
19
19
  def end_of_workday(day)
20
- end_of_workday = Time.parse(BusinessTime::Config.end_of_workday(day))
20
+ end_of_workday = BusinessTime::Config.end_of_workday(day)
21
21
  change_business_time(day,end_of_workday.hour,end_of_workday.min,end_of_workday.sec)
22
22
  end
23
23
 
@@ -26,7 +26,7 @@ module BusinessTime
26
26
  # Note: It pretends that this day is a workday whether or not it really is a
27
27
  # workday.
28
28
  def beginning_of_workday(day)
29
- beginning_of_workday = Time.parse(BusinessTime::Config.beginning_of_workday(day))
29
+ beginning_of_workday = BusinessTime::Config.beginning_of_workday(day)
30
30
  change_business_time(day,beginning_of_workday.hour,beginning_of_workday.min,beginning_of_workday.sec)
31
31
  end
32
32
 
@@ -98,6 +98,16 @@ module BusinessTime
98
98
  prev_business_time
99
99
  end
100
100
 
101
+ # Returns the time parameter itself if it is a business day
102
+ # or else returns the previous business day
103
+ def previous_business_day(time)
104
+ while !time.workday?
105
+ time = time - 1.day
106
+ end
107
+
108
+ time
109
+ end
110
+
101
111
  def work_hours_total(day)
102
112
  return 0 unless day.workday?
103
113
 
@@ -106,15 +116,15 @@ module BusinessTime
106
116
  if hours = BusinessTime::Config.work_hours[day]
107
117
  BusinessTime::Config.work_hours_total[day] ||= begin
108
118
  hours_last = hours.last
109
- if hours_last == '00:00'
110
- (Time.parse('23:59') - Time.parse(hours.first)) + 1.minute
119
+ if hours_last == ParsedTime.new(0, 0)
120
+ (ParsedTime.new(23, 59) - hours.first) + 1.minute
111
121
  else
112
- Time.parse(hours_last) - Time.parse(hours.first)
122
+ hours_last - hours.first
113
123
  end
114
124
  end
115
125
  else
116
126
  BusinessTime::Config.work_hours_total[:default] ||= begin
117
- Time.parse(BusinessTime::Config.end_of_workday) - Time.parse(BusinessTime::Config.beginning_of_workday)
127
+ BusinessTime::Config.end_of_workday - BusinessTime::Config.beginning_of_workday
118
128
  end
119
129
  end
120
130
  end
@@ -122,11 +132,7 @@ module BusinessTime
122
132
  private
123
133
 
124
134
  def change_business_time time, hour, min=0, sec=0
125
- if Time.zone
126
- time.in_time_zone(Time.zone).change(:hour => hour, :min => min, :sec => sec)
127
- else
128
- time.change(:hour => hour, :min => min, :sec => sec)
129
- end
135
+ time.change(:hour => hour, :min => min, :sec => sec)
130
136
  end
131
137
  end
132
138
 
@@ -159,9 +165,34 @@ module BusinessTime
159
165
  first_day + days_in_between + last_day
160
166
  end * direction
161
167
  end
162
-
168
+
163
169
  def during_business_hours?
164
170
  self.workday? && self.to_i.between?(Time.beginning_of_workday(self).to_i, Time.end_of_workday(self).to_i)
165
171
  end
172
+
173
+ def consecutive_workdays
174
+ workday? ? consecutive_days { |date| date.workday? } : []
175
+ end
176
+
177
+ def consecutive_non_working_days
178
+ !workday? ? consecutive_days { |date| !date.workday? } : []
179
+ end
180
+
181
+ private
182
+
183
+ def consecutive_days
184
+ days = []
185
+ date = self + 1.day
186
+ while yield(date)
187
+ days << date
188
+ date += 1.day
189
+ end
190
+ date = self - 1.day
191
+ while yield(date)
192
+ days << date
193
+ date -= 1.day
194
+ end
195
+ (days << self).sort
196
+ end
166
197
  end
167
198
  end
@@ -1,3 +1,3 @@
1
1
  module BusinessTime
2
- VERSION = "0.7.4"
2
+ VERSION = "0.9.3"
3
3
  end
data/lib/business_time.rb CHANGED
@@ -4,10 +4,12 @@ require 'active_support/time'
4
4
  require 'time'
5
5
  require 'yaml'
6
6
 
7
+ require 'business_time/parsed_time'
8
+ require 'business_time/version'
7
9
  require 'business_time/config'
8
10
  require 'business_time/business_hours'
9
11
  require 'business_time/business_days'
10
- require 'business_time/core_ext/fixnum'
12
+ require 'business_time/core_ext/integer'
11
13
 
12
14
  require 'business_time/time_extensions'
13
15
  require 'business_time/core_ext/date'
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.7.4
4
+ version: 0.9.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - bokmann
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-04-12 00:00:00.000000000 Z
11
+ date: 2017-11-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: 3.1.0
19
+ version: 3.2.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: 3.1.0
26
+ version: 3.2.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: tzinfo
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -96,7 +96,7 @@ dependencies:
96
96
  version: '0'
97
97
  description: Have you ever wanted to do things like "6.business_days.from_now" and
98
98
  have weekends and holidays taken into account? Now you can.
99
- email: dbock@codesherpas.com
99
+ email: dbock@javaguy.org
100
100
  executables: []
101
101
  extensions: []
102
102
  extra_rdoc_files: []
@@ -109,8 +109,9 @@ files:
109
109
  - lib/business_time/config.rb
110
110
  - lib/business_time/core_ext/active_support/time_with_zone.rb
111
111
  - lib/business_time/core_ext/date.rb
112
- - lib/business_time/core_ext/fixnum.rb
112
+ - lib/business_time/core_ext/integer.rb
113
113
  - lib/business_time/core_ext/time.rb
114
+ - lib/business_time/parsed_time.rb
114
115
  - lib/business_time/time_extensions.rb
115
116
  - lib/business_time/version.rb
116
117
  - lib/generators/business_time/config_generator.rb
@@ -137,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
137
138
  version: '0'
138
139
  requirements: []
139
140
  rubyforge_project:
140
- rubygems_version: 2.2.2
141
+ rubygems_version: 2.6.13
141
142
  signing_key:
142
143
  specification_version: 4
143
144
  summary: Support for doing time math in business hours and days