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