fat_period 1.1.1 → 1.3.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/.travis.yml +1 -1
- data/fat_period.gemspec +1 -1
- data/lib/fat_period/period.rb +94 -63
- data/lib/fat_period/version.rb +1 -1
- metadata +12 -12
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: cb2c6b600f446fa2bff8080b8056a32ceb3fda88be68c03db076591753c4992e
|
4
|
+
data.tar.gz: f4d65daa079f338c2a1a7ab5a0d326ae9af884d819a72077f1f24e8128ed70de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3059659aed2c2000401a6fceca177b9b7ea633adaaa1012bcb20db1069845bdc7dff35b97a53289a498399d1bc2217ceffd06c4dae94377ce7a47710c71ab2af
|
7
|
+
data.tar.gz: ab0e1a8aebca6fc8acf0e952e9a48956767b03e7cd9071d6487b52e0a544519e6801b927ae3b2f5021e1fc3ac8aeb95114488192fcd343b265b235efc38a6784
|
data/.travis.yml
CHANGED
data/fat_period.gemspec
CHANGED
@@ -23,9 +23,9 @@ Gem::Specification.new do |spec|
|
|
23
23
|
spec.add_development_dependency 'bundler'
|
24
24
|
spec.add_development_dependency 'rake'
|
25
25
|
spec.add_development_dependency 'rspec'
|
26
|
+
spec.add_development_dependency 'debug', '>= 1.0.0'
|
26
27
|
spec.add_development_dependency 'pry'
|
27
28
|
spec.add_development_dependency 'pry-doc'
|
28
|
-
spec.add_development_dependency 'pry-byebug'
|
29
29
|
|
30
30
|
spec.add_runtime_dependency 'fat_core', '>= 4.8.3'
|
31
31
|
end
|
data/lib/fat_period/period.rb
CHANGED
@@ -1,5 +1,3 @@
|
|
1
|
-
# -*- coding: utf-8 -*-
|
2
|
-
|
3
1
|
require 'fat_core/date'
|
4
2
|
require 'fat_core/range'
|
5
3
|
require 'fat_core/string'
|
@@ -29,8 +27,9 @@ class Period
|
|
29
27
|
# @raise [ArgumentError] if first date is later than last date
|
30
28
|
# @return [Period]
|
31
29
|
def initialize(first, last)
|
32
|
-
@first = Date.ensure_date(first)
|
33
|
-
@last = Date.ensure_date(last)
|
30
|
+
@first = Date.ensure_date(first).freeze
|
31
|
+
@last = Date.ensure_date(last).freeze
|
32
|
+
freeze
|
34
33
|
|
35
34
|
return unless @first > @last
|
36
35
|
|
@@ -71,36 +70,59 @@ class Period
|
|
71
70
|
Period.new(first, second) if first && second
|
72
71
|
end
|
73
72
|
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
73
|
+
PHRASE_RE = %r{\A
|
74
|
+
# One or both of from and to parts
|
75
|
+
((from\s+(?<from_part>[-_a-z0-9]+)\s*)?
|
76
|
+
(to\s+(?<to_part>[-_a-z0-9]+))?)
|
77
|
+
# Wholly optional chunk part
|
78
|
+
(\s+per\s+(?<chunk_part>\w+))?\z}xi
|
79
|
+
|
80
|
+
# Return an array of periods, either a single period as in `Period.parse`
|
81
|
+
# from a String phrase in which a `from spec` is introduced with 'from' and,
|
82
|
+
# optionally, a `to spec` is introduced with 'to', or a number of periods if
|
83
|
+
# there is a 'per <chunk>' modifier. A phrase with only a `to spec` is
|
84
|
+
# treated the same as one with only a from spec. If neither 'from' nor 'to'
|
85
|
+
# appear in phrase, treat the whole string as a from spec.
|
79
86
|
#
|
80
87
|
# @example
|
81
|
-
# Period.parse_phrase('from 2014-11 to 2015-3Q') #=> Period('2014-11-01..2015-09-30')
|
82
|
-
# Period.parse_phrase('from 2014-11') #=> Period('2014-11-01..2014-11-30')
|
83
|
-
# Period.parse_phrase('from 2015-3Q') #=> Period('2015-09-01..2015-12-31')
|
84
|
-
# Period.parse_phrase('to 2015-3Q') #=> Period('2015-09-01..2015-12-31')
|
85
|
-
# Period.parse_phrase('2015-3Q')
|
86
|
-
#
|
87
|
-
#
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
88
|
+
# Period.parse_phrase('from 2014-11 to 2015-3Q') #=> [Period('2014-11-01..2015-09-30')]
|
89
|
+
# Period.parse_phrase('from 2014-11') #=> [Period('2014-11-01..2014-11-30')]
|
90
|
+
# Period.parse_phrase('from 2015-3Q') #=> [Period('2015-09-01..2015-12-31')]
|
91
|
+
# Period.parse_phrase('to 2015-3Q') #=> [Period('2015-09-01..2015-12-31')]
|
92
|
+
# Period.parse_phrase('from 2015-3Q') #=> [Period('2015-09-01..2015-12-31')]
|
93
|
+
# Period.parse_phrase('from 2015 per month') #=> [
|
94
|
+
# Period('2015-01-01..2015-01-31'),
|
95
|
+
# Period('2015-02-01..2015-02-28'),
|
96
|
+
# ...
|
97
|
+
# Period('2015-12-01..2015-12-31')
|
98
|
+
# ]
|
99
|
+
#
|
100
|
+
# @param phrase [String] with 'from <spec> to <spec> [per <chunk>]'
|
101
|
+
# @return [Array<Period>] translated from phrase
|
102
|
+
def self.parse_phrase(phrase,
|
103
|
+
partial_first: false, partial_last: false, round_up_last: false)
|
104
|
+
phrase = phrase.downcase.clean
|
105
|
+
mm = phrase.match(PHRASE_RE)
|
106
|
+
raise ArgumentError, "invalid period phrase: `#{phrase}`" unless mm
|
107
|
+
|
108
|
+
if mm[:from_part] && mm[:to_part].nil?
|
109
|
+
from_part = mm[:from_part]
|
110
|
+
to_part = nil
|
111
|
+
elsif mm[:from_part].nil? && mm[:to_part]
|
112
|
+
from_part = mm[:to_part]
|
113
|
+
to_part = nil
|
99
114
|
else
|
100
|
-
|
101
|
-
|
115
|
+
from_part = mm[:from_part]
|
116
|
+
to_part = mm[:to_part]
|
117
|
+
end
|
118
|
+
|
119
|
+
whole_period = parse(from_part, to_part)
|
120
|
+
if mm[:chunk_part].nil?
|
121
|
+
[whole_period]
|
122
|
+
else
|
123
|
+
whole_period.chunks(size: mm[:chunk_part], partial_first: partial_first,
|
124
|
+
partial_last: partial_last, round_up_last: round_up_last)
|
102
125
|
end
|
103
|
-
parse(from_phrase, to_phrase)
|
104
126
|
end
|
105
127
|
|
106
128
|
# @group Conversion
|
@@ -180,6 +202,20 @@ class Period
|
|
180
202
|
!(self == other)
|
181
203
|
end
|
182
204
|
|
205
|
+
# Return the hash value for this Period. Make Period's with identical
|
206
|
+
# values test eql? so that they may be used as hash keys.
|
207
|
+
#
|
208
|
+
# @return [Integer]
|
209
|
+
def hash
|
210
|
+
(first.hash | last.hash)
|
211
|
+
end
|
212
|
+
|
213
|
+
def eql?(other)
|
214
|
+
return nil unless other.is_a?(Period)
|
215
|
+
|
216
|
+
hash == other.hash
|
217
|
+
end
|
218
|
+
|
183
219
|
# Return whether this Period contains the given date.
|
184
220
|
#
|
185
221
|
# @param date [Date] date to test
|
@@ -327,9 +363,7 @@ class Period
|
|
327
363
|
raise ArgumentError, 'chunk is nil' unless chunk
|
328
364
|
|
329
365
|
chunk = chunk.to_sym
|
330
|
-
unless CHUNKS.include?(chunk)
|
331
|
-
raise ArgumentError, "unknown chunk name: #{chunk}"
|
332
|
-
end
|
366
|
+
raise ArgumentError, "unknown chunk name: #{chunk}" unless CHUNKS.include?(chunk)
|
333
367
|
|
334
368
|
date = Date.ensure_date(date)
|
335
369
|
method = "#{chunk}_containing".to_sym
|
@@ -456,20 +490,20 @@ class Period
|
|
456
490
|
end
|
457
491
|
end
|
458
492
|
|
459
|
-
# Return the chunk symbol represented by the number of days given, but allow
|
460
|
-
# deviation from the minimum and maximum number of days for periods larger
|
461
|
-
# than bimonths. The default tolerance is +/-10%, but that can be
|
462
|
-
# reason for allowing a bit of tolerance for the larger
|
463
|
-
# financial statements meant to cover a given calendar
|
464
|
-
# or long by a few days due to such things as
|
465
|
-
# accounting convenience. For example, a bank might
|
466
|
-
# statements approximately every 30 days, but issue them
|
467
|
-
# avoid having the closing date fall on a weekend or
|
468
|
-
# be able to recognize them as "monthly", even
|
469
|
-
# be a few days shorter or longer than any
|
470
|
-
# eliminate this "fudge factor" by setting
|
471
|
-
# the number of days corresponds to none of
|
472
|
-
# return the symbol `:irregular`.
|
493
|
+
# Return the chunk symbol represented by the number of days given, but allow
|
494
|
+
# a deviation from the minimum and maximum number of days for periods larger
|
495
|
+
# than bimonths. The default tolerance is +/-10%, but that can be
|
496
|
+
# adjusted. The reason for allowing a bit of tolerance for the larger
|
497
|
+
# periods is that financial statements meant to cover a given calendar
|
498
|
+
# period are often short or long by a few days due to such things as
|
499
|
+
# weekends, holidays, or accounting convenience. For example, a bank might
|
500
|
+
# issuer "monthly" statements approximately every 30 days, but issue them
|
501
|
+
# earlier or later to avoid having the closing date fall on a weekend or
|
502
|
+
# holiday. We still want to be able to recognize them as "monthly", even
|
503
|
+
# though the period covered might be a few days shorter or longer than any
|
504
|
+
# possible calendar month. You can eliminate this "fudge factor" by setting
|
505
|
+
# the `tolerance_pct` to zero. If the number of days corresponds to none of
|
506
|
+
# the defined calendar periods, return the symbol `:irregular`.
|
473
507
|
#
|
474
508
|
# @example
|
475
509
|
# Period.days_to_chunk(360) #=> :year
|
@@ -478,12 +512,12 @@ class Period
|
|
478
512
|
# Period.days_to_chunk(88, 0) #=> :irregular
|
479
513
|
#
|
480
514
|
# @param days [Integer] the number of days in the period under test
|
481
|
-
# @param tolerance_pct [
|
515
|
+
# @param tolerance_pct [Numeric] the percent deviation allowed, e.g. 10 => 10%
|
482
516
|
# @return [Symbol] symbol for the period corresponding to days number of days
|
483
517
|
def self.days_to_chunk(days, tolerance_pct = 10)
|
484
518
|
result = :irregular
|
485
519
|
CHUNK_RANGE.each_pair do |chunk, rng|
|
486
|
-
if [
|
520
|
+
if %i[semimonth biweek week day].include?(chunk)
|
487
521
|
# Be strict for shorter periods.
|
488
522
|
if rng.cover?(days)
|
489
523
|
result = chunk
|
@@ -574,9 +608,7 @@ class Period
|
|
574
608
|
def chunks(size: :month, partial_first: false, partial_last: false,
|
575
609
|
round_up_last: false)
|
576
610
|
chunk_size = size.to_sym
|
577
|
-
unless CHUNKS.include?(chunk_size)
|
578
|
-
raise ArgumentError, "unknown chunk size '#{chunk_size}'"
|
579
|
-
end
|
611
|
+
raise ArgumentError, "unknown chunk size '#{chunk_size}'" unless CHUNKS.include?(chunk_size)
|
580
612
|
|
581
613
|
containing_period = Period.chunk_containing(first, chunk_size)
|
582
614
|
return [dup] if self == containing_period
|
@@ -598,26 +630,25 @@ class Period
|
|
598
630
|
return result
|
599
631
|
end
|
600
632
|
|
633
|
+
# The first chunk
|
601
634
|
chunk_start = first.dup
|
602
635
|
chunk_end = chunk_start.end_of_chunk(chunk_size)
|
603
636
|
if chunk_start.beginning_of_chunk?(chunk_size) || partial_first
|
604
637
|
# Keep the first chunk if it's whole or partials allowed
|
605
638
|
result << Period.new(chunk_start, chunk_end)
|
606
|
-
chunk_start = chunk_end + 1.day
|
607
|
-
chunk_end = chunk_start.end_of_chunk(chunk_size)
|
608
|
-
else
|
609
|
-
# Discard the partial first or move to next whole chunk
|
610
|
-
chunk_start = chunk_end + 1.day
|
611
|
-
chunk_end = chunk_start.end_of_chunk(chunk_size)
|
612
639
|
end
|
640
|
+
chunk_start = chunk_end + 1.day
|
641
|
+
chunk_end = chunk_start.end_of_chunk(chunk_size)
|
642
|
+
|
613
643
|
# Add Whole chunks
|
614
644
|
while chunk_end <= last
|
615
645
|
result << Period.new(chunk_start, chunk_end)
|
616
646
|
chunk_start = chunk_end + 1.day
|
617
647
|
chunk_end = chunk_start.end_of_chunk(chunk_size)
|
618
648
|
end
|
649
|
+
|
619
650
|
# Possibly append the final chunk to result
|
620
|
-
if chunk_start
|
651
|
+
if chunk_start <= last
|
621
652
|
if round_up_last
|
622
653
|
result << Period.new(chunk_start, chunk_end)
|
623
654
|
elsif partial_last
|
@@ -625,15 +656,15 @@ class Period
|
|
625
656
|
else
|
626
657
|
result
|
627
658
|
end
|
628
|
-
|
659
|
+
end
|
660
|
+
if partial_last && !partial_first && result.empty?
|
629
661
|
# Catch the case where the period is too small to make a whole chunk and
|
630
662
|
# partial_first is false, so it did not get included as the initial
|
631
663
|
# partial chunk, yet a partial_last is allowed, so include the whole
|
632
664
|
# period as a partial chunk.
|
633
665
|
result << Period.new(first, last)
|
634
|
-
else
|
635
|
-
result
|
636
666
|
end
|
667
|
+
result
|
637
668
|
end
|
638
669
|
|
639
670
|
# @group Set operations
|
@@ -677,7 +708,7 @@ class Period
|
|
677
708
|
to_range.superset_of?(other.to_range)
|
678
709
|
end
|
679
710
|
|
680
|
-
# Does this period wholly contain but not coincident with `other`?
|
711
|
+
# Does this period wholly contain but is not coincident with `other`?
|
681
712
|
#
|
682
713
|
# @example
|
683
714
|
# Period.parse('2015').proper_superset_of?(Period.parse('2015-2Q')) #=> true
|
data/lib/fat_period/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fat_period
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel E. Doherty
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-04-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -53,21 +53,21 @@ dependencies:
|
|
53
53
|
- !ruby/object:Gem::Version
|
54
54
|
version: '0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: debug
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
59
|
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
61
|
+
version: 1.0.0
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
68
|
+
version: 1.0.0
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
|
-
name: pry
|
70
|
+
name: pry
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - ">="
|
@@ -81,7 +81,7 @@ dependencies:
|
|
81
81
|
- !ruby/object:Gem::Version
|
82
82
|
version: '0'
|
83
83
|
- !ruby/object:Gem::Dependency
|
84
|
-
name: pry-
|
84
|
+
name: pry-doc
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
87
|
- - ">="
|
@@ -108,7 +108,7 @@ dependencies:
|
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
110
|
version: 4.8.3
|
111
|
-
description:
|
111
|
+
description:
|
112
112
|
email:
|
113
113
|
- ded-law@ddoherty.net
|
114
114
|
executables: []
|
@@ -132,7 +132,7 @@ files:
|
|
132
132
|
homepage: https://github.com/ddoherty03/fat_period
|
133
133
|
licenses: []
|
134
134
|
metadata: {}
|
135
|
-
post_install_message:
|
135
|
+
post_install_message:
|
136
136
|
rdoc_options: []
|
137
137
|
require_paths:
|
138
138
|
- lib
|
@@ -147,8 +147,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
147
147
|
- !ruby/object:Gem::Version
|
148
148
|
version: '0'
|
149
149
|
requirements: []
|
150
|
-
rubygems_version: 3.
|
151
|
-
signing_key:
|
150
|
+
rubygems_version: 3.3.3
|
151
|
+
signing_key:
|
152
152
|
specification_version: 4
|
153
153
|
summary: Implements a Period class as a Range of Dates.
|
154
154
|
test_files: []
|