module TimeBoss describe Calendars::Gregorian do let(:subject) { described_class.new } context 'days' do it 'can get today' do day = subject.today expect(day).to be_instance_of(TimeBoss::Calendar::Day) expect(day.start_date).to eq Date.today end it 'can get yesterday' do day = subject.yesterday expect(day).to be_instance_of(TimeBoss::Calendar::Day) expect(day.start_date).to eq Date.yesterday end it 'can get tomorrow' do day = subject.tomorrow expect(day).to be_instance_of(TimeBoss::Calendar::Day) expect(day.start_date).to eq Date.tomorrow end end context 'quarters' do describe '#quarter' do it 'knows 2017Q2' do quarter = subject.quarter(2017, 2) expect(quarter.name).to eq '2017Q2' expect(quarter.title).to eq 'Q2 2017' expect(quarter.year_index).to eq 2017 expect(quarter.index).to eq 2 expect(quarter.start_date).to eq Date.parse('2017-04-01') expect(quarter.end_date).to eq Date.parse('2017-06-30') expect(quarter.to_range).to eq quarter.start_date..quarter.end_date end it 'knows 2018Q3' do quarter = subject.quarter(2018, 3) expect(quarter.name).to eq '2018Q3' expect(quarter.title).to eq 'Q3 2018' expect(quarter.year_index).to eq 2018 expect(quarter.index).to eq 3 expect(quarter.start_date).to eq Date.parse('2018-07-01') expect(quarter.end_date).to eq Date.parse('2018-09-30') expect(quarter.to_range).to eq quarter.start_date..quarter.end_date end it 'knows 2019Q4' do quarter = subject.quarter(2019, 4) expect(quarter.year_index).to eq 2019 expect(quarter.index).to eq 4 expect(quarter.name).to eq '2019Q4' expect(quarter.title).to eq 'Q4 2019' expect(quarter.start_date).to eq Date.parse('2019-10-01') expect(quarter.end_date).to eq Date.parse('2019-12-31') expect(quarter.to_range).to eq quarter.start_date..quarter.end_date end end describe '#quarter_for' do it 'knows what quarter 2018-07-05 is in' do quarter = subject.quarter_for(Date.parse('2018-07-05')) expect(quarter.name).to eq '2018Q3' end it 'knows what quarter 2018-06-22 is in' do quarter = subject.quarter_for(Date.parse('2018-06-22')) expect(quarter.name).to eq '2018Q2' end end describe '#quarters_for' do it 'knows what quarters are in 2020' do basis = subject.year(2020) periods = subject.quarters_for(basis) expect(periods.map(&:name)).to eq %w[2020Q1 2020Q2 2020Q3 2020Q4] end it 'knows what quarter 2018M7 is in' do basis = subject.month(2018, 7) periods = subject.quarters_for(basis) expect(periods.map(&:name)).to eq %w[2018Q3] end end describe '#this_quarter' do let(:today) { double } let(:quarter) { double } it 'gets the quarter for today' do allow(Date).to receive(:today).and_return today expect(subject).to receive(:quarter_for).with(today).and_return quarter expect(subject.this_quarter).to eq quarter end end describe '#format' do let(:entry) { subject.quarter(2015, 3) } it 'can do a default format' do expect(entry.format).to eq '2015H2Q1' end it 'can format with only the quarter' do expect(entry.format(:quarter)).to eq '2015Q3' end it 'ignores stupidity' do expect(entry.format(:day, :banana)).to eq '2015Q3' end end context 'relative' do let(:this_quarter) { subject.quarter(2015, 3) } let(:quarter) { double } before(:each) { allow(subject).to receive(:this_quarter).and_return this_quarter } it 'can get the last quarter' do allow(this_quarter).to receive(:previous).and_return quarter expect(subject.last_quarter).to eq quarter end it 'can get the next quarter' do allow(this_quarter).to receive(:next).and_return quarter expect(subject.next_quarter).to eq quarter end it 'can get some number of quarters' do quarters = subject.quarters(5) expect(quarters.length).to eq 5 quarters.each { |q| expect(q).to be_a TimeBoss::Calendar::Quarter } expect(quarters.map(&:name)).to eq ['2015Q3', '2015Q4', '2016Q1', '2016Q2', '2016Q3'] end it 'can get a quarter ahead' do quarter = subject.quarters_ahead(4) expect(quarter).to be_a TimeBoss::Calendar::Quarter expect(quarter.name).to eq '2016Q3' end it 'can get some number of quarters back' do quarters = subject.quarters_back(5) expect(quarters.length).to eq 5 quarters.each { |q| expect(q).to be_a TimeBoss::Calendar::Quarter } expect(quarters.map(&:name)).to eq ['2014Q3', '2014Q4', '2015Q1', '2015Q2', '2015Q3'] end it 'can get a quarter ago' do quarter = subject.quarters_ago(4) expect(quarter).to be_a TimeBoss::Calendar::Quarter expect(quarter.name).to eq '2014Q3' end end end context 'months' do describe '#month' do it 'knows 2017M2' do month = subject.month(2017, 2) expect(month.name).to eq '2017M2' expect(month.title).to eq 'February 2017' expect(month.year_index).to eq 2017 expect(month.index).to eq 2 expect(month.start_date).to eq Date.parse('2017-02-01') expect(month.end_date).to eq Date.parse('2017-02-28') expect(month.to_range).to eq month.start_date..month.end_date end it 'knows 2018M3' do month = subject.month(2018, 3) expect(month.name).to eq '2018M3' expect(month.title).to eq 'March 2018' expect(month.year_index).to eq 2018 expect(month.index).to eq 3 expect(month.start_date).to eq Date.parse('2018-03-01') expect(month.end_date).to eq Date.parse('2018-03-31') expect(month.to_range).to eq month.start_date..month.end_date end it 'knows 2019M11' do month = subject.month(2019, 11) expect(month.year_index).to eq 2019 expect(month.index).to eq 11 expect(month.name).to eq '2019M11' expect(month.title).to eq 'November 2019' expect(month.start_date).to eq Date.parse('2019-11-01') expect(month.end_date).to eq Date.parse('2019-11-30') expect(month.to_range).to eq month.start_date..month.end_date end end describe '#month_for' do it 'knows what month 2018-07-05 is in' do month = subject.month_for(Date.parse('2018-07-05')) expect(month.name).to eq '2018M7' end it 'knows what month 2018-06-22 is in' do month = subject.month_for(Date.parse('2018-06-22')) expect(month.name).to eq '2018M6' end end describe '#months_for' do it 'knows what months are in 2020' do basis = subject.year(2020) periods = subject.months_for(basis) expect(periods.map(&:name)).to eq %w[2020M1 2020M2 2020M3 2020M4 2020M5 2020M6 2020M7 2020M8 2020M9 2020M10 2020M11 2020M12] end it 'knows what months are in 2018Q2' do basis = subject.parse('2018Q2') periods = subject.months_for(basis) expect(periods.map(&:name)).to eq %w[2018M4 2018M5 2018M6] end it 'knows what month 2019-12-12 is in' do basis = subject.parse('2019-12-12') periods = subject.months_for(basis) expect(periods.map(&:name)).to eq %w[2019M12] end end describe '#this_month' do let(:today) { double } let(:month) { double } it 'gets the month for today' do allow(Date).to receive(:today).and_return today expect(subject).to receive(:month_for).with(today).and_return month expect(subject.this_month).to eq month end end describe '#format' do let(:entry) { subject.month(2015, 8) } it 'can do a default format' do expect(entry.format).to eq '2015H2Q1M2' end it 'can format with only the quarter' do expect(entry.format(:quarter)).to eq '2015Q3M2' end it 'ignores stupidity' do expect(entry.format(:banana, :half, :week)).to eq '2015H2M2' end end context 'relative' do let(:this_month) { subject.month(2015, 3) } let(:month) { double } before(:each) { allow(subject).to receive(:this_month).and_return this_month } it 'can get the last month' do allow(this_month).to receive(:previous).and_return month expect(subject.last_month).to eq month end it 'can get the next month' do allow(this_month).to receive(:next).and_return month expect(subject.next_month).to eq month end it 'can get some number of months' do months = subject.months(5) expect(months.length).to eq 5 months.each { |m| expect(m).to be_a TimeBoss::Calendar::Month } expect(months.map(&:name)).to eq ['2015M3', '2015M4', '2015M5', '2015M6', '2015M7'] end it 'can get a month ahead' do month = subject.months_ahead(4) expect(month).to be_a TimeBoss::Calendar::Month expect(month.name).to eq '2015M7' end it 'can get some number of months back' do months = subject.months_back(5) expect(months.length).to eq 5 months.each { |m| expect(m).to be_a TimeBoss::Calendar::Month } expect(months.map(&:name)).to eq ['2014M11', '2014M12', '2015M1', '2015M2', '2015M3'] end it 'can get a month ago' do month = subject.months_ago(4) expect(month).to be_a TimeBoss::Calendar::Month expect(month.name).to eq '2014M11' end end end context 'weeks' do it 'is uninterested in weeks' do expect { subject.this_week }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError expect { subject.parse('2020W3') }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError expect { subject.weeks_ago(2) }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError end end context 'years' do describe '#year' do it 'knows 2016' do year = subject.year(2016) expect(year.name).to eq '2016' expect(year.title).to eq '2016' expect(year.year_index).to eq 2016 expect(year.index).to eq 1 expect(year.start_date).to eq Date.parse('2016-01-01') expect(year.end_date).to eq Date.parse('2016-12-31') expect(year.to_range).to eq year.start_date..year.end_date end it 'knows 2017' do year = subject.year(2017) expect(year.name).to eq '2017' expect(year.title).to eq '2017' expect(year.year_index).to eq 2017 expect(year.index).to eq 1 expect(year.start_date).to eq Date.parse('2017-01-01') expect(year.end_date).to eq Date.parse('2017-12-31') expect(year.to_range).to eq year.start_date..year.end_date end it 'knows 2018' do year = subject.year(2018) expect(year.name).to eq '2018' expect(year.title).to eq '2018' expect(year.year_index).to eq 2018 expect(year.index).to eq 1 expect(year.start_date).to eq Date.parse('2018-01-01') expect(year.end_date).to eq Date.parse('2018-12-31') expect(year.to_range).to eq year.start_date..year.end_date end end describe '#year_for' do it 'knows what year 2018-04-07 is in' do year = subject.year_for(Date.parse('2018-04-07')) expect(year.name).to eq '2018' end it 'knows what year 2016-12-27 is in' do year = subject.year_for(Date.parse('2016-12-27')) expect(year.name).to eq '2016' end end describe '#years_for' do it 'knows what years are in 2020 (duh)' do basis = subject.year(2020) periods = subject.years_for(basis) expect(periods.map(&:name)).to eq %w[2020] end it 'knows what year 2018Q2 is in' do basis = subject.parse('2018Q2') periods = subject.years_for(basis) expect(periods.map(&:name)).to eq %w[2018] end it 'knows what years 2019-12-12 is in' do basis = subject.parse('2019-12-12') periods = subject.years_for(basis) expect(periods.map(&:name)).to eq %w[2019] end end describe '#this_year' do let(:today) { double } let(:year) { double } it 'gets the year for today' do allow(Date).to receive(:today).and_return today expect(subject).to receive(:year_for).with(today).and_return year expect(subject.this_year).to eq year end end describe '#format' do let(:entry) { subject.parse('2020M8') } it 'can do a default format' do expect(entry.format).to eq '2020H2Q1M2' end it 'can format with only the quarter' do expect(entry.format(:quarter)).to eq '2020Q3M2' end context 'with days' do let(:entry) { subject.parse('2020D201') } it 'can do a default format' do expect(entry.format).to eq '2020H2Q1M1D19' end end end context 'relative' do let(:this_year) { subject.year(2015) } let(:year) { double } before(:each) { allow(subject).to receive(:this_year).and_return this_year } it 'can get the last year' do allow(this_year).to receive(:previous).and_return year expect(subject.last_year).to eq year end it 'can get the next year' do allow(this_year).to receive(:next).and_return year expect(subject.next_year).to eq year end it 'can get some number of years' do years = subject.years(5) expect(years.length).to eq 5 years.each { |y| expect(y).to be_a TimeBoss::Calendar::Year } expect(years.map(&:name)).to eq ['2015', '2016', '2017', '2018', '2019'] end it 'can get some number of years back' do years = subject.years_back(5) expect(years.length).to eq 5 years.each { |y| expect(y).to be_a TimeBoss::Calendar::Year } expect(years.map(&:name)).to eq ['2011', '2012', '2013', '2014', '2015'] end end end describe '#parse' do it 'can parse a year' do date = subject.parse('2018') expect(date).to be_a TimeBoss::Calendar::Year expect(date.name).to eq '2018' end it 'can parse a quarter identifier' do date = subject.parse('2017Q2') expect(date).to be_a TimeBoss::Calendar::Quarter expect(date.name).to eq '2017Q2' end it 'can parse a month identifier' do date = subject.parse('2017M4') expect(date).to be_a TimeBoss::Calendar::Month expect(date.name).to eq '2017M4' end it 'cannot parse a week within a year' do expect { subject.parse('2018W37') }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError end it 'cannot parse a week within a quarter' do expect { subject.parse('2017Q2W2') }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError end it 'cannot parse a week within a month' do expect { subject.parse('2017M4W1') }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError end it 'can parse a date' do date = subject.parse('2017-04-08') expect(date).to be_a TimeBoss::Calendar::Day expect(date.start_date).to eq Date.parse('2017-04-08') expect(date.end_date).to eq Date.parse('2017-04-08') end it 'can parse an aesthetically displeasing date' do date = subject.parse('20170408') expect(date).to be_a TimeBoss::Calendar::Day expect(date.start_date).to eq Date.parse('2017-04-08') expect(date.end_date).to eq Date.parse('2017-04-08') end it 'gives you this year if you give it nothing' do year = subject.this_year expect(subject.parse(nil)).to eq year expect(subject.parse('')).to eq year end end context 'expressions' do it 'can parse waypoints' do result = subject.parse('this_year') expect(result).to be_a TimeBoss::Calendar::Year expect(result).to be_current end it 'can parse mathematic expressions' do result = subject.parse('this_month + 2') expect(result).to be_a TimeBoss::Calendar::Month expect(result).to eq subject.months_ahead(2) end context 'ranges' do before(:each) { allow(subject).to receive(:this_year).and_return subject.year(2018) } let(:result) { subject.parse('this_year-2 .. this_year') } it 'can parse range expressions' do expect(result).to be_a TimeBoss::Calendar::Period expect(result.to_s).to eq "2016: 2016-01-01 thru 2016-12-31 .. 2018: 2018-01-01 thru 2018-12-31" end it 'can get an overall start date for a range' do expect(result.start_date).to eq Date.parse('2016-01-01') end it 'can get an overall end date for a range' do expect(result.end_date).to eq Date.parse('2018-12-31') end context 'sub-periods' do it 'can get the months included in a range' do entries = result.months entries.each { |e| expect(e).to be_a TimeBoss::Calendar::Month } expect(entries.map(&:name)).to include('2016M1', '2016M9', '2017M3', '2018M12') end it 'cannot get the weeks included in a range' do expect { result.weeks }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError end it 'can get the days included in a range' do entries = result.days entries.each { |e| expect(e).to be_a TimeBoss::Calendar::Day } expect(entries.map(&:name)).to include('2016-01-01', '2016-05-12', '2017-09-22', '2018-12-31') end end end end context 'shifting' do context 'from day' do let(:basis) { subject.parse('2020-04-21') } it 'cannot shift to a different week' do expect { basis.last_week }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError expect { basis.in_week }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError end it 'can shift to a different quarter' do allow(subject).to receive(:this_quarter).and_return subject.parse('2020Q3') result = basis.quarters_ago(2) expect(result).to be_a TimeBoss::Calendar::Day expect(result.to_s).to eq '2020-01-21' expect(basis.in_quarter).to eq 21 end it 'can shift to a different year' do allow(subject).to receive(:this_year).and_return subject.parse('2019') result = basis.years_ahead(3) expect(result).to be_a TimeBoss::Calendar::Day expect(result.to_s).to eq '2022-04-22' expect(basis.in_year).to eq 112 end end context 'from month' do let(:basis) { subject.parse('2017M4') } it 'cannot shift to a different week' do expect { basis.last_week }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError expect { basis.in_week }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError end it 'can shift to a different year' do allow(subject).to receive(:this_year).and_return subject.parse('2020') result = basis.years_ahead(4) expect(result).to be_a TimeBoss::Calendar::Month expect(result.name).to eq '2024M4' expect(basis.in_year).to eq 4 end end context 'from quarter' do let(:basis) { subject.parse('2018Q2') } it 'cannot shift to a different month' do expect(basis.months_ago(4)).to be nil expect(basis.in_month).to be nil end it 'can shift to a different half' do allow(subject).to receive(:this_half).and_return subject.parse('2020H1') result = basis.last_half expect(result).to be_a TimeBoss::Calendar::Quarter expect(result.name).to eq '2019Q4' expect(basis.in_half).to eq 2 end end context 'from year' do let(:basis) { subject.parse('2014') } it 'cannot shift to a different half' do expect(basis.next_half).to be nil expect(basis.in_half).to be nil end it 'shifts to a different year, but knows how useless that is' do allow(subject).to receive(:this_year).and_return subject.parse('2020') result = basis.years_ago(2) expect(result).to be_a TimeBoss::Calendar::Year expect(result.name).to eq '2018' expect(basis.in_year).to eq 1 end end end context 'units' do let(:calendar) { described_class.new } context 'day' do let(:start_date) { Date.parse('2019-09-30') } let(:subject) { TimeBoss::Calendar::Day.new(calendar, start_date) } context 'links' do it 'can get its previous' do expect(subject.previous.name).to eq '2019-09-29' end it 'can get its next' do expect(subject.next.name).to eq '2019-10-01' end it 'can offset backwards' do expect(subject.offset(-3).name).to eq '2019-09-27' expect((subject - 3).name).to eq '2019-09-27' end it 'can offset forwards' do expect(subject.offset(4).name).to eq '2019-10-04' expect((subject + 4).name).to eq '2019-10-04' end end end context 'quarter' do let(:start_date) { Date.parse('2019-10-01') } let(:end_date) { Date.parse('2019-12-31') } let(:subject) { TimeBoss::Calendar::Quarter.new(calendar, 2019, 4, start_date, end_date) } context 'links' do it 'can get the next quarter' do quarter = subject.next expect(quarter.to_s).to include('2020Q1', '2020-01-01', '2020-03-31') end it 'can get the next next quarter' do quarter = subject.next.next expect(quarter.to_s).to include('2020Q2', '2020-04-01', '2020-06-30') end it 'can get the next next previous quarter' do quarter = subject.next.next.previous expect(quarter.to_s).to include('2020Q1', '2020-01-01', '2020-03-31') end it 'can get the next previous quarter' do quarter = subject.next.previous expect(quarter.to_s).to eq subject.to_s end it 'can get the previous quarter' do quarter = subject.previous expect(quarter.to_s).to include('2019Q3', '2019-07-01', '2019-09-30') end it 'can offset backwards' do expect(subject.offset(-4).name).to eq '2018Q4' expect((subject - 4).name).to eq '2018Q4' end it 'can offset forwards' do expect(subject.offset(2).name).to eq '2020Q2' expect((subject + 2).name).to eq '2020Q2' end end end end end end