timeboss 0.3.1

Sign up to get free protection for your applications and to get access to all the features.
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