biz 0.0.1 → 1.0.0
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.
- 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
|