workhours 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: efe9aedb65271311321dc107a2b7c98df335c741
4
+ data.tar.gz: adf759a2801d14156b0606891bd333ddb7c4fe0f
5
+ SHA512:
6
+ metadata.gz: 7cb8ba8f431fa514d2e3ff6760eca5f701faa8c013625a0b78416ac34c81e0197f33e6d889b6750b427068f853f2b4ed1dd28ffc48fbde7a1c5b1959da34c694
7
+ data.tar.gz: f26e5f6071f6cb862ba4aa7008ee5f6176c80cebde302d015a937703254805910decea3eb8a34c7d7c81b8a9b439c427c05ae1c0c0b4f31fb816f9f4d3f97778
@@ -0,0 +1,22 @@
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
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
@@ -0,0 +1 @@
1
+ workhours
@@ -0,0 +1 @@
1
+ 2.1.2
@@ -0,0 +1,8 @@
1
+ language: ruby
2
+ services: mongodb
3
+
4
+ rvm:
5
+ - 1.9.3
6
+ - 2.0.0
7
+ - 2.1.1
8
+
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in buisness_hours.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 glebtv
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.
@@ -0,0 +1,50 @@
1
+ # Workhours
2
+
3
+ Gem to calculate *buisness hours*, things like .is_open?, .is_closed?, .opens_at, .closes_at
4
+
5
+ Some code based on [buisness_time](https://github.com/bokmann/business_time) gem which unfortuanately handles all
6
+ configs globally and is buggy.
7
+
8
+ Uses ```tod``` gem to properly handle parsing and math with TimeOfDay (time without date).
9
+
10
+ ## Installation
11
+
12
+ Add this line to your application's Gemfile:
13
+
14
+ gem 'workhours'
15
+
16
+ And then execute:
17
+
18
+ $ bundle
19
+
20
+ Or install it yourself as:
21
+
22
+ $ gem install workhours
23
+
24
+ ## Usage
25
+
26
+ Initialization:
27
+
28
+ # default week - mon-fri, 9am-6pm
29
+ week = Workhours::Week.new
30
+
31
+ # custom hours or days
32
+ week = Workhours::Week.new(open: '12:00', close: '20:00', week: %w(mon tue fri sat))
33
+ week = Workhours::Week.new(holidays: [Date.parse('2014-01-01')], week: Workhours::ALL_WEEK)
34
+ # fully custom work hours
35
+ week = Workhours::Week.new(hours: ['mon 12:00-15:10', 'mon 15:00-16:00'])
36
+
37
+ Methods:
38
+
39
+ week.is_open?([time])
40
+ week.is_closed?([time])
41
+ week.opens_at([time]) # returns nil if currently open
42
+ week.closes_at([time]) # returns nil if currently closed
43
+
44
+ ## Contributing
45
+
46
+ 1. Fork it ( https://github.com/rs-pro/workhours/fork )
47
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
48
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
49
+ 4. Push to the branch (`git push origin my-new-feature`)
50
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,17 @@
1
+ require "workhours/version"
2
+
3
+ require 'tod'
4
+ require "workhours/week"
5
+ require "workhours/period"
6
+ require "workhours/util"
7
+
8
+ module Workhours
9
+ ALL_DAYS = ::Time::RFC2822_DAY_NAME.map(&:downcase)
10
+
11
+ extend Util
12
+
13
+ class MultidayPeriodError < Exception; end
14
+ class NoHoursError < Exception; end
15
+ class NoClosingError < Exception; end
16
+ end
17
+
@@ -0,0 +1,4 @@
1
+ module Workhours
2
+
3
+ end
4
+
@@ -0,0 +1,83 @@
1
+ module Workhours
2
+ class Period
3
+ attr_accessor :wday, :shift
4
+ def initialize(wday, beginning, ending)
5
+ @wday = wday
6
+ beginning = TimeOfDay.parse(beginning) unless beginning.is_a?(TimeOfDay)
7
+ ending = TimeOfDay.parse(ending) unless ending.is_a?(TimeOfDay)
8
+ if ending.second_of_day != 0 && ending < beginning
9
+ raise MultidayPeriodError.new()
10
+ end
11
+ @shift = Shift.new(beginning, ending)
12
+ end
13
+
14
+ def inspect
15
+ "<Workhours::Period wday:#{wday} beginning:#{beginning.to_s} ending:#{ending.to_s}>"
16
+ end
17
+
18
+ def is_inside_range?(tod)
19
+ shift.include?(tod)
20
+ end
21
+
22
+ def is_today?(time)
23
+ Workhours.is_today?(wday, time)
24
+ end
25
+ def is_tomorrow?(time)
26
+ Workhours.is_tomorrow?(wday, time)
27
+ end
28
+ def is_yesterday?(time)
29
+ Workhours.is_yesterday?(wday, time)
30
+ end
31
+
32
+ def beginning
33
+ shift.beginning
34
+ end
35
+ def ending
36
+ shift.ending
37
+ end
38
+
39
+ def ending_time(time)
40
+ if shift.ending.second_of_day == 0
41
+ (time.to_date + 1).at(shift.ending)
42
+ else
43
+ time.to_date.at(shift.ending)
44
+ end
45
+ end
46
+
47
+ def is_active?(time)
48
+ tod = time.to_time_of_day
49
+ if is_today?(time)
50
+ if tod.second_of_day == 0 && ending.second_of_day == 0 && beginning.second_of_day > 0
51
+ # 10:00-0:00 is NOT active on 0:00 of current day, but on 0:00 of next day
52
+ false
53
+ else
54
+ is_inside_range?(tod)
55
+ end
56
+ else
57
+ if tod.second_of_day == 0 && ending.second_of_day == 0 && is_tomorrow?(time)
58
+ # 10:00-0:00 is active on 0:00 of next day
59
+ true
60
+ else
61
+ false
62
+ end
63
+ end
64
+ end
65
+
66
+ def to_s
67
+ "#{wday} #{beginning.to_s}-#{ending.to_s}"
68
+ end
69
+
70
+ def overlaps?(other_day)
71
+ if wday != other_day.wday
72
+ return false
73
+ end
74
+
75
+ open_inside = beginning > other_day.beginning && beginning < other_day.ending
76
+ close_inside = ending > other_day.beginning && ending < other_day.ending
77
+ outside = beginning < other_day.beginning && ending > other_day.ending
78
+ #puts "#{to_s} vs #{other_day.to_s}: oi:#{open_inside} ci:#{close_inside} ou:#{outside}"
79
+ open_inside || close_inside || outside
80
+ end
81
+ end
82
+ end
83
+
@@ -0,0 +1,29 @@
1
+ module Workhours
2
+ module Util
3
+ def wday_to_int(day_name)
4
+ ALL_DAYS.find_index(day_name.to_s.downcase)
5
+ end
6
+
7
+ def int_to_wday(num)
8
+ ALL_DAYS[num]
9
+ end
10
+
11
+ def next_day(day_name)
12
+ int_to_wday((wday_to_int(day_name) + 1) % 7)
13
+ end
14
+ def prev_day(day_name)
15
+ int_to_wday((wday_to_int(day_name) + 6) % 7)
16
+ end
17
+
18
+ def is_today?(day_name, time)
19
+ time.wday == wday_to_int(day_name)
20
+ end
21
+ def is_tomorrow?(day_name, time)
22
+ time.wday == wday_to_int(next_day(day_name))
23
+ end
24
+ def is_yesterday?(day_name, time)
25
+ time.wday == wday_to_int(prev_day(day_name))
26
+ end
27
+ end
28
+ end
29
+
@@ -0,0 +1,3 @@
1
+ module Workhours
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,170 @@
1
+ module Workhours
2
+ DEFAULTS = {
3
+ holidays: [],
4
+ open: '09:00',
5
+ close: '18:00',
6
+ week: %w(mon tue wed thu fri),
7
+ hours: [],
8
+ }
9
+ class Week
10
+ attr_reader *DEFAULTS.keys
11
+
12
+ def initialize(options = {})
13
+ DEFAULTS.each_pair do |k, v|
14
+ instance_variable_set("@#{k}", v.dup)
15
+ end
16
+ options.each_pair do |k, v|
17
+ instance_variable_set("@#{k}", v.dup)
18
+ end
19
+
20
+ @export_hours = {}
21
+
22
+ if hours.empty?
23
+ week.each do |wday|
24
+ hours.push Workhours::Period.new(wday, open, close)
25
+ end
26
+ else
27
+ @export_hours = hours.dup
28
+ @hours = hours.map do |h|
29
+ if h.is_a?(Workhours::Period)
30
+ h
31
+ else
32
+ pr = h.split(' ')
33
+ times = pr[1].split('-')
34
+ beginning = TimeOfDay.parse(times[0])
35
+ ending = TimeOfDay.parse(times[1])
36
+ if ending < beginning
37
+ [Workhours::Period.new(pr[0], times[0], "0:00"), Workhours::Period.new(Workhours.next_day(pr[0]), "0:00", times[1])]
38
+ else
39
+ Workhours::Period.new(pr[0], times[0], times[1])
40
+ end
41
+ end
42
+ end.flatten
43
+ end
44
+ raise NoHoursError.new if @hours.empty?
45
+ end
46
+
47
+ def export(holidays: true)
48
+ ret = {}
49
+ if @export_hours.empty?
50
+ (DEFAULTS.keys - [:hours]).each do |k|
51
+ ret[k] = send(k)
52
+ end
53
+ else
54
+ ret[:hours] = @export_hours
55
+ end
56
+
57
+ if holidays
58
+ ret[:holidays] = @holidays
59
+ end
60
+ ret
61
+ end
62
+
63
+ def inspect
64
+ "<Workhours::Week #{export.inspect}>"
65
+ end
66
+
67
+ def hours_active(time)
68
+ hours.select { |h| h.is_active?(time) }
69
+ end
70
+
71
+ def is_open?(time = Time.now)
72
+ !hours_active(time).first.nil? && !is_holiday?(time)
73
+ end
74
+ def is_closed?(time = Time.now)
75
+ !is_open?(time)
76
+ end
77
+ def hours_on(date)
78
+ if is_holiday?(date)
79
+ []
80
+ else
81
+ hours.select { |h| h.is_today?(date) }.sort_by { |h| h.beginning }
82
+ end
83
+ end
84
+ def is_open_on?(date)
85
+ hours_on(date).first.nil? && !is_holiday?(date)
86
+ end
87
+
88
+ def is_holiday?(date)
89
+ holidays.include?(date.to_date)
90
+ end
91
+
92
+ def opens_at(time = Time.now)
93
+ if is_open?(time)
94
+ nil
95
+ else
96
+ next_open_time(time)
97
+ end
98
+ end
99
+ def closes_at(time = Time.now)
100
+ if is_closed?(time)
101
+ nil
102
+ else
103
+ next_closing_time(time)
104
+ end
105
+ end
106
+
107
+ def next_open_time(time)
108
+ date = time.to_date
109
+ counter = 0
110
+ loop do
111
+ hours = hours_on(date)
112
+ if counter == 0 && !hours.empty?
113
+ after = hours.select { |h| h.beginning > time.to_time_of_day || h.ending > time.to_time_of_day }
114
+ if after.empty?
115
+ hours = []
116
+ else
117
+ return date.at(after[0].beginning)
118
+ end
119
+ end
120
+
121
+ if hours.empty?
122
+ date += 1
123
+ else
124
+ return date.at(hours[0].beginning)
125
+ end
126
+ counter += 1
127
+ end
128
+ end
129
+
130
+ def next_closing_time(time)
131
+ active = hours_active(time).first
132
+ tmp_time = active.ending_time(time)
133
+ counter = 0
134
+ loop do
135
+ counter += 1; raise NoClosingError.new if counter > 1000
136
+ found = false
137
+ hours.each do |h|
138
+ next if h == active
139
+ if h.is_active?(tmp_time)
140
+ active = h
141
+ tmp_time = h.ending_time(tmp_time)
142
+ found = true
143
+ end
144
+ end
145
+ unless found
146
+ return tmp_time
147
+ end
148
+ end
149
+ end
150
+
151
+ def hours_overlap?
152
+ hours.each do |h1|
153
+ hours.each do |h2|
154
+ next if h1 == h2
155
+ if h1.overlaps?(h2)
156
+ return [h1, h2]
157
+ end
158
+ end
159
+ end
160
+ false
161
+ end
162
+
163
+ def week_int
164
+ week.map do |d|
165
+ Workhours.wday_to_int(d)
166
+ end
167
+ end
168
+ end
169
+ end
170
+
@@ -0,0 +1,21 @@
1
+ # coding: utf-8
2
+
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+
5
+ require 'rubygems'
6
+
7
+ require 'simplecov'
8
+ SimpleCov.start
9
+
10
+ require 'timecop'
11
+
12
+ require 'bundler/setup'
13
+
14
+ require 'workhours'
15
+
16
+ RSpec.configure do |config|
17
+ config.mock_with :rspec
18
+ config.expect_with :rspec do |c|
19
+ c.syntax = [:should, :expect]
20
+ end
21
+ end
@@ -0,0 +1,235 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Workhours' do
4
+ describe 'util' do
5
+ it 'days' do
6
+ days = ["sun", "mon", "tue", "wed", "thu", "fri", "sat"]
7
+ days.each_with_index do |d, i|
8
+ Workhours.wday_to_int(d).should eq i
9
+ Workhours.int_to_wday(i).should eq d
10
+ end
11
+ end
12
+ it 'next_day' do
13
+ Workhours.next_day("sun").should eq "mon"
14
+ Workhours.next_day("mon").should eq "tue"
15
+ Workhours.next_day("tue").should eq "wed"
16
+ Workhours.next_day("wed").should eq "thu"
17
+ Workhours.next_day("thu").should eq "fri"
18
+ Workhours.next_day("fri").should eq "sat"
19
+ Workhours.next_day("sat").should eq "sun"
20
+ end
21
+ it 'prev_day' do
22
+ Workhours.prev_day("sun").should eq "sat"
23
+ Workhours.prev_day("mon").should eq "sun"
24
+ Workhours.prev_day("tue").should eq "mon"
25
+ Workhours.prev_day("wed").should eq "tue"
26
+ Workhours.prev_day("thu").should eq "wed"
27
+ Workhours.prev_day("fri").should eq "thu"
28
+ Workhours.prev_day("sat").should eq "fri"
29
+ end
30
+
31
+ describe 'period' do
32
+ let(:period) { Workhours::Period.new(:mon, '10:00', '14:00') }
33
+ it '#active?' do
34
+ period.is_active?(Time.parse('2014-08-04 09:59')).should eq(false)
35
+ period.is_active?(Time.parse('2014-08-04 10:00')).should eq(true)
36
+ period.is_active?(Time.parse('2014-08-04 10:01')).should eq(true)
37
+ period.is_active?(Time.parse('2014-08-04 13:59')).should eq(true)
38
+ period.is_active?(Time.parse('2014-08-04 14:00')).should eq(true)
39
+ period.is_active?(Time.parse('2014-08-04 14:01')).should eq(false)
40
+ period.is_active?(Time.parse('2014-08-05 12:00')).should eq(false)
41
+ end
42
+ end
43
+
44
+ describe 'day border' do
45
+ let(:beginning) { Workhours::Period.new(:tue, '0:00', '14:00') }
46
+ let(:ending) { Workhours::Period.new(:tue, '10:00', '0:00') }
47
+ it '#active?' do
48
+ beginning.is_active?(Time.parse('2014-08-04 0:00')).should eq(false)
49
+ ending.is_active?(Time.parse('2014-08-04 0:00')).should eq(false)
50
+ beginning.is_active?(Time.parse('2014-08-05 0:00')).should eq(true)
51
+ ending.is_active?(Time.parse('2014-08-05 0:00')).should eq(false)
52
+ beginning.is_active?(Time.parse('2014-08-06 0:00')).should eq(false)
53
+ ending.is_active?(Time.parse('2014-08-06 0:00')).should eq(true)
54
+ end
55
+ end
56
+ end
57
+
58
+ describe "week" do
59
+ describe "has defaults" do
60
+ let(:week) { Workhours::Week.new }
61
+
62
+ it "has default holidays" do
63
+ week.holidays.should eq []
64
+ end
65
+ it "has default beginning of workday" do
66
+ week.open.should eq '09:00'
67
+ end
68
+ it "has default end of workday" do
69
+ week.close.should eq '18:00'
70
+ end
71
+ it "has default work week" do
72
+ week.week.should eq %w(mon tue wed thu fri)
73
+ week.week_int.should eq [1, 2, 3, 4, 5]
74
+ end
75
+ it "has default work hours" do
76
+ week.hours.map(&:inspect).should eq [
77
+ "<Workhours::Period wday:mon beginning:09:00:00 ending:18:00:00>",
78
+ "<Workhours::Period wday:tue beginning:09:00:00 ending:18:00:00>",
79
+ "<Workhours::Period wday:wed beginning:09:00:00 ending:18:00:00>",
80
+ "<Workhours::Period wday:thu beginning:09:00:00 ending:18:00:00>",
81
+ "<Workhours::Period wday:fri beginning:09:00:00 ending:18:00:00>"
82
+ ]
83
+ end
84
+ end
85
+
86
+ describe "handles custom hours" do
87
+ it 'finds overlaps' do
88
+ week = Workhours::Week.new(hours: ['mon 12:00-15:10', 'mon 15:00-16:00'])
89
+ week.hours_overlap?.length.should eq(2)
90
+ week.hours_overlap?.should be_truthy
91
+ week = Workhours::Week.new(hours: ['mon 15:00-16:00', 'mon 12:00-15:10'])
92
+ week.hours_overlap?.length.should eq(2)
93
+ week.hours_overlap?.should be_truthy
94
+
95
+ week = Workhours::Week.new(hours: ['mon 12:00-15:00', 'mon 15:00-16:00'])
96
+ week.hours_overlap?.should eq(false)
97
+ week = Workhours::Week.new(hours: ['tue 15:00-16:00', 'mon 12:00-15:10'])
98
+ week.hours_overlap?.should eq(false)
99
+ end
100
+
101
+ describe 'with mon and two periods' do
102
+ let(:week) { Workhours::Week.new(hours: ['mon 12:00-15:00', 'mon 15:00-16:00']) }
103
+
104
+ it 'is open on monday at given hours' do
105
+ week.is_open?(Time.parse('2014-08-04 10:00')).should eq(false)
106
+ week.is_open?(Time.parse('2014-08-04 10:00')).should eq(false)
107
+ week.is_open?(Time.parse('2014-08-04 11:59')).should eq(false)
108
+ week.is_open?(Time.parse('2014-08-04 12:00')).should eq(true)
109
+ week.is_open?(Time.parse('2014-08-04 12:01')).should eq(true)
110
+ week.is_open?(Time.parse('2014-08-04 15:00')).should eq(true)
111
+ week.is_open?(Time.parse('2014-08-04 15:01')).should eq(true)
112
+ week.is_open?(Time.parse('2014-08-04 16:00')).should eq(true)
113
+ week.is_open?(Time.parse('2014-08-04 16:01')).should eq(false)
114
+ week.is_open?(Time.parse('2014-08-05 15:00')).should eq(false)
115
+ week.is_open?(Time.parse('2014-08-06 15:30')).should eq(false)
116
+ week.is_open?(Time.parse('2014-08-06 16:00')).should eq(false)
117
+ end
118
+
119
+ it 'handles default times' do
120
+ Timecop.freeze(Time.parse('2014-08-04 10:00')) { week.is_open?.should eq(false) }
121
+ Timecop.freeze(Time.parse('2014-08-04 10:00')) { week.is_open?.should eq(false) }
122
+ Timecop.freeze(Time.parse('2014-08-04 11:59')) { week.is_open?.should eq(false) }
123
+ Timecop.freeze(Time.parse('2014-08-04 12:00')) { week.is_open?.should eq(true) }
124
+ Timecop.freeze(Time.parse('2014-08-04 12:01')) { week.is_open?.should eq(true) }
125
+ Timecop.freeze(Time.parse('2014-08-04 15:00')) { week.is_open?.should eq(true) }
126
+ Timecop.freeze(Time.parse('2014-08-04 15:01')) { week.is_open?.should eq(true) }
127
+ Timecop.freeze(Time.parse('2014-08-04 16:00')) { week.is_open?.should eq(true) }
128
+ Timecop.freeze(Time.parse('2014-08-04 16:01')) { week.is_open?.should eq(false) }
129
+ Timecop.freeze(Time.parse('2014-08-05 13:00')) { week.is_open?.should eq(false) }
130
+ Timecop.freeze(Time.parse('2014-08-06 15:00')) { week.is_open?.should eq(false) }
131
+ Timecop.freeze(Time.parse('2014-08-06 15:30')) { week.is_open?.should eq(false) }
132
+ Timecop.freeze(Time.parse('2014-08-06 16:00')) { week.is_open?.should eq(false) }
133
+ end
134
+ end
135
+ end
136
+
137
+ describe 'with holidays' do
138
+ let(:week) { Workhours::Week.new(holidays: [Date.parse("2014-08-04")]) }
139
+ it '#is_open?' do
140
+ week.is_open?(Time.parse('2014-08-03 10:00')).should eq(false)
141
+ week.is_open?(Time.parse('2014-08-04 08:59')).should eq(false)
142
+ week.is_open?(Time.parse('2014-08-04 09:00')).should eq(false)
143
+ week.is_open?(Time.parse('2014-08-05 09:00')).should eq(true)
144
+ week.is_open?(Time.parse('2014-08-06 09:00')).should eq(true)
145
+ end
146
+ end
147
+
148
+ describe 'with defaults' do
149
+ let(:week) { Workhours::Week.new }
150
+ it '#is_open?' do
151
+ week.is_open?(Time.parse('2014-08-03 10:00')).should eq(false)
152
+ week.is_open?(Time.parse('2014-08-04 08:59')).should eq(false)
153
+ week.is_open?(Time.parse('2014-08-04 09:00')).should eq(true)
154
+ week.is_open?(Time.parse('2014-08-05 09:00')).should eq(true)
155
+ week.is_open?(Time.parse('2014-08-06 09:00')).should eq(true)
156
+ week.is_open?(Time.parse('2014-08-07 09:00')).should eq(true)
157
+ week.is_open?(Time.parse('2014-08-08 09:00')).should eq(true)
158
+ week.is_open?(Time.parse('2014-08-09 09:00')).should eq(false)
159
+ week.is_open?(Time.parse('2014-08-09 10:00')).should eq(false)
160
+ end
161
+ it '#opens_at' do
162
+ week.opens_at(Time.parse('2014-08-03 10:00')).should eq(Time.parse('2014-08-04 09:00'))
163
+ week.opens_at(Time.parse('2014-08-04 8:59')).should eq(Time.parse('2014-08-04 09:00'))
164
+ week.opens_at(Time.parse('2014-08-04 9:00')).should be_nil
165
+ end
166
+ it '#closes_at' do
167
+ week.closes_at(Time.parse('2014-08-03 10:00')).should be_nil
168
+ week.closes_at(Time.parse('2014-08-04 08:59')).should be_nil
169
+ week.closes_at(Time.parse('2014-08-04 09:00')).should eq(Time.parse('2014-08-04 18:00'))
170
+ week.closes_at(Time.parse('2014-08-05 09:00')).should eq(Time.parse('2014-08-05 18:00'))
171
+ week.closes_at(Time.parse('2014-08-06 09:00')).should eq(Time.parse('2014-08-06 18:00'))
172
+ week.closes_at(Time.parse('2014-08-07 09:00')).should eq(Time.parse('2014-08-07 18:00'))
173
+ week.closes_at(Time.parse('2014-08-08 09:00')).should eq(Time.parse('2014-08-08 18:00'))
174
+ week.closes_at(Time.parse('2014-08-09 09:00')).should be_nil
175
+ week.closes_at(Time.parse('2014-08-09 10:00')).should be_nil
176
+ end
177
+ end
178
+
179
+ describe 'night shifts' do
180
+ let(:week) { Workhours::Week.new(hours: ['mon 12:00-4:00']) }
181
+
182
+ it '#is_open?' do
183
+ week.is_open?(Time.parse('2014-08-03 10:00')).should eq(false)
184
+ week.is_open?(Time.parse('2014-08-04 0:00')).should eq(false)
185
+ week.is_open?(Time.parse('2014-08-04 12:00')).should eq(true)
186
+ week.is_open?(Time.parse('2014-08-04 12:01')).should eq(true)
187
+ week.is_open?(Time.parse('2014-08-04 12:01')).should eq(true)
188
+ week.is_open?(Time.parse('2014-08-04 23:00')).should eq(true)
189
+ week.is_open?(Time.parse('2014-08-05 00:00')).should eq(true)
190
+ week.is_open?(Time.parse('2014-08-05 03:00')).should eq(true)
191
+ week.is_open?(Time.parse('2014-08-05 04:00')).should eq(true)
192
+ week.is_open?(Time.parse('2014-08-05 04:01')).should eq(false)
193
+ end
194
+
195
+ it '#opens_at' do
196
+ week.opens_at(Time.parse('2014-08-03 10:00')).should eq(Time.parse('2014-08-04 12:00'))
197
+ week.opens_at(Time.parse('2014-08-04 8:59')).should eq(Time.parse('2014-08-04 12:00'))
198
+ week.opens_at(Time.parse('2014-08-04 16:00')).should be_nil
199
+ week.opens_at(Time.parse('2014-08-04 23:00')).should be_nil
200
+ week.opens_at(Time.parse('2014-08-05 03:00')).should be_nil
201
+ week.opens_at(Time.parse('2014-08-05 10:00')).should eq(Time.parse('2014-08-11 12:00'))
202
+ end
203
+ it '#closes_at' do
204
+ week.closes_at(Time.parse('2014-08-04 14:00')).should eq(Time.parse('2014-08-05 04:00'))
205
+ week.closes_at(Time.parse('2014-08-05 10:00')).should be_nil
206
+ end
207
+ end
208
+
209
+ it 'fails with no hours' do
210
+ expect { Workhours::Week.new(week: []) }.to raise_error(Workhours::NoHoursError)
211
+ end
212
+ end
213
+
214
+ describe 'export - custom' do
215
+ let(:week) { Workhours::Week.new(hours: ['mon 12:00-4:00']) }
216
+ it 'export' do
217
+ week.export.should eq({
218
+ holidays: [],
219
+ hours: ['mon 12:00-4:00'],
220
+ })
221
+ end
222
+ end
223
+ describe 'export - default' do
224
+ let(:week) { Workhours::Week.new }
225
+ it 'export' do
226
+ week.export.should eq({
227
+ open: '09:00',
228
+ close: '18:00',
229
+ holidays: [],
230
+ week: %w(mon tue wed thu fri)
231
+ })
232
+ end
233
+ end
234
+ end
235
+
@@ -0,0 +1,30 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'workhours/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "workhours"
8
+ spec.version = Workhours::VERSION
9
+ spec.authors = ["glebtv"]
10
+ spec.email = ["glebtv@gmail.com"]
11
+ spec.summary = %q{Gem to calculate buisness hours}
12
+ spec.description = %q{}
13
+ spec.homepage = "https://github.com/rs-pro/workhours"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "tod"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.6"
24
+ spec.add_development_dependency "rake"
25
+
26
+ spec.add_development_dependency "rspec"
27
+ spec.add_development_dependency "simplecov"
28
+ spec.add_development_dependency "timecop"
29
+ end
30
+
metadata ADDED
@@ -0,0 +1,147 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: workhours
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - glebtv
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-08-02 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: tod
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.6'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.6'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rspec
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: simplecov
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: timecop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: ''
98
+ email:
99
+ - glebtv@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".ruby-gemset"
106
+ - ".ruby-version"
107
+ - ".travis.yml"
108
+ - Gemfile
109
+ - LICENSE.txt
110
+ - README.md
111
+ - Rakefile
112
+ - lib/workhours.rb
113
+ - lib/workhours/multiday_period_error.rb
114
+ - lib/workhours/period.rb
115
+ - lib/workhours/util.rb
116
+ - lib/workhours/version.rb
117
+ - lib/workhours/week.rb
118
+ - spec/spec_helper.rb
119
+ - spec/workhours_spec.rb
120
+ - workhours.gemspec
121
+ homepage: https://github.com/rs-pro/workhours
122
+ licenses:
123
+ - MIT
124
+ metadata: {}
125
+ post_install_message:
126
+ rdoc_options: []
127
+ require_paths:
128
+ - lib
129
+ required_ruby_version: !ruby/object:Gem::Requirement
130
+ requirements:
131
+ - - ">="
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ required_rubygems_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ requirements: []
140
+ rubyforge_project:
141
+ rubygems_version: 2.2.2
142
+ signing_key:
143
+ specification_version: 4
144
+ summary: Gem to calculate buisness hours
145
+ test_files:
146
+ - spec/spec_helper.rb
147
+ - spec/workhours_spec.rb