module TimeBoss describe Calendars::Gregorian do 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 before(:each) { allow(Date).to receive(:today).and_return Date.parse("2019-08-23") } it "knows this week " do expect(subject.this_week.name).to eq "2019W34" expect(subject.this_week.title).to eq "Week of August 19, 2019" end it "knows last week" do expect(subject.last_week.name).to eq "2019W33" expect(subject.last_week.title).to eq "Week of August 12, 2019" end it "knows next week" do expect(subject.next_week.name).to eq "2019W35" expect(subject.next_week.title).to eq "Week of August 26, 2019" end describe "over the years" do [ [2010, "2010-01-04", 52, "2010-12-27"], [2011, "2011-01-03", 52, "2011-12-26"], [2012, "2012-01-02", 52, "2012-12-24"], [2013, "2012-12-31", 52, "2013-12-23"], [2014, "2013-12-30", 52, "2014-12-22"], [2015, "2014-12-29", 53, "2015-12-28"], [2016, "2016-01-04", 52, "2016-12-26"], [2017, "2017-01-02", 52, "2017-12-25"], [2018, "2018-01-01", 52, "2018-12-24"], [2019, "2018-12-31", 52, "2019-12-23"], [2020, "2019-12-30", 53, "2020-12-28"], [2021, "2021-01-04", 52, "2021-12-27"], [2022, "2022-01-03", 52, "2022-12-26"], [2023, "2023-01-02", 52, "2023-12-25"], [2024, "2024-01-01", 52, "2024-12-23"], [2025, "2024-12-30", 52, "2025-12-22"], [2026, "2025-12-29", 53, "2026-12-28"], [2027, "2027-01-04", 52, "2027-12-27"], [2028, "2028-01-03", 52, "2028-12-25"], [2029, "2029-01-01", 52, "2029-12-24"], [2030, "2029-12-31", 52, "2030-12-23"] ].each do |year_number, first_start, num_weeks, last_start| context "year #{year_number}" do let(:year) { subject.year(year_number) } let(:weeks) { year.weeks } it "knows the first week of #{year_number} starts on #{first_start}" do expect(weeks.first.start_date).to eq Date.parse(first_start) end it "can navigate to the second week of the year" do week = weeks.first.next expect(week.start_date).to eq Date.parse(first_start) + 7.days expect(week.year).to eq year end it "knows that every week is 7 days" do weeks.each { |w| expect(w.end_date - w.start_date).to eq 6 } end it "knows there are #{num_weeks} in #{year_number}" do expect(weeks.count).to eq num_weeks end it "knows the last week of #{year_number} starts on #{last_start}" do expect(weeks.first.start_date).to eq Date.parse(first_start) end it "can navigate to the first week of the next year" do week = weeks.last.next expect(week.start_date).to eq Date.parse(last_start) + 7.days expect(week.year).to eq year.next end end end 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 "2020H2Q1M1W3D7" 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 "can parse a week within a year" do date = subject.parse("2018W37") expect(date).to be_a TimeBoss::Calendar::Week expect(date).to be_instance_of TimeBoss::Calendars::Gregorian::Week expect(date.name).to eq "2018W37" end it "can parse a week within a quarter" do date = subject.parse("2017Q2W2") expect(date).to be_a TimeBoss::Calendar::Week expect(date).to be_instance_of TimeBoss::Calendars::Gregorian::Week expect(date.name).to eq "2017W15" end it "can parse a week within a month" do date = subject.parse("2017M4W1") expect(date).to be_a TimeBoss::Calendar::Week expect(date).to be_instance_of TimeBoss::Calendars::Gregorian::Week expect(date.name).to eq "2017W14" 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 nothing if you give it nothing" do expect(subject.parse(nil)).to be nil expect(subject.parse("")).to be nil 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 "can get the weeks included in a range" do entries = result.weeks entries.each { |e| expect(e).to be_a TimeBoss::Calendars::Gregorian::Week } expect(entries.count).to eq 156 expect(entries.map(&:name)).to include("2016W1", "2016W38", "2017W15", "2017W52", "2018W52") 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 "can shift to a different week" do allow(subject).to receive(:this_week).and_return subject.parse("2020W23") result = basis.last_week expect(result).to be_a TimeBoss::Calendar::Day expect(result.to_s).to eq "2020-05-26" expect(basis.in_week).to eq 2 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 week" do let(:basis) { subject.parse("2017W8") } it "cannot shift to a different day" do expect(basis.last_day).to be nil expect(basis.in_day).to be nil end it "can shift to a different month" do allow(subject).to receive(:this_month).and_return subject.parse("2020M4") result = basis.next_month expect(result).to be_a TimeBoss::Calendars::Gregorian::Week expect(result.to_s).to eq "2020W22: 2020-05-25 thru 2020-05-31" expect(basis.in_month).to eq 4 end it "can shift to a different half" do allow(subject).to receive(:this_half).and_return subject.parse("2019H1") result = basis.last_half expect(result).to be_a TimeBoss::Calendars::Gregorian::Week expect(result.to_s).to eq "2018W34: 2018-08-20 thru 2018-08-26" expect(basis.in_half).to eq 8 end end context "from month" do let(:basis) { subject.parse("2017M4") } it "cannot shift to a different week" do expect(basis.last_week).to be nil expect(basis.in_week).to be nil 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