timeboss 0.3.0 → 1.0.5
Sign up to get free protection for your applications and to get access to all the features.
- 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
|