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
@@ -0,0 +1,81 @@
|
|
1
|
+
module Biz
|
2
|
+
class DayOfWeek
|
3
|
+
|
4
|
+
SYMBOLS = [:sun, :mon, :tue, :wed, :thu, :fri, :sat]
|
5
|
+
|
6
|
+
def self.from_time(time)
|
7
|
+
DAYS.fetch(time.wday)
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.from_symbol(symbol)
|
11
|
+
DAYS.fetch(SYMBOLS.index(symbol))
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.first
|
15
|
+
DAYS.first
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.last
|
19
|
+
DAYS.last
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
|
24
|
+
alias_method :from_date, :from_time
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
attr_reader :wday
|
29
|
+
|
30
|
+
def initialize(wday)
|
31
|
+
@wday = Integer(wday)
|
32
|
+
end
|
33
|
+
|
34
|
+
def contains?(week_time)
|
35
|
+
minutes.cover?(week_time)
|
36
|
+
end
|
37
|
+
|
38
|
+
def start_minute
|
39
|
+
wday * Time::MINUTES_IN_DAY
|
40
|
+
end
|
41
|
+
|
42
|
+
def end_minute
|
43
|
+
start_minute + Time::MINUTES_IN_DAY
|
44
|
+
end
|
45
|
+
|
46
|
+
def minutes
|
47
|
+
start_minute..end_minute
|
48
|
+
end
|
49
|
+
|
50
|
+
def week_minute(day_minute)
|
51
|
+
start_minute + day_minute
|
52
|
+
end
|
53
|
+
|
54
|
+
def day_minute(week_minute)
|
55
|
+
(week_minute - 1) % Time::MINUTES_IN_DAY + 1
|
56
|
+
end
|
57
|
+
|
58
|
+
def symbol
|
59
|
+
SYMBOLS.fetch(wday)
|
60
|
+
end
|
61
|
+
|
62
|
+
DAYS = [
|
63
|
+
SUNDAY = new(0),
|
64
|
+
MONDAY = new(1),
|
65
|
+
TUESDAY = new(2),
|
66
|
+
WEDNESDAY = new(3),
|
67
|
+
THURSDAY = new(4),
|
68
|
+
FRIDAY = new(5),
|
69
|
+
SATURDAY = new(6)
|
70
|
+
]
|
71
|
+
|
72
|
+
WEEKDAYS = [
|
73
|
+
MONDAY,
|
74
|
+
TUESDAY,
|
75
|
+
WEDNESDAY,
|
76
|
+
THURSDAY,
|
77
|
+
FRIDAY
|
78
|
+
]
|
79
|
+
|
80
|
+
end
|
81
|
+
end
|
data/lib/biz/day_time.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
module Biz
|
2
|
+
class DayTime
|
3
|
+
|
4
|
+
TIMESTAMP_FORMAT = '%02d:%02d'
|
5
|
+
TIMESTAMP_PATTERN = /(?<hour>\d{2}):(?<minute>\d{2})/
|
6
|
+
|
7
|
+
include Comparable
|
8
|
+
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
def self.from_hour(hour)
|
12
|
+
new(hour * Time::MINUTES_IN_HOUR)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.from_timestamp(timestamp)
|
16
|
+
timestamp.match(TIMESTAMP_PATTERN) { |match|
|
17
|
+
new(match[:hour].to_i * Time::MINUTES_IN_HOUR + match[:minute].to_i)
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.midnight
|
22
|
+
MIDNIGHT
|
23
|
+
end
|
24
|
+
|
25
|
+
def self.noon
|
26
|
+
NOON
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.endnight
|
30
|
+
ENDNIGHT
|
31
|
+
end
|
32
|
+
|
33
|
+
class << self
|
34
|
+
|
35
|
+
alias_method :am, :midnight
|
36
|
+
|
37
|
+
alias_method :pm, :noon
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
attr_reader :day_minute
|
42
|
+
|
43
|
+
delegate strftime: :day_time
|
44
|
+
|
45
|
+
delegate %i[
|
46
|
+
to_i
|
47
|
+
to_int
|
48
|
+
] => :day_minute
|
49
|
+
|
50
|
+
def initialize(day_minute)
|
51
|
+
@day_minute = Integer(day_minute)
|
52
|
+
end
|
53
|
+
|
54
|
+
def hour
|
55
|
+
day_minute / Time::MINUTES_IN_HOUR
|
56
|
+
end
|
57
|
+
|
58
|
+
def minute
|
59
|
+
day_minute % Time::MINUTES_IN_HOUR
|
60
|
+
end
|
61
|
+
|
62
|
+
def timestamp
|
63
|
+
format(TIMESTAMP_FORMAT, hour, minute)
|
64
|
+
end
|
65
|
+
|
66
|
+
def coerce(other)
|
67
|
+
[self.class.new(other), self]
|
68
|
+
end
|
69
|
+
|
70
|
+
protected
|
71
|
+
|
72
|
+
def <=>(other)
|
73
|
+
return nil unless other.respond_to?(:to_i)
|
74
|
+
|
75
|
+
day_minute <=> other.to_i
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def day_time
|
81
|
+
::Time.new(
|
82
|
+
Date::EPOCH.year,
|
83
|
+
Date::EPOCH.month,
|
84
|
+
Date::EPOCH.mday,
|
85
|
+
hour,
|
86
|
+
minute
|
87
|
+
)
|
88
|
+
end
|
89
|
+
|
90
|
+
MIDNIGHT = from_hour(0)
|
91
|
+
NOON = from_hour(12)
|
92
|
+
ENDNIGHT = from_hour(24)
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
data/lib/biz/duration.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
module Biz
|
2
|
+
class Duration
|
3
|
+
|
4
|
+
UNITS = Set.new(%i[second seconds minute minutes hour hours day days])
|
5
|
+
|
6
|
+
include Equalizer.new(:seconds)
|
7
|
+
include Comparable
|
8
|
+
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
def with_unit(scalar, unit)
|
14
|
+
unless UNITS.include?(unit)
|
15
|
+
fail ArgumentError, 'The unit is not supported.'
|
16
|
+
end
|
17
|
+
|
18
|
+
public_send(unit, scalar)
|
19
|
+
end
|
20
|
+
|
21
|
+
def seconds(seconds)
|
22
|
+
new(seconds)
|
23
|
+
end
|
24
|
+
|
25
|
+
alias_method :second, :seconds
|
26
|
+
|
27
|
+
def minutes(minutes)
|
28
|
+
new(minutes * Time::MINUTE)
|
29
|
+
end
|
30
|
+
|
31
|
+
alias_method :minute, :minutes
|
32
|
+
|
33
|
+
def hours(hours)
|
34
|
+
new(hours * Time::HOUR)
|
35
|
+
end
|
36
|
+
|
37
|
+
alias_method :hour, :hours
|
38
|
+
|
39
|
+
def days(days)
|
40
|
+
new(days * Time::DAY)
|
41
|
+
end
|
42
|
+
|
43
|
+
alias_method :day, :days
|
44
|
+
|
45
|
+
end
|
46
|
+
|
47
|
+
attr_reader :seconds
|
48
|
+
|
49
|
+
delegate to_i: :seconds
|
50
|
+
|
51
|
+
def initialize(seconds)
|
52
|
+
@seconds = Integer(seconds)
|
53
|
+
end
|
54
|
+
|
55
|
+
def in_seconds
|
56
|
+
seconds
|
57
|
+
end
|
58
|
+
|
59
|
+
def in_minutes
|
60
|
+
seconds / Time::MINUTE
|
61
|
+
end
|
62
|
+
|
63
|
+
def in_hours
|
64
|
+
seconds / Time::HOUR
|
65
|
+
end
|
66
|
+
|
67
|
+
def in_days
|
68
|
+
seconds / Time::DAY
|
69
|
+
end
|
70
|
+
|
71
|
+
def +(other)
|
72
|
+
self.class.new(seconds + other.seconds)
|
73
|
+
end
|
74
|
+
|
75
|
+
def -(other)
|
76
|
+
self.class.new(seconds - other.seconds)
|
77
|
+
end
|
78
|
+
|
79
|
+
def positive?
|
80
|
+
seconds > 0
|
81
|
+
end
|
82
|
+
|
83
|
+
def abs
|
84
|
+
self.class.new(seconds.abs)
|
85
|
+
end
|
86
|
+
|
87
|
+
def coerce(other)
|
88
|
+
[self.class.new(other), self]
|
89
|
+
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
|
93
|
+
def <=>(other)
|
94
|
+
return nil unless other.respond_to?(:to_i)
|
95
|
+
|
96
|
+
seconds <=> other.to_i
|
97
|
+
end
|
98
|
+
|
99
|
+
end
|
100
|
+
end
|
data/lib/biz/holiday.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
module Biz
|
2
|
+
class Holiday
|
3
|
+
|
4
|
+
include Equalizer.new(:date, :time_zone)
|
5
|
+
|
6
|
+
attr_reader :date,
|
7
|
+
:time_zone
|
8
|
+
|
9
|
+
def initialize(date, time_zone)
|
10
|
+
@date = date
|
11
|
+
@time_zone = time_zone
|
12
|
+
end
|
13
|
+
|
14
|
+
def contains?(time)
|
15
|
+
date == Time.new(time_zone).local(time).to_date
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_time_segment
|
19
|
+
TimeSegment.new(
|
20
|
+
Time.new(time_zone).on_date(date, DayTime.midnight),
|
21
|
+
Time.new(time_zone).on_date(date, DayTime.endnight)
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/lib/biz/interval.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
module Biz
|
2
|
+
class Interval
|
3
|
+
|
4
|
+
include Equalizer.new(:start_time, :end_time, :time_zone)
|
5
|
+
|
6
|
+
attr_reader :start_time,
|
7
|
+
:end_time,
|
8
|
+
:time_zone
|
9
|
+
|
10
|
+
def initialize(start_time, end_time, time_zone)
|
11
|
+
@start_time = start_time
|
12
|
+
@end_time = end_time
|
13
|
+
@time_zone = time_zone
|
14
|
+
end
|
15
|
+
|
16
|
+
def endpoints
|
17
|
+
[start_time, end_time]
|
18
|
+
end
|
19
|
+
|
20
|
+
def contains?(time)
|
21
|
+
(start_time...end_time).cover?(
|
22
|
+
WeekTime.from_time(Time.new(time_zone).local(time))
|
23
|
+
)
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_time_segment(week)
|
27
|
+
TimeSegment.new(
|
28
|
+
*endpoints.map { |endpoint|
|
29
|
+
Time.new(time_zone).during_week(week, endpoint)
|
30
|
+
}
|
31
|
+
)
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
data/lib/biz/periods.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
module Biz
|
2
|
+
class Periods
|
3
|
+
|
4
|
+
attr_reader :schedule
|
5
|
+
|
6
|
+
def initialize(schedule)
|
7
|
+
@schedule = schedule
|
8
|
+
end
|
9
|
+
|
10
|
+
def after(origin)
|
11
|
+
After.new(schedule, origin)
|
12
|
+
end
|
13
|
+
|
14
|
+
def before(origin)
|
15
|
+
Before.new(schedule, origin)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'biz/periods/abstract'
|
22
|
+
|
23
|
+
require 'biz/periods/after'
|
24
|
+
require 'biz/periods/before'
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Biz
|
2
|
+
class Periods
|
3
|
+
class Abstract < Enumerator::Lazy
|
4
|
+
|
5
|
+
extend Forwardable
|
6
|
+
|
7
|
+
attr_reader :schedule,
|
8
|
+
:origin
|
9
|
+
|
10
|
+
def initialize(schedule, origin)
|
11
|
+
@schedule = schedule
|
12
|
+
@origin = origin
|
13
|
+
|
14
|
+
super(periods) do |yielder, period| yielder << period end
|
15
|
+
end
|
16
|
+
|
17
|
+
delegate time_zone: :schedule
|
18
|
+
|
19
|
+
def timeline
|
20
|
+
Timeline.new(self)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def periods
|
26
|
+
weeks.lazy.flat_map { |week|
|
27
|
+
business_periods(week)
|
28
|
+
}.select { |business_period|
|
29
|
+
relevant?(business_period)
|
30
|
+
}.map { |business_period|
|
31
|
+
business_period & boundary
|
32
|
+
}.reject { |business_period|
|
33
|
+
schedule.holidays.any? { |holiday|
|
34
|
+
holiday.contains?(business_period.start_time)
|
35
|
+
}
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def business_periods(week)
|
40
|
+
intervals.lazy.map { |interval| interval.to_time_segment(week) }
|
41
|
+
end
|
42
|
+
|
43
|
+
def intervals
|
44
|
+
schedule.intervals
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Biz
|
2
|
+
class Periods
|
3
|
+
class After < Abstract
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def weeks
|
8
|
+
Range.new(
|
9
|
+
Week.since_epoch(Time.new(time_zone).local(origin)),
|
10
|
+
Week.since_epoch(Time::HEAT_DEATH)
|
11
|
+
)
|
12
|
+
end
|
13
|
+
|
14
|
+
def relevant?(period)
|
15
|
+
origin < period.end_time
|
16
|
+
end
|
17
|
+
|
18
|
+
def boundary
|
19
|
+
TimeSegment.after(origin)
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Biz
|
2
|
+
class Periods
|
3
|
+
class Before < Abstract
|
4
|
+
|
5
|
+
private
|
6
|
+
|
7
|
+
def weeks
|
8
|
+
Week.since_epoch(Time.new(time_zone).local(origin)).downto(
|
9
|
+
Week.since_epoch(Time::BIG_BANG)
|
10
|
+
)
|
11
|
+
end
|
12
|
+
|
13
|
+
def relevant?(period)
|
14
|
+
origin > period.start_time
|
15
|
+
end
|
16
|
+
|
17
|
+
def boundary
|
18
|
+
TimeSegment.before(origin)
|
19
|
+
end
|
20
|
+
|
21
|
+
def intervals
|
22
|
+
super.reverse
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|