timeboss 1.0.5 → 1.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/ruby.yml +1 -1
- data/README.md +7 -2
- data/lib/timeboss/calendar/period.rb +4 -2
- data/lib/timeboss/calendar/support/has_fiscal_weeks.rb +17 -0
- data/lib/timeboss/calendar/support/has_iso_weeks.rb +30 -0
- data/lib/timeboss/calendar/support/monthly_unit.rb +2 -3
- data/lib/timeboss/calendar/support/unit.rb +6 -0
- data/lib/timeboss/calendar.rb +3 -3
- data/lib/timeboss/calendars/broadcast.rb +2 -0
- data/lib/timeboss/calendars/gregorian.rb +2 -4
- data/lib/timeboss/version.rb +1 -1
- data/lib/timeboss.rb +1 -0
- data/spec/calendar/support/monthly_unit_spec.rb +26 -21
- data/spec/calendars/gregorian_spec.rb +129 -20
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3091d788f78273741aeb0206a87765fed72e3908099c2596b70589740aa63c26
|
4
|
+
data.tar.gz: 2a0cd5b28cc9b05c07d3fe06aef86644876864b7b24eda946105706d0626622f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f58a6f56a3bf0cb2f44a00aee3b5a4d09fdbcd4b2cd8b06c2942fa4eefc508b0afff19dd577bef26bd7b5f187ca1ccb380e5efdd5b357da829ad2936d36c12ff
|
7
|
+
data.tar.gz: 5f2b1b5013f49c81e3e5f9066e9e4c3063ddd70231a7199010a79b822bc3531ead0d0bf285d9d045fb0434dcfa6e0a5564aac138a8b25b07e2a374b1c049faf9
|
data/.github/workflows/ruby.yml
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# TimeBoss ![Build Status](https://github.com/kevinstuffandthings/timeboss/actions/workflows/ruby.yml/badge.svg) [![Gem Version](https://badge.fury.io/rb/timeboss.svg)](https://badge.fury.io/rb/timeboss)
|
1
|
+
# TimeBoss ![Build Status](https://github.com/kevinstuffandthings/timeboss/actions/workflows/ruby.yml/badge.svg) [![Gem Version](https://badge.fury.io/rb/timeboss.svg)](https://badge.fury.io/rb/timeboss)
|
2
2
|
|
3
3
|
A gem providing convenient navigation of the [Broadcast Calendar](https://en.wikipedia.org/wiki/Broadcast_calendar), the standard Gregorian calendar, and is easily extensible to support multiple financial calendars.
|
4
4
|
|
@@ -160,7 +160,7 @@ $ tbsh
|
|
160
160
|
If you want to try things out locally without installing the gem or updating your ruby environment, you can use [Docker](https://docker.com):
|
161
161
|
|
162
162
|
```bash
|
163
|
-
$ docker run --rm -it ruby:
|
163
|
+
$ docker run --rm -it ruby:3.0-slim /bin/bash -c "gem install timeboss shellable >/dev/null && tbsh"
|
164
164
|
```
|
165
165
|
|
166
166
|
_Having trouble with the REPL? If you are using the examples from the [Usage](#Usage) section, keep in mind that the REPL is already in the context of the calendar -- so you don't need to specify the receiver!_
|
@@ -170,9 +170,14 @@ To create a custom calendar, simply extend the `TimeBoss::Calendar` class, and i
|
|
170
170
|
|
171
171
|
```ruby
|
172
172
|
require 'timeboss/calendar'
|
173
|
+
require 'timeboss/calendar/support/has_fiscal_weeks'
|
173
174
|
|
174
175
|
module MyCalendars
|
175
176
|
class AugustFiscal < TimeBoss::Calendar
|
177
|
+
# The calendar we wish to implement has "fiscal weeks", meaning that the weeks start on
|
178
|
+
# the day of the containing period.
|
179
|
+
include TimeBoss::Calendar::Support::HasFiscalWeeks
|
180
|
+
|
176
181
|
def initialize
|
177
182
|
# For each calendar, operation, the class will be instantiated with an ordinal value
|
178
183
|
# for `year` and `month`. It is the instance's job to translate those ordinals into
|
@@ -113,8 +113,10 @@ module TimeBoss
|
|
113
113
|
|
114
114
|
%w[day week month quarter half year].each do |size|
|
115
115
|
define_method(size.pluralize) do
|
116
|
-
entry = calendar.public_send("#{size}_for", self.begin.start_date)
|
117
|
-
build_entries
|
116
|
+
entry = calendar.public_send("#{size}_for", self.begin.start_date) || self.begin.public_send(size, 1)
|
117
|
+
entries = build_entries(entry)
|
118
|
+
entries.pop if size == "week" && self.end.next.public_send(size, 1) == entries.last
|
119
|
+
entries
|
118
120
|
end
|
119
121
|
|
120
122
|
define_method(size) do |index = nil|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TimeBoss
|
4
|
+
class Calendar
|
5
|
+
module Support
|
6
|
+
module HasFiscalWeeks
|
7
|
+
def weeks_in(year:)
|
8
|
+
num_weeks = (((year.end_date - year.start_date) + 1) / 7.0).to_i
|
9
|
+
num_weeks.times.map do |i|
|
10
|
+
start_date = year.start_date + (i * 7).days
|
11
|
+
Week.new(self, start_date, start_date + 6.days)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module TimeBoss
|
4
|
+
class Calendar
|
5
|
+
module Support
|
6
|
+
module HasIsoWeeks
|
7
|
+
def weeks_in(year:)
|
8
|
+
weeks = []
|
9
|
+
start_date = Date.commercial(year.year_index)
|
10
|
+
end_date = Date.commercial(year.next.year_index)
|
11
|
+
while start_date < end_date
|
12
|
+
weeks << Week.new(self, start_date, start_date + 6.days)
|
13
|
+
start_date += 7.days
|
14
|
+
end
|
15
|
+
weeks
|
16
|
+
end
|
17
|
+
|
18
|
+
class Week < Calendar::Week
|
19
|
+
def index
|
20
|
+
start_date.cweek
|
21
|
+
end
|
22
|
+
|
23
|
+
def year
|
24
|
+
calendar.year(start_date.cwyear)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -23,10 +23,9 @@ module TimeBoss
|
|
23
23
|
# Get a list of weeks contained within this period.
|
24
24
|
# @return [Array<Week>]
|
25
25
|
def weeks
|
26
|
+
raise UnsupportedUnitError unless calendar.supports_weeks?
|
26
27
|
base = calendar.year(year_index)
|
27
|
-
|
28
|
-
num_weeks.times.map { |i| Week.new(calendar, base.start_date + (i * 7).days, base.start_date + ((i * 7) + 6).days) }
|
29
|
-
.select { |w| w.start_date.between?(start_date, end_date) }
|
28
|
+
calendar.weeks_in(year: base).select { |w| (w.dates & dates).count >= 4 }
|
30
29
|
end
|
31
30
|
|
32
31
|
private
|
data/lib/timeboss/calendar.rb
CHANGED
@@ -35,11 +35,11 @@ module TimeBoss
|
|
35
35
|
end
|
36
36
|
|
37
37
|
# Can this calendar support weeks?
|
38
|
-
#
|
39
|
-
#
|
38
|
+
# To support weeks, a calendar must implement a `#weeks_in(year:)` method that returns an array of
|
39
|
+
# `Calendar::Week` objects.
|
40
40
|
# @return [Boolean]
|
41
41
|
def supports_weeks?
|
42
|
-
|
42
|
+
respond_to?(:weeks_in)
|
43
43
|
end
|
44
44
|
|
45
45
|
def self.register!
|
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "../calendar"
|
4
|
+
require_relative "../calendar/support/has_fiscal_weeks"
|
4
5
|
|
5
6
|
module TimeBoss
|
6
7
|
module Calendars
|
7
8
|
class Broadcast < Calendar
|
9
|
+
include Support::HasFiscalWeeks
|
8
10
|
register!
|
9
11
|
|
10
12
|
def initialize
|
@@ -1,20 +1,18 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative "../calendar"
|
4
|
+
require_relative "../calendar/support/has_iso_weeks"
|
4
5
|
|
5
6
|
module TimeBoss
|
6
7
|
module Calendars
|
7
8
|
class Gregorian < Calendar
|
9
|
+
include Support::HasIsoWeeks
|
8
10
|
register!
|
9
11
|
|
10
12
|
def initialize
|
11
13
|
super(basis: Basis)
|
12
14
|
end
|
13
15
|
|
14
|
-
def supports_weeks?
|
15
|
-
false
|
16
|
-
end
|
17
|
-
|
18
16
|
private
|
19
17
|
|
20
18
|
class Basis < Calendar::Support::MonthBasis
|
data/lib/timeboss/version.rb
CHANGED
data/lib/timeboss.rb
CHANGED
@@ -11,7 +11,7 @@ module TimeBoss
|
|
11
11
|
|
12
12
|
describe MonthlyUnit do
|
13
13
|
let(:described_class) { TestMonthBasedChunk }
|
14
|
-
let(:calendar) { double }
|
14
|
+
let(:calendar) { double(supports_weeks?: false, weeks_in: nil) }
|
15
15
|
let(:start_date) { Date.parse("2018-06-25") }
|
16
16
|
let(:end_date) { Date.parse("2018-08-26") }
|
17
17
|
let(:subject) { described_class.new(calendar, 2018, 4, start_date, end_date) }
|
@@ -27,29 +27,34 @@ module TimeBoss
|
|
27
27
|
end
|
28
28
|
|
29
29
|
describe "#weeks" do
|
30
|
-
let(:
|
31
|
-
before(:each) { allow(calendar).to receive(:
|
30
|
+
let(:supports_weeks) { false }
|
31
|
+
before(:each) { allow(calendar).to receive(:supports_weeks?).and_return supports_weeks }
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
expect(result.map { |w| w.start_date.to_s }).to eq [
|
38
|
-
"2018-06-25",
|
39
|
-
"2018-07-02",
|
40
|
-
"2018-07-09",
|
41
|
-
"2018-07-16",
|
42
|
-
"2018-07-23",
|
43
|
-
"2018-07-30",
|
44
|
-
"2018-08-06",
|
45
|
-
"2018-08-13",
|
46
|
-
"2018-08-20"
|
47
|
-
]
|
33
|
+
context "unsupported" do
|
34
|
+
it "blows up when weeks are not supported" do
|
35
|
+
expect { subject.weeks }.to raise_error TimeBoss::Calendar::Support::Unit::UnsupportedUnitError
|
36
|
+
end
|
48
37
|
end
|
49
38
|
|
50
|
-
|
51
|
-
|
52
|
-
|
39
|
+
context "supported" do
|
40
|
+
let(:supports_weeks) { true }
|
41
|
+
let(:base) { double(start_date: Date.parse("2018-01-01"), end_date: Date.parse("2018-12-30")) }
|
42
|
+
let(:weeks) do
|
43
|
+
["2018-04-19", "2018-06-25", "2018-07-16", "2018-08-20", "2018-09-30"].map do |date|
|
44
|
+
start_date = Date.parse(date)
|
45
|
+
TimeBoss::Calendar::Week.new(calendar, start_date, start_date + 6.days)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
before(:each) do
|
49
|
+
allow(calendar).to receive(:year).with(2018).and_return base
|
50
|
+
allow(calendar).to receive(:weeks_in).with(year: base).and_return weeks
|
51
|
+
end
|
52
|
+
|
53
|
+
it "can get the relevant weeks for the period" do
|
54
|
+
result = subject.weeks
|
55
|
+
result.each { |w| expect(w).to be_instance_of TimeBoss::Calendar::Week }
|
56
|
+
expect(result.map { |w| w.start_date.to_s }).to eq ["2018-06-25", "2018-07-16", "2018-08-20"]
|
57
|
+
end
|
53
58
|
end
|
54
59
|
end
|
55
60
|
|
@@ -1,7 +1,6 @@
|
|
1
1
|
module TimeBoss
|
2
2
|
describe Calendars::Gregorian do
|
3
|
-
|
4
|
-
|
3
|
+
subject { described_class.new }
|
5
4
|
context "days" do
|
6
5
|
it "can get today" do
|
7
6
|
day = subject.today
|
@@ -293,10 +292,80 @@ module TimeBoss
|
|
293
292
|
end
|
294
293
|
|
295
294
|
context "weeks" do
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
expect
|
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
|
300
369
|
end
|
301
370
|
end
|
302
371
|
|
@@ -394,7 +463,7 @@ module TimeBoss
|
|
394
463
|
let(:entry) { subject.parse("2020D201") }
|
395
464
|
|
396
465
|
it "can do a default format" do
|
397
|
-
expect(entry.format).to eq "
|
466
|
+
expect(entry.format).to eq "2020H2Q1M1W3D7"
|
398
467
|
end
|
399
468
|
end
|
400
469
|
end
|
@@ -449,16 +518,25 @@ module TimeBoss
|
|
449
518
|
expect(date.name).to eq "2017M4"
|
450
519
|
end
|
451
520
|
|
452
|
-
it "
|
453
|
-
|
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"
|
454
526
|
end
|
455
527
|
|
456
|
-
it "
|
457
|
-
|
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"
|
458
533
|
end
|
459
534
|
|
460
|
-
it "
|
461
|
-
|
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"
|
462
540
|
end
|
463
541
|
|
464
542
|
it "can parse a date" do
|
@@ -518,8 +596,11 @@ module TimeBoss
|
|
518
596
|
expect(entries.map(&:name)).to include("2016M1", "2016M9", "2017M3", "2018M12")
|
519
597
|
end
|
520
598
|
|
521
|
-
it "
|
522
|
-
|
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")
|
523
604
|
end
|
524
605
|
|
525
606
|
it "can get the days included in a range" do
|
@@ -535,9 +616,12 @@ module TimeBoss
|
|
535
616
|
context "from day" do
|
536
617
|
let(:basis) { subject.parse("2020-04-21") }
|
537
618
|
|
538
|
-
it "
|
539
|
-
|
540
|
-
|
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
|
541
625
|
end
|
542
626
|
|
543
627
|
it "can shift to a different quarter" do
|
@@ -557,12 +641,37 @@ module TimeBoss
|
|
557
641
|
end
|
558
642
|
end
|
559
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
|
+
|
560
669
|
context "from month" do
|
561
670
|
let(:basis) { subject.parse("2017M4") }
|
562
671
|
|
563
672
|
it "cannot shift to a different week" do
|
564
|
-
expect
|
565
|
-
expect
|
673
|
+
expect(basis.last_week).to be nil
|
674
|
+
expect(basis.in_week).to be nil
|
566
675
|
end
|
567
676
|
|
568
677
|
it "can shift to a different year" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: timeboss
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0
|
4
|
+
version: 1.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin McDonald
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-01-17 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -184,6 +184,8 @@ files:
|
|
184
184
|
- lib/timeboss/calendar/period.rb
|
185
185
|
- lib/timeboss/calendar/quarter.rb
|
186
186
|
- lib/timeboss/calendar/support/formatter.rb
|
187
|
+
- lib/timeboss/calendar/support/has_fiscal_weeks.rb
|
188
|
+
- lib/timeboss/calendar/support/has_iso_weeks.rb
|
187
189
|
- lib/timeboss/calendar/support/month_basis.rb
|
188
190
|
- lib/timeboss/calendar/support/monthly_unit.rb
|
189
191
|
- lib/timeboss/calendar/support/navigable.rb
|