timeboss 1.0.1 → 1.1.2
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.
- checksums.yaml +5 -5
- data/.github/workflows/gem-push.yml +31 -0
- data/.github/workflows/ruby.yml +6 -4
- data/.travis.yml +11 -1
- data/Gemfile +2 -1
- data/README.md +7 -2
- data/Rakefile +3 -1
- data/bin/tbsh +7 -6
- data/lib/tasks/calendars.rake +4 -4
- data/lib/tasks/timeboss.rake +2 -2
- data/lib/timeboss/calendar/day.rb +3 -2
- data/lib/timeboss/calendar/half.rb +2 -1
- data/lib/timeboss/calendar/month.rb +2 -1
- data/lib/timeboss/calendar/parser.rb +9 -8
- data/lib/timeboss/calendar/period.rb +9 -6
- data/lib/timeboss/calendar/quarter.rb +2 -1
- data/lib/timeboss/calendar/support/formatter.rb +5 -4
- 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/month_basis.rb +1 -1
- data/lib/timeboss/calendar/support/monthly_unit.rb +8 -8
- data/lib/timeboss/calendar/support/navigable.rb +2 -1
- data/lib/timeboss/calendar/support/shiftable.rb +12 -11
- data/lib/timeboss/calendar/support/translatable.rb +3 -2
- data/lib/timeboss/calendar/support/unit.rb +20 -13
- data/lib/timeboss/calendar/waypoints/absolute.rb +4 -3
- data/lib/timeboss/calendar/waypoints/relative.rb +14 -13
- data/lib/timeboss/calendar/week.rb +3 -2
- data/lib/timeboss/calendar/year.rb +2 -1
- data/lib/timeboss/calendar.rb +8 -7
- data/lib/timeboss/calendars/broadcast.rb +10 -7
- data/lib/timeboss/calendars/gregorian.rb +4 -5
- data/lib/timeboss/calendars.rb +3 -2
- data/lib/timeboss/version.rb +2 -1
- data/lib/timeboss.rb +2 -0
- data/spec/{calendar → lib/timeboss/calendar}/day_spec.rb +14 -14
- data/spec/{calendar → lib/timeboss/calendar}/quarter_spec.rb +9 -9
- data/spec/lib/timeboss/calendar/support/monthly_unit_spec.rb +91 -0
- data/spec/{calendar → lib/timeboss/calendar}/support/unit_spec.rb +23 -22
- data/spec/{calendar → lib/timeboss/calendar}/week_spec.rb +20 -20
- data/spec/lib/timeboss/calendars/broadcast_spec.rb +796 -0
- data/spec/lib/timeboss/calendars/gregorian_spec.rb +793 -0
- data/spec/{calendars_spec.rb → lib/timeboss/calendars_spec.rb} +19 -19
- data/spec/spec_helper.rb +2 -2
- data/timeboss.gemspec +17 -14
- metadata +53 -23
- data/spec/calendar/support/monthly_unit_spec.rb +0 -85
- data/spec/calendars/broadcast_spec.rb +0 -796
- data/spec/calendars/gregorian_spec.rb +0 -684
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: b856796bb4e37e5776aeed86c19dd42832af3091b189f01878d907de5cbf1864
|
4
|
+
data.tar.gz: 824c18de591069e4c46d01dc14dc2c1a2ee54881b670218014adf3d4d220a0da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 24bc5de8281c36802fb79b92b79a2381794710d320d2890d022c40f40aef4cedb1e3fe963085e3e1ea39fe9f51715994ccca4e5844c0e8bfb2784cbb65c2a339
|
7
|
+
data.tar.gz: a1fb6c4825ad05f9dd21a8514c6e6dc24a4904cb235a2a1d16b888e3664fa5f92c6ea8b4ccef3c09198c8e2aea272fb662610bd25fc5ed210f2b095feb90fab3
|
@@ -0,0 +1,31 @@
|
|
1
|
+
name: Ruby Gem
|
2
|
+
|
3
|
+
on:
|
4
|
+
release:
|
5
|
+
types: [published]
|
6
|
+
|
7
|
+
jobs:
|
8
|
+
build:
|
9
|
+
name: Build + Publish
|
10
|
+
runs-on: ubuntu-latest
|
11
|
+
permissions:
|
12
|
+
contents: read
|
13
|
+
packages: write
|
14
|
+
|
15
|
+
steps:
|
16
|
+
- uses: actions/checkout@v2
|
17
|
+
- name: Set up Ruby 2.6
|
18
|
+
uses: actions/setup-ruby@v1
|
19
|
+
with:
|
20
|
+
ruby-version: 2.6.x
|
21
|
+
|
22
|
+
- name: Publish to RubyGems
|
23
|
+
run: |
|
24
|
+
mkdir -p $HOME/.gem
|
25
|
+
touch $HOME/.gem/credentials
|
26
|
+
chmod 0600 $HOME/.gem/credentials
|
27
|
+
printf -- "---\n:rubygems_api_key: ${GEM_HOST_API_KEY}\n" > $HOME/.gem/credentials
|
28
|
+
gem build *.gemspec
|
29
|
+
gem push *.gem
|
30
|
+
env:
|
31
|
+
GEM_HOST_API_KEY: "${{secrets.RUBYGEMS_AUTH_TOKEN}}"
|
data/.github/workflows/ruby.yml
CHANGED
@@ -17,6 +17,9 @@ jobs:
|
|
17
17
|
test:
|
18
18
|
|
19
19
|
runs-on: ubuntu-latest
|
20
|
+
strategy:
|
21
|
+
matrix:
|
22
|
+
ruby-version: ['2.7', '3.0']
|
20
23
|
|
21
24
|
steps:
|
22
25
|
- uses: actions/checkout@v2
|
@@ -24,10 +27,9 @@ jobs:
|
|
24
27
|
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
25
28
|
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
26
29
|
# uses: ruby/setup-ruby@v1
|
27
|
-
uses: ruby/setup-ruby@
|
30
|
+
uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
|
28
31
|
with:
|
29
|
-
ruby-version:
|
30
|
-
|
31
|
-
run: bundle install
|
32
|
+
ruby-version: ${{ matrix.ruby-version }}
|
33
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
32
34
|
- name: Run tests
|
33
35
|
run: bundle exec rspec
|
data/.travis.yml
CHANGED
@@ -1,7 +1,17 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
- 2.4
|
4
3
|
- 2.5
|
5
4
|
- 2.6
|
5
|
+
- 2.7
|
6
|
+
- 3.0
|
6
7
|
script:
|
7
8
|
- bundle exec rspec
|
9
|
+
deploy:
|
10
|
+
provider: rubygems
|
11
|
+
api_key:
|
12
|
+
secure: f7s86k3ZRgaao32goumx0EFSquj8v8vwLBQP0uPd5lZ5OlDjUIbzAGCLpP4IARFf7MhYUUxWqwU7A0Z2MJHgmf4hT2VdVco4kGC4WztMgSI2JwY8Uo21/QJgI82jIfZ6yfSjF8OC3eh9irqJxXfhzspO9DY4p+nJkMJnpG5Y1e5FjS9zM3gS80TD9fauIMEi3fOLDNYEZ95SgjrkX2MHDYQWN1nfFlkRtybSHJ2u2Ad3Ulr5c/1gIoJviJCm8l5Bwo3MnvBtSuHHjFOaH9UTcmDUGpBjr7GMoqn3m053aB1F3ImYwL9+il0rtj+PE1lNaVbUM/QDKp8gDcbo433m8oMiGRpouz0fdIi95fqsshZmSU9sZX6HPiOuURXXwrjW7n3bj71+qZ7zWPTyZB8p3Y6ocp/r6Aj0ewELJksjnbYqSuyYv0o5sKTh2AUMawcqWAnDlZWgMq4UqKQiaWlhZMN1guQIWO6Xq9xdoiIxcqRUTJ7dUAGsfv+GIs2iPLvh20DHudYN70J5b4xzZLFgPQOJbTGlQQtC18m2PaYvcdsZ1qzttQIs0fcgeKno1Ltcman6/yqbAdKsSjifLUcdqHiWOUk5Dh5l4S+iSVazILVFFHwV89JI1+ipuS1nnIaRcmfIkV3GB+aXcbwwYc89mLkXBVmezs+scygK0KUhoyU=
|
13
|
+
gem: timeboss
|
14
|
+
on:
|
15
|
+
tags: true
|
16
|
+
repo: kevinstuffandthings/timeboss
|
17
|
+
skip_cleanup: 'true'
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
# TimeBoss
|
1
|
+
# TimeBoss  [](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
|
data/Rakefile
CHANGED
data/bin/tbsh
CHANGED
@@ -1,13 +1,14 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
3
|
+
require "timeboss"
|
4
|
+
require "timeboss/calendars"
|
5
|
+
require "shellable"
|
5
6
|
|
6
7
|
calendar = if ARGV.length == 1
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
TimeBoss::Calendars[ARGV.first]
|
9
|
+
else
|
10
|
+
TimeBoss::Calendars.first.calendar
|
11
|
+
end
|
11
12
|
|
12
13
|
abort "Unknown calendar" if calendar.nil?
|
13
14
|
|
data/lib/tasks/calendars.rake
CHANGED
@@ -1,17 +1,17 @@
|
|
1
|
-
require
|
1
|
+
require "./lib/timeboss/calendars"
|
2
2
|
|
3
3
|
namespace :timeboss do
|
4
4
|
namespace :calendars do
|
5
5
|
TimeBoss::Calendars.each do |entry|
|
6
6
|
namespace entry.name do
|
7
7
|
desc "Evaluate an expression for the #{entry.name} calendar"
|
8
|
-
task :evaluate, %i[expression] => [
|
8
|
+
task :evaluate, %i[expression] => ["timeboss:init"] do |_, args|
|
9
9
|
puts entry.calendar.parse(args[:expression])
|
10
10
|
end
|
11
11
|
|
12
12
|
desc "Open a REPL with the #{entry.name} calendar"
|
13
|
-
task repl: [
|
14
|
-
require
|
13
|
+
task repl: ["timeboss:init"] do
|
14
|
+
require "shellable"
|
15
15
|
Shellable.open(entry.calendar)
|
16
16
|
end
|
17
17
|
|
data/lib/tasks/timeboss.rake
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require_relative "./support/unit"
|
3
4
|
|
4
5
|
module TimeBoss
|
5
6
|
class Calendar
|
@@ -17,7 +18,7 @@ module TimeBoss
|
|
17
18
|
# Get a "pretty" representation of this day.
|
18
19
|
# @return [String] (e.g. "August 3, 2020")
|
19
20
|
def title
|
20
|
-
start_date.strftime(
|
21
|
+
start_date.strftime("%B %-d, %Y")
|
21
22
|
end
|
22
23
|
|
23
24
|
alias_method :to_s, :name
|
@@ -1,8 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module TimeBoss
|
3
4
|
class Calendar
|
4
5
|
class Parser
|
5
|
-
RANGE_DELIMITER =
|
6
|
+
RANGE_DELIMITER = ".."
|
6
7
|
InvalidPeriodIdentifierError = Class.new(StandardError)
|
7
8
|
attr_reader :calendar
|
8
9
|
|
@@ -11,7 +12,7 @@ module TimeBoss
|
|
11
12
|
end
|
12
13
|
|
13
14
|
def parse(identifier = nil)
|
14
|
-
return nil unless (identifier ||
|
15
|
+
return nil unless (identifier || "").strip.length > 0
|
15
16
|
return parse_identifier(identifier) unless identifier&.include?(RANGE_DELIMITER)
|
16
17
|
bases = identifier.split(RANGE_DELIMITER).map { |i| parse_identifier(i.strip) } unless identifier.nil?
|
17
18
|
bases ||= [parse_identifier(nil)]
|
@@ -24,13 +25,13 @@ module TimeBoss
|
|
24
25
|
|
25
26
|
def parse_identifier(identifier)
|
26
27
|
captures = identifier&.match(/^([^-]+)(\s*[+-]\s*[0-9]+)$/)&.captures
|
27
|
-
base, offset = captures || [identifier,
|
28
|
-
period = parse_period(base&.strip)
|
29
|
-
period.offset(offset.gsub(/\s+/,
|
28
|
+
base, offset = captures || [identifier, "0"]
|
29
|
+
(period = parse_period(base&.strip)) || raise(InvalidPeriodIdentifierError)
|
30
|
+
period.offset(offset.gsub(/\s+/, "").to_i)
|
30
31
|
end
|
31
32
|
|
32
33
|
def parse_period(identifier)
|
33
|
-
return calendar.
|
34
|
+
return calendar.public_send(identifier) if calendar.respond_to?(identifier.to_s)
|
34
35
|
parse_term(identifier || Date.today.year.to_s)
|
35
36
|
end
|
36
37
|
|
@@ -38,13 +39,13 @@ module TimeBoss
|
|
38
39
|
return Day.new(calendar, Date.parse(identifier)) if identifier.match?(/^[0-9]{4}-?[01][0-9]-?[0-3][0-9]$/)
|
39
40
|
|
40
41
|
raise InvalidPeriodIdentifierError unless identifier.match?(/^[HQMWD0-9]+$/)
|
41
|
-
period =
|
42
|
+
period = identifier.to_i == 0 ? calendar.this_year : calendar.year(identifier.to_i)
|
42
43
|
%w[half quarter month week day].each do |size|
|
43
44
|
prefix = size[0].upcase
|
44
45
|
next unless identifier.include?(prefix)
|
45
46
|
junk, identifier = identifier.split(prefix)
|
46
47
|
raise InvalidPeriodIdentifierError if junk.match?(/\D/)
|
47
|
-
period = period.
|
48
|
+
(period = period.public_send(size.pluralize)[identifier.to_i - 1]) || raise(InvalidPeriodIdentifierError)
|
48
49
|
end
|
49
50
|
period
|
50
51
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module TimeBoss
|
3
4
|
class Calendar
|
4
5
|
class Period
|
@@ -28,8 +29,8 @@ module TimeBoss
|
|
28
29
|
|
29
30
|
%i[name title to_s].each do |message|
|
30
31
|
define_method(message) do
|
31
|
-
text = self.begin.
|
32
|
-
text = "#{text} #{Parser::RANGE_DELIMITER} #{self.end.
|
32
|
+
text = self.begin.public_send(message)
|
33
|
+
text = "#{text} #{Parser::RANGE_DELIMITER} #{self.end.public_send(message)}" unless self.end == self.begin
|
33
34
|
text
|
34
35
|
end
|
35
36
|
end
|
@@ -112,12 +113,14 @@ module TimeBoss
|
|
112
113
|
|
113
114
|
%w[day week month quarter half year].each do |size|
|
114
115
|
define_method(size.pluralize) do
|
115
|
-
entry = calendar.
|
116
|
-
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
|
117
120
|
end
|
118
121
|
|
119
122
|
define_method(size) do |index = nil|
|
120
|
-
entries =
|
123
|
+
entries = public_send(size.pluralize)
|
121
124
|
return entries[index - 1] unless index.nil?
|
122
125
|
return nil unless entries.length == 1
|
123
126
|
entries.first
|
@@ -127,7 +130,7 @@ module TimeBoss
|
|
127
130
|
# Express this period as a date range.
|
128
131
|
# @return [Range<Date, Date>]
|
129
132
|
def to_range
|
130
|
-
@_to_range ||= start_date
|
133
|
+
@_to_range ||= start_date..end_date
|
131
134
|
end
|
132
135
|
|
133
136
|
def inspect
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require_relative "./translatable"
|
3
4
|
|
4
5
|
module TimeBoss
|
5
6
|
class Calendar
|
@@ -18,10 +19,10 @@ module TimeBoss
|
|
18
19
|
end
|
19
20
|
|
20
21
|
def to_s
|
21
|
-
base, text =
|
22
|
+
base, text = "year", unit.year.name
|
22
23
|
periods.each do |period|
|
23
|
-
sub = unit.
|
24
|
-
index = sub.
|
24
|
+
(sub = unit.public_send(period)) || break
|
25
|
+
index = sub.public_send("in_#{base}")
|
25
26
|
text += "#{period[0].upcase}#{index}"
|
26
27
|
base = period
|
27
28
|
end
|
@@ -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
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
2
|
+
|
3
|
+
require_relative "./unit"
|
3
4
|
|
4
5
|
module TimeBoss
|
5
6
|
class Calendar
|
@@ -22,10 +23,9 @@ module TimeBoss
|
|
22
23
|
# Get a list of weeks contained within this period.
|
23
24
|
# @return [Array<Week>]
|
24
25
|
def weeks
|
26
|
+
raise UnsupportedUnitError unless calendar.supports_weeks?
|
25
27
|
base = calendar.year(year_index)
|
26
|
-
|
27
|
-
num_weeks.times.map { |i| Week.new(calendar, base.start_date + (i * 7).days, base.start_date + ((i * 7) + 6).days) }
|
28
|
-
.select { |w| w.start_date.between?(start_date, end_date) }
|
28
|
+
calendar.weeks_in(year: base).select { |w| (w.dates & dates).count >= 4 }
|
29
29
|
end
|
30
30
|
|
31
31
|
private
|
@@ -36,17 +36,17 @@ module TimeBoss
|
|
36
36
|
|
37
37
|
def up
|
38
38
|
if index == max_index
|
39
|
-
calendar.
|
39
|
+
calendar.public_send(self.class.type, year_index + 1, 1)
|
40
40
|
else
|
41
|
-
calendar.
|
41
|
+
calendar.public_send(self.class.type, year_index, index + 1)
|
42
42
|
end
|
43
43
|
end
|
44
44
|
|
45
45
|
def down
|
46
46
|
if index == 1
|
47
|
-
calendar.
|
47
|
+
calendar.public_send(self.class.type, year_index - 1, max_index)
|
48
48
|
else
|
49
|
-
calendar.
|
49
|
+
calendar.public_send(self.class.type, year_index, index - 1)
|
50
50
|
end
|
51
51
|
end
|
52
52
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module TimeBoss
|
3
4
|
class Calendar
|
4
5
|
module Support
|
@@ -61,7 +62,7 @@ module TimeBoss
|
|
61
62
|
entry = self
|
62
63
|
while quantity > 0
|
63
64
|
entries << entry
|
64
|
-
entry = entry.
|
65
|
+
entry = entry.public_send(navigator)
|
65
66
|
quantity -= 1
|
66
67
|
end
|
67
68
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module TimeBoss
|
3
4
|
class Calendar
|
4
5
|
module Support
|
@@ -7,21 +8,21 @@ module TimeBoss
|
|
7
8
|
periods = period.pluralize
|
8
9
|
|
9
10
|
define_method("in_#{period}") do
|
10
|
-
base =
|
11
|
+
base = public_send(periods)
|
11
12
|
return unless base.length == 1
|
12
|
-
base.first.
|
13
|
+
base.first.public_send(self.class.type.to_s.pluralize).find_index { |p| p == self } + 1
|
13
14
|
end
|
14
15
|
|
15
16
|
define_method("#{periods}_ago") do |offset|
|
16
|
-
base_offset =
|
17
|
-
(calendar.
|
17
|
+
(base_offset = public_send("in_#{period}")) || return
|
18
|
+
(calendar.public_send("this_#{period}") - offset).public_send(self.class.type.to_s.pluralize)[base_offset - 1]
|
18
19
|
end
|
19
20
|
|
20
|
-
define_method("#{periods}_ahead") { |o|
|
21
|
+
define_method("#{periods}_ahead") { |o| public_send("#{periods}_ago", o * -1) }
|
21
22
|
|
22
|
-
define_method("last_#{period}") {
|
23
|
-
define_method("this_#{period}") {
|
24
|
-
define_method("next_#{period}") {
|
23
|
+
define_method("last_#{period}") { public_send("#{periods}_ago", 1) }
|
24
|
+
define_method("this_#{period}") { public_send("#{periods}_ago", 0) }
|
25
|
+
define_method("next_#{period}") { public_send("#{periods}_ahead", 1) }
|
25
26
|
end
|
26
27
|
|
27
28
|
alias_method :yesterday, :last_day
|
@@ -133,7 +134,7 @@ module TimeBoss
|
|
133
134
|
# Get the index-relative month 1 month forward.
|
134
135
|
# Returns nil if no single month can be identified.
|
135
136
|
# @return [Calendar::Month, nil]
|
136
|
-
|
137
|
+
|
137
138
|
### Quarters
|
138
139
|
|
139
140
|
# @!method in_quarter
|
@@ -167,7 +168,7 @@ module TimeBoss
|
|
167
168
|
# Get the index-relative quarter 1 quarter forward.
|
168
169
|
# Returns nil if no single quarter can be identified.
|
169
170
|
# @return [Calendar::Quarter, nil]
|
170
|
-
|
171
|
+
|
171
172
|
### Halves
|
172
173
|
|
173
174
|
# @!method in_half
|
@@ -201,7 +202,7 @@ module TimeBoss
|
|
201
202
|
# Get the index-relative half 1 half forward.
|
202
203
|
# Returns nil if no single half can be identified.
|
203
204
|
# @return [Calendar::Half, nil]
|
204
|
-
|
205
|
+
|
205
206
|
### Years
|
206
207
|
|
207
208
|
# @!method in_year
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module TimeBoss
|
3
4
|
class Calendar
|
4
5
|
module Support
|
@@ -8,10 +9,10 @@ module TimeBoss
|
|
8
9
|
PERIODS.each do |period|
|
9
10
|
periods = period.pluralize
|
10
11
|
|
11
|
-
define_method(periods) { calendar.
|
12
|
+
define_method(periods) { calendar.public_send("#{periods}_for", self) }
|
12
13
|
|
13
14
|
define_method(period) do |index = nil|
|
14
|
-
entries =
|
15
|
+
entries = public_send(periods)
|
15
16
|
return entries[index - 1] unless index.nil?
|
16
17
|
return nil unless entries.length == 1
|
17
18
|
entries.first
|
@@ -1,8 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative
|
4
|
-
require_relative
|
5
|
-
require_relative
|
2
|
+
|
3
|
+
require_relative "./navigable"
|
4
|
+
require_relative "./translatable"
|
5
|
+
require_relative "./shiftable"
|
6
|
+
require_relative "./formatter"
|
6
7
|
|
7
8
|
module TimeBoss
|
8
9
|
class Calendar
|
@@ -16,7 +17,7 @@ module TimeBoss
|
|
16
17
|
UnsupportedUnitError = Class.new(StandardError)
|
17
18
|
|
18
19
|
def self.type
|
19
|
-
|
20
|
+
name.demodulize.underscore
|
20
21
|
end
|
21
22
|
|
22
23
|
def initialize(calendar, start_date, end_date)
|
@@ -28,8 +29,8 @@ module TimeBoss
|
|
28
29
|
# Is the specified unit equal to this one, based on its unit type and date range?
|
29
30
|
# @param entry [Unit] the unit to compare
|
30
31
|
# @return [Boolean] true when periods are equal
|
31
|
-
def ==(
|
32
|
-
self.class ==
|
32
|
+
def ==(other)
|
33
|
+
self.class == other.class && start_date == other.start_date && end_date == other.end_date
|
33
34
|
end
|
34
35
|
|
35
36
|
# Format this period based on specified granularities.
|
@@ -59,33 +60,39 @@ module TimeBoss
|
|
59
60
|
def offset(value)
|
60
61
|
method = value.negative? ? :previous : :next
|
61
62
|
base = self
|
62
|
-
value.abs.times { base = base.
|
63
|
+
value.abs.times { base = base.public_send(method) }
|
63
64
|
base
|
64
65
|
end
|
65
66
|
|
66
67
|
# Move some number of units forward from this unit.
|
67
68
|
# @param value [Integer]
|
68
69
|
# @return [Unit]
|
69
|
-
def +(
|
70
|
-
offset(
|
70
|
+
def +(other)
|
71
|
+
offset(other)
|
71
72
|
end
|
72
73
|
|
73
74
|
# Move some number of units backward from this unit.
|
74
75
|
# @param value [Integer]
|
75
76
|
# @return [Unit]
|
76
|
-
def -(
|
77
|
-
offset(-
|
77
|
+
def -(other)
|
78
|
+
offset(-other)
|
78
79
|
end
|
79
80
|
|
80
81
|
# Express this period as a date range.
|
81
82
|
# @return [Range<Date, Date>]
|
82
83
|
def to_range
|
83
|
-
@_to_range ||= start_date
|
84
|
+
@_to_range ||= start_date..end_date
|
84
85
|
end
|
85
86
|
|
86
87
|
def inspect
|
87
88
|
"#<#{self.class.name} start_date=#{start_date}, end_date=#{end_date}>"
|
88
89
|
end
|
90
|
+
|
91
|
+
protected
|
92
|
+
|
93
|
+
def dates
|
94
|
+
@_dates ||= to_range.to_a
|
95
|
+
end
|
89
96
|
end
|
90
97
|
end
|
91
98
|
end
|
@@ -1,4 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module TimeBoss
|
3
4
|
class Calendar
|
4
5
|
module Waypoints
|
@@ -9,13 +10,13 @@ module TimeBoss
|
|
9
10
|
|
10
11
|
define_method type do |year_index, index = 1|
|
11
12
|
month = (index * size) - size + 1
|
12
|
-
months = (month
|
13
|
+
months = (month..month + size - 1).map { |i| basis.new(year_index, i) }
|
13
14
|
klass.new(self, year_index, index, months.first.start_date, months.last.end_date)
|
14
15
|
end
|
15
16
|
|
16
17
|
define_method "#{type}_for" do |date|
|
17
|
-
window =
|
18
|
-
|
18
|
+
window = public_send(type, date.year - 1, 1)
|
19
|
+
loop do
|
19
20
|
break window if window.to_range.include?(date)
|
20
21
|
window = window.next
|
21
22
|
end
|