opening_hours 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in opening_hours.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Albert Hild
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,55 @@
1
+ # OpeningHours
2
+
3
+ Let you apply opening hours, closed periods and holidays to all kind of business including timezone support. Heavily based on the code from pleax (RPCFN#10: Business Hours competition winner) - thx a lot: [Source](https://gist.github.com/pleax/e9c0da1a6e92dd12cbc7)
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'opening_hours'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install opening_hours
18
+
19
+ ## Usage
20
+
21
+ Init new hours object:
22
+ ```ruby
23
+ hours = OpeningHours.new("9:00 AM", "3:00 PM", "Berlin")
24
+ ```
25
+
26
+ Methods to set the hours, closed times or holidays:
27
+ ```ruby
28
+ hours.update :fri, "10:00 AM", "5:00 PM"
29
+ hours.update "Dec 24, 2010", "8:00 AM", "1:00 PM"
30
+ hours.closed :sun, :wed, "Dec 25, 2010"
31
+ ```
32
+
33
+ Calculate the deadline (next available working hour)
34
+ ```ruby
35
+ # offset and time
36
+ hours.calculate_deadline(4*60*60, "Dec 23, 2010 8:00 PM")
37
+ => "Fri, 24 Dec 2010 12:00:00 +0100"
38
+
39
+ # now with timezone
40
+ hours.calculate_deadline(0, "Dec 23, 2010 9:00 AM -0400")
41
+ => "Thu, 23 Dec 2010 14:00:00 +0100"
42
+ ```
43
+
44
+ Check if business is open right now:
45
+ ```ruby
46
+ hours.now_open?
47
+ ```
48
+
49
+ ## Contributing
50
+
51
+ 1. Fork it
52
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
53
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
54
+ 4. Push to the branch (`git push origin my-new-feature`)
55
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,3 @@
1
+ class OpeningHours
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,133 @@
1
+ require 'time'
2
+ require 'date'
3
+ require 'active_support/time'
4
+ require "opening_hours/version"
5
+
6
+ class OpeningHours
7
+
8
+ class OpenHours
9
+
10
+ attr_reader :open, :close
11
+
12
+ def initialize(open, close)
13
+ @open, @close = open, close
14
+ end
15
+
16
+ def duration
17
+ @duration ||= @open < @close ? @close - @open : 0
18
+ end
19
+
20
+ CLOSED = new(0, 0)
21
+
22
+ def self.parse(open, close)
23
+ open = Time.parse(open)
24
+ close = Time.parse(close)
25
+
26
+ open = TimeUtils::seconds_from_midnight(open)
27
+ close = TimeUtils::seconds_from_midnight(close)
28
+
29
+ new(open, close)
30
+ end
31
+
32
+ def offset(seconds)
33
+ self.class.new([@open, seconds].max, @close)
34
+ end
35
+
36
+ end
37
+
38
+ module TimeUtils
39
+
40
+ class << self
41
+
42
+ def seconds_from_midnight(time)
43
+ time.hour*60*60 + time.min*60 + time.sec
44
+ end
45
+
46
+ def time_from_midnight(seconds)
47
+ hours, seconds = seconds.divmod(60 * 60)
48
+ minutes, seconds = seconds.divmod(60)
49
+ [hours, minutes, seconds]
50
+ end
51
+
52
+ end
53
+
54
+ end
55
+
56
+ # makes it possible to access each day like: b.week[:sun].open, or iterate through all weekdays
57
+ attr_reader :week
58
+
59
+ WEEK_DAYS = Time::RFC2822_DAY_NAME.map { |m| m.downcase.to_sym }
60
+
61
+ def initialize(start_time, end_time, time_zone = Time.now.gmt_offset)
62
+ open_hours = OpenHours.parse(start_time, end_time)
63
+
64
+ @time_zone = time_zone
65
+
66
+ @week = {}
67
+ WEEK_DAYS.each do |day|
68
+ @week[day] = open_hours
69
+ end
70
+
71
+ @specific_days = {}
72
+ end
73
+
74
+ def update(day, start_time, end_time, time_zone = Time.now.zone)
75
+
76
+ @time_zone = time_zone
77
+
78
+ set_open_hours day, OpenHours.parse(start_time, end_time)
79
+ end
80
+
81
+ def closed(*days)
82
+ days.each do |day|
83
+ set_open_hours day, OpenHours::CLOSED
84
+ end
85
+ end
86
+
87
+ def now_open?
88
+ calculate_deadline(0, Time.now.to_s) == Time.now.to_formatted_s(:rfc822)
89
+ end
90
+
91
+ def calculate_deadline(job_duration, start_date_time)
92
+ Time.zone = @time_zone
93
+ start_date_time = Time.zone.parse(start_date_time)
94
+
95
+ today = Date.civil(start_date_time.year, start_date_time.month, start_date_time.day)
96
+ open_hours = get_open_hours(today).offset(TimeUtils::seconds_from_midnight(start_date_time))
97
+
98
+ # here is possible to use strict greater operator if you want to stop on edge of previous business day.
99
+ # see "BusinessHours schedule without exceptions should flip the edge" spec
100
+ while job_duration >= open_hours.duration
101
+ job_duration -= open_hours.duration
102
+
103
+ today = today.next
104
+ open_hours = get_open_hours(today)
105
+ end
106
+
107
+ Time.zone.local(today.year, today.month, today.day, *TimeUtils::time_from_midnight(open_hours.open + job_duration)).to_formatted_s(:rfc822)
108
+ end
109
+
110
+ private
111
+
112
+ def get_open_hours(date)
113
+ @specific_days[date] || @week[WEEK_DAYS[date.wday]]
114
+ end
115
+
116
+ def set_open_hours(day, open_hours)
117
+ case day
118
+ when Symbol
119
+ @week[day] = open_hours
120
+ when String
121
+ @specific_days[Date.parse(day)] = open_hours
122
+ end
123
+ end
124
+
125
+ end
126
+
127
+ class Time
128
+ class << self
129
+ def parse_rfc822(date, now=self.now)
130
+ parse(date, now).to_formatted_s(:rfc822)
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'opening_hours/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "opening_hours"
8
+ gem.version = OpeningHours::VERSION
9
+ gem.authors = ["Albert Hild"]
10
+ gem.email = ["mail@albert-hild.de"]
11
+ gem.description = %q{Opening hours for all kind of businesses}
12
+ gem.summary = %q{Let you apply opening hours, closed periods and holidays to all kind of business including timezone support. Heavily based on: https://gist.github.com/pleax/e9c0da1a6e92dd12cbc7 }
13
+ gem.homepage = "https://github.com/alberthild/opening_hours"
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ["lib"]
19
+
20
+ gem.add_development_dependency "rspec"
21
+ gem.add_runtime_dependency "activesupport"
22
+ end
@@ -0,0 +1,186 @@
1
+ require 'opening_hours'
2
+ require 'active_support/time'
3
+
4
+ describe OpeningHours do
5
+
6
+ subject { OpeningHours.new("9:00 AM", "3:00 PM") }
7
+
8
+ it { should respond_to(:update) }
9
+
10
+ it { should respond_to(:closed) }
11
+
12
+ it { should respond_to(:calculate_deadline) }
13
+
14
+ it { should respond_to(:week) }
15
+
16
+ context "schedule without exceptions" do
17
+ before { @hours = OpeningHours.new("9:00 AM", "3:00 PM") }
18
+
19
+ it "should handle start time during open hours" do
20
+ @hours.calculate_deadline(1*60*60, "Jun 7, 2010 9:10 AM").should == Time.parse_rfc822("Jun 7, 2010 10:10 AM")
21
+ end
22
+
23
+ it "should handle start time before open hours" do
24
+ @hours.calculate_deadline(2*60*60, "Jun 7, 2010 8:45 AM").should == Time.parse_rfc822("Jun 7, 2010 11:00 AM")
25
+ end
26
+
27
+ it "should handle start time after open hours" do
28
+ @hours.calculate_deadline(2*60*60, "Jun 7, 2010 10:45 PM").should == Time.parse_rfc822("Jun 8, 2010 11:00 AM")
29
+ end
30
+
31
+ it "should finish job next day if not enough time left" do
32
+ @hours.calculate_deadline(2*60*60, "Jun 7, 2010 2:45 PM").should == Time.parse_rfc822("Jun 8, 2010 10:45 AM")
33
+ end
34
+
35
+ it "should process huge job for several days" do
36
+ @hours.calculate_deadline(20*60*60, "Jun 7, 2010 10:45 AM").should == Time.parse_rfc822("Jun 10, 2010 12:45 PM")
37
+ end
38
+
39
+ it "should flip the edge" do
40
+ @hours.calculate_deadline(6*60*60, "Jun 7, 2010 9:00 AM").should == Time.parse_rfc822("Jun 8, 2010 9:00 AM")
41
+ end
42
+
43
+ # this is also possible, but I prefer previous variant
44
+ #
45
+ # it "should NOT flip the edge" do
46
+ # @hours.calculate_deadline(6*60*60, "Jun 7, 2010 9:00 AM").should == Time.parse("Jun 7, 2010 3:00 PM")
47
+ # end
48
+
49
+ context "on dst changes" do
50
+
51
+ it "should respect changing to dst" do
52
+ @hours.calculate_deadline(8*60*60, "March 27, 2010 2:00 PM").should == Time.parse_rfc822("March 29, 2010 10:00 AM")
53
+ end
54
+
55
+ it "should respect changing to dst" do
56
+ @hours.calculate_deadline(2*60*60, "March 27, 2010 2:00 PM").should == Time.parse_rfc822("March 28, 2010 10:00 AM")
57
+ end
58
+
59
+ it "should respect changing from dst" do
60
+ @hours.calculate_deadline(8*60*60, "October 31, 2010 2:00 PM").should == Time.parse_rfc822("November 2, 2010 10:00 AM")
61
+ end
62
+
63
+ it "should respect changing from dst" do
64
+ @hours.calculate_deadline(2*60*60, "October 31, 2010 2:00 PM").should == Time.parse_rfc822("November 1, 2010 10:00 AM")
65
+ end
66
+
67
+ end
68
+
69
+ end
70
+
71
+ context "schedule with closed weekdays" do
72
+ before do
73
+ @hours = OpeningHours.new("9:00 AM", "3:00 PM")
74
+ @hours.closed :sun, :wed
75
+ end
76
+
77
+ it "should skip closed days" do
78
+ @hours.calculate_deadline(2*60*60, "Jun 5, 2010 2:45 PM").should == Time.parse_rfc822("Jun 7, 2010 10:45 AM")
79
+ end
80
+
81
+ it "should skip closed days even if work scheduled to closed day" do
82
+ @hours.calculate_deadline(2*60*60, "Jun 6, 2010 11:45 AM").should == Time.parse_rfc822("Jun 7, 2010 11:00 AM")
83
+ end
84
+ end
85
+
86
+ context "schedule with closed specific days" do
87
+
88
+ before do
89
+ @hours = OpeningHours.new("9:00 AM", "3:00 PM")
90
+ @hours.closed "Dec 25, 2010"
91
+ end
92
+
93
+ it "should skip closed days" do
94
+ @hours.calculate_deadline(2*60*60, "Dec 24, 2010 2:45 PM").should == Time.parse_rfc822("Dec 26, 2010 10:45 AM")
95
+ end
96
+
97
+ it "should skip closed days even if work scheduled to closed day" do
98
+ @hours.calculate_deadline(2*60*60, "Dec 25, 2010 11:45 AM").should == Time.parse_rfc822("Dec 26, 2010 11:00 AM")
99
+ end
100
+
101
+ end
102
+
103
+ context "schedule with both closed weekdays and specific days" do
104
+
105
+ before do
106
+ @hours = OpeningHours.new("9:00 AM", "3:00 PM")
107
+ @hours.closed :sun, :wed, "Dec 25, 2010"
108
+ end
109
+
110
+ it "should skip closed days" do
111
+ @hours.calculate_deadline(2*60*60, "Dec 24, 2010 2:45 PM").should == Time.parse_rfc822("Dec 27, 2010 10:45 AM")
112
+ end
113
+ end
114
+
115
+ context "schedule with different open hours in weekdays" do
116
+ before do
117
+ @hours = OpeningHours.new("9:00 AM", "3:00 PM")
118
+ @hours.update :fri, "10:00 AM", "5:00 PM"
119
+ end
120
+
121
+ it "should spend open hours" do
122
+ @hours.calculate_deadline(14*60*60, "Jun 3, 2010 9:00 AM").should == Time.parse_rfc822("Jun 5, 2010 10:00 AM")
123
+ end
124
+
125
+ end
126
+
127
+ context "schedule with different open hours in specific days" do
128
+ before do
129
+ @hours = OpeningHours.new("9:00 AM", "3:00 PM")
130
+ @hours.update "Dec 24, 2010", "8:00 AM", "1:00 PM"
131
+ end
132
+
133
+ it "should spend open hours" do
134
+ @hours.calculate_deadline(12*60*60, "Dec 23, 2010 9:00 AM").should == Time.parse_rfc822("Dec 25, 2010 10:00 AM")
135
+ end
136
+
137
+ it "should spend open hours if started at the day" do
138
+ @hours.calculate_deadline(6*60*60, "Dec 24, 2010 12:00 PM").should == Time.parse_rfc822("Dec 25, 2010 2:00 PM")
139
+ end
140
+
141
+ end
142
+
143
+ context "original tests" do
144
+
145
+ before do
146
+ @hours = OpeningHours.new("9:00 AM", "3:00 PM")
147
+ @hours.update :fri, "10:00 AM", "5:00 PM"
148
+ @hours.update "Dec 24, 2010", "8:00 AM", "1:00 PM"
149
+ @hours.closed :sun, :wed, "Dec 25, 2010"
150
+ end
151
+
152
+ it "should pass test #1" do
153
+ @hours.calculate_deadline(2*60*60, "Jun 7, 2010 9:10 AM").should == Time.parse_rfc822("Mon Jun 07 11:10:00 2010")
154
+ end
155
+
156
+ it "should pass test #2" do
157
+ @hours.calculate_deadline(15*60, "Jun 8, 2010 2:48 PM").should == Time.parse_rfc822("Thu Jun 10 09:03:00 2010")
158
+ end
159
+
160
+ it "should pass test #3" do
161
+ @hours.calculate_deadline(7*60*60, "Dec 24, 2010 6:45 AM").should == Time.parse_rfc822("Mon Dec 27 11:00:00 2010")
162
+ end
163
+
164
+ end
165
+
166
+ context "timezone checks" do
167
+ before do
168
+ Time.zone = "Moscow"
169
+ @hours = OpeningHours.new("9:00 AM", "3:00 PM", "Moscow")
170
+ end
171
+
172
+ it "should spend open hours in the right time zone" do
173
+ @hours.calculate_deadline(2*60*60, "Dec 23, 2010 9:00 AM").should == Time.zone.parse("Dec 23, 2010 11:00 AM").to_formatted_s(:rfc822)
174
+ end
175
+
176
+ it "should spend open hours in the right time zone" do
177
+ @hours.calculate_deadline(4*60*60, "Dec 23, 2010 8:00 PM -0900").should == Time.zone.parse("Dec 24, 2010 01:00 PM").to_formatted_s(:rfc822)
178
+ end
179
+
180
+ it "should spend open hours in the right time zone" do
181
+ @hours.calculate_deadline(0, "Dec 23, 2010 08:00 PM +0800").should == Time.zone.parse("Dec 24, 2010 9:00 AM").to_formatted_s(:rfc822)
182
+ end
183
+
184
+ end
185
+
186
+ end
metadata ADDED
@@ -0,0 +1,88 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: opening_hours
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Albert Hild
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-03-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rspec
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: activesupport
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: '0'
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: '0'
46
+ description: Opening hours for all kind of businesses
47
+ email:
48
+ - mail@albert-hild.de
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - Gemfile
55
+ - LICENSE.txt
56
+ - README.md
57
+ - Rakefile
58
+ - lib/opening_hours.rb
59
+ - lib/opening_hours/version.rb
60
+ - opening_hours.gemspec
61
+ - spec/opening_hours_spec.rb
62
+ homepage: https://github.com/alberthild/opening_hours
63
+ licenses: []
64
+ post_install_message:
65
+ rdoc_options: []
66
+ require_paths:
67
+ - lib
68
+ required_ruby_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ required_rubygems_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ! '>='
78
+ - !ruby/object:Gem::Version
79
+ version: '0'
80
+ requirements: []
81
+ rubyforge_project:
82
+ rubygems_version: 1.8.24
83
+ signing_key:
84
+ specification_version: 3
85
+ summary: ! 'Let you apply opening hours, closed periods and holidays to all kind of
86
+ business including timezone support. Heavily based on: https://gist.github.com/pleax/e9c0da1a6e92dd12cbc7'
87
+ test_files:
88
+ - spec/opening_hours_spec.rb