workhours 0.0.1

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.
@@ -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