fat_period 1.0.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 +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/README.md +36 -0
- data/Rakefile +6 -0
- data/bin/console +13 -0
- data/bin/setup +8 -0
- data/fat_period.gemspec +31 -0
- data/lib/fat_period/date.rb +31 -0
- data/lib/fat_period/period.rb +785 -0
- data/lib/fat_period/version.rb +3 -0
- data/lib/fat_period.rb +3 -0
- metadata +160 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 06d57a27d2bdf83d78c5d0a02b470af0a9f59d69
|
4
|
+
data.tar.gz: 7beb9253ef8d2020d24d41dbb30c9b92d402cbbc
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 8936394c6426abb4048f95f2e2930dcafa822bb273831df12e02d26f100a6d6778581bf2e00b665e34b37fc327b242fd85cfa17bd24a8524d87142256cdd0a03
|
7
|
+
data.tar.gz: e2fd7548fa3f8134b4fd3e79b8ff91aff97d30348cf3d5556faa6c4564adab292518df8748532fa3d3858a444ac4f2c210c7f4e99aecee6e2c32572626f02440
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
# FatPeriod
|
2
|
+
|
3
|
+
Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/fat_period`. To experiment with that code, run `bin/console` for an interactive prompt.
|
4
|
+
|
5
|
+
TODO: Delete this and the text above, and describe your gem
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
```ruby
|
12
|
+
gem 'fat_period'
|
13
|
+
```
|
14
|
+
|
15
|
+
And then execute:
|
16
|
+
|
17
|
+
$ bundle
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
|
21
|
+
$ gem install fat_period
|
22
|
+
|
23
|
+
## Usage
|
24
|
+
|
25
|
+
TODO: Write usage instructions here
|
26
|
+
|
27
|
+
## Development
|
28
|
+
|
29
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
30
|
+
|
31
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
32
|
+
|
33
|
+
## Contributing
|
34
|
+
|
35
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/Daniel E. Doherty/fat_period.
|
36
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'fat_period'
|
5
|
+
require 'pry'
|
6
|
+
|
7
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
8
|
+
# with your gem easier. You can also use a different console, if you like.
|
9
|
+
@dd1 = Date.parse('2016-01-31')
|
10
|
+
@dd2 = Date.parse('2016-01-30')
|
11
|
+
@dd3 = Date.parse('2016-01-29')
|
12
|
+
|
13
|
+
Pry.start
|
data/bin/setup
ADDED
data/fat_period.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'fat_period/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'fat_period'
|
8
|
+
spec.version = FatPeriod::VERSION
|
9
|
+
spec.authors = ['Daniel E. Doherty']
|
10
|
+
spec.email = ['ded-law@ddoherty.net']
|
11
|
+
|
12
|
+
spec.summary = %q{Implements a Period class as a Range of Dates.}
|
13
|
+
spec.homepage = 'https://github.com/ddoherty03/fat_period'
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
16
|
+
f.match(%r{^(test|spec|features)/})
|
17
|
+
end
|
18
|
+
# Don't install any executables.
|
19
|
+
# spec.bindir = 'bin'
|
20
|
+
# spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
21
|
+
spec.require_paths = ['lib']
|
22
|
+
|
23
|
+
spec.add_development_dependency 'bundler', '~> 1.14'
|
24
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
25
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
26
|
+
spec.add_development_dependency 'pry'
|
27
|
+
spec.add_development_dependency 'pry-doc'
|
28
|
+
spec.add_development_dependency 'pry-byebug'
|
29
|
+
|
30
|
+
spec.add_runtime_dependency 'fat_core', '~> 4.0', '>= 4.1'
|
31
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'date'
|
2
|
+
|
3
|
+
module FatPeriod
|
4
|
+
# An extension of Date for methods useful with respect to FatPeriod::Periods.
|
5
|
+
module Date
|
6
|
+
# Return the Period of the given chunk size that contains this Date. Chunk
|
7
|
+
# can be one of :year, :half, :quarter, :bimonth, :month, :semimonth,
|
8
|
+
# :biweek, :week, or :day.
|
9
|
+
#
|
10
|
+
# @example
|
11
|
+
# date = Date.parse('2015-06-13')
|
12
|
+
# date.expand_to_period(:week) #=> Period(2015-06-08..2015-06-14)
|
13
|
+
# date.expand_to_period(:semimonth) #=> Period(2015-06-01..2015-06-15)
|
14
|
+
# date.expand_to_period(:quarter) #=> Period(2015-04-01..2015-06-30)
|
15
|
+
#
|
16
|
+
# @param chunk [Symbol] one of :year, :half, :quarter, :bimonth, :month,
|
17
|
+
# :semimonth, :biweek, :week, or :day
|
18
|
+
# @return [Period] Period of size `chunk` containing self
|
19
|
+
def expand_to_period(chunk)
|
20
|
+
require 'fat_period'
|
21
|
+
Period.new(beginning_of_chunk(chunk), end_of_chunk(chunk))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
# An extension Date for methods useful with respect to FatPeriod::Periods.
|
27
|
+
class Date
|
28
|
+
include FatPeriod::Date
|
29
|
+
# @!parse include FatPeriod::Date
|
30
|
+
# @!parse extend FatPeriod::Date::ClassMethods
|
31
|
+
end
|
@@ -0,0 +1,785 @@
|
|
1
|
+
# -*- coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'fat_core/date'
|
4
|
+
require 'fat_core/range'
|
5
|
+
require 'fat_core/string'
|
6
|
+
|
7
|
+
class Period
|
8
|
+
# Return the first Date of the Period
|
9
|
+
#
|
10
|
+
# @return [Date]
|
11
|
+
attr_reader :first
|
12
|
+
|
13
|
+
# Return the last Date of the Period
|
14
|
+
#
|
15
|
+
# @return [Date]
|
16
|
+
attr_reader :last
|
17
|
+
|
18
|
+
# @group Construction
|
19
|
+
#
|
20
|
+
# Return a new Period from the Date `first` to the Date `last` inclusive. Both
|
21
|
+
# parameters can be either a Date object or a String that can be parsed as a
|
22
|
+
# valid Date with `Date.parse`.
|
23
|
+
#
|
24
|
+
# @param first [Date, String] first date of Period
|
25
|
+
# @param last [Date, String] last date of Period
|
26
|
+
# @raise [ArgumentError] if string is not parseable as a Date or
|
27
|
+
# @raise [ArgumentError] if first date is later than last date
|
28
|
+
# @return [Period]
|
29
|
+
def initialize(first, last)
|
30
|
+
if first.is_a?(Date)
|
31
|
+
@first = first
|
32
|
+
elsif first.respond_to?(:to_s)
|
33
|
+
begin
|
34
|
+
@first = Date.parse(first.to_s)
|
35
|
+
rescue ArgumentError => ex
|
36
|
+
if ex.message =~ /invalid date/
|
37
|
+
raise ArgumentError, "you gave an invalid date '#{first}'"
|
38
|
+
else
|
39
|
+
raise
|
40
|
+
end
|
41
|
+
end
|
42
|
+
else
|
43
|
+
raise ArgumentError, 'use Date or String to initialize Period'
|
44
|
+
end
|
45
|
+
|
46
|
+
if last.is_a?(Date)
|
47
|
+
@last = last
|
48
|
+
elsif last.respond_to?(:to_s)
|
49
|
+
begin
|
50
|
+
@last = Date.parse(last.to_s)
|
51
|
+
rescue ArgumentError => ex
|
52
|
+
if ex.message =~ /invalid date/
|
53
|
+
raise ArgumentError, "you gave an invalid date '#{last}'"
|
54
|
+
else
|
55
|
+
raise
|
56
|
+
end
|
57
|
+
end
|
58
|
+
else
|
59
|
+
raise ArgumentError, 'use Date or String to initialize Period'
|
60
|
+
end
|
61
|
+
if @first > @last
|
62
|
+
raise ArgumentError, "Period's first date is later than its last date"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# These need to come after initialize is defined
|
67
|
+
|
68
|
+
# Period from commercial beginning of time to today
|
69
|
+
TO_DATE = Period.new(Date::BOT, Date.current)
|
70
|
+
|
71
|
+
# Period from commercial beginning of time to commercial end of time.
|
72
|
+
FOREVER = Period.new(Date::BOT, Date::EOT)
|
73
|
+
|
74
|
+
# @group Conversion
|
75
|
+
|
76
|
+
# Convert this Period to a Range.
|
77
|
+
#
|
78
|
+
# @return [Range]
|
79
|
+
def to_range
|
80
|
+
(first..last)
|
81
|
+
end
|
82
|
+
|
83
|
+
# Return a string representing this Period using compact format for years,
|
84
|
+
# halves, quarters, or months that represent a whole period; otherwise, just
|
85
|
+
# format the period as 'YYYY-MM-DD to YYYY-MM-DD'.
|
86
|
+
#
|
87
|
+
# @example
|
88
|
+
# Period.new('2016-01-01', '2016-03-31') #=> '2016-1Q'
|
89
|
+
# Period.new('2016-01-01', '2016-12-31') #=> '2016'
|
90
|
+
# Period.new('2016-01-01', '2016-11-30') #=> '2016-01-01 to 2016-11-30'
|
91
|
+
#
|
92
|
+
# @return [String] concise representation of Period
|
93
|
+
def to_s
|
94
|
+
if first.beginning_of_year? && last.end_of_year? && first.year == last.year
|
95
|
+
first.year.to_s
|
96
|
+
elsif first.beginning_of_half? &&
|
97
|
+
last.end_of_half? &&
|
98
|
+
first.year == last.year &&
|
99
|
+
first.half == last.half
|
100
|
+
"#{first.year}-#{first.half}H"
|
101
|
+
elsif first.beginning_of_quarter? &&
|
102
|
+
last.end_of_quarter? &&
|
103
|
+
first.year == last.year &&
|
104
|
+
first.quarter == last.quarter
|
105
|
+
"#{first.year}-#{first.quarter}Q"
|
106
|
+
elsif first.beginning_of_month? &&
|
107
|
+
last.end_of_month? &&
|
108
|
+
first.year == last.year &&
|
109
|
+
first.month == last.month
|
110
|
+
"#{first.year}-%02d" % first.month
|
111
|
+
else
|
112
|
+
"#{first.iso} to #{last.iso}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# A concise way to print out Periods for inspection as
|
117
|
+
# 'Period(YYYY-MM-DD..YYYY-MM-DD)'.
|
118
|
+
#
|
119
|
+
# @return [String]
|
120
|
+
def inspect
|
121
|
+
"Period(#{first.iso}..#{last.iso})"
|
122
|
+
end
|
123
|
+
|
124
|
+
# Allow erb documents can directly interpolate ranges
|
125
|
+
def tex_quote
|
126
|
+
"#{first.iso}--#{last.iso}"
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
include Comparable
|
131
|
+
|
132
|
+
# @group Comparison
|
133
|
+
#
|
134
|
+
# Comparable base: periods are compared by first, then by last and are equal
|
135
|
+
# only if their first and last dates are equal. Sorting will be by first date,
|
136
|
+
# then last, so periods starting on the same date will sort from smallest to
|
137
|
+
# largest.
|
138
|
+
#
|
139
|
+
# @param other [Period] @return [Integer] -1 if self < other; 0 if self ==
|
140
|
+
# other; 1 if self > other
|
141
|
+
def <=>(other)
|
142
|
+
return nil unless other.is_a?(Period)
|
143
|
+
[first, last] <=> [other.first, other.last]
|
144
|
+
end
|
145
|
+
|
146
|
+
# Comparable does not include this.
|
147
|
+
def !=(other)
|
148
|
+
!(self == other)
|
149
|
+
end
|
150
|
+
|
151
|
+
# Return whether this Period contains the given date.
|
152
|
+
#
|
153
|
+
# @param date [Date] date to test
|
154
|
+
# @return [Boolean] is the given date within this Period?
|
155
|
+
def contains?(date)
|
156
|
+
date = date.to_date if date.respond_to?(:to_date)
|
157
|
+
raise ArgumentError, 'argument must be a Date' unless date.is_a?(Date)
|
158
|
+
to_range.cover?(date)
|
159
|
+
end
|
160
|
+
alias === contains?
|
161
|
+
|
162
|
+
include Enumerable
|
163
|
+
|
164
|
+
# @group Enumeration
|
165
|
+
|
166
|
+
# Yield each day in this Period.
|
167
|
+
def each
|
168
|
+
d = first
|
169
|
+
while d <= last
|
170
|
+
yield d
|
171
|
+
d += 1.day
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
# Return an Array of the days in the Period that are trading days on the NYSE.
|
176
|
+
# See FatCore::Date for how trading days are determined.
|
177
|
+
#
|
178
|
+
# @return [Array<Date>] trading days in this period
|
179
|
+
def trading_days
|
180
|
+
select(&:nyse_workday?)
|
181
|
+
end
|
182
|
+
|
183
|
+
# @group Size
|
184
|
+
|
185
|
+
# Return the number of days in the period
|
186
|
+
def size
|
187
|
+
(last - first + 1).to_i
|
188
|
+
end
|
189
|
+
alias length size
|
190
|
+
alias days size
|
191
|
+
|
192
|
+
# Return the fractional number of months in the period. By default, use the
|
193
|
+
# average number of days in a month, but allow the user to override the
|
194
|
+
# assumption with a parameter.
|
195
|
+
def months(days_in_month = 30.436875)
|
196
|
+
(days / days_in_month.to_f).to_f
|
197
|
+
end
|
198
|
+
|
199
|
+
# Return the fractional number of years in the period. By default, use the
|
200
|
+
# average number of days in a year, but allow the user to override the
|
201
|
+
# assumption with a parameter.
|
202
|
+
def years(days_in_year = 365.2425)
|
203
|
+
(days / days_in_year.to_f).to_f
|
204
|
+
end
|
205
|
+
|
206
|
+
# @group Parsing
|
207
|
+
#
|
208
|
+
# Return a period based on two date specs passed as strings (see
|
209
|
+
# `FatCore::Date.parse_spec`), a 'from' and a 'to' spec. The returned period
|
210
|
+
# begins on the first day of the period given as the `from` spec and ends on
|
211
|
+
# the last day given as the `to` spec. If the to spec is not given or is nil,
|
212
|
+
# the from spec is used for both the from- and to-spec.
|
213
|
+
#
|
214
|
+
# @example
|
215
|
+
# Period.parse('2014-11').inspect #=> Period('2014-11-01..2014-11-30')
|
216
|
+
# Period.parse('2014-11', '2015-3Q').inspect #=> Period('2014-11-01..2015-09-30')
|
217
|
+
# # Assuming this executes in December, 2014
|
218
|
+
# Period.parse('last_month', 'this_month').inspect #=> Period('2014-11-01..2014-12-31')
|
219
|
+
#
|
220
|
+
# @param from [String] spec ala FatCore::Date.parse_spec
|
221
|
+
# @param to [String] spec ala FatCore::Date.parse_spec
|
222
|
+
# @return [Period] from beginning of `from` to end of `to`
|
223
|
+
def self.parse(from, to = nil)
|
224
|
+
raise ArgumentError, 'Period.parse missing argument' unless from
|
225
|
+
to ||= from
|
226
|
+
first = Date.parse_spec(from, :from)
|
227
|
+
second = Date.parse_spec(to, :to)
|
228
|
+
Period.new(first, second) if first && second
|
229
|
+
end
|
230
|
+
|
231
|
+
# Return a period as in `Period.parse` from a String phrase in which the from
|
232
|
+
# spec is introduced with 'from' and, optionally, the to spec is introduced
|
233
|
+
# with 'to'. A phrase with only a to spec is treated the same as one with
|
234
|
+
# only a from spec. If neither 'from' nor 'to' appear in phrase, treat the
|
235
|
+
# whole string as a from spec.
|
236
|
+
#
|
237
|
+
# @example
|
238
|
+
# Period.parse_phrase('from 2014-11 to 2015-3Q') #=> Period('2014-11-01..2015-09-30')
|
239
|
+
# Period.parse_phrase('from 2014-11') #=> Period('2014-11-01..2014-11-30')
|
240
|
+
# Period.parse_phrase('from 2015-3Q') #=> Period('2015-09-01..2015-12-31')
|
241
|
+
# Period.parse_phrase('to 2015-3Q') #=> Period('2015-09-01..2015-12-31')
|
242
|
+
# Period.parse_phrase('2015-3Q') #=> Period('2015-09-01..2015-12-31')
|
243
|
+
#
|
244
|
+
# @param phrase [String] with 'from <spec> to <spec>'
|
245
|
+
# @return [Period] translated from phrase
|
246
|
+
def self.parse_phrase(phrase)
|
247
|
+
phrase = phrase.clean
|
248
|
+
if phrase =~ /\Afrom (.*) to (.*)\z/
|
249
|
+
from_phrase = $1
|
250
|
+
to_phrase = $2
|
251
|
+
elsif phrase =~ /\Afrom (.*)\z/
|
252
|
+
from_phrase = $1
|
253
|
+
to_phrase = nil
|
254
|
+
elsif phrase =~ /\Ato (.*)\z/
|
255
|
+
from_phrase = $1
|
256
|
+
else
|
257
|
+
from_phrase = phrase
|
258
|
+
to_phrase = nil
|
259
|
+
end
|
260
|
+
parse(from_phrase, to_phrase)
|
261
|
+
end
|
262
|
+
|
263
|
+
# Possibly useful class method to take an array of periods and join all the
|
264
|
+
# contiguous ones, then return an array of the disjoint periods not
|
265
|
+
# contiguous to one another. An array of periods with no gaps should return
|
266
|
+
# an array of only one period spanning all the given periods.
|
267
|
+
#
|
268
|
+
# Return an array of periods that represent the concatenation of all
|
269
|
+
# adjacent periods in the given periods.
|
270
|
+
# def self.meld_periods(*periods)
|
271
|
+
# melded_periods = []
|
272
|
+
# while (this_period = periods.pop)
|
273
|
+
# melded_periods.each do |mp|
|
274
|
+
# if mp.overlaps?(this_period)
|
275
|
+
# melded_periods.delete(mp)
|
276
|
+
# melded_periods << mp.union(this_period)
|
277
|
+
# break
|
278
|
+
# elsif mp.contiguous?(this_period)
|
279
|
+
# melded_periods.delete(mp)
|
280
|
+
# melded_periods << mp.join(this_period)
|
281
|
+
# break
|
282
|
+
# end
|
283
|
+
# end
|
284
|
+
# end
|
285
|
+
# melded_periods
|
286
|
+
# end
|
287
|
+
#
|
288
|
+
# @group Chunking
|
289
|
+
#
|
290
|
+
|
291
|
+
# An Array of the valid Symbols for calendar chunks plus the Symbol :irregular
|
292
|
+
# for other periods.
|
293
|
+
CHUNKS = [
|
294
|
+
:day, :week, :biweek, :semimonth, :month, :bimonth,
|
295
|
+
:quarter, :half, :year, :irregular
|
296
|
+
]
|
297
|
+
|
298
|
+
# An Array of Ranges for the number of days that can be covered by each chunk.
|
299
|
+
CHUNK_RANGE = {
|
300
|
+
day: (1..1), week: (7..7), biweek: (14..14), semimonth: (15..16),
|
301
|
+
month: (28..31), bimonth: (59..62), quarter: (90..92),
|
302
|
+
half: (180..183), year: (365..366)
|
303
|
+
}
|
304
|
+
|
305
|
+
# Return the chunk symbol represented by this period if it covers a single
|
306
|
+
# calendar period; otherwise return :irregular.
|
307
|
+
#
|
308
|
+
# @example
|
309
|
+
# Period.new('2016-02-01', '2016-02-29').chunk_sym #=> :month
|
310
|
+
# Period.new('2016-02-01', '2016-02-28').chunk_sym #=> :irregular
|
311
|
+
# Period.new('2016-02-01', '2017-02-28').chunk_sym #=> :irregular
|
312
|
+
# Period.new('2016-01-01', '2016-03-31').chunk_sym #=> :quarter
|
313
|
+
# Period.new('2016-01-02', '2016-04-01').chunk_sym #=> :irregular
|
314
|
+
#
|
315
|
+
# @return [Symbol]
|
316
|
+
def chunk_sym
|
317
|
+
if first.beginning_of_year? && last.end_of_year? &&
|
318
|
+
CHUNK_RANGE[:year].cover?(size)
|
319
|
+
:year
|
320
|
+
elsif first.beginning_of_half? && last.end_of_half? &&
|
321
|
+
CHUNK_RANGE[:half].cover?(size)
|
322
|
+
:half
|
323
|
+
elsif first.beginning_of_quarter? && last.end_of_quarter? &&
|
324
|
+
CHUNK_RANGE[:quarter].cover?(size)
|
325
|
+
:quarter
|
326
|
+
elsif first.beginning_of_bimonth? && last.end_of_bimonth? &&
|
327
|
+
CHUNK_RANGE[:bimonth].cover?(size)
|
328
|
+
:bimonth
|
329
|
+
elsif first.beginning_of_month? && last.end_of_month? &&
|
330
|
+
CHUNK_RANGE[:month].cover?(size)
|
331
|
+
:month
|
332
|
+
elsif first.beginning_of_semimonth? && last.end_of_semimonth &&
|
333
|
+
CHUNK_RANGE[:semimonth].cover?(size)
|
334
|
+
:semimonth
|
335
|
+
elsif first.beginning_of_biweek? && last.end_of_biweek? &&
|
336
|
+
CHUNK_RANGE[:biweek].cover?(size)
|
337
|
+
:biweek
|
338
|
+
elsif first.beginning_of_week? && last.end_of_week? &&
|
339
|
+
CHUNK_RANGE[:week].cover?(size)
|
340
|
+
:week
|
341
|
+
elsif first == last
|
342
|
+
:day
|
343
|
+
else
|
344
|
+
:irregular
|
345
|
+
end
|
346
|
+
end
|
347
|
+
|
348
|
+
# Return a string name for this period based solely on the number of days in
|
349
|
+
# the period. Any period sufficiently close to 30 days will return the string
|
350
|
+
# 'Month', and any period sufficiently close to 90 days will return 'Quarter'.
|
351
|
+
# However for the shorter periods, periods less than month, no tolerance is
|
352
|
+
# applied. The amount of tolerance for the longer periods can be controlled
|
353
|
+
# with the `tolerance_pct` parameter, which default to 10%. If no calendar
|
354
|
+
# period corresponds to the length of the period, return 'Period'.
|
355
|
+
#
|
356
|
+
# @example
|
357
|
+
# Period.new('2015-05-15', '2015-06-17').chunk_name #=> 'Month' (within 10%)
|
358
|
+
# Period.new('2015-05-15', '2015-06-17').chunk_name(8) #=> 'Period' (but not 8%)
|
359
|
+
#
|
360
|
+
# @param tolerance_pct [Numeric] long period tolerance as a percent, 10 by default
|
361
|
+
# @return [String] the name for this period based solely on the number of days
|
362
|
+
# in the period.
|
363
|
+
def chunk_name(tolerance_pct = 10)
|
364
|
+
case Period.days_to_chunk(length, tolerance_pct)
|
365
|
+
when :year
|
366
|
+
'Year'
|
367
|
+
when :half
|
368
|
+
'Half'
|
369
|
+
when :quarter
|
370
|
+
'Quarter'
|
371
|
+
when :bimonth
|
372
|
+
'Bimonth'
|
373
|
+
when :month
|
374
|
+
'Month'
|
375
|
+
when :semimonth
|
376
|
+
'Semimonth'
|
377
|
+
when :biweek
|
378
|
+
'Biweek'
|
379
|
+
when :week
|
380
|
+
'Week'
|
381
|
+
when :day
|
382
|
+
'Day'
|
383
|
+
else
|
384
|
+
'Period'
|
385
|
+
end
|
386
|
+
end
|
387
|
+
|
388
|
+
# Return the chunk symbol represented by the number of days given, but allow a
|
389
|
+
# deviation from the minimum and maximum number of days for periods larger
|
390
|
+
# than bimonths. The default tolerance is +/-10%, but that can be adjusted. The
|
391
|
+
# reason for allowing a bit of tolerance for the larger periods is that
|
392
|
+
# financial statements meant to cover a given calendar period are often short
|
393
|
+
# or long by a few days due to such things as weekends, holidays, or
|
394
|
+
# accounting convenience. For example, a bank might issuer "monthly"
|
395
|
+
# statements approximately every 30 days, but issue them earlier or later to
|
396
|
+
# avoid having the closing date fall on a weekend or holiday. We still want to
|
397
|
+
# be able to recognize them as "monthly", even though the period covered might
|
398
|
+
# be a few days shorter or longer than any possible calendar month. You can
|
399
|
+
# eliminate this "fudge factor" by setting the `tolerance_pct` to zero. If
|
400
|
+
# the number of days corresponds to none of the defined calendar periods,
|
401
|
+
# return the symbol `:irregular`.
|
402
|
+
#
|
403
|
+
# @example
|
404
|
+
# Period.days_to_chunk(360) #=> :year
|
405
|
+
# Period.days_to_chunk(360, 0) #=> :irregular
|
406
|
+
# Period.days_to_chunk(88) #=> :quarter
|
407
|
+
# Period.days_to_chunk(88, 0) #=> :irregular
|
408
|
+
#
|
409
|
+
# @param days [Integer] the number of days in the period under test
|
410
|
+
# @param tolerance_pct [Numberic] the percent deviation allowed, e.g. 10 => 10%
|
411
|
+
# @return [Symbol] symbol for the period corresponding to days number of days
|
412
|
+
def self.days_to_chunk(days, tolerance_pct = 10)
|
413
|
+
result = :irregular
|
414
|
+
CHUNK_RANGE.each_pair do |chunk, rng|
|
415
|
+
if [:semimonth, :biweek, :week, :day].include?(chunk)
|
416
|
+
# Be strict for shorter periods.
|
417
|
+
if rng.cover?(days)
|
418
|
+
result = chunk
|
419
|
+
break
|
420
|
+
end
|
421
|
+
else
|
422
|
+
# Allow some tolerance for longer periods.
|
423
|
+
min = (rng.first * ((100.0 - tolerance_pct) / 100.0)).floor
|
424
|
+
max = (rng.last * ((100.0 + tolerance_pct) / 100.0)).floor
|
425
|
+
if (min..max).cover?(days)
|
426
|
+
result = chunk
|
427
|
+
break
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
result
|
432
|
+
end
|
433
|
+
|
434
|
+
# Return an array of Periods wholly-contained within self in chunks of size,
|
435
|
+
# defaulting to monthly chunks. Partial chunks at the beginning and end of
|
436
|
+
# self are not included unless `partial_first` or `partial_last`,
|
437
|
+
# respectively, are set true. The last chunk can be made to extend beyond the
|
438
|
+
# end of self to make it a whole chunk if `round_up_last` is set true, in
|
439
|
+
# which case, partial_last is ignored.
|
440
|
+
#
|
441
|
+
# @example
|
442
|
+
# Period.parse('2015').chunks(size: :month) #=>
|
443
|
+
# [Period(2015-01-01..2015-01-31),
|
444
|
+
# Period(2015-02-01..2015-02-28),
|
445
|
+
# Period(2015-03-01..2015-03-31),
|
446
|
+
# Period(2015-04-01..2015-04-30),
|
447
|
+
# Period(2015-05-01..2015-05-31),
|
448
|
+
# Period(2015-06-01..2015-06-30),
|
449
|
+
# Period(2015-07-01..2015-07-31),
|
450
|
+
# Period(2015-08-01..2015-08-31),
|
451
|
+
# Period(2015-09-01..2015-09-30),
|
452
|
+
# Period(2015-10-01..2015-10-31),
|
453
|
+
# Period(2015-11-01..2015-11-30),
|
454
|
+
# Period(2015-12-01..2015-12-31)
|
455
|
+
# ]
|
456
|
+
#
|
457
|
+
# Period.parse('2015').chunks(size: :week) #=>
|
458
|
+
# [Period(2015-01-05..2015-01-11), # Note that first week starts after Jan 1.
|
459
|
+
# Period(2015-01-12..2015-01-18),
|
460
|
+
# Period(2015-01-19..2015-01-25),
|
461
|
+
# Period(2015-01-26..2015-02-01),
|
462
|
+
# ...
|
463
|
+
# Period(2015-12-07..2015-12-13),
|
464
|
+
# Period(2015-12-14..2015-12-20),
|
465
|
+
# Period(2015-12-21..2015-12-27)] # Note that last week ends before Dec 31
|
466
|
+
#
|
467
|
+
# Period.parse('2015').chunks(size: :week, partial_first: true, partial_last: true) #=>
|
468
|
+
# [Period(2015-01-01..2015-01-04), # Note the partial week starting Jan 1
|
469
|
+
# Period(2015-01-05..2015-01-11),
|
470
|
+
# Period(2015-01-12..2015-01-18),
|
471
|
+
# Period(2015-01-19..2015-01-25),
|
472
|
+
# Period(2015-01-26..2015-02-01),
|
473
|
+
# ...
|
474
|
+
# Period(2015-12-07..2015-12-13),
|
475
|
+
# Period(2015-12-14..2015-12-20),
|
476
|
+
# Period(2015-12-21..2015-12-27)
|
477
|
+
# Period(2015-12-28..2015-12-31) # Note partial week ending Dec 31
|
478
|
+
# ]
|
479
|
+
#
|
480
|
+
# Period.parse('2015').chunks(size: :week, partial_first: true, round_up_last: true) #=>
|
481
|
+
# [Period(2015-01-01..2015-01-04), # Note the partial week starting Jan 1
|
482
|
+
# Period(2015-01-05..2015-01-11),
|
483
|
+
# Period(2015-01-12..2015-01-18),
|
484
|
+
# Period(2015-01-19..2015-01-25),
|
485
|
+
# Period(2015-01-26..2015-02-01),
|
486
|
+
# ...
|
487
|
+
# Period(2015-12-07..2015-12-13),
|
488
|
+
# Period(2015-12-14..2015-12-20),
|
489
|
+
# Period(2015-12-21..2015-12-27)
|
490
|
+
# Period(2015-12-28..2016-01-03) # Note full week extending beyond self
|
491
|
+
# ]
|
492
|
+
#
|
493
|
+
# @raise ArgumentError if size of chunks is larger than self or if an invalid
|
494
|
+
# chunk size.
|
495
|
+
# @param size [Symbol] a chunk symbol, :year, :half. :quarter, etc.
|
496
|
+
# @param partial_first [Boolean] allow a period less than a full :size period
|
497
|
+
# as the first period in the returned array.
|
498
|
+
# @param partial_last [Boolean] allow a period less than a full :size period
|
499
|
+
# as the last period in the returned array.
|
500
|
+
# @param round_up_last [Boolean] allow the last period in the returned array
|
501
|
+
# to extend beyond the end of self.
|
502
|
+
# @return [Array<Period>] periods that subdivide self into chunks of size, `size`
|
503
|
+
def chunks(size: :month, partial_first: false, partial_last: false,
|
504
|
+
round_up_last: false)
|
505
|
+
size = size.to_sym
|
506
|
+
unless CHUNKS.include?(size)
|
507
|
+
raise ArgumentError, "unknown chunk size '#{size}'"
|
508
|
+
end
|
509
|
+
if CHUNK_RANGE[size].first > length
|
510
|
+
if partial_first || partial_last
|
511
|
+
return [self]
|
512
|
+
else
|
513
|
+
raise ArgumentError, "any #{size} is longer than this period's #{length} days"
|
514
|
+
end
|
515
|
+
end
|
516
|
+
result = []
|
517
|
+
chunk_start = first.dup
|
518
|
+
while chunk_start <= last
|
519
|
+
case size
|
520
|
+
when :year
|
521
|
+
unless partial_first
|
522
|
+
chunk_start += 1.day until chunk_start.beginning_of_year?
|
523
|
+
end
|
524
|
+
chunk_end = chunk_start.end_of_year
|
525
|
+
when :half
|
526
|
+
unless partial_first
|
527
|
+
chunk_start += 1.day until chunk_start.beginning_of_half?
|
528
|
+
end
|
529
|
+
chunk_end = chunk_start.end_of_half
|
530
|
+
when :quarter
|
531
|
+
unless partial_first
|
532
|
+
chunk_start += 1.day until chunk_start.beginning_of_quarter?
|
533
|
+
end
|
534
|
+
chunk_end = chunk_start.end_of_quarter
|
535
|
+
when :bimonth
|
536
|
+
unless partial_first
|
537
|
+
chunk_start += 1.day until chunk_start.beginning_of_bimonth?
|
538
|
+
end
|
539
|
+
chunk_end = (chunk_start.end_of_month + 1.day).end_of_month
|
540
|
+
when :month
|
541
|
+
unless partial_first
|
542
|
+
chunk_start += 1.day until chunk_start.beginning_of_month?
|
543
|
+
end
|
544
|
+
chunk_end = chunk_start.end_of_month
|
545
|
+
when :semimonth
|
546
|
+
unless partial_first
|
547
|
+
chunk_start += 1.day until chunk_start.beginning_of_semimonth?
|
548
|
+
end
|
549
|
+
chunk_end = chunk_start.end_of_semimonth
|
550
|
+
when :biweek
|
551
|
+
unless partial_first
|
552
|
+
chunk_start += 1.day until chunk_start.beginning_of_biweek?
|
553
|
+
end
|
554
|
+
chunk_end = chunk_start.end_of_biweek
|
555
|
+
when :week
|
556
|
+
unless partial_first
|
557
|
+
chunk_start += 1.day until chunk_start.beginning_of_week?
|
558
|
+
end
|
559
|
+
chunk_end = chunk_start.end_of_week
|
560
|
+
when :day
|
561
|
+
chunk_end = chunk_start
|
562
|
+
else
|
563
|
+
raise ArgumentError, "invalid chunk size '#{size}'"
|
564
|
+
end
|
565
|
+
if chunk_end <= last
|
566
|
+
result << Period.new(chunk_start, chunk_end)
|
567
|
+
elsif round_up_last
|
568
|
+
result << Period.new(chunk_start, chunk_end)
|
569
|
+
elsif partial_last
|
570
|
+
result << Period.new(chunk_start, last)
|
571
|
+
else
|
572
|
+
break
|
573
|
+
end
|
574
|
+
chunk_start = result.last.last + 1.day
|
575
|
+
end
|
576
|
+
result
|
577
|
+
end
|
578
|
+
|
579
|
+
# @group Set operations
|
580
|
+
|
581
|
+
# Is this period contained wholly within or coincident with `other`?
|
582
|
+
#
|
583
|
+
# @example
|
584
|
+
# Period.parse('2015-2Q').subset_of?(Period.parse('2015')) #=> true
|
585
|
+
# Period.parse('2015-2Q').subset_of?(Period.parse('2015-2Q')) #=> true
|
586
|
+
# Period.parse('2015-2Q').subset_of?(Period.parse('2015-02')) #=> false
|
587
|
+
#
|
588
|
+
# @param other [Period] other Period
|
589
|
+
# @return [Boolean] self within or coincident with `other`?
|
590
|
+
def subset_of?(other)
|
591
|
+
to_range.subset_of?(other.to_range)
|
592
|
+
end
|
593
|
+
|
594
|
+
# Is this period contained wholly within but not coincident with `other`?
|
595
|
+
#
|
596
|
+
# @example
|
597
|
+
# Period.parse('2015-2Q').proper_subset_of?(Period.parse('2015')) #=> true
|
598
|
+
# Period.parse('2015-2Q').proper_subset_of?(Period.parse('2015-2Q')) #=> false
|
599
|
+
# Period.parse('2015-2Q').proper_subset_of?(Period.parse('2015-02')) #=> false
|
600
|
+
#
|
601
|
+
# @param other [Period] other Period
|
602
|
+
# @return [Boolean] self within `other`?
|
603
|
+
def proper_subset_of?(other)
|
604
|
+
to_range.proper_subset_of?(other.to_range)
|
605
|
+
end
|
606
|
+
|
607
|
+
# Does this period wholly contain or is coincident with `other`?
|
608
|
+
#
|
609
|
+
# @example
|
610
|
+
# Period.parse('2015').superset_of?(Period.parse('2015-2Q')) #=> true
|
611
|
+
# Period.parse('2015-2Q').superset_of?(Period.parse('2015-2Q')) #=> true
|
612
|
+
# Period.parse('2015-02').superset_of?(Period.parse('2015-2Q')) #=> false
|
613
|
+
#
|
614
|
+
# @param other [Period] other Period
|
615
|
+
# @return [Boolean] self contains or coincident with `other`?
|
616
|
+
def superset_of?(other)
|
617
|
+
to_range.superset_of?(other.to_range)
|
618
|
+
end
|
619
|
+
|
620
|
+
# Does this period wholly contain but not coincident with `other`?
|
621
|
+
#
|
622
|
+
# @example
|
623
|
+
# Period.parse('2015').proper_superset_of?(Period.parse('2015-2Q')) #=> true
|
624
|
+
# Period.parse('2015-2Q').proper_superset_of?(Period.parse('2015-2Q')) #=> false
|
625
|
+
# Period.parse('2015-02').proper_superset_of?(Period.parse('2015-2Q')) #=> false
|
626
|
+
#
|
627
|
+
# @param other [Period] other Period
|
628
|
+
# @return [Boolean] self contains `other`?
|
629
|
+
def proper_superset_of?(other)
|
630
|
+
to_range.proper_superset_of?(other.to_range)
|
631
|
+
end
|
632
|
+
|
633
|
+
# Return the Period that is the intersection of self with `other` or nil if
|
634
|
+
# there is no intersection.
|
635
|
+
#
|
636
|
+
# @example
|
637
|
+
# Period.parse('2015-3Q') & Period.parse('2015-2Q') #=> nil
|
638
|
+
# Period.parse('2015') & Period.parse('2015-2Q') #=> Period(2015-2Q)
|
639
|
+
# pp1 = Period.parse_phrase('from 2015 to 2015-3Q') #=> Period(2015-01-01..2015-09-30)
|
640
|
+
# pp2 = Period.parse_phrase('from 2015-2H') #=> Period(2015-07-01..2015-12-31)
|
641
|
+
# pp1 & pp2 #=> Period(2015-07-01..2015-09-30)
|
642
|
+
#
|
643
|
+
# @param other [Period] other Period
|
644
|
+
# @return [Period, nil] self intersect `other`?
|
645
|
+
def intersection(other)
|
646
|
+
result = to_range.intersection(other.to_range)
|
647
|
+
if result.nil?
|
648
|
+
nil
|
649
|
+
else
|
650
|
+
Period.new(result.first, result.last)
|
651
|
+
end
|
652
|
+
end
|
653
|
+
alias & intersection
|
654
|
+
alias narrow_to intersection
|
655
|
+
|
656
|
+
# Return the Period that is the union of self with `other` or nil if
|
657
|
+
# they neither overlap nor are contiguous
|
658
|
+
#
|
659
|
+
# @example
|
660
|
+
# Period.parse('2015-3Q') + Period.parse('2015-2Q') #=> Period(2015-04-01..2015-09-30)
|
661
|
+
# Period.parse('2015') + Period.parse('2015-2Q') #=> Period(2015-01-01..2015-12-31)
|
662
|
+
# Period.parse('2015') + Period.parse('2017') #=> nil
|
663
|
+
# pp1 = Period.parse_phrase('from 2015-4Q to 2016-1H') #=> Period(2015-10-01-2015..2015-12-31)
|
664
|
+
# pp2 = Period.parse_phrase('from 2015-3Q to 2015-11') #=> Period(2015-07-01-2015..2015-11-30)
|
665
|
+
# pp1 + pp2 #=> Period(2015-10-01..2015-11-30)
|
666
|
+
#
|
667
|
+
# @param other [Period] other Period
|
668
|
+
# @return [Period, nil] self union `other`?
|
669
|
+
def union(other)
|
670
|
+
result = to_range.union(other.to_range)
|
671
|
+
return nil if result.nil?
|
672
|
+
Period.new(result.first, result.last)
|
673
|
+
end
|
674
|
+
alias + union
|
675
|
+
|
676
|
+
# Return an array of periods that are this period excluding any overlap with
|
677
|
+
# other. If there is no overlap, return an array with a period equal to self
|
678
|
+
# as the sole member.
|
679
|
+
#
|
680
|
+
# @example
|
681
|
+
# Period.parse('2015-1Q') - Period.parse('2015-02')
|
682
|
+
# #=> [Period(2015-01-01..2015-01-31), Period(2015-03-01..2015-03-31)]
|
683
|
+
# Period.parse('2015-2Q') - Period.parse('2015-02')
|
684
|
+
# #=> [Period(2015-04-01..2015-06-30)]
|
685
|
+
#
|
686
|
+
# @param other [Period] the other period to exclude from self
|
687
|
+
# @return [Array<Period>] self less the part of other that overlaps
|
688
|
+
def difference(other)
|
689
|
+
ranges = to_range.difference(other.to_range)
|
690
|
+
ranges.each.map { |r| Period.new(r.first, r.last) }
|
691
|
+
end
|
692
|
+
alias - difference
|
693
|
+
|
694
|
+
# Return whether this period overlaps the `other` period. To overlap, the
|
695
|
+
# periods must have at least one day in common.
|
696
|
+
#
|
697
|
+
# @example
|
698
|
+
# Period.parse('2012').overlaps?(Period.parse('2016')) #=> false
|
699
|
+
# Period.parse('2016-32W').overlaps?(Period.parse('2016')) #=> true
|
700
|
+
# pp1 = Period.new('2016-03-12', '2016-03-15')
|
701
|
+
# pp2 = Period.new('2016-03-16', '2016-03-25')
|
702
|
+
# pp1.overlaps?(pp2) #=> false (being contiguous is not overlapping)
|
703
|
+
#
|
704
|
+
# @param other [Period] the other period to test for overlap
|
705
|
+
# @return [Boolean] does self overlap with other?
|
706
|
+
def overlaps?(other)
|
707
|
+
to_range.overlaps?(other.to_range)
|
708
|
+
end
|
709
|
+
|
710
|
+
# Return whether any of the given periods overlap any other.
|
711
|
+
#
|
712
|
+
# @example
|
713
|
+
# pds = []
|
714
|
+
# pds << Period.parse('2015-1H')
|
715
|
+
# pds << Period.parse('2016-2H')
|
716
|
+
# pds << Period.parse('2015-04')
|
717
|
+
# Period.overlaps_among?(pds) #=> true
|
718
|
+
#
|
719
|
+
# @param periods [Array<Period>] periods to test for overlaps
|
720
|
+
# @return [Boolean] true if any one of periods overlaps another
|
721
|
+
def self.overlaps_among?(periods)
|
722
|
+
Range.overlaps_among?(periods.map(&:to_range))
|
723
|
+
end
|
724
|
+
|
725
|
+
# Return whether any of the given periods overlap any other but only if the
|
726
|
+
# overlaps occur within the self; overlaps outside self are ignored.
|
727
|
+
#
|
728
|
+
# @example
|
729
|
+
# pds = []
|
730
|
+
# pds << Period.parse('2015-1H')
|
731
|
+
# pds << Period.parse('2016-2H')
|
732
|
+
# pds << Period.parse('2015-04')
|
733
|
+
# yr2015 = Period.parse('2015')
|
734
|
+
# yr2016 = Period.parse('2016')
|
735
|
+
# yr2015.overlaps_among?(pds) #=> true
|
736
|
+
# yr2016.overlaps_among?(pds) #=> false (overlap is in 2015)
|
737
|
+
#
|
738
|
+
# @param periods [Array<Period>] periods to test for overlaps
|
739
|
+
# @return [Boolean] true if any one of periods overlaps another
|
740
|
+
def overlaps_among?(periods)
|
741
|
+
to_range.overlaps_among?(periods.map(&:to_range))
|
742
|
+
end
|
743
|
+
|
744
|
+
# Return whether the given periods "span" self, that is, do they collectively
|
745
|
+
# cover all of self with no overlaps and no gaps?
|
746
|
+
#
|
747
|
+
# @example
|
748
|
+
# ppds = []
|
749
|
+
# ppds << Period.parse('2016-1Q')
|
750
|
+
# ppds << Period.parse('2016-2Q')
|
751
|
+
# ppds << Period.parse('2016-2H')
|
752
|
+
# Period.parse('2016').spanned_by?(ppds) #=> true
|
753
|
+
#
|
754
|
+
# # There's a bit of the year at the beginning that isn't covered by
|
755
|
+
# # one of these weeks:
|
756
|
+
# ppds = Period.parse('2016').chunks(size: :week)
|
757
|
+
# Period.parse('2016').spanned_by?(ppds) #=> false
|
758
|
+
#
|
759
|
+
# @param periods [Array<Period>] periods to test for spanning self
|
760
|
+
# @return [Boolean] do periods span self?
|
761
|
+
def spanned_by?(periods)
|
762
|
+
to_range.spanned_by?(periods.map(&:to_range))
|
763
|
+
end
|
764
|
+
|
765
|
+
# Return an Array of Periods representing the gaps within self not covered by
|
766
|
+
# the Array of Periods `periods`. Overlaps among the periods do not affect
|
767
|
+
# the result nor do gaps outside the range of self. Ordering among the
|
768
|
+
# `periods` does not matter.
|
769
|
+
#
|
770
|
+
# @example
|
771
|
+
# some_qs = []
|
772
|
+
# some_qs << Period.parse('2015-1Q')
|
773
|
+
# some_qs << Period.parse('2015-3Q')
|
774
|
+
# some_qs << Period.parse('2015-11')
|
775
|
+
# some_qs << Period.parse('2015-12')
|
776
|
+
# Period.parse('2015').gaps(some_qs) #=>
|
777
|
+
# [Period(2015-04-01..2015-06-30), Period(2015-10-01..2015-10-31)]
|
778
|
+
#
|
779
|
+
# @param periods [Array<Period>] periods to examine for coverage of self
|
780
|
+
# @return [Array<Periods>] periods that are not covered by `periods`
|
781
|
+
def gaps(periods)
|
782
|
+
to_range.gaps(periods.map(&:to_range))
|
783
|
+
.map { |r| Period.new(r.first, r.last) }
|
784
|
+
end
|
785
|
+
end
|
data/lib/fat_period.rb
ADDED
metadata
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fat_period
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Daniel E. Doherty
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-05-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.14'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.14'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: pry-doc
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry-byebug
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: fat_core
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '4.0'
|
104
|
+
- - ">="
|
105
|
+
- !ruby/object:Gem::Version
|
106
|
+
version: '4.1'
|
107
|
+
type: :runtime
|
108
|
+
prerelease: false
|
109
|
+
version_requirements: !ruby/object:Gem::Requirement
|
110
|
+
requirements:
|
111
|
+
- - "~>"
|
112
|
+
- !ruby/object:Gem::Version
|
113
|
+
version: '4.0'
|
114
|
+
- - ">="
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '4.1'
|
117
|
+
description:
|
118
|
+
email:
|
119
|
+
- ded-law@ddoherty.net
|
120
|
+
executables: []
|
121
|
+
extensions: []
|
122
|
+
extra_rdoc_files: []
|
123
|
+
files:
|
124
|
+
- ".gitignore"
|
125
|
+
- ".rspec"
|
126
|
+
- ".travis.yml"
|
127
|
+
- Gemfile
|
128
|
+
- README.md
|
129
|
+
- Rakefile
|
130
|
+
- bin/console
|
131
|
+
- bin/setup
|
132
|
+
- fat_period.gemspec
|
133
|
+
- lib/fat_period.rb
|
134
|
+
- lib/fat_period/date.rb
|
135
|
+
- lib/fat_period/period.rb
|
136
|
+
- lib/fat_period/version.rb
|
137
|
+
homepage: https://github.com/ddoherty03/fat_period
|
138
|
+
licenses: []
|
139
|
+
metadata: {}
|
140
|
+
post_install_message:
|
141
|
+
rdoc_options: []
|
142
|
+
require_paths:
|
143
|
+
- lib
|
144
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
145
|
+
requirements:
|
146
|
+
- - ">="
|
147
|
+
- !ruby/object:Gem::Version
|
148
|
+
version: '0'
|
149
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
150
|
+
requirements:
|
151
|
+
- - ">="
|
152
|
+
- !ruby/object:Gem::Version
|
153
|
+
version: '0'
|
154
|
+
requirements: []
|
155
|
+
rubyforge_project:
|
156
|
+
rubygems_version: 2.5.2
|
157
|
+
signing_key:
|
158
|
+
specification_version: 4
|
159
|
+
summary: Implements a Period class as a Range of Dates.
|
160
|
+
test_files: []
|