timeboss 1.0.0 → 1.1.1

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