timeboss 1.0.1 → 1.1.2
Sign up to get free protection for your applications and to get access to all the features.
- 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 ![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
|
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
|