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,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require 'active_support/core_ext/class/subclasses'
|
3
|
+
require './lib/timeboss/calendar'
|
4
|
+
Dir['./lib/timeboss/calendars/*.rb'].each { |f| require f }
|
5
|
+
|
6
|
+
module TimeBoss
|
7
|
+
module Calendars
|
8
|
+
extend self
|
9
|
+
extend Enumerable
|
10
|
+
delegate :each, :length, to: :all
|
11
|
+
|
12
|
+
def all
|
13
|
+
@_all ||= TimeBoss::Calendar.subclasses.map do |klass|
|
14
|
+
Entry.new(klass.to_s.demodulize.underscore.to_sym, klass)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](name)
|
19
|
+
find { |e| e.name == name.to_sym }&.calendar
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
Entry = Struct.new(:name, :klass) do
|
25
|
+
def calendar
|
26
|
+
@_calendar ||= klass.new
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
require_relative '../calendar'
|
3
|
+
|
4
|
+
module TimeBoss
|
5
|
+
module Calendars
|
6
|
+
class Broadcast < Calendar
|
7
|
+
def initialize
|
8
|
+
super(basis: Basis)
|
9
|
+
end
|
10
|
+
|
11
|
+
private
|
12
|
+
|
13
|
+
class Basis < Calendar::Support::MonthBasis
|
14
|
+
def start_date
|
15
|
+
@_start_date ||= begin
|
16
|
+
date = Date.civil(year, month, 1)
|
17
|
+
date - (date.wday + 6) % 7
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def end_date
|
22
|
+
@_end_date ||= begin
|
23
|
+
date = Date.civil(year, month, -1)
|
24
|
+
date - date.wday
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module TimeBoss
|
2
|
+
module Support
|
3
|
+
module Shellable
|
4
|
+
def self.open(context)
|
5
|
+
context.extend(self).open_shell
|
6
|
+
end
|
7
|
+
|
8
|
+
def open_shell
|
9
|
+
require 'irb'
|
10
|
+
IRB.setup nil
|
11
|
+
IRB.conf[:MAIN_CONTEXT] = IRB::Irb.new.context
|
12
|
+
require 'irb/ext/multi-irb'
|
13
|
+
IRB.irb nil, self
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module TimeBoss
|
2
|
+
class Calendar
|
3
|
+
describe Day do
|
4
|
+
let(:calendar) { instance_double(TimeBoss::Calendar) }
|
5
|
+
let(:start_date) { Date.parse('2019-09-30') }
|
6
|
+
let(:subject) { described_class.new(calendar, start_date) }
|
7
|
+
|
8
|
+
it 'knows its stuff' do
|
9
|
+
expect(subject.start_date).to eq start_date
|
10
|
+
expect(subject.end_date).to eq start_date
|
11
|
+
expect(subject.to_range).to eq start_date..start_date
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'knows its name' do
|
15
|
+
expect(subject.name).to eq start_date.to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'knows its title' do
|
19
|
+
expect(subject.title).to eq 'September 30, 2019'
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'can stringify itself' do
|
23
|
+
expect(subject.to_s).to eq subject.name
|
24
|
+
end
|
25
|
+
|
26
|
+
describe '#index' do
|
27
|
+
before(:each) { allow(calendar).to receive(:year_for).with(start_date).and_return double(start_date: start_date - 3) }
|
28
|
+
|
29
|
+
it 'gets its index within the year' do
|
30
|
+
expect(subject.index).to eq 4
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#current?' do
|
35
|
+
it 'knows when it is' do
|
36
|
+
allow(Date).to receive(:today).and_return start_date
|
37
|
+
expect(subject).to be_current
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'knows when it is not' do
|
41
|
+
expect(subject).not_to be_current
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'navigation' do
|
46
|
+
it 'can get the previous date' do
|
47
|
+
result = subject.previous
|
48
|
+
expect(result).to be_a described_class
|
49
|
+
expect(result.start_date).to eq start_date - 1.day
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'can get the next date' do
|
53
|
+
result = subject.next
|
54
|
+
expect(result).to be_a described_class
|
55
|
+
expect(result.start_date).to eq start_date + 1.day
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module TimeBoss
|
2
|
+
class Calendar
|
3
|
+
describe Quarter do
|
4
|
+
let(:calendar) { instance_double(TimeBoss::Calendar) }
|
5
|
+
let(:start_date) { Date.parse('2019-09-30') }
|
6
|
+
let(:end_date) { Date.parse('2019-12-29') }
|
7
|
+
let(:subject) { described_class.new(calendar, 2019, 4, start_date, end_date) }
|
8
|
+
|
9
|
+
it 'knows its stuff' do
|
10
|
+
expect(subject.name).to eq '2019Q4'
|
11
|
+
expect(subject.start_date).to eq start_date
|
12
|
+
expect(subject.end_date).to eq end_date
|
13
|
+
expect(subject.to_range).to eq start_date..end_date
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'can stringify itself' do
|
17
|
+
expect(subject.to_s).to include('2019Q4', start_date.to_s, end_date.to_s)
|
18
|
+
end
|
19
|
+
|
20
|
+
describe '#current?' do
|
21
|
+
it 'knows when it is' do
|
22
|
+
allow(Date).to receive(:today).and_return start_date
|
23
|
+
expect(subject).to be_current
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'knows when it is not' do
|
27
|
+
expect(subject).not_to be_current
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module TimeBoss
|
2
|
+
class Calendar
|
3
|
+
module Support
|
4
|
+
describe MonthBased do
|
5
|
+
class ChunkMonthBased < described_class
|
6
|
+
NUM_MONTHS = 2
|
7
|
+
|
8
|
+
def name
|
9
|
+
"#{year_index}C#{index}"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
let(:described_class) { ChunkMonthBased }
|
13
|
+
let(:calendar) { double }
|
14
|
+
let(:start_date) { Date.parse('2018-06-25') }
|
15
|
+
let(:end_date) { Date.parse('2018-08-26') }
|
16
|
+
let(:subject) { described_class.new(calendar, 2018, 4, start_date, end_date) }
|
17
|
+
|
18
|
+
it 'knows its stuff' do
|
19
|
+
expect(subject.start_date).to eq start_date
|
20
|
+
expect(subject.end_date).to eq end_date
|
21
|
+
expect(subject.to_range).to eq start_date..end_date
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'can stringify itself' do
|
25
|
+
expect(subject.to_s).to eq "2018C4: 2018-06-25 thru 2018-08-26"
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#weeks' do
|
29
|
+
let(:base) { double(start_date: Date.parse('2018-01-01'), end_date: Date.parse('2018-12-30')) }
|
30
|
+
before(:each) { allow(calendar).to receive(:year).with(2018).and_return base }
|
31
|
+
|
32
|
+
it 'can get the relevant weeks for the period' do
|
33
|
+
result = subject.weeks
|
34
|
+
result.each { |w| expect(w).to be_instance_of TimeBoss::Calendar::Week }
|
35
|
+
expect(result.map(&:name)).to eq %w[2018W26 2018W27 2018W28 2018W29 2018W30 2018W31 2018W32 2018W33 2018W34]
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'navigation' do
|
40
|
+
let(:result) { double }
|
41
|
+
|
42
|
+
describe '#previous' do
|
43
|
+
it 'moves easily within itself' do
|
44
|
+
expect(calendar).to receive(:chunk_month_based).with(48, 3).and_return result
|
45
|
+
expect(described_class.new(calendar, 48, 4, nil, nil).previous).to eq result
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'flips to the previous container' do
|
49
|
+
expect(calendar).to receive(:chunk_month_based).with(47, 6).and_return result
|
50
|
+
expect(described_class.new(calendar, 48, 1, nil, nil).previous).to eq result
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe '#next' do
|
55
|
+
it 'moves easily within itself' do
|
56
|
+
expect(calendar).to receive(:chunk_month_based).with(48, 3).and_return result
|
57
|
+
expect(described_class.new(calendar, 48, 2, nil, nil).next).to eq result
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'flips to the previous container' do
|
61
|
+
expect(calendar).to receive(:chunk_month_based).with(48, 1).and_return result
|
62
|
+
expect(described_class.new(calendar, 47, 6, nil, nil).next).to eq result
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module TimeBoss
|
2
|
+
class Calendar
|
3
|
+
module Support
|
4
|
+
describe Unit do
|
5
|
+
class ChunkUnit < described_class
|
6
|
+
end
|
7
|
+
let(:described_class) { ChunkUnit }
|
8
|
+
let(:calendar) { double }
|
9
|
+
let(:start_date) { Date.parse('2018-06-25') }
|
10
|
+
let(:end_date) { Date.parse('2018-08-26') }
|
11
|
+
let(:subject) { described_class.new(calendar, start_date, end_date) }
|
12
|
+
|
13
|
+
it 'knows its stuff' do
|
14
|
+
expect(subject.start_date).to eq start_date
|
15
|
+
expect(subject.end_date).to eq end_date
|
16
|
+
expect(subject.to_range).to eq start_date..end_date
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#current?' do
|
20
|
+
it 'is not current right now' do
|
21
|
+
expect(subject).not_to be_current
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'is current when today falls in the middle' do
|
25
|
+
allow(Date).to receive(:today).and_return start_date + 3.days
|
26
|
+
expect(subject).to be_current
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
context 'periods' do
|
31
|
+
before(:each) do
|
32
|
+
allow(calendar).to receive(:days_for).with(subject).and_return %w[D1 D2 D3 D4 D5 D6 D7 D8]
|
33
|
+
allow(calendar).to receive(:weeks_for).with(subject).and_return %w[W1 W2 W3 W4]
|
34
|
+
allow(calendar).to receive(:months_for).with(subject).and_return %w[M1 M2 M3]
|
35
|
+
allow(calendar).to receive(:quarters_for).with(subject).and_return %w[Q1 Q2]
|
36
|
+
allow(calendar).to receive(:halves_for).with(subject).and_return %w[H1]
|
37
|
+
allow(calendar).to receive(:years_for).with(subject).and_return %w[Y1]
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'knows about its days' do
|
41
|
+
expect(subject.days).to eq %w[D1 D2 D3 D4 D5 D6 D7 D8]
|
42
|
+
expect(subject.day).to be nil
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'knows about its weeks' do
|
46
|
+
expect(subject.weeks).to eq %w[W1 W2 W3 W4]
|
47
|
+
expect(subject.week).to be nil
|
48
|
+
end
|
49
|
+
|
50
|
+
it 'knows about its months' do
|
51
|
+
expect(subject.months).to eq %w[M1 M2 M3]
|
52
|
+
expect(subject.month).to be nil
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'knows about its quarters' do
|
56
|
+
expect(subject.quarters).to eq %w[Q1 Q2]
|
57
|
+
expect(subject.quarter).to be nil
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'knows about its halves' do
|
61
|
+
expect(subject.halves).to eq %w[H1]
|
62
|
+
expect(subject.half).to eq 'H1'
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'knows about its years' do
|
66
|
+
expect(subject.years).to eq %w[Y1]
|
67
|
+
expect(subject.year).to eq 'Y1'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
context 'navigation' do
|
72
|
+
let(:result) { double }
|
73
|
+
|
74
|
+
describe '#offset' do
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'can increment' do
|
78
|
+
expect(subject).to receive(:offset).with(7).and_return result
|
79
|
+
expect(subject + 7).to eq result
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'can decrement' do
|
83
|
+
expect(subject).to receive(:offset).with(-23).and_return result
|
84
|
+
expect(subject - 23).to eq result
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module TimeBoss
|
2
|
+
class Calendar
|
3
|
+
describe Week do
|
4
|
+
let(:calendar) { instance_double(TimeBoss::Calendar) }
|
5
|
+
let(:start_date) { Date.parse('2048-04-06') }
|
6
|
+
let(:end_date) { Date.parse('2048-04-12') }
|
7
|
+
let(:subject) { described_class.new(calendar, 2048, 15, start_date, end_date) }
|
8
|
+
|
9
|
+
it 'knows its stuff' do
|
10
|
+
expect(subject.start_date).to eq start_date
|
11
|
+
expect(subject.end_date).to eq end_date
|
12
|
+
expect(subject.to_range).to eq start_date..end_date
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'knows its name' do
|
16
|
+
expect(subject.name).to eq '2048W15'
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'knows its title' do
|
20
|
+
expect(subject.title).to eq "Week of April 6, 2048"
|
21
|
+
end
|
22
|
+
|
23
|
+
it 'can stringify itself' do
|
24
|
+
expect(subject.to_s).to include(subject.name, start_date.to_s, end_date.to_s)
|
25
|
+
end
|
26
|
+
|
27
|
+
describe '#current?' do
|
28
|
+
it 'knows when it is' do
|
29
|
+
allow(Date).to receive(:today).and_return start_date
|
30
|
+
expect(subject).to be_current
|
31
|
+
end
|
32
|
+
|
33
|
+
it 'knows when it is not' do
|
34
|
+
expect(subject).not_to be_current
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
context 'navigation' do
|
39
|
+
let(:calendar) { TimeBoss::Calendars::Broadcast.new }
|
40
|
+
|
41
|
+
describe '#previous' do
|
42
|
+
it 'can back up simply' do
|
43
|
+
result = subject.previous
|
44
|
+
expect(result).to be_a described_class
|
45
|
+
expect(result.to_s).to eq "2048W14: 2048-03-30 thru 2048-04-05"
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'can wrap to the previous 52-week year' do
|
49
|
+
result = described_class.new(calendar, 2022, 1, Date.parse('2021-12-27'), Date.parse('2022-01-02')).previous
|
50
|
+
expect(result).to be_a described_class
|
51
|
+
expect(result.to_s).to eq "2021W52: 2021-12-20 thru 2021-12-26"
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'can wrap to the previous 53-week year' do
|
55
|
+
result = described_class.new(calendar, 2024, 1, Date.parse('2024-01-01'), Date.parse('2024-01-07')).previous
|
56
|
+
expect(result).to be_a described_class
|
57
|
+
expect(result.to_s).to eq "2023W53: 2023-12-25 thru 2023-12-31"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
describe '#next' do
|
62
|
+
it 'can move forward simply' do
|
63
|
+
result = subject.next
|
64
|
+
expect(result).to be_a described_class
|
65
|
+
expect(result.to_s).to eq "2048W16: 2048-04-13 thru 2048-04-19"
|
66
|
+
end
|
67
|
+
|
68
|
+
it 'can wrap from week 52 to the next year' do
|
69
|
+
result = described_class.new(calendar, 2021, 52, Date.parse('2021-12-20'), Date.parse('2021-12-26')).next
|
70
|
+
expect(result).to be_a described_class
|
71
|
+
expect(result.to_s).to eq "2022W1: 2021-12-27 thru 2022-01-02"
|
72
|
+
end
|
73
|
+
|
74
|
+
it 'can wrap from week 53 to the next year' do
|
75
|
+
result = described_class.new(calendar, 2023, 53, Date.parse('2023-12-25'), Date.parse('2023-12-31')).next
|
76
|
+
expect(result).to be_a described_class
|
77
|
+
expect(result.to_s).to eq "2024W1: 2024-01-01 thru 2024-01-07"
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
@@ -0,0 +1,777 @@
|
|
1
|
+
module TimeBoss
|
2
|
+
describe Calendars::Broadcast do
|
3
|
+
let(:subject) { described_class.new }
|
4
|
+
|
5
|
+
context 'quarters' do
|
6
|
+
describe '#quarter' do
|
7
|
+
it 'knows 2017Q2' do
|
8
|
+
quarter = subject.quarter(2017, 2)
|
9
|
+
expect(quarter.name).to eq '2017Q2'
|
10
|
+
expect(quarter.title).to eq 'Q2 2017'
|
11
|
+
expect(quarter.year_index).to eq 2017
|
12
|
+
expect(quarter.index).to eq 2
|
13
|
+
expect(quarter.start_date).to eq Date.parse('2017-03-27')
|
14
|
+
expect(quarter.end_date).to eq Date.parse('2017-06-25')
|
15
|
+
expect(quarter.to_range).to eq quarter.start_date..quarter.end_date
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'knows 2018Q3' do
|
19
|
+
quarter = subject.quarter(2018, 3)
|
20
|
+
expect(quarter.name).to eq '2018Q3'
|
21
|
+
expect(quarter.title).to eq 'Q3 2018'
|
22
|
+
expect(quarter.year_index).to eq 2018
|
23
|
+
expect(quarter.index).to eq 3
|
24
|
+
expect(quarter.start_date).to eq Date.parse('2018-06-25')
|
25
|
+
expect(quarter.end_date).to eq Date.parse('2018-09-30')
|
26
|
+
expect(quarter.to_range).to eq quarter.start_date..quarter.end_date
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'knows 2019Q4' do
|
30
|
+
quarter = subject.quarter(2019, 4)
|
31
|
+
expect(quarter.year_index).to eq 2019
|
32
|
+
expect(quarter.index).to eq 4
|
33
|
+
expect(quarter.name).to eq '2019Q4'
|
34
|
+
expect(quarter.title).to eq 'Q4 2019'
|
35
|
+
expect(quarter.start_date).to eq Date.parse('2019-09-30')
|
36
|
+
expect(quarter.end_date).to eq Date.parse('2019-12-29')
|
37
|
+
expect(quarter.to_range).to eq quarter.start_date..quarter.end_date
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
describe '#quarter_for' do
|
42
|
+
it 'knows what quarter 2018-06-27 is in' do
|
43
|
+
quarter = subject.quarter_for(Date.parse('2018-06-27'))
|
44
|
+
expect(quarter.name).to eq '2018Q3'
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'knows what quarter 2018-06-22 is in' do
|
48
|
+
quarter = subject.quarter_for(Date.parse('2018-06-22'))
|
49
|
+
expect(quarter.name).to eq '2018Q2'
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
describe '#quarters_for' do
|
54
|
+
it 'knows what quarters are in 2020' do
|
55
|
+
basis = subject.year(2020)
|
56
|
+
periods = subject.quarters_for(basis)
|
57
|
+
expect(periods.map(&:name)).to eq %w[2020Q1 2020Q2 2020Q3 2020Q4]
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'knows what quarter 2018M7 is in' do
|
61
|
+
basis = subject.month(2018, 7)
|
62
|
+
periods = subject.quarters_for(basis)
|
63
|
+
expect(periods.map(&:name)).to eq %w[2018Q3]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
describe '#this_quarter' do
|
68
|
+
let(:today) { double }
|
69
|
+
let(:quarter) { double }
|
70
|
+
|
71
|
+
it 'gets the quarter for today' do
|
72
|
+
allow(Date).to receive(:today).and_return today
|
73
|
+
expect(subject).to receive(:quarter_for).with(today).and_return quarter
|
74
|
+
expect(subject.this_quarter).to eq quarter
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
describe '#format' do
|
79
|
+
let(:entry) { subject.quarter(2015, 3) }
|
80
|
+
|
81
|
+
it 'can do a default format' do
|
82
|
+
expect(entry.format).to eq '2015H2Q1'
|
83
|
+
end
|
84
|
+
|
85
|
+
it 'can format with only the quarter' do
|
86
|
+
expect(entry.format(:quarter)).to eq '2015Q3'
|
87
|
+
end
|
88
|
+
|
89
|
+
it 'ignores stupidity' do
|
90
|
+
expect(entry.format(:day, :banana)).to eq '2015Q3'
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'relative' do
|
95
|
+
let(:this_quarter) { subject.quarter(2015, 3) }
|
96
|
+
let(:quarter) { double }
|
97
|
+
before(:each) { allow(subject).to receive(:this_quarter).and_return this_quarter }
|
98
|
+
|
99
|
+
it 'can get the last quarter' do
|
100
|
+
allow(this_quarter).to receive(:previous).and_return quarter
|
101
|
+
expect(subject.last_quarter).to eq quarter
|
102
|
+
end
|
103
|
+
|
104
|
+
it 'can get the next quarter' do
|
105
|
+
allow(this_quarter).to receive(:next).and_return quarter
|
106
|
+
expect(subject.next_quarter).to eq quarter
|
107
|
+
end
|
108
|
+
|
109
|
+
it 'can get some number of quarters' do
|
110
|
+
quarters = subject.quarters(5)
|
111
|
+
expect(quarters.length).to eq 5
|
112
|
+
quarters.each { |q| expect(q).to be_a TimeBoss::Calendar::Quarter }
|
113
|
+
expect(quarters.map(&:name)).to eq ['2015Q3', '2015Q4', '2016Q1', '2016Q2', '2016Q3']
|
114
|
+
end
|
115
|
+
|
116
|
+
it 'can get a quarter hence' do
|
117
|
+
quarter = subject.quarters_hence(4)
|
118
|
+
expect(quarter).to be_a TimeBoss::Calendar::Quarter
|
119
|
+
expect(quarter.name).to eq '2016Q3'
|
120
|
+
end
|
121
|
+
|
122
|
+
it 'can get some number of quarters back' do
|
123
|
+
quarters = subject.quarters_back(5)
|
124
|
+
expect(quarters.length).to eq 5
|
125
|
+
quarters.each { |q| expect(q).to be_a TimeBoss::Calendar::Quarter }
|
126
|
+
expect(quarters.map(&:name)).to eq ['2014Q3', '2014Q4', '2015Q1', '2015Q2', '2015Q3']
|
127
|
+
end
|
128
|
+
|
129
|
+
it 'can get a quarter ago' do
|
130
|
+
quarter = subject.quarters_ago(4)
|
131
|
+
expect(quarter).to be_a TimeBoss::Calendar::Quarter
|
132
|
+
expect(quarter.name).to eq '2014Q3'
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
context 'months' do
|
138
|
+
describe '#month' do
|
139
|
+
it 'knows 2017M2' do
|
140
|
+
month = subject.month(2017, 2)
|
141
|
+
expect(month.name).to eq '2017M2'
|
142
|
+
expect(month.title).to eq 'February 2017'
|
143
|
+
expect(month.year_index).to eq 2017
|
144
|
+
expect(month.index).to eq 2
|
145
|
+
expect(month.start_date).to eq Date.parse('2017-01-30')
|
146
|
+
expect(month.end_date).to eq Date.parse('2017-02-26')
|
147
|
+
expect(month.to_range).to eq month.start_date..month.end_date
|
148
|
+
end
|
149
|
+
|
150
|
+
it 'knows 2018M3' do
|
151
|
+
month = subject.month(2018, 3)
|
152
|
+
expect(month.name).to eq '2018M3'
|
153
|
+
expect(month.title).to eq 'March 2018'
|
154
|
+
expect(month.year_index).to eq 2018
|
155
|
+
expect(month.index).to eq 3
|
156
|
+
expect(month.start_date).to eq Date.parse('2018-02-26')
|
157
|
+
expect(month.end_date).to eq Date.parse('2018-03-25')
|
158
|
+
expect(month.to_range).to eq month.start_date..month.end_date
|
159
|
+
end
|
160
|
+
|
161
|
+
it 'knows 2019M11' do
|
162
|
+
month = subject.month(2019, 11)
|
163
|
+
expect(month.year_index).to eq 2019
|
164
|
+
expect(month.index).to eq 11
|
165
|
+
expect(month.name).to eq '2019M11'
|
166
|
+
expect(month.title).to eq 'November 2019'
|
167
|
+
expect(month.start_date).to eq Date.parse('2019-10-28')
|
168
|
+
expect(month.end_date).to eq Date.parse('2019-11-24')
|
169
|
+
expect(month.to_range).to eq month.start_date..month.end_date
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
describe '#month_for' do
|
174
|
+
it 'knows what month 2018-06-27 is in' do
|
175
|
+
month = subject.month_for(Date.parse('2018-06-27'))
|
176
|
+
expect(month.name).to eq '2018M7'
|
177
|
+
end
|
178
|
+
|
179
|
+
it 'knows what month 2018-06-22 is in' do
|
180
|
+
month = subject.month_for(Date.parse('2018-06-22'))
|
181
|
+
expect(month.name).to eq '2018M6'
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
describe '#months_for' do
|
186
|
+
it 'knows what months are in 2020' do
|
187
|
+
basis = subject.year(2020)
|
188
|
+
periods = subject.months_for(basis)
|
189
|
+
expect(periods.map(&:name)).to eq %w[2020M1 2020M2 2020M3 2020M4 2020M5 2020M6 2020M7 2020M8 2020M9 2020M10 2020M11 2020M12]
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'knows what months are in 2018Q2' do
|
193
|
+
basis = subject.parse('2018Q2')
|
194
|
+
periods = subject.months_for(basis)
|
195
|
+
expect(periods.map(&:name)).to eq %w[2018M4 2018M5 2018M6]
|
196
|
+
end
|
197
|
+
|
198
|
+
it 'knows what month 2019-12-12 is in' do
|
199
|
+
basis = subject.parse('2019-12-12')
|
200
|
+
periods = subject.months_for(basis)
|
201
|
+
expect(periods.map(&:name)).to eq %w[2019M12]
|
202
|
+
end
|
203
|
+
end
|
204
|
+
|
205
|
+
describe '#this_month' do
|
206
|
+
let(:today) { double }
|
207
|
+
let(:month) { double }
|
208
|
+
|
209
|
+
it 'gets the month for today' do
|
210
|
+
allow(Date).to receive(:today).and_return today
|
211
|
+
expect(subject).to receive(:month_for).with(today).and_return month
|
212
|
+
expect(subject.this_month).to eq month
|
213
|
+
end
|
214
|
+
end
|
215
|
+
|
216
|
+
describe '#format' do
|
217
|
+
let(:entry) { subject.month(2015, 8) }
|
218
|
+
|
219
|
+
it 'can do a default format' do
|
220
|
+
expect(entry.format).to eq '2015H2Q1M2'
|
221
|
+
end
|
222
|
+
|
223
|
+
it 'can format with only the quarter' do
|
224
|
+
expect(entry.format(:quarter)).to eq '2015Q3M2'
|
225
|
+
end
|
226
|
+
|
227
|
+
it 'ignores stupidity' do
|
228
|
+
expect(entry.format(:banana, :half, :week)).to eq '2015H2M2'
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
context 'relative' do
|
233
|
+
let(:this_month) { subject.month(2015, 3) }
|
234
|
+
let(:month) { double }
|
235
|
+
before(:each) { allow(subject).to receive(:this_month).and_return this_month }
|
236
|
+
|
237
|
+
it 'can get the last month' do
|
238
|
+
allow(this_month).to receive(:previous).and_return month
|
239
|
+
expect(subject.last_month).to eq month
|
240
|
+
end
|
241
|
+
|
242
|
+
it 'can get the next month' do
|
243
|
+
allow(this_month).to receive(:next).and_return month
|
244
|
+
expect(subject.next_month).to eq month
|
245
|
+
end
|
246
|
+
|
247
|
+
it 'can get some number of months' do
|
248
|
+
months = subject.months(5)
|
249
|
+
expect(months.length).to eq 5
|
250
|
+
months.each { |m| expect(m).to be_a TimeBoss::Calendar::Month }
|
251
|
+
expect(months.map(&:name)).to eq ['2015M3', '2015M4', '2015M5', '2015M6', '2015M7']
|
252
|
+
end
|
253
|
+
|
254
|
+
it 'can get a month hence' do
|
255
|
+
month = subject.months_hence(4)
|
256
|
+
expect(month).to be_a TimeBoss::Calendar::Month
|
257
|
+
expect(month.name).to eq '2015M7'
|
258
|
+
end
|
259
|
+
|
260
|
+
it 'can get some number of months back' do
|
261
|
+
months = subject.months_back(5)
|
262
|
+
expect(months.length).to eq 5
|
263
|
+
months.each { |m| expect(m).to be_a TimeBoss::Calendar::Month }
|
264
|
+
expect(months.map(&:name)).to eq ['2014M11', '2014M12', '2015M1', '2015M2', '2015M3']
|
265
|
+
end
|
266
|
+
|
267
|
+
it 'can get a month ago' do
|
268
|
+
month = subject.months_ago(4)
|
269
|
+
expect(month).to be_a TimeBoss::Calendar::Month
|
270
|
+
expect(month.name).to eq '2014M11'
|
271
|
+
end
|
272
|
+
end
|
273
|
+
end
|
274
|
+
|
275
|
+
context 'weeks' do
|
276
|
+
before(:each) { allow(Date).to receive(:today).and_return Date.parse('2019-08-23') }
|
277
|
+
|
278
|
+
it 'knows this week 'do
|
279
|
+
expect(subject.this_week.name).to eq '2019W34'
|
280
|
+
expect(subject.this_week.title).to eq 'Week of August 19, 2019'
|
281
|
+
end
|
282
|
+
|
283
|
+
it 'knows last week' do
|
284
|
+
expect(subject.last_week.name).to eq '2019W33'
|
285
|
+
expect(subject.last_week.title).to eq 'Week of August 12, 2019'
|
286
|
+
end
|
287
|
+
|
288
|
+
it 'knows next week' do
|
289
|
+
expect(subject.next_week.name).to eq '2019W35'
|
290
|
+
expect(subject.next_week.title).to eq 'Week of August 26, 2019'
|
291
|
+
end
|
292
|
+
end
|
293
|
+
|
294
|
+
context 'years' do
|
295
|
+
describe '#year' do
|
296
|
+
it 'knows 2016' do
|
297
|
+
year = subject.year(2016)
|
298
|
+
expect(year.name).to eq '2016'
|
299
|
+
expect(year.title).to eq '2016'
|
300
|
+
expect(year.year_index).to eq 2016
|
301
|
+
expect(year.index).to eq 1
|
302
|
+
expect(year.start_date).to eq Date.parse('2015-12-28')
|
303
|
+
expect(year.end_date).to eq Date.parse('2016-12-25')
|
304
|
+
expect(year.to_range).to eq year.start_date..year.end_date
|
305
|
+
end
|
306
|
+
|
307
|
+
it 'knows 2017' do
|
308
|
+
year = subject.year(2017)
|
309
|
+
expect(year.name).to eq '2017'
|
310
|
+
expect(year.title).to eq '2017'
|
311
|
+
expect(year.year_index).to eq 2017
|
312
|
+
expect(year.index).to eq 1
|
313
|
+
expect(year.start_date).to eq Date.parse('2016-12-26')
|
314
|
+
expect(year.end_date).to eq Date.parse('2017-12-31')
|
315
|
+
expect(year.to_range).to eq year.start_date..year.end_date
|
316
|
+
end
|
317
|
+
|
318
|
+
it 'knows 2018' do
|
319
|
+
year = subject.year(2018)
|
320
|
+
expect(year.name).to eq '2018'
|
321
|
+
expect(year.title).to eq '2018'
|
322
|
+
expect(year.year_index).to eq 2018
|
323
|
+
expect(year.index).to eq 1
|
324
|
+
expect(year.start_date).to eq Date.parse('2018-01-01')
|
325
|
+
expect(year.end_date).to eq Date.parse('2018-12-30')
|
326
|
+
expect(year.to_range).to eq year.start_date..year.end_date
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
describe '#year_for' do
|
331
|
+
it 'knows what year 2018-04-07 is in' do
|
332
|
+
year = subject.year_for(Date.parse('2018-04-07'))
|
333
|
+
expect(year.name).to eq '2018'
|
334
|
+
end
|
335
|
+
|
336
|
+
it 'knows what year 2016-12-27 is in' do
|
337
|
+
year = subject.year_for(Date.parse('2016-12-27'))
|
338
|
+
expect(year.name).to eq '2017'
|
339
|
+
end
|
340
|
+
end
|
341
|
+
|
342
|
+
describe '#years_for' do
|
343
|
+
it 'knows what years are in 2020 (duh)' do
|
344
|
+
basis = subject.year(2020)
|
345
|
+
periods = subject.years_for(basis)
|
346
|
+
expect(periods.map(&:name)).to eq %w[2020]
|
347
|
+
end
|
348
|
+
|
349
|
+
it 'knows what year 2018Q2 is in' do
|
350
|
+
basis = subject.parse('2018Q2')
|
351
|
+
periods = subject.years_for(basis)
|
352
|
+
expect(periods.map(&:name)).to eq %w[2018]
|
353
|
+
end
|
354
|
+
|
355
|
+
it 'knows what years 2019-12-12 is in' do
|
356
|
+
basis = subject.parse('2019-12-12')
|
357
|
+
periods = subject.years_for(basis)
|
358
|
+
expect(periods.map(&:name)).to eq %w[2019]
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
describe '#this_year' do
|
363
|
+
let(:today) { double }
|
364
|
+
let(:year) { double }
|
365
|
+
|
366
|
+
it 'gets the year for today' do
|
367
|
+
allow(Date).to receive(:today).and_return today
|
368
|
+
expect(subject).to receive(:year_for).with(today).and_return year
|
369
|
+
expect(subject.this_year).to eq year
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
describe '#format' do
|
374
|
+
let(:entry) { subject.parse('2020W24') }
|
375
|
+
|
376
|
+
it 'can do a default format' do
|
377
|
+
expect(entry.format).to eq '2020H1Q2M3W2'
|
378
|
+
end
|
379
|
+
|
380
|
+
it 'can format with only the quarter' do
|
381
|
+
expect(entry.format(:quarter)).to eq '2020Q2W11'
|
382
|
+
end
|
383
|
+
|
384
|
+
it 'can format with only the quarter + month' do
|
385
|
+
expect(entry.format(:quarter, :month)).to eq '2020Q2M3W2'
|
386
|
+
expect(entry.format(:month, :quarter)).to eq '2020Q2M3W2'
|
387
|
+
end
|
388
|
+
|
389
|
+
it 'ignores stupidity' do
|
390
|
+
expect(entry.format(:day, :month, :banana)).to eq '2020M6W2'
|
391
|
+
end
|
392
|
+
end
|
393
|
+
|
394
|
+
context 'relative' do
|
395
|
+
let(:this_year) { subject.year(2015) }
|
396
|
+
let(:year) { double }
|
397
|
+
before(:each) { allow(subject).to receive(:this_year).and_return this_year }
|
398
|
+
|
399
|
+
it 'can get the last year' do
|
400
|
+
allow(this_year).to receive(:previous).and_return year
|
401
|
+
expect(subject.last_year).to eq year
|
402
|
+
end
|
403
|
+
|
404
|
+
it 'can get the next year' do
|
405
|
+
allow(this_year).to receive(:next).and_return year
|
406
|
+
expect(subject.next_year).to eq year
|
407
|
+
end
|
408
|
+
|
409
|
+
it 'can get some number of years' do
|
410
|
+
years = subject.years(5)
|
411
|
+
expect(years.length).to eq 5
|
412
|
+
years.each { |y| expect(y).to be_a TimeBoss::Calendar::Year }
|
413
|
+
expect(years.map(&:name)).to eq ['2015', '2016', '2017', '2018', '2019']
|
414
|
+
end
|
415
|
+
|
416
|
+
it 'can get some number of years back' do
|
417
|
+
years = subject.years_back(5)
|
418
|
+
expect(years.length).to eq 5
|
419
|
+
years.each { |y| expect(y).to be_a TimeBoss::Calendar::Year }
|
420
|
+
expect(years.map(&:name)).to eq ['2011', '2012', '2013', '2014', '2015']
|
421
|
+
end
|
422
|
+
end
|
423
|
+
end
|
424
|
+
|
425
|
+
describe '#parse' do
|
426
|
+
it 'can parse a year' do
|
427
|
+
date = subject.parse('2018')
|
428
|
+
expect(date).to be_a TimeBoss::Calendar::Year
|
429
|
+
expect(date.name).to eq '2018'
|
430
|
+
end
|
431
|
+
|
432
|
+
it 'can parse a quarter identifier' do
|
433
|
+
date = subject.parse('2017Q2')
|
434
|
+
expect(date).to be_a TimeBoss::Calendar::Quarter
|
435
|
+
expect(date.name).to eq '2017Q2'
|
436
|
+
end
|
437
|
+
|
438
|
+
it 'can parse a month identifier' do
|
439
|
+
date = subject.parse('2017M4')
|
440
|
+
expect(date).to be_a TimeBoss::Calendar::Month
|
441
|
+
expect(date.name).to eq '2017M4'
|
442
|
+
end
|
443
|
+
|
444
|
+
it 'can parse a week within a year' do
|
445
|
+
date = subject.parse('2018W37')
|
446
|
+
expect(date).to be_a TimeBoss::Calendar::Week
|
447
|
+
expect(date.name).to eq '2018W37'
|
448
|
+
end
|
449
|
+
|
450
|
+
it 'can parse a week within a quarter' do
|
451
|
+
date = subject.parse('2017Q2W2')
|
452
|
+
expect(date).to be_a TimeBoss::Calendar::Week
|
453
|
+
expect(date.name).to eq '2017W15'
|
454
|
+
end
|
455
|
+
|
456
|
+
it 'can parse a week within a month' do
|
457
|
+
date = subject.parse('2017M4W1')
|
458
|
+
expect(date).to be_a TimeBoss::Calendar::Week
|
459
|
+
expect(date.name).to eq '2017W14'
|
460
|
+
end
|
461
|
+
|
462
|
+
it 'can parse a date' do
|
463
|
+
date = subject.parse('2017-04-08')
|
464
|
+
expect(date).to be_a TimeBoss::Calendar::Day
|
465
|
+
expect(date.start_date).to eq Date.parse('2017-04-08')
|
466
|
+
expect(date.end_date).to eq Date.parse('2017-04-08')
|
467
|
+
end
|
468
|
+
|
469
|
+
it 'can parse an aesthetically displeasing date' do
|
470
|
+
date = subject.parse('20170408')
|
471
|
+
expect(date).to be_a TimeBoss::Calendar::Day
|
472
|
+
expect(date.start_date).to eq Date.parse('2017-04-08')
|
473
|
+
expect(date.end_date).to eq Date.parse('2017-04-08')
|
474
|
+
end
|
475
|
+
|
476
|
+
it 'gives you this year if you give it nothing' do
|
477
|
+
year = subject.this_year
|
478
|
+
expect(subject.parse(nil)).to eq year
|
479
|
+
expect(subject.parse('')).to eq year
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
context 'expressions' do
|
484
|
+
it 'can parse waypoints' do
|
485
|
+
result = subject.parse('this_year')
|
486
|
+
expect(result).to be_a TimeBoss::Calendar::Year
|
487
|
+
expect(result).to be_current
|
488
|
+
end
|
489
|
+
|
490
|
+
it 'can parse mathematic expressions' do
|
491
|
+
result = subject.parse('this_month + 2')
|
492
|
+
expect(result).to be_a TimeBoss::Calendar::Month
|
493
|
+
expect(result).to eq subject.months_hence(2)
|
494
|
+
end
|
495
|
+
|
496
|
+
context 'ranges' do
|
497
|
+
before(:each) { allow(subject).to receive(:this_year).and_return subject.year(2018) }
|
498
|
+
let(:result) { subject.parse('this_year-2 .. this_year') }
|
499
|
+
|
500
|
+
it 'can parse range expressions' do
|
501
|
+
expect(result).to be_a TimeBoss::Calendar::Period
|
502
|
+
expect(result.to_s).to eq "2016: 2015-12-28 thru 2016-12-25 .. 2018: 2018-01-01 thru 2018-12-30"
|
503
|
+
end
|
504
|
+
|
505
|
+
it 'can get an overall start date for a range' do
|
506
|
+
expect(result.start_date).to eq Date.parse('2015-12-28')
|
507
|
+
end
|
508
|
+
|
509
|
+
it 'can get an overall end date for a range' do
|
510
|
+
expect(result.end_date).to eq Date.parse('2018-12-30')
|
511
|
+
end
|
512
|
+
|
513
|
+
context 'sub-periods' do
|
514
|
+
it 'can get the months included in a range' do
|
515
|
+
entries = result.months
|
516
|
+
entries.each { |e| expect(e).to be_a TimeBoss::Calendar::Month }
|
517
|
+
expect(entries.map(&:name)).to include('2016M1', '2016M9', '2017M3', '2018M12')
|
518
|
+
end
|
519
|
+
|
520
|
+
it 'can get the weeks included in a range' do
|
521
|
+
entries = result.weeks
|
522
|
+
entries.each { |e| expect(e).to be_a TimeBoss::Calendar::Week }
|
523
|
+
expect(entries.map(&:name)).to include('2016W1', '2016W38', '2017W15', '2017W53', '2018W52')
|
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('2015-12-28', '2016-04-30', '2017-09-22', '2018-12-30')
|
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 'can shift to a different week' do
|
540
|
+
allow(subject).to receive(:this_week).and_return subject.parse('2020W23')
|
541
|
+
result = basis.last_week
|
542
|
+
expect(result).to be_a TimeBoss::Calendar::Day
|
543
|
+
expect(result.to_s).to eq '2020-05-26'
|
544
|
+
expect(basis.in_week).to eq 2
|
545
|
+
end
|
546
|
+
|
547
|
+
it 'can shift to a different quarter' do
|
548
|
+
allow(subject).to receive(:this_quarter).and_return subject.parse('2020Q3')
|
549
|
+
result = basis.quarters_ago(2)
|
550
|
+
expect(result).to be_a TimeBoss::Calendar::Day
|
551
|
+
expect(result.to_s).to eq '2020-01-21'
|
552
|
+
expect(basis.in_quarter).to eq 23
|
553
|
+
end
|
554
|
+
|
555
|
+
it 'can shift to a different year' do
|
556
|
+
allow(subject).to receive(:this_year).and_return subject.parse('2019')
|
557
|
+
result = basis.years_hence(3)
|
558
|
+
expect(result).to be_a TimeBoss::Calendar::Day
|
559
|
+
expect(result.to_s).to eq '2022-04-19'
|
560
|
+
expect(basis.in_year).to eq 114
|
561
|
+
end
|
562
|
+
end
|
563
|
+
|
564
|
+
context 'from week' do
|
565
|
+
let(:basis) { subject.parse('2017W8') }
|
566
|
+
|
567
|
+
it 'cannot shift to a different day' do
|
568
|
+
expect(basis.last_day).to be nil
|
569
|
+
expect(basis.in_day).to be nil
|
570
|
+
end
|
571
|
+
|
572
|
+
it 'can shift to a different month' do
|
573
|
+
allow(subject).to receive(:this_month).and_return subject.parse('2020M4')
|
574
|
+
result = basis.next_month
|
575
|
+
expect(result).to be_a TimeBoss::Calendar::Week
|
576
|
+
expect(result.to_s).to eq '2020W20: 2020-05-11 thru 2020-05-17'
|
577
|
+
expect(basis.in_month).to eq 3
|
578
|
+
end
|
579
|
+
|
580
|
+
it 'can shift to a different half' do
|
581
|
+
allow(subject).to receive(:this_half).and_return subject.parse('2019H1')
|
582
|
+
result = basis.last_half
|
583
|
+
expect(result).to be_a TimeBoss::Calendar::Week
|
584
|
+
expect(result.to_s).to eq '2018W33: 2018-08-13 thru 2018-08-19'
|
585
|
+
expect(basis.in_half).to eq 8
|
586
|
+
end
|
587
|
+
end
|
588
|
+
|
589
|
+
context 'from month' do
|
590
|
+
let(:basis) { subject.parse('2017M4') }
|
591
|
+
|
592
|
+
it 'cannot shift to a different week' do
|
593
|
+
expect(basis.last_week).to be nil
|
594
|
+
expect(basis.in_week).to be nil
|
595
|
+
end
|
596
|
+
|
597
|
+
it 'can shift to a different year' do
|
598
|
+
allow(subject).to receive(:this_year).and_return subject.parse('2020')
|
599
|
+
result = basis.years_hence(4)
|
600
|
+
expect(result).to be_a TimeBoss::Calendar::Month
|
601
|
+
expect(result.name).to eq '2024M4'
|
602
|
+
expect(basis.in_year).to eq 4
|
603
|
+
end
|
604
|
+
end
|
605
|
+
|
606
|
+
context 'from quarter' do
|
607
|
+
let(:basis) { subject.parse('2018Q2') }
|
608
|
+
|
609
|
+
it 'cannot shift to a different month' do
|
610
|
+
expect(basis.months_ago(4)).to be nil
|
611
|
+
expect(basis.in_month).to be nil
|
612
|
+
end
|
613
|
+
|
614
|
+
it 'can shift to a different half' do
|
615
|
+
allow(subject).to receive(:this_half).and_return subject.parse('2020H1')
|
616
|
+
result = basis.last_half
|
617
|
+
expect(result).to be_a TimeBoss::Calendar::Quarter
|
618
|
+
expect(result.name).to eq '2019Q4'
|
619
|
+
expect(basis.in_half).to eq 2
|
620
|
+
end
|
621
|
+
end
|
622
|
+
|
623
|
+
context 'from year' do
|
624
|
+
let(:basis) { subject.parse('2014') }
|
625
|
+
|
626
|
+
it 'cannot shift to a different half' do
|
627
|
+
expect(basis.next_half).to be nil
|
628
|
+
expect(basis.in_half).to be nil
|
629
|
+
end
|
630
|
+
|
631
|
+
it 'shifts to a different year, but knows how useless that is' do
|
632
|
+
allow(subject).to receive(:this_year).and_return subject.parse('2020')
|
633
|
+
result = basis.years_ago(2)
|
634
|
+
expect(result).to be_a TimeBoss::Calendar::Year
|
635
|
+
expect(result.name).to eq '2018'
|
636
|
+
expect(basis.in_year).to eq 1
|
637
|
+
end
|
638
|
+
end
|
639
|
+
end
|
640
|
+
|
641
|
+
context 'units' do
|
642
|
+
let(:calendar) { described_class.new }
|
643
|
+
|
644
|
+
context 'day' do
|
645
|
+
let(:start_date) { Date.parse('2019-09-30') }
|
646
|
+
let(:subject) { TimeBoss::Calendar::Day.new(calendar, start_date) }
|
647
|
+
|
648
|
+
context 'links' do
|
649
|
+
it 'can get its previous' do
|
650
|
+
expect(subject.previous.name).to eq '2019-09-29'
|
651
|
+
end
|
652
|
+
|
653
|
+
it 'can get its next' do
|
654
|
+
expect(subject.next.name).to eq '2019-10-01'
|
655
|
+
end
|
656
|
+
|
657
|
+
it 'can offset backwards' do
|
658
|
+
expect(subject.offset(-3).name).to eq '2019-09-27'
|
659
|
+
expect((subject - 3).name).to eq '2019-09-27'
|
660
|
+
end
|
661
|
+
|
662
|
+
it 'can offset forwards' do
|
663
|
+
expect(subject.offset(4).name).to eq '2019-10-04'
|
664
|
+
expect((subject + 4).name).to eq '2019-10-04'
|
665
|
+
end
|
666
|
+
end
|
667
|
+
end
|
668
|
+
|
669
|
+
context 'week' do
|
670
|
+
context 'links' do
|
671
|
+
context 'within year' do
|
672
|
+
let(:parent) { calendar.parse('2020') }
|
673
|
+
let(:week) { parent.weeks.first }
|
674
|
+
|
675
|
+
it 'knows itself first' do
|
676
|
+
expect(week.to_s).to include('2020W1', '2019-12-30', '2020-01-05')
|
677
|
+
end
|
678
|
+
|
679
|
+
it 'can get its next week' do
|
680
|
+
subject = week.next
|
681
|
+
expect(subject).to be_a TimeBoss::Calendar::Week
|
682
|
+
expect(subject.to_s).to include('2020W2', '2020-01-06', '2020-01-12')
|
683
|
+
end
|
684
|
+
|
685
|
+
it 'can get its previous week' do
|
686
|
+
subject = week.previous
|
687
|
+
expect(subject).to be_a TimeBoss::Calendar::Week
|
688
|
+
expect(subject.to_s).to include('2019W52', '2019-12-23', '2019-12-29')
|
689
|
+
end
|
690
|
+
|
691
|
+
it 'can offset backwards' do
|
692
|
+
expect(week.offset(-4).name).to eq '2019W49'
|
693
|
+
expect((week - 4).name).to eq '2019W49'
|
694
|
+
end
|
695
|
+
|
696
|
+
it 'can offset forwards' do
|
697
|
+
expect((week + 2).name).to eq '2020W3'
|
698
|
+
end
|
699
|
+
end
|
700
|
+
|
701
|
+
context 'within quarter' do
|
702
|
+
let(:parent) { calendar.parse('2019Q3') }
|
703
|
+
let(:week) { parent.weeks.last }
|
704
|
+
|
705
|
+
it 'knows itself first' do
|
706
|
+
expect(week.to_s).to include('2019W39', '2019-09-23', '2019-09-29')
|
707
|
+
end
|
708
|
+
|
709
|
+
it 'can get its next week' do
|
710
|
+
subject = week.next
|
711
|
+
expect(subject).to be_a TimeBoss::Calendar::Week
|
712
|
+
expect(subject.to_s).to include('2019W40', '2019-09-30', '2019-10-06')
|
713
|
+
end
|
714
|
+
|
715
|
+
it 'can get its previous week' do
|
716
|
+
subject = week.previous
|
717
|
+
expect(subject).to be_a TimeBoss::Calendar::Week
|
718
|
+
expect(subject.to_s).to include('2019W38', '2019-09-16', '2019-09-22')
|
719
|
+
end
|
720
|
+
|
721
|
+
it 'can offset backwards' do
|
722
|
+
expect(week.offset(-4).name).to eq '2019W35'
|
723
|
+
expect((week - 4).name).to eq '2019W35'
|
724
|
+
end
|
725
|
+
|
726
|
+
it 'can offset forwards' do
|
727
|
+
expect((week + 2).name).to eq '2019W41'
|
728
|
+
end
|
729
|
+
end
|
730
|
+
end
|
731
|
+
end
|
732
|
+
|
733
|
+
context 'quarter' do
|
734
|
+
let(:start_date) { Date.parse('2019-09-30') }
|
735
|
+
let(:end_date) { Date.parse('2019-12-29') }
|
736
|
+
let(:subject) { TimeBoss::Calendar::Quarter.new(calendar, 2019, 4, start_date, end_date) }
|
737
|
+
|
738
|
+
context 'links' do
|
739
|
+
it 'can get the next quarter' do
|
740
|
+
quarter = subject.next
|
741
|
+
expect(quarter.to_s).to include('2020Q1', '2019-12-30', '2020-03-29')
|
742
|
+
end
|
743
|
+
|
744
|
+
it 'can get the next next quarter' do
|
745
|
+
quarter = subject.next.next
|
746
|
+
expect(quarter.to_s).to include('2020Q2', '2020-03-30', '2020-06-28')
|
747
|
+
end
|
748
|
+
|
749
|
+
it 'can get the next next previous quarter' do
|
750
|
+
quarter = subject.next.next.previous
|
751
|
+
expect(quarter.to_s).to include('2020Q1', '2019-12-30', '2020-03-29')
|
752
|
+
end
|
753
|
+
|
754
|
+
it 'can get the next previous quarter' do
|
755
|
+
quarter = subject.next.previous
|
756
|
+
expect(quarter.to_s).to eq subject.to_s
|
757
|
+
end
|
758
|
+
|
759
|
+
it 'can get the previous quarter' do
|
760
|
+
quarter = subject.previous
|
761
|
+
expect(quarter.to_s).to include('2019Q3', '2019-07-01', '2019-09-29')
|
762
|
+
end
|
763
|
+
|
764
|
+
it 'can offset backwards' do
|
765
|
+
expect(subject.offset(-4).name).to eq '2018Q4'
|
766
|
+
expect((subject - 4).name).to eq '2018Q4'
|
767
|
+
end
|
768
|
+
|
769
|
+
it 'can offset forwards' do
|
770
|
+
expect(subject.offset(2).name).to eq '2020Q2'
|
771
|
+
expect((subject + 2).name).to eq '2020Q2'
|
772
|
+
end
|
773
|
+
end
|
774
|
+
end
|
775
|
+
end
|
776
|
+
end
|
777
|
+
end
|