timeboss 0.3.0 → 1.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 +4 -4
- data/.github/workflows/gem-push.yml +31 -0
- data/.github/workflows/ruby.yml +35 -0
- data/.travis.yml +3 -2
- data/Gemfile +2 -1
- data/README.md +17 -7
- data/Rakefile +3 -1
- data/bin/tbsh +2 -2
- data/lib/tasks/calendars.rake +5 -5
- data/lib/tasks/timeboss.rake +2 -2
- data/lib/timeboss.rb +1 -0
- data/lib/timeboss/calendar.rb +5 -4
- data/lib/timeboss/calendar/day.rb +3 -2
- data/lib/timeboss/calendar/half.rb +2 -1
- data/lib/timeboss/calendar/month.rb +2 -1
- data/lib/timeboss/calendar/parser.rb +9 -8
- data/lib/timeboss/calendar/period.rb +88 -12
- data/lib/timeboss/calendar/quarter.rb +2 -1
- data/lib/timeboss/calendar/support/formatter.rb +5 -4
- data/lib/timeboss/calendar/support/month_basis.rb +1 -1
- data/lib/timeboss/calendar/support/monthly_unit.rb +7 -6
- data/lib/timeboss/calendar/support/navigable.rb +2 -1
- data/lib/timeboss/calendar/support/shiftable.rb +12 -11
- data/lib/timeboss/calendar/support/translatable.rb +3 -2
- data/lib/timeboss/calendar/support/unit.rb +18 -13
- data/lib/timeboss/calendar/waypoints/absolute.rb +4 -3
- data/lib/timeboss/calendar/waypoints/relative.rb +14 -13
- data/lib/timeboss/calendar/week.rb +3 -2
- data/lib/timeboss/calendar/year.rb +2 -1
- data/lib/timeboss/calendars.rb +3 -2
- data/lib/timeboss/calendars/broadcast.rb +8 -7
- data/lib/timeboss/calendars/gregorian.rb +2 -1
- data/lib/timeboss/version.rb +2 -1
- data/spec/calendar/day_spec.rb +14 -14
- data/spec/calendar/quarter_spec.rb +9 -9
- data/spec/calendar/support/monthly_unit_spec.rb +36 -35
- data/spec/calendar/support/unit_spec.rb +23 -22
- data/spec/calendar/week_spec.rb +20 -20
- data/spec/calendars/broadcast_spec.rb +310 -310
- data/spec/calendars/gregorian_spec.rb +258 -258
- data/spec/calendars_spec.rb +19 -19
- data/spec/spec_helper.rb +2 -2
- data/timeboss.gemspec +16 -14
- metadata +33 -5
- data/lib/timeboss/support/shellable.rb +0 -17
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require_relative "./translatable"
|
3
4
|
|
4
5
|
module TimeBoss
|
5
6
|
class Calendar
|
@@ -18,10 +19,10 @@ module TimeBoss
|
|
18
19
|
end
|
19
20
|
|
20
21
|
def to_s
|
21
|
-
base, text =
|
22
|
+
base, text = "year", unit.year.name
|
22
23
|
periods.each do |period|
|
23
|
-
sub = unit.
|
24
|
-
index = sub.
|
24
|
+
(sub = unit.public_send(period)) || break
|
25
|
+
index = sub.public_send("in_#{base}")
|
25
26
|
text += "#{period[0].upcase}#{index}"
|
26
27
|
base = period
|
27
28
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require_relative "./unit"
|
3
4
|
|
4
5
|
module TimeBoss
|
5
6
|
class Calendar
|
@@ -25,7 +26,7 @@ module TimeBoss
|
|
25
26
|
base = calendar.year(year_index)
|
26
27
|
num_weeks = (((base.end_date - base.start_date) + 1) / 7.0).to_i
|
27
28
|
num_weeks.times.map { |i| Week.new(calendar, base.start_date + (i * 7).days, base.start_date + ((i * 7) + 6).days) }
|
28
|
-
|
29
|
+
.select { |w| w.start_date.between?(start_date, end_date) }
|
29
30
|
end
|
30
31
|
|
31
32
|
private
|
@@ -36,17 +37,17 @@ module TimeBoss
|
|
36
37
|
|
37
38
|
def up
|
38
39
|
if index == max_index
|
39
|
-
calendar.
|
40
|
+
calendar.public_send(self.class.type, year_index + 1, 1)
|
40
41
|
else
|
41
|
-
calendar.
|
42
|
+
calendar.public_send(self.class.type, year_index, index + 1)
|
42
43
|
end
|
43
44
|
end
|
44
45
|
|
45
46
|
def down
|
46
47
|
if index == 1
|
47
|
-
calendar.
|
48
|
+
calendar.public_send(self.class.type, year_index - 1, max_index)
|
48
49
|
else
|
49
|
-
calendar.
|
50
|
+
calendar.public_send(self.class.type, year_index, index - 1)
|
50
51
|
end
|
51
52
|
end
|
52
53
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module TimeBoss
|
3
4
|
class Calendar
|
4
5
|
module Support
|
@@ -61,7 +62,7 @@ module TimeBoss
|
|
61
62
|
entry = self
|
62
63
|
while quantity > 0
|
63
64
|
entries << entry
|
64
|
-
entry = entry.
|
65
|
+
entry = entry.public_send(navigator)
|
65
66
|
quantity -= 1
|
66
67
|
end
|
67
68
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module TimeBoss
|
3
4
|
class Calendar
|
4
5
|
module Support
|
@@ -7,21 +8,21 @@ module TimeBoss
|
|
7
8
|
periods = period.pluralize
|
8
9
|
|
9
10
|
define_method("in_#{period}") do
|
10
|
-
base =
|
11
|
+
base = public_send(periods)
|
11
12
|
return unless base.length == 1
|
12
|
-
base.first.
|
13
|
+
base.first.public_send(self.class.type.to_s.pluralize).find_index { |p| p == self } + 1
|
13
14
|
end
|
14
15
|
|
15
16
|
define_method("#{periods}_ago") do |offset|
|
16
|
-
base_offset =
|
17
|
-
(calendar.
|
17
|
+
(base_offset = public_send("in_#{period}")) || return
|
18
|
+
(calendar.public_send("this_#{period}") - offset).public_send(self.class.type.to_s.pluralize)[base_offset - 1]
|
18
19
|
end
|
19
20
|
|
20
|
-
define_method("#{periods}_ahead") { |o|
|
21
|
+
define_method("#{periods}_ahead") { |o| public_send("#{periods}_ago", o * -1) }
|
21
22
|
|
22
|
-
define_method("last_#{period}") {
|
23
|
-
define_method("this_#{period}") {
|
24
|
-
define_method("next_#{period}") {
|
23
|
+
define_method("last_#{period}") { public_send("#{periods}_ago", 1) }
|
24
|
+
define_method("this_#{period}") { public_send("#{periods}_ago", 0) }
|
25
|
+
define_method("next_#{period}") { public_send("#{periods}_ahead", 1) }
|
25
26
|
end
|
26
27
|
|
27
28
|
alias_method :yesterday, :last_day
|
@@ -133,7 +134,7 @@ module TimeBoss
|
|
133
134
|
# Get the index-relative month 1 month forward.
|
134
135
|
# Returns nil if no single month can be identified.
|
135
136
|
# @return [Calendar::Month, nil]
|
136
|
-
|
137
|
+
|
137
138
|
### Quarters
|
138
139
|
|
139
140
|
# @!method in_quarter
|
@@ -167,7 +168,7 @@ module TimeBoss
|
|
167
168
|
# Get the index-relative quarter 1 quarter forward.
|
168
169
|
# Returns nil if no single quarter can be identified.
|
169
170
|
# @return [Calendar::Quarter, nil]
|
170
|
-
|
171
|
+
|
171
172
|
### Halves
|
172
173
|
|
173
174
|
# @!method in_half
|
@@ -201,7 +202,7 @@ module TimeBoss
|
|
201
202
|
# Get the index-relative half 1 half forward.
|
202
203
|
# Returns nil if no single half can be identified.
|
203
204
|
# @return [Calendar::Half, nil]
|
204
|
-
|
205
|
+
|
205
206
|
### Years
|
206
207
|
|
207
208
|
# @!method in_year
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module TimeBoss
|
3
4
|
class Calendar
|
4
5
|
module Support
|
@@ -8,10 +9,10 @@ module TimeBoss
|
|
8
9
|
PERIODS.each do |period|
|
9
10
|
periods = period.pluralize
|
10
11
|
|
11
|
-
define_method(periods) { calendar.
|
12
|
+
define_method(periods) { calendar.public_send("#{periods}_for", self) }
|
12
13
|
|
13
14
|
define_method(period) do |index = nil|
|
14
|
-
entries =
|
15
|
+
entries = public_send(periods)
|
15
16
|
return entries[index - 1] unless index.nil?
|
16
17
|
return nil unless entries.length == 1
|
17
18
|
entries.first
|
@@ -1,8 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
2
|
+
|
3
|
+
require_relative "./navigable"
|
4
|
+
require_relative "./translatable"
|
5
|
+
require_relative "./shiftable"
|
6
|
+
require_relative "./formatter"
|
6
7
|
|
7
8
|
module TimeBoss
|
8
9
|
class Calendar
|
@@ -16,7 +17,7 @@ module TimeBoss
|
|
16
17
|
UnsupportedUnitError = Class.new(StandardError)
|
17
18
|
|
18
19
|
def self.type
|
19
|
-
|
20
|
+
name.demodulize.underscore
|
20
21
|
end
|
21
22
|
|
22
23
|
def initialize(calendar, start_date, end_date)
|
@@ -28,8 +29,8 @@ module TimeBoss
|
|
28
29
|
# Is the specified unit equal to this one, based on its unit type and date range?
|
29
30
|
# @param entry [Unit] the unit to compare
|
30
31
|
# @return [Boolean] true when periods are equal
|
31
|
-
def ==(
|
32
|
-
self.class ==
|
32
|
+
def ==(other)
|
33
|
+
self.class == other.class && start_date == other.start_date && end_date == other.end_date
|
33
34
|
end
|
34
35
|
|
35
36
|
# Format this period based on specified granularities.
|
@@ -59,28 +60,32 @@ module TimeBoss
|
|
59
60
|
def offset(value)
|
60
61
|
method = value.negative? ? :previous : :next
|
61
62
|
base = self
|
62
|
-
value.abs.times { base = base.
|
63
|
+
value.abs.times { base = base.public_send(method) }
|
63
64
|
base
|
64
65
|
end
|
65
66
|
|
66
67
|
# Move some number of units forward from this unit.
|
67
68
|
# @param value [Integer]
|
68
69
|
# @return [Unit]
|
69
|
-
def +(
|
70
|
-
offset(
|
70
|
+
def +(other)
|
71
|
+
offset(other)
|
71
72
|
end
|
72
73
|
|
73
74
|
# Move some number of units backward from this unit.
|
74
75
|
# @param value [Integer]
|
75
76
|
# @return [Unit]
|
76
|
-
def -(
|
77
|
-
offset(-
|
77
|
+
def -(other)
|
78
|
+
offset(-other)
|
78
79
|
end
|
79
80
|
|
80
81
|
# Express this period as a date range.
|
81
82
|
# @return [Range<Date, Date>]
|
82
83
|
def to_range
|
83
|
-
@_to_range ||= start_date
|
84
|
+
@_to_range ||= start_date..end_date
|
85
|
+
end
|
86
|
+
|
87
|
+
def inspect
|
88
|
+
"#<#{self.class.name} start_date=#{start_date}, end_date=#{end_date}>"
|
84
89
|
end
|
85
90
|
end
|
86
91
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module TimeBoss
|
3
4
|
class Calendar
|
4
5
|
module Waypoints
|
@@ -9,13 +10,13 @@ module TimeBoss
|
|
9
10
|
|
10
11
|
define_method type do |year_index, index = 1|
|
11
12
|
month = (index * size) - size + 1
|
12
|
-
months = (month
|
13
|
+
months = (month..month + size - 1).map { |i| basis.new(year_index, i) }
|
13
14
|
klass.new(self, year_index, index, months.first.start_date, months.last.end_date)
|
14
15
|
end
|
15
16
|
|
16
17
|
define_method "#{type}_for" do |date|
|
17
|
-
window =
|
18
|
-
|
18
|
+
window = public_send(type, date.year - 1, 1)
|
19
|
+
loop do
|
19
20
|
break window if window.to_range.include?(date)
|
20
21
|
window = window.next
|
21
22
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module TimeBoss
|
3
4
|
class Calendar
|
4
5
|
module Waypoints
|
@@ -6,17 +7,17 @@ module TimeBoss
|
|
6
7
|
%i[day week month quarter half year].each do |type|
|
7
8
|
types = type.to_s.pluralize
|
8
9
|
|
9
|
-
define_method("this_#{type}") {
|
10
|
-
define_method("last_#{type}") {
|
11
|
-
define_method("next_#{type}") {
|
10
|
+
define_method("this_#{type}") { public_send("#{type}_for", Date.today) }
|
11
|
+
define_method("last_#{type}") { public_send("this_#{type}").previous }
|
12
|
+
define_method("next_#{type}") { public_send("this_#{type}").next }
|
12
13
|
|
13
|
-
define_method("#{types}_for") { |p|
|
14
|
+
define_method("#{types}_for") { |p| public_send("#{type}_for", p.start_date).until(p.end_date) }
|
14
15
|
|
15
|
-
define_method("#{types}_back") { |q|
|
16
|
-
define_method("#{types}_ago") { |q|
|
16
|
+
define_method("#{types}_back") { |q| public_send("this_#{type}").previous(q) }
|
17
|
+
define_method("#{types}_ago") { |q| public_send("this_#{type}").ago(q) }
|
17
18
|
|
18
|
-
define_method("#{types}_forward") { |q|
|
19
|
-
define_method("#{types}_ahead") { |q|
|
19
|
+
define_method("#{types}_forward") { |q| public_send("this_#{type}").next(q) }
|
20
|
+
define_method("#{types}_ahead") { |q| public_send("this_#{type}").ahead(q) }
|
20
21
|
alias_method types.to_sym, "#{types}_forward".to_sym
|
21
22
|
end
|
22
23
|
|
@@ -66,7 +67,7 @@ module TimeBoss
|
|
66
67
|
# Get the day that occurred the specified number of days ahead.
|
67
68
|
# @param quantity [Integer] the number of days after today
|
68
69
|
# @return [Calendar::Day]
|
69
|
-
|
70
|
+
|
70
71
|
### Weeks
|
71
72
|
|
72
73
|
# @!method this_week
|
@@ -105,7 +106,7 @@ module TimeBoss
|
|
105
106
|
# Get the week that occurred the specified number of weeks ahead.
|
106
107
|
# @param quantity [Integer] the number of weeks after this week
|
107
108
|
# @return [Calendar::Week]
|
108
|
-
|
109
|
+
|
109
110
|
### Months
|
110
111
|
|
111
112
|
# @!method this_month
|
@@ -144,7 +145,7 @@ module TimeBoss
|
|
144
145
|
# Get the month that occurred the specified number of months ahead.
|
145
146
|
# @param quantity [Integer] the number of months after this month
|
146
147
|
# @return [Calendar::Month]
|
147
|
-
|
148
|
+
|
148
149
|
### Quarters
|
149
150
|
|
150
151
|
# @!method this_quarter
|
@@ -183,7 +184,7 @@ module TimeBoss
|
|
183
184
|
# Get the quarter that occurred the specified number of days ahead.
|
184
185
|
# @param quantity [Integer] the number of quarters after this quarter
|
185
186
|
# @return [Calendar::Quarter]
|
186
|
-
|
187
|
+
|
187
188
|
### Halves
|
188
189
|
|
189
190
|
# @!method this_half
|
@@ -222,7 +223,7 @@ module TimeBoss
|
|
222
223
|
# Get the half that occurred the specified number of halves ahead.
|
223
224
|
# @param quantity [Integer] the number of halves after this half
|
224
225
|
# @return [Calendar::Half]
|
225
|
-
|
226
|
+
|
226
227
|
### Years
|
227
228
|
|
228
229
|
# @!method this_year
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require_relative "./support/unit"
|
3
4
|
|
4
5
|
module TimeBoss
|
5
6
|
class Calendar
|
@@ -18,7 +19,7 @@ module TimeBoss
|
|
18
19
|
# Get a "pretty" representation of this week.
|
19
20
|
# @return [String] (e.g. "Week of August 3, 2020")
|
20
21
|
def title
|
21
|
-
"Week of #{start_date.strftime(
|
22
|
+
"Week of #{start_date.strftime("%B %-d, %Y")}"
|
22
23
|
end
|
23
24
|
|
24
25
|
# Get a stringified representation of this week.
|
data/lib/timeboss/calendars.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require_relative "calendar"
|
3
4
|
|
4
5
|
module TimeBoss
|
5
6
|
module Calendars
|
@@ -50,4 +51,4 @@ module TimeBoss
|
|
50
51
|
end
|
51
52
|
end
|
52
53
|
|
53
|
-
Dir[File.expand_path(
|
54
|
+
Dir[File.expand_path("../calendars/*.rb", __FILE__)].each { |f| require f }
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require_relative "../calendar"
|
3
4
|
|
4
5
|
module TimeBoss
|
5
6
|
module Calendars
|
@@ -15,16 +16,16 @@ module TimeBoss
|
|
15
16
|
class Basis < Calendar::Support::MonthBasis
|
16
17
|
def start_date
|
17
18
|
@_start_date ||= begin
|
18
|
-
|
19
|
-
|
20
|
-
|
19
|
+
date = Date.civil(year, month, 1)
|
20
|
+
date - (date.wday + 6) % 7
|
21
|
+
end
|
21
22
|
end
|
22
23
|
|
23
24
|
def end_date
|
24
25
|
@_end_date ||= begin
|
25
|
-
|
26
|
-
|
27
|
-
|
26
|
+
date = Date.civil(year, month, -1)
|
27
|
+
date - date.wday
|
28
|
+
end
|
28
29
|
end
|
29
30
|
end
|
30
31
|
end
|
data/lib/timeboss/version.rb
CHANGED
data/spec/calendar/day_spec.rb
CHANGED
@@ -2,54 +2,54 @@ module TimeBoss
|
|
2
2
|
class Calendar
|
3
3
|
describe Day do
|
4
4
|
let(:calendar) { instance_double(TimeBoss::Calendar) }
|
5
|
-
let(:start_date) { Date.parse(
|
5
|
+
let(:start_date) { Date.parse("2019-09-30") }
|
6
6
|
let(:subject) { described_class.new(calendar, start_date) }
|
7
7
|
|
8
|
-
it
|
8
|
+
it "knows its stuff" do
|
9
9
|
expect(subject.start_date).to eq start_date
|
10
10
|
expect(subject.end_date).to eq start_date
|
11
11
|
expect(subject.to_range).to eq start_date..start_date
|
12
12
|
end
|
13
13
|
|
14
|
-
it
|
14
|
+
it "knows its name" do
|
15
15
|
expect(subject.name).to eq start_date.to_s
|
16
16
|
end
|
17
17
|
|
18
|
-
it
|
19
|
-
expect(subject.title).to eq
|
18
|
+
it "knows its title" do
|
19
|
+
expect(subject.title).to eq "September 30, 2019"
|
20
20
|
end
|
21
21
|
|
22
|
-
it
|
22
|
+
it "can stringify itself" do
|
23
23
|
expect(subject.to_s).to eq subject.name
|
24
24
|
end
|
25
25
|
|
26
|
-
describe
|
26
|
+
describe "#index" do
|
27
27
|
before(:each) { allow(subject).to receive(:year).and_return double(start_date: start_date - 3) }
|
28
28
|
|
29
|
-
it
|
29
|
+
it "gets its index within the year" do
|
30
30
|
expect(subject.index).to eq 4
|
31
31
|
end
|
32
32
|
end
|
33
33
|
|
34
|
-
describe
|
35
|
-
it
|
34
|
+
describe "#current?" do
|
35
|
+
it "knows when it is" do
|
36
36
|
allow(Date).to receive(:today).and_return start_date
|
37
37
|
expect(subject).to be_current
|
38
38
|
end
|
39
39
|
|
40
|
-
it
|
40
|
+
it "knows when it is not" do
|
41
41
|
expect(subject).not_to be_current
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
|
-
context
|
46
|
-
it
|
45
|
+
context "navigation" do
|
46
|
+
it "can get the previous date" do
|
47
47
|
result = subject.previous
|
48
48
|
expect(result).to be_a described_class
|
49
49
|
expect(result.start_date).to eq start_date - 1.day
|
50
50
|
end
|
51
51
|
|
52
|
-
it
|
52
|
+
it "can get the next date" do
|
53
53
|
result = subject.next
|
54
54
|
expect(result).to be_a described_class
|
55
55
|
expect(result.start_date).to eq start_date + 1.day
|