timeboss 0.0.5
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 +7 -0
- data/.gitignore +2 -0
- data/.rspec +2 -0
- data/.travis.yml +13 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +22 -0
- data/README.md +207 -0
- data/Rakefile +5 -0
- data/lib/tasks/calendars.rake +15 -0
- data/lib/tasks/timeboss.rake +6 -0
- data/lib/timeboss.rb +2 -0
- data/lib/timeboss/calendar.rb +28 -0
- data/lib/timeboss/calendar/day.rb +36 -0
- data/lib/timeboss/calendar/half.rb +18 -0
- data/lib/timeboss/calendar/month.rb +18 -0
- data/lib/timeboss/calendar/parser.rb +52 -0
- data/lib/timeboss/calendar/period.rb +69 -0
- data/lib/timeboss/calendar/quarter.rb +18 -0
- data/lib/timeboss/calendar/support/formatter.rb +31 -0
- data/lib/timeboss/calendar/support/month_based.rb +51 -0
- data/lib/timeboss/calendar/support/month_basis.rb +18 -0
- data/lib/timeboss/calendar/support/shiftable.rb +51 -0
- data/lib/timeboss/calendar/support/unit.rb +59 -0
- data/lib/timeboss/calendar/waypoints.rb +87 -0
- data/lib/timeboss/calendar/week.rb +45 -0
- data/lib/timeboss/calendar/year.rb +18 -0
- data/lib/timeboss/calendars.rb +30 -0
- data/lib/timeboss/calendars/broadcast.rb +30 -0
- data/lib/timeboss/support/shellable.rb +17 -0
- data/lib/timeboss/version.rb +4 -0
- data/spec/calendar/day_spec.rb +60 -0
- data/spec/calendar/quarter_spec.rb +32 -0
- data/spec/calendar/support/month_based_spec.rb +69 -0
- data/spec/calendar/support/unit_spec.rb +90 -0
- data/spec/calendar/week_spec.rb +83 -0
- data/spec/calendars/broadcast_spec.rb +777 -0
- data/spec/calendars_spec.rb +42 -0
- data/spec/spec_helper.rb +12 -0
- data/timeboss.gemspec +30 -0
- metadata +188 -0
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module TimeBoss
|
3
|
+
class Calendar
|
4
|
+
class Period
|
5
|
+
attr_reader :begin, :end
|
6
|
+
delegate :start_date, to: :begin
|
7
|
+
delegate :end_date, to: :end
|
8
|
+
|
9
|
+
%i[name title to_s].each do |message|
|
10
|
+
define_method(message) do
|
11
|
+
text = self.begin.send(message)
|
12
|
+
text = "#{text} #{Parser::RANGE_DELIMITER} #{self.end.send(message)}" unless self.end == self.begin
|
13
|
+
text
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
%w[week month quarter half year].each do |size|
|
18
|
+
define_method(size.pluralize) do
|
19
|
+
entry = calendar.send("#{size}_for", self.begin.start_date)
|
20
|
+
build_entries entry
|
21
|
+
end
|
22
|
+
|
23
|
+
define_method(size) do
|
24
|
+
entries = send(size.pluralize)
|
25
|
+
return nil unless entries.length == 1
|
26
|
+
entries.first
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def current?
|
31
|
+
to_range.include?(Date.today)
|
32
|
+
end
|
33
|
+
|
34
|
+
def days
|
35
|
+
to_range.map { |d| Day.new(calendar, d) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def day
|
39
|
+
entries = days
|
40
|
+
return nil unless entries.length == 1
|
41
|
+
entries.first
|
42
|
+
end
|
43
|
+
|
44
|
+
def to_range
|
45
|
+
@_to_range ||= start_date .. end_date
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
attr_reader :calendar
|
51
|
+
|
52
|
+
def initialize(calendar, begin_basis, end_basis = nil)
|
53
|
+
@calendar = calendar
|
54
|
+
@begin = begin_basis
|
55
|
+
@end = end_basis || @begin
|
56
|
+
end
|
57
|
+
|
58
|
+
def build_entries(entry)
|
59
|
+
return [] if entry.start_date > self.end.end_date
|
60
|
+
entries = [entry]
|
61
|
+
while entry.end_date < self.end.end_date
|
62
|
+
entry = entry.next
|
63
|
+
entries << entry
|
64
|
+
end
|
65
|
+
entries
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative './support/month_based'
|
3
|
+
|
4
|
+
module TimeBoss
|
5
|
+
class Calendar
|
6
|
+
class Quarter < Support::MonthBased
|
7
|
+
NUM_MONTHS = 3
|
8
|
+
|
9
|
+
def name
|
10
|
+
"#{year_index}Q#{index}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def title
|
14
|
+
"Q#{index} #{year_index}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative './shiftable'
|
3
|
+
|
4
|
+
module TimeBoss
|
5
|
+
class Calendar
|
6
|
+
module Support
|
7
|
+
private
|
8
|
+
|
9
|
+
class Formatter
|
10
|
+
PERIODS = Shiftable::PERIODS.reverse.map(&:to_sym).drop(1)
|
11
|
+
attr_reader :unit, :periods
|
12
|
+
|
13
|
+
def initialize(unit, periods)
|
14
|
+
@unit = unit
|
15
|
+
@periods = PERIODS & periods.map(&:to_sym).push(unit.class.type.to_sym)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_s
|
19
|
+
base, text = 'year', unit.year.name
|
20
|
+
periods.each do |period|
|
21
|
+
sub = unit.send(period) or break
|
22
|
+
index = sub.send("in_#{base}")
|
23
|
+
text += "#{period[0].upcase}#{index}"
|
24
|
+
base = period
|
25
|
+
end
|
26
|
+
text
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative './unit'
|
3
|
+
|
4
|
+
module TimeBoss
|
5
|
+
class Calendar
|
6
|
+
module Support
|
7
|
+
class MonthBased < Unit
|
8
|
+
attr_reader :year_index, :index
|
9
|
+
|
10
|
+
def initialize(calendar, year_index, index, start_date, end_date)
|
11
|
+
super(calendar, start_date, end_date)
|
12
|
+
@year_index = year_index
|
13
|
+
@index = index
|
14
|
+
end
|
15
|
+
|
16
|
+
def next
|
17
|
+
if index == max_index
|
18
|
+
calendar.send(self.class.type, year_index + 1, 1)
|
19
|
+
else
|
20
|
+
calendar.send(self.class.type, year_index, index + 1)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def previous
|
25
|
+
if index == 1
|
26
|
+
calendar.send(self.class.type, year_index - 1, max_index)
|
27
|
+
else
|
28
|
+
calendar.send(self.class.type, year_index, index - 1)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_s
|
33
|
+
"#{name}: #{start_date} thru #{end_date}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def weeks
|
37
|
+
base = calendar.year(year_index)
|
38
|
+
num_weeks = (((base.end_date - base.start_date) + 1) / 7.0).to_i
|
39
|
+
num_weeks.times.map { |i| Week.new(calendar, year_index, i + 1, base.start_date + (i * 7).days, base.start_date + ((i * 7) + 6).days) }
|
40
|
+
.select { |w| w.start_date.between?(start_date, end_date) }
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def max_index
|
46
|
+
12 / self.class::NUM_MONTHS
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module TimeBoss
|
2
|
+
class Calendar
|
3
|
+
module Support
|
4
|
+
class MonthBasis
|
5
|
+
attr_reader :year, :month
|
6
|
+
|
7
|
+
def initialize(year, month)
|
8
|
+
@year = year
|
9
|
+
@month = month
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_range
|
13
|
+
@_to_range ||= start_date .. end_date
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module TimeBoss
|
3
|
+
class Calendar
|
4
|
+
module Support
|
5
|
+
module Shiftable
|
6
|
+
PERIODS = %w[day week month quarter half year]
|
7
|
+
|
8
|
+
PERIODS.each do |period|
|
9
|
+
periods = period.pluralize
|
10
|
+
|
11
|
+
define_method periods do
|
12
|
+
calendar.send("#{periods}_for", self)
|
13
|
+
end
|
14
|
+
|
15
|
+
define_method period do
|
16
|
+
entries = send(periods)
|
17
|
+
return nil unless entries.length == 1
|
18
|
+
entries.first
|
19
|
+
end
|
20
|
+
|
21
|
+
define_method "in_#{period}" do
|
22
|
+
base = send(periods)
|
23
|
+
return unless base.length == 1
|
24
|
+
base.first.send(self.class.type.to_s.pluralize).find_index { |p| p == self } + 1
|
25
|
+
end
|
26
|
+
|
27
|
+
define_method "#{periods}_ago" do |offset|
|
28
|
+
base_offset = send("in_#{period}") or return
|
29
|
+
(calendar.send("this_#{period}") - offset).send(self.class.type.to_s.pluralize)[base_offset - 1]
|
30
|
+
end
|
31
|
+
|
32
|
+
define_method "last_#{period}" do
|
33
|
+
send("#{periods}_ago", 1)
|
34
|
+
end
|
35
|
+
|
36
|
+
define_method "this_#{period}" do
|
37
|
+
send("#{periods}_ago", 0)
|
38
|
+
end
|
39
|
+
|
40
|
+
define_method "#{periods}_hence" do |offset|
|
41
|
+
send("#{periods}_ago", offset * -1)
|
42
|
+
end
|
43
|
+
|
44
|
+
define_method "next_#{period}" do
|
45
|
+
send("#{periods}_hence", 1)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative './shiftable'
|
3
|
+
require_relative './formatter'
|
4
|
+
|
5
|
+
module TimeBoss
|
6
|
+
class Calendar
|
7
|
+
module Support
|
8
|
+
class Unit
|
9
|
+
include Shiftable
|
10
|
+
attr_reader :calendar, :start_date, :end_date
|
11
|
+
|
12
|
+
def self.type
|
13
|
+
self.name.demodulize.underscore
|
14
|
+
end
|
15
|
+
|
16
|
+
def initialize(calendar, start_date, end_date)
|
17
|
+
@calendar = calendar
|
18
|
+
@start_date = start_date
|
19
|
+
@end_date = end_date
|
20
|
+
end
|
21
|
+
|
22
|
+
def ==(entry)
|
23
|
+
self.class == entry.class && self.start_date == entry.start_date && self.end_date == entry.end_date
|
24
|
+
end
|
25
|
+
|
26
|
+
def format(*periods)
|
27
|
+
Formatter.new(self, periods.presence || Formatter::PERIODS).to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def thru(unit)
|
31
|
+
Period.new(calendar, self, unit)
|
32
|
+
end
|
33
|
+
|
34
|
+
def current?
|
35
|
+
Date.today.between?(start_date, end_date)
|
36
|
+
end
|
37
|
+
|
38
|
+
def offset(value)
|
39
|
+
method = value.negative? ? :previous : :next
|
40
|
+
base = self
|
41
|
+
value.abs.times { base = base.send(method) }
|
42
|
+
base
|
43
|
+
end
|
44
|
+
|
45
|
+
def +(value)
|
46
|
+
offset(value)
|
47
|
+
end
|
48
|
+
|
49
|
+
def -(value)
|
50
|
+
offset(-value)
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_range
|
54
|
+
@_to_range ||= start_date .. end_date
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module TimeBoss
|
3
|
+
class Calendar
|
4
|
+
module Waypoints
|
5
|
+
%i[month quarter half year].each do |type|
|
6
|
+
klass = TimeBoss::Calendar.const_get(type.to_s.classify)
|
7
|
+
size = klass.const_get("NUM_MONTHS")
|
8
|
+
|
9
|
+
define_method type do |year_index, index = 1|
|
10
|
+
month = (index * size) - size + 1
|
11
|
+
months = (month .. month + size - 1).map { |i| basis.new(year_index, i) }
|
12
|
+
klass.new(self, year_index, index, months.first.start_date, months.last.end_date)
|
13
|
+
end
|
14
|
+
|
15
|
+
define_method "#{type}_for" do |date|
|
16
|
+
window = send(type, date.year - 1, 1)
|
17
|
+
while true
|
18
|
+
break window if window.to_range.include?(date)
|
19
|
+
window = window.next
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
%i[day week month quarter half year].each do |type|
|
25
|
+
define_method("this_#{type}") { send("#{type}_for", Date.today) }
|
26
|
+
define_method("last_#{type}") { send("this_#{type}").previous }
|
27
|
+
define_method("next_#{type}") { send("this_#{type}").next }
|
28
|
+
|
29
|
+
define_method "#{type.to_s.pluralize}_for" do |entry|
|
30
|
+
first = send("#{type}_for", entry.start_date)
|
31
|
+
constituents = [first]
|
32
|
+
until first.end_date >= entry.end_date
|
33
|
+
first = first.next
|
34
|
+
constituents << first
|
35
|
+
end
|
36
|
+
constituents
|
37
|
+
end
|
38
|
+
|
39
|
+
define_method "#{type.to_s.pluralize}_back" do |quantity|
|
40
|
+
windows = []
|
41
|
+
window = send("this_#{type}")
|
42
|
+
while quantity > 0
|
43
|
+
windows << window.dup
|
44
|
+
window = window.previous
|
45
|
+
quantity -= 1
|
46
|
+
end
|
47
|
+
windows.reverse
|
48
|
+
end
|
49
|
+
|
50
|
+
define_method "#{type.to_s.pluralize}_ago" do |quantity|
|
51
|
+
send("#{type.to_s.pluralize}_back", quantity + 1).first
|
52
|
+
end
|
53
|
+
|
54
|
+
define_method type.to_s.pluralize do |quantity|
|
55
|
+
windows = []
|
56
|
+
window = send("this_#{type}")
|
57
|
+
while quantity > 0
|
58
|
+
windows << window.dup
|
59
|
+
window = window.next
|
60
|
+
quantity -= 1
|
61
|
+
end
|
62
|
+
windows
|
63
|
+
end
|
64
|
+
|
65
|
+
define_method "#{type.to_s.pluralize}_hence" do |quantity|
|
66
|
+
send(type.to_s.pluralize, quantity + 1).last
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
%i[yesterday today tomorrow].each do |period|
|
71
|
+
define_method(period) { Day.new(self, Date.send(period)) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def day(year_index, index)
|
75
|
+
year(year_index).days[index - 1]
|
76
|
+
end
|
77
|
+
|
78
|
+
def day_for(date)
|
79
|
+
Day.new(self, date)
|
80
|
+
end
|
81
|
+
|
82
|
+
def week_for(date)
|
83
|
+
year_for(date).weeks.find { |w| w.to_range.include?(date) }
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative './support/unit'
|
3
|
+
|
4
|
+
module TimeBoss
|
5
|
+
class Calendar
|
6
|
+
class Week < Support::Unit
|
7
|
+
attr_reader :year_index, :index
|
8
|
+
|
9
|
+
def initialize(calendar, year_index, index, start_date, end_date)
|
10
|
+
super(calendar, start_date, end_date)
|
11
|
+
@year_index = year_index
|
12
|
+
@index = index
|
13
|
+
end
|
14
|
+
|
15
|
+
def name
|
16
|
+
"#{year_index}W#{index}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def title
|
20
|
+
"Week of #{start_date.strftime('%B %-d, %Y')}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_s
|
24
|
+
"#{name}: #{start_date} thru #{end_date}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def previous
|
28
|
+
if index == 1
|
29
|
+
(calendar.year_for(start_date) - 1).weeks.last
|
30
|
+
else
|
31
|
+
self.class.new(calendar, year_index, index - 1, start_date - 1.week, end_date - 1.week)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def next
|
36
|
+
weeks = calendar.year_for(start_date).weeks
|
37
|
+
if index == weeks.last.index
|
38
|
+
self.class.new(calendar, year_index + 1, 1, start_date + 1.week, end_date + 1.week)
|
39
|
+
else
|
40
|
+
weeks[index]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative './support/month_based'
|
3
|
+
|
4
|
+
module TimeBoss
|
5
|
+
class Calendar
|
6
|
+
class Year < Support::MonthBased
|
7
|
+
NUM_MONTHS = 12
|
8
|
+
|
9
|
+
def name
|
10
|
+
year_index.to_s
|
11
|
+
end
|
12
|
+
|
13
|
+
def title
|
14
|
+
name
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|