alphasights-business_time 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
@@ -0,0 +1,21 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## VIM
14
+ *.swp
15
+
16
+ ## PROJECT::GENERAL
17
+ coverage
18
+ rdoc
19
+ pkg
20
+
21
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 bokmann
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,152 @@
1
+ = business_time
2
+
3
+ ActiveSupport gives us some great helpers so we can do things like:
4
+
5
+ 5.days.ago
6
+
7
+ and
8
+
9
+ 8.hours.from_now
10
+
11
+ as well as helpers to do that from any provided date or time.
12
+
13
+ I needed this, but taking into account business hours/days and holidays.
14
+
15
+ == Usage
16
+ * install the gem
17
+
18
+ gem install business_time
19
+
20
+ or
21
+
22
+ sudo gem install business_time
23
+
24
+ if you require sudo to install gems
25
+
26
+ * open up your console
27
+
28
+ # if in irb, add these lines:
29
+
30
+ require 'rubygems'
31
+ require 'active_support'
32
+ require 'business_time'
33
+
34
+ # try these examples, using the current time:
35
+
36
+ 1.business_hour.from_now
37
+ 4.business_hours.from_now
38
+ 8.business_hours.from_now
39
+
40
+ 1.business_hour.ago
41
+ 4.business_hours.ago
42
+ 8.business_hours.ago
43
+
44
+ 1.business_day.from_now
45
+ 4.business_days.from_now
46
+ 8.business_days.from_now
47
+
48
+ 1.business_day.ago
49
+ 4.business_days.ago
50
+ 8.business_days.ago
51
+
52
+ # and we can do it from any Date or Time object.
53
+ my_birthday = Date.parse("August 4th, 1969")
54
+ 8.business_days.after(my_birthday)
55
+ 8.business_days.before(my_birthday)
56
+
57
+ my_birthday = Time.parse("August 4th, 1969, 8:32 am")
58
+ 8.business_days.after(my_birthday)
59
+ 8.business_days.before(my_birthday)
60
+
61
+
62
+ # We can adjust the start and end time of our business hours
63
+ BusinessTime::Config.beginning_of_workday = "8:30 am"
64
+ BusinessTime::Config.end_of_workday = "5:30 pm"
65
+
66
+ # and we can add holidays that don't count as business days
67
+ # July 5 in 2010 is a monday that the U.S. takes off because our independence day falls on that Sunday.
68
+ three_day_weekend = Date.parse("July 5th, 2010")
69
+ BusinessTime::Config.holidays << three_day_weekend
70
+ friday_afternoon = Time.parse("July 2nd, 2010, 4:50 pm")
71
+ tuesday_morning = 1.business_hour.after(friday_afternoon)
72
+
73
+ == Usage in Rails
74
+ The code above should work on a rails console without any issue. You will want to add a line something like:
75
+
76
+ config.gem "business_time"
77
+
78
+ to your environment.rb file. Or if you're using bundler, add this line to your Gemfile:
79
+
80
+ gem "business_time"
81
+
82
+ This gem also includes a generator so you can bootstrap some stuff in your environment:
83
+
84
+ ./script/generate business_time_config
85
+
86
+ Or in Rails 3:
87
+
88
+ script/rails generate business_time:config
89
+
90
+ The generator will add a /config/business_time.yml and a /config/initializers/business_time.rb file that will cause the start of business day, the end of business day, and your holidays to be loaded from the yaml file. You might want to programatically load your holidays from a database table, but you will want to pay attention to how the initializer works - you will want to make sure that the initializer sets stuff up appropriately so rails instances on mongrels or passenger will have the appropriate data as they come up and down.
91
+
92
+ == Outside of Rails
93
+ This code does depend on ActiveSupport, but nothing else within rails. Even then, it would be pretty easy to break that dependency as well (but would add some code bloat and remove some clarity). Feel free to use it on any ruby project you'd like!
94
+
95
+ == Timezone support
96
+ This gem strives to be timezone-agnostic. Due to some complications in the handling of timezones in the built in Time class, and some complexities (bugs?) in the timeWithZone class, this was harder than expected... but here's the idea:
97
+
98
+ * When you configure the gem with something like 9:00am as the start time,
99
+ this is agnostic of time zone.
100
+ * When you are dealing with a Time or TimeWithZone class, the timezone is
101
+ preserved and the beginning and end of times for the business day are
102
+ referenced in that time zone.
103
+
104
+ This can lead to some wierd looking effects if, say, you are in the Eastern time zone but doing everything in UTC times... Your business day will appear to start and end at 9:00 and 5:00 UTC. If this seems perplexing to you, I can almost guarantee you are in over your head with timezones in other ways too, this is just the first place you encountered it. 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. I'm hoping Arild and I write some good blog entries on the subject at http://blog.codesherpas.com.
105
+
106
+ == Contributors
107
+ * David Bock http://github.com/bokmann
108
+ * Enrico Bianco http://github.com/enricob
109
+ * Arild Shirazi http://github.com/ashirazi
110
+
111
+ (Special thanks for Arild on the complexities of dealing with TimeWithZone)
112
+
113
+ == Note on Patches/Pull Requests
114
+
115
+ * Fork the project.
116
+ * Make your feature addition or bug fix.
117
+ * Add tests for it. This is important so I don't break it in a
118
+ future version unintentionally.
119
+ * Commit, do not mess with rakefile, version, or history.
120
+ (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)
121
+ * Send me a pull request. Bonus points for topic branches.
122
+
123
+ == TODO
124
+
125
+ * if it doesn't pollute the logic too much, I'd like to vary the days counted as 'business days'. Bakers often
126
+ don't work on Mondays, for instance. I'd do it in something like this:
127
+
128
+ BusinessTime::Config.work_week = [:tue, :wed, :thur, :fri, :sat]
129
+
130
+ * Arild has pointed out that there may be some logical inconsistencies
131
+ regaring the beginning_of_workday and end_of workday times not actually
132
+ being considered inside of the workday. I'd like to make sure that they
133
+ work as if the beginning_of_workday is included and the end_of_workday is
134
+ not included, just like the '...' range operator in Ruby.
135
+
136
+ == NOT TODO
137
+
138
+ * I spent way too much time in my previous java-programmer life building frameworks that worshipped complexity,
139
+ always trying to give the developer-user ultimate flexibility at the expense of the 'surface area' of the api.
140
+ Never again - I will sooner limit functionality to 80% so that something stays usable and let people fork.
141
+ * While there have been requests to add 'business minutes' and even 'business seconds' to this gem, I won't
142
+ entertain a pullup request with such things. If you find it useful, great. Most users won't, and they don't
143
+ need the baggage.
144
+ * I'm torn on the inclusion of some kind of business_duration_between two Times, for the following reasons:
145
+ * As a duration, it will be in seconds. This would naturally be business-seconds, which I trimmed out above.
146
+ * The logic is complex and error prone.
147
+ * There is no logical place to put it, as most of this gem mirrors the Timehelpers in ActiveSupport.
148
+ As a result I'm unlikely to add it unless I have an epiphany or someone wows me with an awesome pullup request.
149
+
150
+ == Copyright
151
+
152
+ Copyright (c) 2010 bokmann. See LICENSE for details.
@@ -0,0 +1,55 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "alphasights-business_time"
8
+ gem.summary = %Q{Support for doing time math in business hours and days}
9
+ gem.description = %Q{Have you ever wanted to do things like "6.business_days.from_now" and have weekends and holidays taken into account? Now you can.}
10
+ gem.email = "dbock@codesherpas.com"
11
+ gem.homepage = "http://github.com/slawosz/business_time/tree/hours_left"
12
+ gem.authors = ["bokmann"]
13
+ gem.add_development_dependency "shoulda", ">= 0"
14
+ gem.add_dependency('activesupport','>= 2.0.0')
15
+ gem.files += FileList['lib/generators/**/*.rb']
16
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
17
+ end
18
+ Jeweler::GemcutterTasks.new
19
+ rescue LoadError
20
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
21
+ end
22
+
23
+ require 'rake/testtask'
24
+ Rake::TestTask.new(:test) do |test|
25
+ test.libs << 'lib' << 'test'
26
+ test.pattern = 'test/**/test_*.rb'
27
+ test.verbose = true
28
+ end
29
+
30
+ begin
31
+ require 'rcov/rcovtask'
32
+ Rcov::RcovTask.new do |test|
33
+ test.libs << 'test'
34
+ test.pattern = 'test/**/test_*.rb'
35
+ test.verbose = true
36
+ end
37
+ rescue LoadError
38
+ task :rcov do
39
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
40
+ end
41
+ end
42
+
43
+ task :test => :check_dependencies
44
+
45
+ task :default => :test
46
+
47
+ require 'rake/rdoctask'
48
+ Rake::RDocTask.new do |rdoc|
49
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
50
+
51
+ rdoc.rdoc_dir = 'rdoc'
52
+ rdoc.title = "business_time #{version}"
53
+ rdoc.rdoc_files.include('README*')
54
+ rdoc.rdoc_files.include('lib/**/*.rb')
55
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.0
@@ -0,0 +1,6 @@
1
+ require "business_time/config"
2
+ require "business_time/business_hours"
3
+ require "business_time/business_days"
4
+ require "extensions/date"
5
+ require "extensions/time"
6
+ require "extensions/fixnum"
@@ -0,0 +1,36 @@
1
+ module BusinessTime
2
+
3
+ class BusinessDays
4
+ def initialize(days)
5
+ @days = days
6
+ end
7
+
8
+ def after(time = Time.now)
9
+ time = Time.zone ? Time.zone.parse(time.to_s) : Time.parse(time.to_s)
10
+ @days.times do
11
+ begin
12
+ time = time + 1.day
13
+ end until Time.workday?(time)
14
+ end
15
+ time
16
+ end
17
+
18
+ alias_method :from_now, :after
19
+ alias_method :since, :after
20
+
21
+ def before(time = Time.now)
22
+ time = Time.zone ? Time.zone.parse(time.to_s) : Time.parse(time.to_s)
23
+ @days.times do
24
+ begin
25
+ time = time - 1.day
26
+ end until Time.workday?(time)
27
+ end
28
+ time
29
+ end
30
+
31
+ alias_method :ago, :before
32
+ alias_method :until, :before
33
+
34
+ end
35
+
36
+ end
@@ -0,0 +1,69 @@
1
+ module BusinessTime
2
+
3
+ class BusinessHours
4
+ def initialize(hours)
5
+ @hours = hours
6
+ end
7
+
8
+ def ago
9
+ Time.zone ? before(Time.zone.now) : before(Time.now)
10
+ end
11
+
12
+ def from_now
13
+ Time.zone ? after(Time.zone.now) : after(Time.now)
14
+ end
15
+
16
+ def after(time)
17
+ after_time = Time.roll_forward(time)
18
+ # Step through the hours, skipping over non-business hours
19
+ @hours.times do
20
+ after_time = after_time + 1.hour
21
+
22
+ # Ignore hours before opening and after closing
23
+ if (after_time > Time.end_of_workday(after_time))
24
+ after_time = after_time + off_hours
25
+ end
26
+
27
+ # Ignore weekends and holidays
28
+ while !Time.workday?(after_time)
29
+ after_time = after_time + 1.day
30
+ end
31
+ end
32
+ after_time
33
+ end
34
+
35
+ def before(time)
36
+ before_time = Time.roll_forward(time)
37
+ # Step through the hours, skipping over non-business hours
38
+ @hours.times do
39
+ before_time = before_time - 1.hour
40
+
41
+ # Ignore hours before opening and after closing
42
+ if (before_time < Time.beginning_of_workday(before_time))
43
+ before_time = before_time - off_hours
44
+ end
45
+
46
+ # Ignore weekends and holidays
47
+ while !Time.workday?(before_time)
48
+ before_time = before_time - 1.day
49
+ end
50
+ end
51
+ before_time
52
+ end
53
+
54
+ private
55
+
56
+ def off_hours
57
+ return @gap if @gap
58
+ if Time.zone
59
+ gap_end = Time.zone.parse(BusinessTime::Config.beginning_of_workday)
60
+ gap_begin = (Time.zone.parse(BusinessTime::Config.end_of_workday)-1.day)
61
+ else
62
+ gap_end = Time.parse(BusinessTime::Config.beginning_of_workday)
63
+ gap_begin = (Time.parse(BusinessTime::Config.end_of_workday) - 1.day)
64
+ end
65
+ @gap = gap_end - gap_begin
66
+ end
67
+ end
68
+
69
+ end
@@ -0,0 +1,59 @@
1
+ module BusinessTime
2
+
3
+ # controls the behavior of this gem. You can change
4
+ # the beginning_of_workday, end_of_workday, and the list of holidays
5
+ # manually, or with a yaml file and the load method.
6
+ class Config
7
+ class << self
8
+ # You can set this yourself, either by the load method below, or
9
+ # by saying
10
+ # BusinessTime::Config.beginning_of_workday = "8:30 am"
11
+ # someplace in the initializers of your application.
12
+ attr_accessor :beginning_of_workday
13
+
14
+ # You can set this yourself, either by the load method below, or
15
+ # by saying
16
+ # BusinessTime::Config.end_of_workday = "5:30 pm"
17
+ # someplace in the initializers of your application.
18
+ attr_accessor :end_of_workday
19
+
20
+ # You can set this yourself, either by the load method below, or
21
+ # by saying
22
+ # BusinessTime::Config.holidays << my_holiday_date_object
23
+ # someplace in the initializers of your application.
24
+ attr_accessor :holidays
25
+
26
+ end
27
+
28
+ def self.reset
29
+ self.holidays = []
30
+ self.beginning_of_workday = "9:00 am"
31
+ self.end_of_workday = "5:00 pm"
32
+ end
33
+
34
+ # loads the config data from a yaml file written as:
35
+ #
36
+ # business_time:
37
+ # beginning_od_workday: 8:30 am
38
+ # end_of_workday: 5:30 pm
39
+ # holidays:
40
+ # - Jan 1st, 2010
41
+ # - July 4th, 2010
42
+ # - Dec 25th, 2010
43
+ def self.load(filename)
44
+ self.reset
45
+ data = YAML::load(File.open(filename))
46
+ self.beginning_of_workday = data["business_time"]["beginning_of_workday"]
47
+ self.end_of_workday = data["business_time"]["end_of_workday"]
48
+ data["business_time"]["holidays"].each do |holiday|
49
+ self.holidays <<
50
+ Time.zone ? Time.zone.parse(holiday) : Time.parse(holiday)
51
+ end
52
+
53
+ end
54
+
55
+ #reset the first time we are loaded.
56
+ self.reset
57
+ end
58
+
59
+ end
@@ -0,0 +1,10 @@
1
+ # Add workday and weekday concepts to the Date class
2
+ class Date
3
+ def workday?
4
+ self.weekday? && !BusinessTime::Config.holidays.include?(self)
5
+ end
6
+
7
+ def weekday?
8
+ [1,2,3,4,5].include? self.wday
9
+ end
10
+ end