timeboss 0.3.1 → 1.1.0
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 +4 -4
- data/.github/workflows/gem-push.yml +31 -0
- data/.github/workflows/ruby.yml +35 -0
- data/.travis.yml +3 -2
- data/Gemfile +2 -1
- data/README.md +22 -7
- data/Rakefile +3 -1
- data/bin/tbsh +2 -2
- data/lib/tasks/calendars.rake +5 -5
- 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 +13 -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 +24 -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/day_spec.rb +14 -14
- data/spec/calendar/quarter_spec.rb +9 -9
- data/spec/calendar/support/monthly_unit_spec.rb +50 -44
- data/spec/calendar/support/unit_spec.rb +23 -22
- data/spec/calendar/week_spec.rb +20 -20
- data/spec/calendars/broadcast_spec.rb +310 -310
- data/spec/calendars/gregorian_spec.rb +376 -267
- data/spec/calendars_spec.rb +19 -19
- data/spec/spec_helper.rb +2 -2
- data/timeboss.gemspec +16 -14
- metadata +35 -4
- data/lib/timeboss/support/shellable.rb +0 -17
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
|
|
@@ -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}}"
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# This workflow uses actions that are not certified by GitHub.
|
|
2
|
+
# They are provided by a third-party and are governed by
|
|
3
|
+
# separate terms of service, privacy policy, and support
|
|
4
|
+
# documentation.
|
|
5
|
+
# This workflow will download a prebuilt Ruby version, install dependencies and run tests with Rake
|
|
6
|
+
# For more information see: https://github.com/marketplace/actions/setup-ruby-jruby-and-truffleruby
|
|
7
|
+
|
|
8
|
+
name: Ruby
|
|
9
|
+
|
|
10
|
+
on:
|
|
11
|
+
push:
|
|
12
|
+
branches: [ master ]
|
|
13
|
+
pull_request:
|
|
14
|
+
branches: [ master ]
|
|
15
|
+
|
|
16
|
+
jobs:
|
|
17
|
+
test:
|
|
18
|
+
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
strategy:
|
|
21
|
+
matrix:
|
|
22
|
+
ruby-version: ['2.7', '3.0']
|
|
23
|
+
|
|
24
|
+
steps:
|
|
25
|
+
- uses: actions/checkout@v2
|
|
26
|
+
- name: Set up Ruby
|
|
27
|
+
# To automatically get bug fixes and new Ruby versions for ruby/setup-ruby,
|
|
28
|
+
# change this to (see https://github.com/ruby/setup-ruby#versioning):
|
|
29
|
+
# uses: ruby/setup-ruby@v1
|
|
30
|
+
uses: ruby/setup-ruby@473e4d8fe5dd94ee328fdfca9f8c9c7afc9dae5e
|
|
31
|
+
with:
|
|
32
|
+
ruby-version: ${{ matrix.ruby-version }}
|
|
33
|
+
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
|
|
34
|
+
- name: Run tests
|
|
35
|
+
run: bundle exec rspec
|
data/.travis.yml
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
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
|
|
8
9
|
deploy:
|
|
9
10
|
provider: rubygems
|
|
10
11
|
api_key:
|
|
11
|
-
secure:
|
|
12
|
+
secure: f7s86k3ZRgaao32goumx0EFSquj8v8vwLBQP0uPd5lZ5OlDjUIbzAGCLpP4IARFf7MhYUUxWqwU7A0Z2MJHgmf4hT2VdVco4kGC4WztMgSI2JwY8Uo21/QJgI82jIfZ6yfSjF8OC3eh9irqJxXfhzspO9DY4p+nJkMJnpG5Y1e5FjS9zM3gS80TD9fauIMEi3fOLDNYEZ95SgjrkX2MHDYQWN1nfFlkRtybSHJ2u2Ad3Ulr5c/1gIoJviJCm8l5Bwo3MnvBtSuHHjFOaH9UTcmDUGpBjr7GMoqn3m053aB1F3ImYwL9+il0rtj+PE1lNaVbUM/QDKp8gDcbo433m8oMiGRpouz0fdIi95fqsshZmSU9sZX6HPiOuURXXwrjW7n3bj71+qZ7zWPTyZB8p3Y6ocp/r6Aj0ewELJksjnbYqSuyYv0o5sKTh2AUMawcqWAnDlZWgMq4UqKQiaWlhZMN1guQIWO6Xq9xdoiIxcqRUTJ7dUAGsfv+GIs2iPLvh20DHudYN70J5b4xzZLFgPQOJbTGlQQtC18m2PaYvcdsZ1qzttQIs0fcgeKno1Ltcman6/yqbAdKsSjifLUcdqHiWOUk5Dh5l4S+iSVazILVFFHwV89JI1+ipuS1nnIaRcmfIkV3GB+aXcbwwYc89mLkXBVmezs+scygK0KUhoyU=
|
|
12
13
|
gem: timeboss
|
|
13
14
|
on:
|
|
14
15
|
tags: 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
|
|
|
@@ -40,7 +40,7 @@ You can ask simple questions of the calendar:
|
|
|
40
40
|
|
|
41
41
|
```ruby
|
|
42
42
|
period = calendar.parse('2019Q4') # or '2018', or '2019-12-21', or '2020W32', or '2020M3W2'
|
|
43
|
-
# => #<TimeBoss::Calendar::Quarter
|
|
43
|
+
# => #<TimeBoss::Calendar::Quarter start_date=2019-09-30, end_date=2019-12-29>
|
|
44
44
|
period.to_s
|
|
45
45
|
# => "2019Q4: 2019-09-30 thru 2019-12-29"
|
|
46
46
|
period.next.start_date.to_s # try previous, too!
|
|
@@ -117,6 +117,9 @@ week = calendar.parse('2014W29').this_week
|
|
|
117
117
|
|
|
118
118
|
calendar.this_week.next_year.to_s # run 2020W29
|
|
119
119
|
# => "2021W29: 2021-07-12 thru 2021-07-18"
|
|
120
|
+
|
|
121
|
+
calendar.week(2016, this_week.in_year) # run 2020-07-22
|
|
122
|
+
# => #<TimeBoss::Calendar::Week start_date=2016-07-18, end_date=2016-07-24>
|
|
120
123
|
```
|
|
121
124
|
|
|
122
125
|
Complicated range expressions can be parsed using the `..` range operator, or evaluated with `thru`:
|
|
@@ -136,7 +139,7 @@ period = calendar.parse('2020W3..2020Q1')
|
|
|
136
139
|
The examples above are just samples. Try different periods, operators, etc. All of the non-week-based operations will work similarly on the `TimeBoss::Calendars::Gregorian` calendar.
|
|
137
140
|
|
|
138
141
|
### REPL
|
|
139
|
-
To open a REPL for the broadcast calendar, use the `tbsh` executable, or the `timeboss:calendars:broadcast:repl` rake task.
|
|
142
|
+
To open a [REPL](https://repl.it/github/kevinstuffandthings/timeboss) locally for the broadcast calendar, use the `tbsh` executable, or the `timeboss:calendars:broadcast:repl` rake task.
|
|
140
143
|
|
|
141
144
|
For the Gregorian calendar (or any other implemented calendars), supply the name on the command line.
|
|
142
145
|
- `tbsh gregorian`
|
|
@@ -147,13 +150,19 @@ You will find yourself in the context of an instantiated `TimeBoss::Calendar` ob
|
|
|
147
150
|
```bash
|
|
148
151
|
$ tbsh
|
|
149
152
|
2.4.1 :001 > next_quarter
|
|
150
|
-
=> #<TimeBoss::Calendar::Quarter
|
|
153
|
+
=> #<TimeBoss::Calendar::Quarter start_date=2020-09-28, end_date=2020-12-27>
|
|
151
154
|
2.4.1 :002 > last_year
|
|
152
|
-
=> #<TimeBoss::Calendar::Year
|
|
155
|
+
=> #<TimeBoss::Calendar::Year start_date=2018-12-31, end_date=2019-12-29>
|
|
153
156
|
2.4.1 :003 > parse('this_quarter .. this_quarter+4').months.map(&:name)
|
|
154
157
|
=> ["2020M7", "2020M8", "2020M9", "2020M10", "2020M11", "2020M12", "2021M1", "2021M2", "2021M3", "2021M4", "2021M5", "2021M6", "2021M7", "2021M8", "2021M9"]
|
|
155
158
|
```
|
|
156
159
|
|
|
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
|
+
|
|
162
|
+
```bash
|
|
163
|
+
$ docker run --rm -it ruby:3.0-slim /bin/bash -c "gem install timeboss shellable >/dev/null && tbsh"
|
|
164
|
+
```
|
|
165
|
+
|
|
157
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!_
|
|
158
167
|
|
|
159
168
|
## Creating new calendars
|
|
@@ -161,9 +170,14 @@ To create a custom calendar, simply extend the `TimeBoss::Calendar` class, and i
|
|
|
161
170
|
|
|
162
171
|
```ruby
|
|
163
172
|
require 'timeboss/calendar'
|
|
173
|
+
require 'timeboss/calendar/support/has_fiscal_weeks'
|
|
164
174
|
|
|
165
175
|
module MyCalendars
|
|
166
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
|
+
|
|
167
181
|
def initialize
|
|
168
182
|
# For each calendar, operation, the class will be instantiated with an ordinal value
|
|
169
183
|
# for `year` and `month`. It is the instance's job to translate those ordinals into
|
|
@@ -229,5 +243,6 @@ With the new calendar implemented, it can be accessed in one of 2 ways:
|
|
|
229
243
|
calendar.this_year
|
|
230
244
|
```
|
|
231
245
|
|
|
232
|
-
|
|
233
|
-
|
|
246
|
+
# Problems?
|
|
247
|
+
Please submit an [issue](https://github.com/kevinstuffandthings/timeboss/issues).
|
|
248
|
+
We'll figure out how to get you up and running with TimeBoss as smoothly as possible.
|
data/Rakefile
CHANGED
data/bin/tbsh
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
|
|
3
3
|
require 'timeboss/calendars'
|
|
4
|
-
require '
|
|
4
|
+
require 'shellable'
|
|
5
5
|
|
|
6
6
|
calendar = if ARGV.length == 1
|
|
7
7
|
TimeBoss::Calendars[ARGV.first]
|
|
@@ -12,4 +12,4 @@ calendar = if ARGV.length == 1
|
|
|
12
12
|
abort "Unknown calendar" if calendar.nil?
|
|
13
13
|
|
|
14
14
|
puts "Active calendar: #{calendar.title}"
|
|
15
|
-
|
|
15
|
+
Shellable.open(calendar)
|
data/lib/tasks/calendars.rake
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
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
|
|
15
|
-
|
|
13
|
+
task repl: ["timeboss:init"] do
|
|
14
|
+
require "shellable"
|
|
15
|
+
Shellable.open(entry.calendar)
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
task shell: ["timeboss:calendars:#{entry.name}:repl"]
|
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,11 @@ 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
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def inspect
|
|
137
|
+
"#<#{self.class.name}[#{self.begin.inspect}..#{self.end.inspect}] start_date=#{start_date}, end_date=#{end_date}>"
|
|
131
138
|
end
|
|
132
139
|
|
|
133
140
|
private
|
|
@@ -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
|