timeboss 0.1.0 → 0.2.3
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/.gitignore +5 -4
- data/.replit +2 -0
- data/README.md +15 -8
- data/bin/tbsh +1 -1
- data/lib/tasks/calendars.rake +4 -2
- data/lib/timeboss/calendar.rb +9 -1
- data/lib/timeboss/calendar/day.rb +0 -1
- data/lib/timeboss/calendar/half.rb +0 -1
- data/lib/timeboss/calendar/month.rb +0 -1
- data/lib/timeboss/calendar/parser.rb +0 -1
- data/lib/timeboss/calendar/period.rb +0 -1
- data/lib/timeboss/calendar/quarter.rb +0 -1
- data/lib/timeboss/calendar/support/formatter.rb +3 -2
- data/lib/timeboss/calendar/support/month_basis.rb +0 -2
- data/lib/timeboss/calendar/support/monthly_unit.rb +0 -2
- data/lib/timeboss/calendar/support/navigable.rb +0 -1
- data/lib/timeboss/calendar/support/shiftable.rb +218 -29
- data/lib/timeboss/calendar/support/translatable.rb +92 -0
- data/lib/timeboss/calendar/support/unit.rb +4 -1
- data/lib/timeboss/calendar/waypoints.rb +0 -1
- data/lib/timeboss/calendar/waypoints/absolute.rb +0 -1
- data/lib/timeboss/calendar/waypoints/relative.rb +0 -1
- data/lib/timeboss/calendar/week.rb +1 -1
- data/lib/timeboss/calendar/year.rb +0 -1
- data/lib/timeboss/calendars.rb +0 -1
- data/lib/timeboss/calendars/gregorian.rb +28 -0
- data/lib/timeboss/version.rb +1 -1
- data/spec/calendar/support/monthly_unit_spec.rb +6 -0
- data/spec/calendar/week_spec.rb +6 -1
- data/spec/calendars/gregorian_spec.rb +685 -0
- metadata +7 -49
- data/doc/TimeBoss.html +0 -146
- data/doc/TimeBoss/Calendar.html +0 -137
- data/doc/TimeBoss/Calendar/Day.html +0 -594
- data/doc/TimeBoss/Calendar/Half.html +0 -396
- data/doc/TimeBoss/Calendar/Month.html +0 -396
- data/doc/TimeBoss/Calendar/Parser.html +0 -386
- data/doc/TimeBoss/Calendar/Period.html +0 -841
- data/doc/TimeBoss/Calendar/Quarter.html +0 -396
- data/doc/TimeBoss/Calendar/Support.html +0 -131
- data/doc/TimeBoss/Calendar/Support/Formatter.html +0 -459
- data/doc/TimeBoss/Calendar/Support/MonthBased.html +0 -591
- data/doc/TimeBoss/Calendar/Support/MonthBasis.html +0 -437
- data/doc/TimeBoss/Calendar/Support/MonthlyUnit.html +0 -591
- data/doc/TimeBoss/Calendar/Support/Navigable.html +0 -723
- data/doc/TimeBoss/Calendar/Support/Shiftable.html +0 -138
- data/doc/TimeBoss/Calendar/Support/Unit.html +0 -1299
- data/doc/TimeBoss/Calendar/Waypoints.html +0 -155
- data/doc/TimeBoss/Calendar/Waypoints/Absolute.html +0 -1378
- data/doc/TimeBoss/Calendar/Waypoints/Relative.html +0 -4308
- data/doc/TimeBoss/Calendar/Week.html +0 -671
- data/doc/TimeBoss/Calendar/Year.html +0 -319
- data/doc/TimeBoss/Calendars.html +0 -336
- data/doc/TimeBoss/Calendars/Broadcast.html +0 -221
- data/doc/TimeBoss/Calendars/Broadcast/Basis.html +0 -278
- data/doc/TimeBoss/Calendars/Entry.html +0 -399
- data/doc/TimeBoss/Support.html +0 -115
- data/doc/TimeBoss/Support/Shellable.html +0 -249
- data/doc/_index.html +0 -416
- data/doc/class_list.html +0 -51
- data/doc/css/common.css +0 -1
- data/doc/css/full_list.css +0 -58
- data/doc/css/style.css +0 -496
- data/doc/file.README.html +0 -299
- data/doc/file_list.html +0 -56
- data/doc/frames.html +0 -17
- data/doc/index.html +0 -299
- data/doc/js/app.js +0 -314
- data/doc/js/full_list.js +0 -216
- data/doc/js/jquery.js +0 -4
- data/doc/method_list.html +0 -1139
- data/doc/top-level-namespace.html +0 -110
- data/lib/timeboss/calendar/support.rb +0 -8
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
module TimeBoss
|
3
|
+
class Calendar
|
4
|
+
module Support
|
5
|
+
module Translatable
|
6
|
+
PERIODS = %w[day week month quarter half year]
|
7
|
+
|
8
|
+
PERIODS.each do |period|
|
9
|
+
periods = period.pluralize
|
10
|
+
|
11
|
+
define_method(periods) { calendar.send("#{periods}_for", self) }
|
12
|
+
|
13
|
+
define_method(period) do
|
14
|
+
entries = send(periods)
|
15
|
+
return nil unless entries.length == 1
|
16
|
+
entries.first
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
#
|
21
|
+
# i hate this
|
22
|
+
#
|
23
|
+
|
24
|
+
### Days
|
25
|
+
|
26
|
+
# @!method days
|
27
|
+
# Get a list of days that fall within this unit.
|
28
|
+
# @return [Array<Calendar::Day>]
|
29
|
+
|
30
|
+
# @!method day
|
31
|
+
# Get the day this unit represents.
|
32
|
+
# Returns nil if no single day can be identified.
|
33
|
+
# @return [Array<Calendar::Day>, nil]
|
34
|
+
|
35
|
+
### Weeks
|
36
|
+
|
37
|
+
# @!method weeks
|
38
|
+
# Get a list of weeks that fall within this unit.
|
39
|
+
# @return [Array<Calendar::Week>]
|
40
|
+
|
41
|
+
# @!method week
|
42
|
+
# Get the week this unit represents.
|
43
|
+
# Returns nil if no single week can be identified.
|
44
|
+
# @return [Array<Calendar::Week>, nil]
|
45
|
+
|
46
|
+
### Months
|
47
|
+
|
48
|
+
# @!method months
|
49
|
+
# Get a list of months that fall within this unit.
|
50
|
+
# @return [Array<Calendar::Month>]
|
51
|
+
|
52
|
+
# @!method month
|
53
|
+
# Get the month this unit represents.
|
54
|
+
# Returns nil if no single month can be identified.
|
55
|
+
# @return [Array<Calendar::Month>, nil]
|
56
|
+
|
57
|
+
### Quarters
|
58
|
+
|
59
|
+
# @!method quarters
|
60
|
+
# Get a list of quarters that fall within this unit.
|
61
|
+
# @return [Array<Calendar::Quarter>]
|
62
|
+
|
63
|
+
# @!method quarter
|
64
|
+
# Get the quarter this unit represents.
|
65
|
+
# Returns nil if no single quarter can be identified.
|
66
|
+
# @return [Array<Calendar::Quarter>, nil]
|
67
|
+
|
68
|
+
### Halves
|
69
|
+
|
70
|
+
# @!method halves
|
71
|
+
# Get a list of halves that fall within this unit.
|
72
|
+
# @return [Array<Calendar::Half>]
|
73
|
+
|
74
|
+
# @!method half
|
75
|
+
# Get the half this unit represents.
|
76
|
+
# Returns nil if no single half can be identified.
|
77
|
+
# @return [Array<Calendar::Half>, nil]
|
78
|
+
|
79
|
+
### Years
|
80
|
+
|
81
|
+
# @!method years
|
82
|
+
# Get a list of years that fall within this unit.
|
83
|
+
# @return [Array<Calendar::Year>]
|
84
|
+
|
85
|
+
# @!method year
|
86
|
+
# Get the year this unit represents.
|
87
|
+
# Returns nil if no single year can be identified.
|
88
|
+
# @return [Array<Calendar::Year>, nil]
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -1,17 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
require_relative './navigable'
|
3
|
+
require_relative './translatable'
|
3
4
|
require_relative './shiftable'
|
4
5
|
require_relative './formatter'
|
5
6
|
|
6
7
|
module TimeBoss
|
7
8
|
class Calendar
|
8
9
|
module Support
|
9
|
-
# A unit is the lowest-level base of all days/weeks/months, etc.
|
10
10
|
class Unit
|
11
11
|
include Navigable
|
12
|
+
include Translatable
|
12
13
|
include Shiftable
|
13
14
|
attr_reader :calendar, :start_date, :end_date
|
14
15
|
|
16
|
+
UnsupportedUnitError = Class.new(StandardError)
|
17
|
+
|
15
18
|
def self.type
|
16
19
|
self.name.demodulize.underscore
|
17
20
|
end
|
@@ -3,9 +3,9 @@ require_relative './support/unit'
|
|
3
3
|
|
4
4
|
module TimeBoss
|
5
5
|
class Calendar
|
6
|
-
# Representation of a single week within a calendar.
|
7
6
|
class Week < Support::Unit
|
8
7
|
def initialize(calendar, start_date, end_date)
|
8
|
+
raise UnsupportedUnitError unless calendar.supports_weeks?
|
9
9
|
super(calendar, start_date, end_date)
|
10
10
|
end
|
11
11
|
|
data/lib/timeboss/calendars.rb
CHANGED
@@ -0,0 +1,28 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative '../calendar'
|
3
|
+
|
4
|
+
module TimeBoss
|
5
|
+
module Calendars
|
6
|
+
class Gregorian < Calendar
|
7
|
+
def initialize
|
8
|
+
super(basis: Basis)
|
9
|
+
end
|
10
|
+
|
11
|
+
def supports_weeks?
|
12
|
+
false
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
class Basis < Calendar::Support::MonthBasis
|
18
|
+
def start_date
|
19
|
+
@_start_date ||= Date.civil(year, month, 1)
|
20
|
+
end
|
21
|
+
|
22
|
+
def end_date
|
23
|
+
@_end_date ||= Date.civil(year, month, -1)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/timeboss/version.rb
CHANGED
@@ -30,6 +30,7 @@ module TimeBoss
|
|
30
30
|
before(:each) { allow(calendar).to receive(:year).with(2018).and_return base }
|
31
31
|
|
32
32
|
it 'can get the relevant weeks for the period' do
|
33
|
+
allow(calendar).to receive(:supports_weeks?).and_return true
|
33
34
|
result = subject.weeks
|
34
35
|
result.each { |w| expect(w).to be_instance_of TimeBoss::Calendar::Week }
|
35
36
|
expect(result.map { |w| w.start_date.to_s }).to eq [
|
@@ -44,6 +45,11 @@ module TimeBoss
|
|
44
45
|
'2018-08-20'
|
45
46
|
]
|
46
47
|
end
|
48
|
+
|
49
|
+
it 'blows up when weeks are not supported' do
|
50
|
+
allow(calendar).to receive(:supports_weeks?).and_return false
|
51
|
+
expect { subject.weeks }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError
|
52
|
+
end
|
47
53
|
end
|
48
54
|
|
49
55
|
context 'navigation' do
|
data/spec/calendar/week_spec.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
1
|
module TimeBoss
|
2
2
|
class Calendar
|
3
3
|
describe Week do
|
4
|
-
let(:calendar) { instance_double(TimeBoss::Calendar) }
|
4
|
+
let(:calendar) { instance_double(TimeBoss::Calendar, supports_weeks?: true) }
|
5
5
|
let(:start_date) { Date.parse('2048-04-06') }
|
6
6
|
let(:end_date) { Date.parse('2048-04-12') }
|
7
7
|
let(:subject) { described_class.new(calendar, start_date, end_date) }
|
8
8
|
|
9
|
+
it "doesn't even exist if its calendar doesn't support weeks" do
|
10
|
+
allow(calendar).to receive(:supports_weeks?).and_return false
|
11
|
+
expect { subject }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError
|
12
|
+
end
|
13
|
+
|
9
14
|
it 'knows its stuff' do
|
10
15
|
expect(subject.start_date).to eq start_date
|
11
16
|
expect(subject.end_date).to eq end_date
|
@@ -0,0 +1,685 @@
|
|
1
|
+
module TimeBoss
|
2
|
+
describe Calendars::Gregorian do
|
3
|
+
let(:subject) { described_class.new }
|
4
|
+
|
5
|
+
context 'days' do
|
6
|
+
it 'can get today' do
|
7
|
+
day = subject.today
|
8
|
+
expect(day).to be_instance_of(TimeBoss::Calendar::Day)
|
9
|
+
expect(day.start_date).to eq Date.today
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'can get yesterday' do
|
13
|
+
day = subject.yesterday
|
14
|
+
expect(day).to be_instance_of(TimeBoss::Calendar::Day)
|
15
|
+
expect(day.start_date).to eq Date.yesterday
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'can get tomorrow' do
|
19
|
+
day = subject.tomorrow
|
20
|
+
expect(day).to be_instance_of(TimeBoss::Calendar::Day)
|
21
|
+
expect(day.start_date).to eq Date.tomorrow
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
context 'quarters' do
|
26
|
+
describe '#quarter' do
|
27
|
+
it 'knows 2017Q2' do
|
28
|
+
quarter = subject.quarter(2017, 2)
|
29
|
+
expect(quarter.name).to eq '2017Q2'
|
30
|
+
expect(quarter.title).to eq 'Q2 2017'
|
31
|
+
expect(quarter.year_index).to eq 2017
|
32
|
+
expect(quarter.index).to eq 2
|
33
|
+
expect(quarter.start_date).to eq Date.parse('2017-04-01')
|
34
|
+
expect(quarter.end_date).to eq Date.parse('2017-06-30')
|
35
|
+
expect(quarter.to_range).to eq quarter.start_date..quarter.end_date
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'knows 2018Q3' do
|
39
|
+
quarter = subject.quarter(2018, 3)
|
40
|
+
expect(quarter.name).to eq '2018Q3'
|
41
|
+
expect(quarter.title).to eq 'Q3 2018'
|
42
|
+
expect(quarter.year_index).to eq 2018
|
43
|
+
expect(quarter.index).to eq 3
|
44
|
+
expect(quarter.start_date).to eq Date.parse('2018-07-01')
|
45
|
+
expect(quarter.end_date).to eq Date.parse('2018-09-30')
|
46
|
+
expect(quarter.to_range).to eq quarter.start_date..quarter.end_date
|
47
|
+
end
|
48
|
+
|
49
|
+
it 'knows 2019Q4' do
|
50
|
+
quarter = subject.quarter(2019, 4)
|
51
|
+
expect(quarter.year_index).to eq 2019
|
52
|
+
expect(quarter.index).to eq 4
|
53
|
+
expect(quarter.name).to eq '2019Q4'
|
54
|
+
expect(quarter.title).to eq 'Q4 2019'
|
55
|
+
expect(quarter.start_date).to eq Date.parse('2019-10-01')
|
56
|
+
expect(quarter.end_date).to eq Date.parse('2019-12-31')
|
57
|
+
expect(quarter.to_range).to eq quarter.start_date..quarter.end_date
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#quarter_for' do
|
62
|
+
it 'knows what quarter 2018-07-05 is in' do
|
63
|
+
quarter = subject.quarter_for(Date.parse('2018-07-05'))
|
64
|
+
expect(quarter.name).to eq '2018Q3'
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'knows what quarter 2018-06-22 is in' do
|
68
|
+
quarter = subject.quarter_for(Date.parse('2018-06-22'))
|
69
|
+
expect(quarter.name).to eq '2018Q2'
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '#quarters_for' do
|
74
|
+
it 'knows what quarters are in 2020' do
|
75
|
+
basis = subject.year(2020)
|
76
|
+
periods = subject.quarters_for(basis)
|
77
|
+
expect(periods.map(&:name)).to eq %w[2020Q1 2020Q2 2020Q3 2020Q4]
|
78
|
+
end
|
79
|
+
|
80
|
+
it 'knows what quarter 2018M7 is in' do
|
81
|
+
basis = subject.month(2018, 7)
|
82
|
+
periods = subject.quarters_for(basis)
|
83
|
+
expect(periods.map(&:name)).to eq %w[2018Q3]
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
describe '#this_quarter' do
|
88
|
+
let(:today) { double }
|
89
|
+
let(:quarter) { double }
|
90
|
+
|
91
|
+
it 'gets the quarter for today' do
|
92
|
+
allow(Date).to receive(:today).and_return today
|
93
|
+
expect(subject).to receive(:quarter_for).with(today).and_return quarter
|
94
|
+
expect(subject.this_quarter).to eq quarter
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe '#format' do
|
99
|
+
let(:entry) { subject.quarter(2015, 3) }
|
100
|
+
|
101
|
+
it 'can do a default format' do
|
102
|
+
expect(entry.format).to eq '2015H2Q1'
|
103
|
+
end
|
104
|
+
|
105
|
+
it 'can format with only the quarter' do
|
106
|
+
expect(entry.format(:quarter)).to eq '2015Q3'
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'ignores stupidity' do
|
110
|
+
expect(entry.format(:day, :banana)).to eq '2015Q3'
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
context 'relative' do
|
115
|
+
let(:this_quarter) { subject.quarter(2015, 3) }
|
116
|
+
let(:quarter) { double }
|
117
|
+
before(:each) { allow(subject).to receive(:this_quarter).and_return this_quarter }
|
118
|
+
|
119
|
+
it 'can get the last quarter' do
|
120
|
+
allow(this_quarter).to receive(:previous).and_return quarter
|
121
|
+
expect(subject.last_quarter).to eq quarter
|
122
|
+
end
|
123
|
+
|
124
|
+
it 'can get the next quarter' do
|
125
|
+
allow(this_quarter).to receive(:next).and_return quarter
|
126
|
+
expect(subject.next_quarter).to eq quarter
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'can get some number of quarters' do
|
130
|
+
quarters = subject.quarters(5)
|
131
|
+
expect(quarters.length).to eq 5
|
132
|
+
quarters.each { |q| expect(q).to be_a TimeBoss::Calendar::Quarter }
|
133
|
+
expect(quarters.map(&:name)).to eq ['2015Q3', '2015Q4', '2016Q1', '2016Q2', '2016Q3']
|
134
|
+
end
|
135
|
+
|
136
|
+
it 'can get a quarter ahead' do
|
137
|
+
quarter = subject.quarters_ahead(4)
|
138
|
+
expect(quarter).to be_a TimeBoss::Calendar::Quarter
|
139
|
+
expect(quarter.name).to eq '2016Q3'
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'can get some number of quarters back' do
|
143
|
+
quarters = subject.quarters_back(5)
|
144
|
+
expect(quarters.length).to eq 5
|
145
|
+
quarters.each { |q| expect(q).to be_a TimeBoss::Calendar::Quarter }
|
146
|
+
expect(quarters.map(&:name)).to eq ['2014Q3', '2014Q4', '2015Q1', '2015Q2', '2015Q3']
|
147
|
+
end
|
148
|
+
|
149
|
+
it 'can get a quarter ago' do
|
150
|
+
quarter = subject.quarters_ago(4)
|
151
|
+
expect(quarter).to be_a TimeBoss::Calendar::Quarter
|
152
|
+
expect(quarter.name).to eq '2014Q3'
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
context 'months' do
|
158
|
+
describe '#month' do
|
159
|
+
it 'knows 2017M2' do
|
160
|
+
month = subject.month(2017, 2)
|
161
|
+
expect(month.name).to eq '2017M2'
|
162
|
+
expect(month.title).to eq 'February 2017'
|
163
|
+
expect(month.year_index).to eq 2017
|
164
|
+
expect(month.index).to eq 2
|
165
|
+
expect(month.start_date).to eq Date.parse('2017-02-01')
|
166
|
+
expect(month.end_date).to eq Date.parse('2017-02-28')
|
167
|
+
expect(month.to_range).to eq month.start_date..month.end_date
|
168
|
+
end
|
169
|
+
|
170
|
+
it 'knows 2018M3' do
|
171
|
+
month = subject.month(2018, 3)
|
172
|
+
expect(month.name).to eq '2018M3'
|
173
|
+
expect(month.title).to eq 'March 2018'
|
174
|
+
expect(month.year_index).to eq 2018
|
175
|
+
expect(month.index).to eq 3
|
176
|
+
expect(month.start_date).to eq Date.parse('2018-03-01')
|
177
|
+
expect(month.end_date).to eq Date.parse('2018-03-31')
|
178
|
+
expect(month.to_range).to eq month.start_date..month.end_date
|
179
|
+
end
|
180
|
+
|
181
|
+
it 'knows 2019M11' do
|
182
|
+
month = subject.month(2019, 11)
|
183
|
+
expect(month.year_index).to eq 2019
|
184
|
+
expect(month.index).to eq 11
|
185
|
+
expect(month.name).to eq '2019M11'
|
186
|
+
expect(month.title).to eq 'November 2019'
|
187
|
+
expect(month.start_date).to eq Date.parse('2019-11-01')
|
188
|
+
expect(month.end_date).to eq Date.parse('2019-11-30')
|
189
|
+
expect(month.to_range).to eq month.start_date..month.end_date
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
describe '#month_for' do
|
194
|
+
it 'knows what month 2018-07-05 is in' do
|
195
|
+
month = subject.month_for(Date.parse('2018-07-05'))
|
196
|
+
expect(month.name).to eq '2018M7'
|
197
|
+
end
|
198
|
+
|
199
|
+
it 'knows what month 2018-06-22 is in' do
|
200
|
+
month = subject.month_for(Date.parse('2018-06-22'))
|
201
|
+
expect(month.name).to eq '2018M6'
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe '#months_for' do
|
206
|
+
it 'knows what months are in 2020' do
|
207
|
+
basis = subject.year(2020)
|
208
|
+
periods = subject.months_for(basis)
|
209
|
+
expect(periods.map(&:name)).to eq %w[2020M1 2020M2 2020M3 2020M4 2020M5 2020M6 2020M7 2020M8 2020M9 2020M10 2020M11 2020M12]
|
210
|
+
end
|
211
|
+
|
212
|
+
it 'knows what months are in 2018Q2' do
|
213
|
+
basis = subject.parse('2018Q2')
|
214
|
+
periods = subject.months_for(basis)
|
215
|
+
expect(periods.map(&:name)).to eq %w[2018M4 2018M5 2018M6]
|
216
|
+
end
|
217
|
+
|
218
|
+
it 'knows what month 2019-12-12 is in' do
|
219
|
+
basis = subject.parse('2019-12-12')
|
220
|
+
periods = subject.months_for(basis)
|
221
|
+
expect(periods.map(&:name)).to eq %w[2019M12]
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
describe '#this_month' do
|
226
|
+
let(:today) { double }
|
227
|
+
let(:month) { double }
|
228
|
+
|
229
|
+
it 'gets the month for today' do
|
230
|
+
allow(Date).to receive(:today).and_return today
|
231
|
+
expect(subject).to receive(:month_for).with(today).and_return month
|
232
|
+
expect(subject.this_month).to eq month
|
233
|
+
end
|
234
|
+
end
|
235
|
+
|
236
|
+
describe '#format' do
|
237
|
+
let(:entry) { subject.month(2015, 8) }
|
238
|
+
|
239
|
+
it 'can do a default format' do
|
240
|
+
expect(entry.format).to eq '2015H2Q1M2'
|
241
|
+
end
|
242
|
+
|
243
|
+
it 'can format with only the quarter' do
|
244
|
+
expect(entry.format(:quarter)).to eq '2015Q3M2'
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'ignores stupidity' do
|
248
|
+
expect(entry.format(:banana, :half, :week)).to eq '2015H2M2'
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
context 'relative' do
|
253
|
+
let(:this_month) { subject.month(2015, 3) }
|
254
|
+
let(:month) { double }
|
255
|
+
before(:each) { allow(subject).to receive(:this_month).and_return this_month }
|
256
|
+
|
257
|
+
it 'can get the last month' do
|
258
|
+
allow(this_month).to receive(:previous).and_return month
|
259
|
+
expect(subject.last_month).to eq month
|
260
|
+
end
|
261
|
+
|
262
|
+
it 'can get the next month' do
|
263
|
+
allow(this_month).to receive(:next).and_return month
|
264
|
+
expect(subject.next_month).to eq month
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'can get some number of months' do
|
268
|
+
months = subject.months(5)
|
269
|
+
expect(months.length).to eq 5
|
270
|
+
months.each { |m| expect(m).to be_a TimeBoss::Calendar::Month }
|
271
|
+
expect(months.map(&:name)).to eq ['2015M3', '2015M4', '2015M5', '2015M6', '2015M7']
|
272
|
+
end
|
273
|
+
|
274
|
+
it 'can get a month ahead' do
|
275
|
+
month = subject.months_ahead(4)
|
276
|
+
expect(month).to be_a TimeBoss::Calendar::Month
|
277
|
+
expect(month.name).to eq '2015M7'
|
278
|
+
end
|
279
|
+
|
280
|
+
it 'can get some number of months back' do
|
281
|
+
months = subject.months_back(5)
|
282
|
+
expect(months.length).to eq 5
|
283
|
+
months.each { |m| expect(m).to be_a TimeBoss::Calendar::Month }
|
284
|
+
expect(months.map(&:name)).to eq ['2014M11', '2014M12', '2015M1', '2015M2', '2015M3']
|
285
|
+
end
|
286
|
+
|
287
|
+
it 'can get a month ago' do
|
288
|
+
month = subject.months_ago(4)
|
289
|
+
expect(month).to be_a TimeBoss::Calendar::Month
|
290
|
+
expect(month.name).to eq '2014M11'
|
291
|
+
end
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
context 'weeks' do
|
296
|
+
it 'is uninterested in weeks' do
|
297
|
+
expect { subject.this_week }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError
|
298
|
+
expect { subject.parse('2020W3') }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError
|
299
|
+
expect { subject.weeks_ago(2) }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError
|
300
|
+
end
|
301
|
+
end
|
302
|
+
|
303
|
+
context 'years' do
|
304
|
+
describe '#year' do
|
305
|
+
it 'knows 2016' do
|
306
|
+
year = subject.year(2016)
|
307
|
+
expect(year.name).to eq '2016'
|
308
|
+
expect(year.title).to eq '2016'
|
309
|
+
expect(year.year_index).to eq 2016
|
310
|
+
expect(year.index).to eq 1
|
311
|
+
expect(year.start_date).to eq Date.parse('2016-01-01')
|
312
|
+
expect(year.end_date).to eq Date.parse('2016-12-31')
|
313
|
+
expect(year.to_range).to eq year.start_date..year.end_date
|
314
|
+
end
|
315
|
+
|
316
|
+
it 'knows 2017' do
|
317
|
+
year = subject.year(2017)
|
318
|
+
expect(year.name).to eq '2017'
|
319
|
+
expect(year.title).to eq '2017'
|
320
|
+
expect(year.year_index).to eq 2017
|
321
|
+
expect(year.index).to eq 1
|
322
|
+
expect(year.start_date).to eq Date.parse('2017-01-01')
|
323
|
+
expect(year.end_date).to eq Date.parse('2017-12-31')
|
324
|
+
expect(year.to_range).to eq year.start_date..year.end_date
|
325
|
+
end
|
326
|
+
|
327
|
+
it 'knows 2018' do
|
328
|
+
year = subject.year(2018)
|
329
|
+
expect(year.name).to eq '2018'
|
330
|
+
expect(year.title).to eq '2018'
|
331
|
+
expect(year.year_index).to eq 2018
|
332
|
+
expect(year.index).to eq 1
|
333
|
+
expect(year.start_date).to eq Date.parse('2018-01-01')
|
334
|
+
expect(year.end_date).to eq Date.parse('2018-12-31')
|
335
|
+
expect(year.to_range).to eq year.start_date..year.end_date
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
describe '#year_for' do
|
340
|
+
it 'knows what year 2018-04-07 is in' do
|
341
|
+
year = subject.year_for(Date.parse('2018-04-07'))
|
342
|
+
expect(year.name).to eq '2018'
|
343
|
+
end
|
344
|
+
|
345
|
+
it 'knows what year 2016-12-27 is in' do
|
346
|
+
year = subject.year_for(Date.parse('2016-12-27'))
|
347
|
+
expect(year.name).to eq '2016'
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
describe '#years_for' do
|
352
|
+
it 'knows what years are in 2020 (duh)' do
|
353
|
+
basis = subject.year(2020)
|
354
|
+
periods = subject.years_for(basis)
|
355
|
+
expect(periods.map(&:name)).to eq %w[2020]
|
356
|
+
end
|
357
|
+
|
358
|
+
it 'knows what year 2018Q2 is in' do
|
359
|
+
basis = subject.parse('2018Q2')
|
360
|
+
periods = subject.years_for(basis)
|
361
|
+
expect(periods.map(&:name)).to eq %w[2018]
|
362
|
+
end
|
363
|
+
|
364
|
+
it 'knows what years 2019-12-12 is in' do
|
365
|
+
basis = subject.parse('2019-12-12')
|
366
|
+
periods = subject.years_for(basis)
|
367
|
+
expect(periods.map(&:name)).to eq %w[2019]
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
describe '#this_year' do
|
372
|
+
let(:today) { double }
|
373
|
+
let(:year) { double }
|
374
|
+
|
375
|
+
it 'gets the year for today' do
|
376
|
+
allow(Date).to receive(:today).and_return today
|
377
|
+
expect(subject).to receive(:year_for).with(today).and_return year
|
378
|
+
expect(subject.this_year).to eq year
|
379
|
+
end
|
380
|
+
end
|
381
|
+
|
382
|
+
describe '#format' do
|
383
|
+
let(:entry) { subject.parse('2020M8') }
|
384
|
+
|
385
|
+
it 'can do a default format' do
|
386
|
+
expect(entry.format).to eq '2020H2Q1M2'
|
387
|
+
end
|
388
|
+
|
389
|
+
it 'can format with only the quarter' do
|
390
|
+
expect(entry.format(:quarter)).to eq '2020Q3M2'
|
391
|
+
end
|
392
|
+
|
393
|
+
context 'with days' do
|
394
|
+
let(:entry) { subject.parse('2020D201') }
|
395
|
+
|
396
|
+
it 'can do a default format' do
|
397
|
+
expect(entry.format).to eq '2020H2Q1M1D19'
|
398
|
+
end
|
399
|
+
end
|
400
|
+
end
|
401
|
+
|
402
|
+
context 'relative' do
|
403
|
+
let(:this_year) { subject.year(2015) }
|
404
|
+
let(:year) { double }
|
405
|
+
before(:each) { allow(subject).to receive(:this_year).and_return this_year }
|
406
|
+
|
407
|
+
it 'can get the last year' do
|
408
|
+
allow(this_year).to receive(:previous).and_return year
|
409
|
+
expect(subject.last_year).to eq year
|
410
|
+
end
|
411
|
+
|
412
|
+
it 'can get the next year' do
|
413
|
+
allow(this_year).to receive(:next).and_return year
|
414
|
+
expect(subject.next_year).to eq year
|
415
|
+
end
|
416
|
+
|
417
|
+
it 'can get some number of years' do
|
418
|
+
years = subject.years(5)
|
419
|
+
expect(years.length).to eq 5
|
420
|
+
years.each { |y| expect(y).to be_a TimeBoss::Calendar::Year }
|
421
|
+
expect(years.map(&:name)).to eq ['2015', '2016', '2017', '2018', '2019']
|
422
|
+
end
|
423
|
+
|
424
|
+
it 'can get some number of years back' do
|
425
|
+
years = subject.years_back(5)
|
426
|
+
expect(years.length).to eq 5
|
427
|
+
years.each { |y| expect(y).to be_a TimeBoss::Calendar::Year }
|
428
|
+
expect(years.map(&:name)).to eq ['2011', '2012', '2013', '2014', '2015']
|
429
|
+
end
|
430
|
+
end
|
431
|
+
end
|
432
|
+
|
433
|
+
describe '#parse' do
|
434
|
+
it 'can parse a year' do
|
435
|
+
date = subject.parse('2018')
|
436
|
+
expect(date).to be_a TimeBoss::Calendar::Year
|
437
|
+
expect(date.name).to eq '2018'
|
438
|
+
end
|
439
|
+
|
440
|
+
it 'can parse a quarter identifier' do
|
441
|
+
date = subject.parse('2017Q2')
|
442
|
+
expect(date).to be_a TimeBoss::Calendar::Quarter
|
443
|
+
expect(date.name).to eq '2017Q2'
|
444
|
+
end
|
445
|
+
|
446
|
+
it 'can parse a month identifier' do
|
447
|
+
date = subject.parse('2017M4')
|
448
|
+
expect(date).to be_a TimeBoss::Calendar::Month
|
449
|
+
expect(date.name).to eq '2017M4'
|
450
|
+
end
|
451
|
+
|
452
|
+
it 'cannot parse a week within a year' do
|
453
|
+
expect { subject.parse('2018W37') }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError
|
454
|
+
end
|
455
|
+
|
456
|
+
it 'cannot parse a week within a quarter' do
|
457
|
+
expect { subject.parse('2017Q2W2') }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError
|
458
|
+
end
|
459
|
+
|
460
|
+
it 'cannot parse a week within a month' do
|
461
|
+
expect { subject.parse('2017M4W1') }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError
|
462
|
+
end
|
463
|
+
|
464
|
+
it 'can parse a date' do
|
465
|
+
date = subject.parse('2017-04-08')
|
466
|
+
expect(date).to be_a TimeBoss::Calendar::Day
|
467
|
+
expect(date.start_date).to eq Date.parse('2017-04-08')
|
468
|
+
expect(date.end_date).to eq Date.parse('2017-04-08')
|
469
|
+
end
|
470
|
+
|
471
|
+
it 'can parse an aesthetically displeasing date' do
|
472
|
+
date = subject.parse('20170408')
|
473
|
+
expect(date).to be_a TimeBoss::Calendar::Day
|
474
|
+
expect(date.start_date).to eq Date.parse('2017-04-08')
|
475
|
+
expect(date.end_date).to eq Date.parse('2017-04-08')
|
476
|
+
end
|
477
|
+
|
478
|
+
it 'gives you this year if you give it nothing' do
|
479
|
+
year = subject.this_year
|
480
|
+
expect(subject.parse(nil)).to eq year
|
481
|
+
expect(subject.parse('')).to eq year
|
482
|
+
end
|
483
|
+
end
|
484
|
+
|
485
|
+
context 'expressions' do
|
486
|
+
it 'can parse waypoints' do
|
487
|
+
result = subject.parse('this_year')
|
488
|
+
expect(result).to be_a TimeBoss::Calendar::Year
|
489
|
+
expect(result).to be_current
|
490
|
+
end
|
491
|
+
|
492
|
+
it 'can parse mathematic expressions' do
|
493
|
+
result = subject.parse('this_month + 2')
|
494
|
+
expect(result).to be_a TimeBoss::Calendar::Month
|
495
|
+
expect(result).to eq subject.months_ahead(2)
|
496
|
+
end
|
497
|
+
|
498
|
+
context 'ranges' do
|
499
|
+
before(:each) { allow(subject).to receive(:this_year).and_return subject.year(2018) }
|
500
|
+
let(:result) { subject.parse('this_year-2 .. this_year') }
|
501
|
+
|
502
|
+
it 'can parse range expressions' do
|
503
|
+
expect(result).to be_a TimeBoss::Calendar::Period
|
504
|
+
expect(result.to_s).to eq "2016: 2016-01-01 thru 2016-12-31 .. 2018: 2018-01-01 thru 2018-12-31"
|
505
|
+
end
|
506
|
+
|
507
|
+
it 'can get an overall start date for a range' do
|
508
|
+
expect(result.start_date).to eq Date.parse('2016-01-01')
|
509
|
+
end
|
510
|
+
|
511
|
+
it 'can get an overall end date for a range' do
|
512
|
+
expect(result.end_date).to eq Date.parse('2018-12-31')
|
513
|
+
end
|
514
|
+
|
515
|
+
context 'sub-periods' do
|
516
|
+
it 'can get the months included in a range' do
|
517
|
+
entries = result.months
|
518
|
+
entries.each { |e| expect(e).to be_a TimeBoss::Calendar::Month }
|
519
|
+
expect(entries.map(&:name)).to include('2016M1', '2016M9', '2017M3', '2018M12')
|
520
|
+
end
|
521
|
+
|
522
|
+
it 'cannot get the weeks included in a range' do
|
523
|
+
expect { result.weeks }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError
|
524
|
+
end
|
525
|
+
|
526
|
+
it 'can get the days included in a range' do
|
527
|
+
entries = result.days
|
528
|
+
entries.each { |e| expect(e).to be_a TimeBoss::Calendar::Day }
|
529
|
+
expect(entries.map(&:name)).to include('2016-01-01', '2016-05-12', '2017-09-22', '2018-12-31')
|
530
|
+
end
|
531
|
+
end
|
532
|
+
end
|
533
|
+
end
|
534
|
+
|
535
|
+
context 'shifting' do
|
536
|
+
context 'from day' do
|
537
|
+
let(:basis) { subject.parse('2020-04-21') }
|
538
|
+
|
539
|
+
it 'cannot shift to a different week' do
|
540
|
+
expect { basis.last_week }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError
|
541
|
+
expect { basis.in_week }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError
|
542
|
+
end
|
543
|
+
|
544
|
+
it 'can shift to a different quarter' do
|
545
|
+
allow(subject).to receive(:this_quarter).and_return subject.parse('2020Q3')
|
546
|
+
result = basis.quarters_ago(2)
|
547
|
+
expect(result).to be_a TimeBoss::Calendar::Day
|
548
|
+
expect(result.to_s).to eq '2020-01-21'
|
549
|
+
expect(basis.in_quarter).to eq 21
|
550
|
+
end
|
551
|
+
|
552
|
+
it 'can shift to a different year' do
|
553
|
+
allow(subject).to receive(:this_year).and_return subject.parse('2019')
|
554
|
+
result = basis.years_ahead(3)
|
555
|
+
expect(result).to be_a TimeBoss::Calendar::Day
|
556
|
+
expect(result.to_s).to eq '2022-04-22'
|
557
|
+
expect(basis.in_year).to eq 112
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
context 'from month' do
|
562
|
+
let(:basis) { subject.parse('2017M4') }
|
563
|
+
|
564
|
+
it 'cannot shift to a different week' do
|
565
|
+
expect { basis.last_week }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError
|
566
|
+
expect { basis.in_week }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError
|
567
|
+
end
|
568
|
+
|
569
|
+
it 'can shift to a different year' do
|
570
|
+
allow(subject).to receive(:this_year).and_return subject.parse('2020')
|
571
|
+
result = basis.years_ahead(4)
|
572
|
+
expect(result).to be_a TimeBoss::Calendar::Month
|
573
|
+
expect(result.name).to eq '2024M4'
|
574
|
+
expect(basis.in_year).to eq 4
|
575
|
+
end
|
576
|
+
end
|
577
|
+
|
578
|
+
context 'from quarter' do
|
579
|
+
let(:basis) { subject.parse('2018Q2') }
|
580
|
+
|
581
|
+
it 'cannot shift to a different month' do
|
582
|
+
expect(basis.months_ago(4)).to be nil
|
583
|
+
expect(basis.in_month).to be nil
|
584
|
+
end
|
585
|
+
|
586
|
+
it 'can shift to a different half' do
|
587
|
+
allow(subject).to receive(:this_half).and_return subject.parse('2020H1')
|
588
|
+
result = basis.last_half
|
589
|
+
expect(result).to be_a TimeBoss::Calendar::Quarter
|
590
|
+
expect(result.name).to eq '2019Q4'
|
591
|
+
expect(basis.in_half).to eq 2
|
592
|
+
end
|
593
|
+
end
|
594
|
+
|
595
|
+
context 'from year' do
|
596
|
+
let(:basis) { subject.parse('2014') }
|
597
|
+
|
598
|
+
it 'cannot shift to a different half' do
|
599
|
+
expect(basis.next_half).to be nil
|
600
|
+
expect(basis.in_half).to be nil
|
601
|
+
end
|
602
|
+
|
603
|
+
it 'shifts to a different year, but knows how useless that is' do
|
604
|
+
allow(subject).to receive(:this_year).and_return subject.parse('2020')
|
605
|
+
result = basis.years_ago(2)
|
606
|
+
expect(result).to be_a TimeBoss::Calendar::Year
|
607
|
+
expect(result.name).to eq '2018'
|
608
|
+
expect(basis.in_year).to eq 1
|
609
|
+
end
|
610
|
+
end
|
611
|
+
end
|
612
|
+
|
613
|
+
context 'units' do
|
614
|
+
let(:calendar) { described_class.new }
|
615
|
+
|
616
|
+
context 'day' do
|
617
|
+
let(:start_date) { Date.parse('2019-09-30') }
|
618
|
+
let(:subject) { TimeBoss::Calendar::Day.new(calendar, start_date) }
|
619
|
+
|
620
|
+
context 'links' do
|
621
|
+
it 'can get its previous' do
|
622
|
+
expect(subject.previous.name).to eq '2019-09-29'
|
623
|
+
end
|
624
|
+
|
625
|
+
it 'can get its next' do
|
626
|
+
expect(subject.next.name).to eq '2019-10-01'
|
627
|
+
end
|
628
|
+
|
629
|
+
it 'can offset backwards' do
|
630
|
+
expect(subject.offset(-3).name).to eq '2019-09-27'
|
631
|
+
expect((subject - 3).name).to eq '2019-09-27'
|
632
|
+
end
|
633
|
+
|
634
|
+
it 'can offset forwards' do
|
635
|
+
expect(subject.offset(4).name).to eq '2019-10-04'
|
636
|
+
expect((subject + 4).name).to eq '2019-10-04'
|
637
|
+
end
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
641
|
+
context 'quarter' do
|
642
|
+
let(:start_date) { Date.parse('2019-10-01') }
|
643
|
+
let(:end_date) { Date.parse('2019-12-31') }
|
644
|
+
let(:subject) { TimeBoss::Calendar::Quarter.new(calendar, 2019, 4, start_date, end_date) }
|
645
|
+
|
646
|
+
context 'links' do
|
647
|
+
it 'can get the next quarter' do
|
648
|
+
quarter = subject.next
|
649
|
+
expect(quarter.to_s).to include('2020Q1', '2020-01-01', '2020-03-31')
|
650
|
+
end
|
651
|
+
|
652
|
+
it 'can get the next next quarter' do
|
653
|
+
quarter = subject.next.next
|
654
|
+
expect(quarter.to_s).to include('2020Q2', '2020-04-01', '2020-06-30')
|
655
|
+
end
|
656
|
+
|
657
|
+
it 'can get the next next previous quarter' do
|
658
|
+
quarter = subject.next.next.previous
|
659
|
+
expect(quarter.to_s).to include('2020Q1', '2020-01-01', '2020-03-31')
|
660
|
+
end
|
661
|
+
|
662
|
+
it 'can get the next previous quarter' do
|
663
|
+
quarter = subject.next.previous
|
664
|
+
expect(quarter.to_s).to eq subject.to_s
|
665
|
+
end
|
666
|
+
|
667
|
+
it 'can get the previous quarter' do
|
668
|
+
quarter = subject.previous
|
669
|
+
expect(quarter.to_s).to include('2019Q3', '2019-07-01', '2019-09-30')
|
670
|
+
end
|
671
|
+
|
672
|
+
it 'can offset backwards' do
|
673
|
+
expect(subject.offset(-4).name).to eq '2018Q4'
|
674
|
+
expect((subject - 4).name).to eq '2018Q4'
|
675
|
+
end
|
676
|
+
|
677
|
+
it 'can offset forwards' do
|
678
|
+
expect(subject.offset(2).name).to eq '2020Q2'
|
679
|
+
expect((subject + 2).name).to eq '2020Q2'
|
680
|
+
end
|
681
|
+
end
|
682
|
+
end
|
683
|
+
end
|
684
|
+
end
|
685
|
+
end
|