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