biz 0.0.1 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +110 -11
- data/lib/biz.rb +54 -2
- data/lib/biz/calculation.rb +8 -0
- data/lib/biz/calculation/active.rb +20 -0
- data/lib/biz/calculation/duration_within.rb +17 -0
- data/lib/biz/calculation/for_duration.rb +33 -0
- data/lib/biz/configuration.rb +74 -0
- data/lib/biz/core_ext.rb +12 -0
- data/lib/biz/core_ext/date.rb +15 -0
- data/lib/biz/core_ext/fixnum.rb +11 -0
- data/lib/biz/core_ext/time.rb +11 -0
- data/lib/biz/date.rb +11 -0
- data/lib/biz/day.rb +51 -0
- data/lib/biz/day_of_week.rb +81 -0
- data/lib/biz/day_time.rb +95 -0
- data/lib/biz/duration.rb +100 -0
- data/lib/biz/holiday.rb +26 -0
- data/lib/biz/interval.rb +35 -0
- data/lib/biz/periods.rb +24 -0
- data/lib/biz/periods/abstract.rb +49 -0
- data/lib/biz/periods/after.rb +24 -0
- data/lib/biz/periods/before.rb +27 -0
- data/lib/biz/schedule.rb +37 -0
- data/lib/biz/time.rb +49 -0
- data/lib/biz/time_segment.rb +63 -0
- data/lib/biz/timeline.rb +24 -0
- data/lib/biz/timeline/abstract.rb +41 -0
- data/lib/biz/timeline/backward.rb +24 -0
- data/lib/biz/timeline/forward.rb +24 -0
- data/lib/biz/version.rb +3 -1
- data/lib/biz/week.rb +67 -0
- data/lib/biz/week_time.rb +26 -0
- data/lib/biz/week_time/abstract.rb +75 -0
- data/lib/biz/week_time/end.rb +20 -0
- data/lib/biz/week_time/start.rb +22 -0
- metadata +122 -5
data/lib/biz/schedule.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
module Biz
|
2
|
+
class Schedule
|
3
|
+
|
4
|
+
extend Forwardable
|
5
|
+
|
6
|
+
def initialize(&block)
|
7
|
+
@configuration = Configuration.new(&block)
|
8
|
+
end
|
9
|
+
|
10
|
+
delegate %i[
|
11
|
+
intervals
|
12
|
+
holidays
|
13
|
+
time_zone
|
14
|
+
] => :configuration
|
15
|
+
|
16
|
+
def periods
|
17
|
+
Periods.new(self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def time(scalar, unit)
|
21
|
+
Calculation::ForDuration.new(self, Duration.with_unit(scalar, unit))
|
22
|
+
end
|
23
|
+
|
24
|
+
def within(origin, terminus)
|
25
|
+
Calculation::DurationWithin.new(self, TimeSegment.new(origin, terminus))
|
26
|
+
end
|
27
|
+
|
28
|
+
def business_hours?(time)
|
29
|
+
Calculation::Active.new(self, time).active?
|
30
|
+
end
|
31
|
+
|
32
|
+
protected
|
33
|
+
|
34
|
+
attr_reader :configuration
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
data/lib/biz/time.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Biz
|
2
|
+
class Time
|
3
|
+
|
4
|
+
MINUTE = 60
|
5
|
+
HOUR = 60 * MINUTE
|
6
|
+
DAY = 24 * HOUR
|
7
|
+
WEEK = 7 * DAY
|
8
|
+
|
9
|
+
MINUTES_IN_HOUR = 60
|
10
|
+
HOURS_IN_DAY = 24
|
11
|
+
DAYS_IN_WEEK = 7
|
12
|
+
|
13
|
+
MINUTES_IN_DAY = MINUTES_IN_HOUR * HOURS_IN_DAY
|
14
|
+
MINUTES_IN_WEEK = MINUTES_IN_DAY * DAYS_IN_WEEK
|
15
|
+
|
16
|
+
BIG_BANG = ::Time.new(-10**100)
|
17
|
+
HEAT_DEATH = ::Time.new(10**100)
|
18
|
+
|
19
|
+
attr_reader :time_zone
|
20
|
+
|
21
|
+
def initialize(time_zone)
|
22
|
+
@time_zone = time_zone
|
23
|
+
end
|
24
|
+
|
25
|
+
def local(time)
|
26
|
+
time_zone.utc_to_local(time.utc)
|
27
|
+
end
|
28
|
+
|
29
|
+
def on_date(date, day_time)
|
30
|
+
time_zone.local_to_utc(
|
31
|
+
::Time.utc(
|
32
|
+
date.year,
|
33
|
+
date.month,
|
34
|
+
date.mday,
|
35
|
+
day_time.hour,
|
36
|
+
day_time.minute
|
37
|
+
),
|
38
|
+
true
|
39
|
+
)
|
40
|
+
rescue TZInfo::PeriodNotFound
|
41
|
+
on_date(date, DayTime.new(day_time.day_minute + MINUTES_IN_HOUR))
|
42
|
+
end
|
43
|
+
|
44
|
+
def during_week(week, week_time)
|
45
|
+
on_date(week.start_date + week_time.wday, week_time)
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Biz
|
2
|
+
class TimeSegment
|
3
|
+
|
4
|
+
include Equalizer.new(:start_time, :end_time)
|
5
|
+
|
6
|
+
def self.before(time)
|
7
|
+
new(Time::BIG_BANG, time)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.after(time)
|
11
|
+
new(time, Time::HEAT_DEATH)
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :start_time,
|
15
|
+
:end_time
|
16
|
+
|
17
|
+
def initialize(start_time, end_time)
|
18
|
+
@start_time = start_time
|
19
|
+
@end_time = end_time
|
20
|
+
end
|
21
|
+
|
22
|
+
def duration
|
23
|
+
Duration.new(end_time - start_time)
|
24
|
+
end
|
25
|
+
|
26
|
+
def endpoints
|
27
|
+
[start_time, end_time]
|
28
|
+
end
|
29
|
+
|
30
|
+
def empty?
|
31
|
+
start_time >= end_time
|
32
|
+
end
|
33
|
+
|
34
|
+
def contains?(time)
|
35
|
+
(start_time..end_time).cover?(time)
|
36
|
+
end
|
37
|
+
|
38
|
+
def &(other)
|
39
|
+
self.class.new(
|
40
|
+
lower_bound(other),
|
41
|
+
[lower_bound(other), upper_bound(other)].max
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
def /(other)
|
46
|
+
[
|
47
|
+
self.class.new(start_time, other.start_time),
|
48
|
+
self.class.new(other.end_time, end_time)
|
49
|
+
].reject(&:empty?).map { |potential| self & potential }
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def lower_bound(other)
|
55
|
+
[self, other].map(&:start_time).max
|
56
|
+
end
|
57
|
+
|
58
|
+
def upper_bound(other)
|
59
|
+
[self, other].map(&:end_time).min
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
end
|
data/lib/biz/timeline.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Biz
|
2
|
+
class Timeline
|
3
|
+
|
4
|
+
attr_reader :periods
|
5
|
+
|
6
|
+
def initialize(periods)
|
7
|
+
@periods = periods
|
8
|
+
end
|
9
|
+
|
10
|
+
def forward
|
11
|
+
Forward.new(periods)
|
12
|
+
end
|
13
|
+
|
14
|
+
def backward
|
15
|
+
Backward.new(periods)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'biz/timeline/abstract'
|
22
|
+
|
23
|
+
require 'biz/timeline/forward'
|
24
|
+
require 'biz/timeline/backward'
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Biz
|
2
|
+
class Timeline
|
3
|
+
class Abstract
|
4
|
+
|
5
|
+
attr_reader :periods
|
6
|
+
|
7
|
+
def initialize(periods)
|
8
|
+
@periods = periods.lazy
|
9
|
+
end
|
10
|
+
|
11
|
+
def until(terminus)
|
12
|
+
return enum_for(:until, terminus) unless block_given?
|
13
|
+
|
14
|
+
periods.map { |period|
|
15
|
+
period & comparison_period(period, terminus)
|
16
|
+
}.each do |period|
|
17
|
+
yield period unless period.empty?
|
18
|
+
|
19
|
+
break if occurred?(period, terminus)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def for(duration)
|
24
|
+
return enum_for(:for, duration) unless block_given?
|
25
|
+
|
26
|
+
remaining = duration
|
27
|
+
|
28
|
+
periods.map { |period|
|
29
|
+
period & duration_period(period, remaining)
|
30
|
+
}.each do |period|
|
31
|
+
yield period unless period.empty?
|
32
|
+
|
33
|
+
remaining -= period.duration
|
34
|
+
|
35
|
+
break unless remaining.positive?
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Biz
|
2
|
+
class Timeline
|
3
|
+
class Backward < Abstract
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def occurred?(period, time)
|
8
|
+
period.start_time <= time
|
9
|
+
end
|
10
|
+
|
11
|
+
def comparison_period(period, terminus)
|
12
|
+
TimeSegment.new(terminus, period.end_time)
|
13
|
+
end
|
14
|
+
|
15
|
+
def duration_period(period, duration)
|
16
|
+
TimeSegment.new(
|
17
|
+
period.end_time - duration.in_seconds,
|
18
|
+
period.end_time
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Biz
|
2
|
+
class Timeline
|
3
|
+
class Forward < Abstract
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def occurred?(period, time)
|
8
|
+
period.end_time >= time
|
9
|
+
end
|
10
|
+
|
11
|
+
def comparison_period(period, terminus)
|
12
|
+
TimeSegment.new(period.start_time, terminus)
|
13
|
+
end
|
14
|
+
|
15
|
+
def duration_period(period, duration)
|
16
|
+
TimeSegment.new(
|
17
|
+
period.start_time,
|
18
|
+
period.start_time + duration.in_seconds
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/lib/biz/version.rb
CHANGED
data/lib/biz/week.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
module Biz
|
2
|
+
class Week
|
3
|
+
|
4
|
+
include Comparable
|
5
|
+
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
def self.from_date(date)
|
9
|
+
new(Day.from_date(date).to_i / Time::DAYS_IN_WEEK)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.from_time(time)
|
13
|
+
from_date(time.to_date)
|
14
|
+
end
|
15
|
+
|
16
|
+
class << self
|
17
|
+
|
18
|
+
alias_method :since_epoch, :from_time
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
attr_reader :week
|
23
|
+
|
24
|
+
delegate %i[
|
25
|
+
to_s
|
26
|
+
to_i
|
27
|
+
to_int
|
28
|
+
] => :week
|
29
|
+
|
30
|
+
def initialize(week)
|
31
|
+
@week = Integer(week)
|
32
|
+
end
|
33
|
+
|
34
|
+
def start_date
|
35
|
+
Date.from_day(week * Time::DAYS_IN_WEEK)
|
36
|
+
end
|
37
|
+
|
38
|
+
def succ
|
39
|
+
self.class.new(week.succ)
|
40
|
+
end
|
41
|
+
|
42
|
+
def downto(final_week)
|
43
|
+
return enum_for(:downto, final_week) unless block_given?
|
44
|
+
|
45
|
+
week.downto(final_week.to_i).each do |raw_week|
|
46
|
+
yield self.class.new(raw_week)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def +(other)
|
51
|
+
self.class.new(week + other.week)
|
52
|
+
end
|
53
|
+
|
54
|
+
def coerce(other)
|
55
|
+
[self.class.new(other), self]
|
56
|
+
end
|
57
|
+
|
58
|
+
protected
|
59
|
+
|
60
|
+
def <=>(other)
|
61
|
+
return nil unless other.respond_to?(:to_i)
|
62
|
+
|
63
|
+
week <=> other.to_i
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Biz
|
2
|
+
module WeekTime
|
3
|
+
class << self
|
4
|
+
|
5
|
+
def from_time(time)
|
6
|
+
Start.from_time(time)
|
7
|
+
end
|
8
|
+
|
9
|
+
def start(week_minute)
|
10
|
+
Start.new(week_minute)
|
11
|
+
end
|
12
|
+
|
13
|
+
def end(week_minute)
|
14
|
+
End.new(week_minute)
|
15
|
+
end
|
16
|
+
|
17
|
+
alias_method :build, :start
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
require 'biz/week_time/abstract'
|
24
|
+
|
25
|
+
require 'biz/week_time/end'
|
26
|
+
require 'biz/week_time/start'
|
@@ -0,0 +1,75 @@
|
|
1
|
+
module Biz
|
2
|
+
module WeekTime
|
3
|
+
class Abstract
|
4
|
+
|
5
|
+
include AbstractType
|
6
|
+
include Comparable
|
7
|
+
include Memoizable
|
8
|
+
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
def self.from_time(time)
|
12
|
+
new(
|
13
|
+
time.wday * Time::MINUTES_IN_DAY +
|
14
|
+
time.hour * Time::MINUTES_IN_HOUR +
|
15
|
+
time.min
|
16
|
+
)
|
17
|
+
end
|
18
|
+
|
19
|
+
attr_reader :week_minute
|
20
|
+
|
21
|
+
def initialize(week_minute)
|
22
|
+
@week_minute = Integer(week_minute)
|
23
|
+
end
|
24
|
+
|
25
|
+
def wday_symbol
|
26
|
+
day_of_week.symbol
|
27
|
+
end
|
28
|
+
|
29
|
+
def coerce(other)
|
30
|
+
[self.class.new(other), self]
|
31
|
+
end
|
32
|
+
|
33
|
+
delegate wday: :day_of_week
|
34
|
+
|
35
|
+
delegate %i[
|
36
|
+
hour
|
37
|
+
minute
|
38
|
+
day_minute
|
39
|
+
timestamp
|
40
|
+
] => :day_time
|
41
|
+
|
42
|
+
delegate strftime: :week_time
|
43
|
+
|
44
|
+
delegate %i[
|
45
|
+
to_s
|
46
|
+
to_i
|
47
|
+
to_int
|
48
|
+
] => :week_minute
|
49
|
+
|
50
|
+
protected
|
51
|
+
|
52
|
+
def <=>(other)
|
53
|
+
return nil unless other.respond_to?(:to_i)
|
54
|
+
|
55
|
+
week_minute <=> other.to_i
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def week_time
|
61
|
+
::Time.new(
|
62
|
+
Date::EPOCH.year,
|
63
|
+
Date::EPOCH.month,
|
64
|
+
Date::EPOCH.mday + wday,
|
65
|
+
hour,
|
66
|
+
minute
|
67
|
+
)
|
68
|
+
end
|
69
|
+
|
70
|
+
abstract_method :day_of_week,
|
71
|
+
:day_time
|
72
|
+
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|