fat_period 1.2.0 → 1.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -1
- data/fat_period.gemspec +1 -1
- data/lib/fat_period/period.rb +89 -65
- data/lib/fat_period/version.rb +1 -1
- metadata +8 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a1b3b656730fd4a24f6ec1ae3c14a2828aee91a6ddf18ce8a4481e31e2d55ad3
|
4
|
+
data.tar.gz: eb16ac79598e7c0054bac2ba57a9a5d8abce25cb8e810cbea9ac59ea4c8ead33
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 82b0fb1e076cf644da168edbfadfe44a060dc8ebdc43cdf450280a32466fc31a33aee79591a209d480cf192373cfcd8fe808f46e70e0c8186ff9fb10bdf84c66
|
7
|
+
data.tar.gz: d1a151a0b003ba1bcaa54562928ab42075044ae04bbabca202001c56b1fd697779666c870541f023dddf64e248dd1a1ee2e5eeea5484e2c6f0ee392af469e874
|
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'
|
@@ -72,36 +70,60 @@ class Period
|
|
72
70
|
Period.new(first, second) if first && second
|
73
71
|
end
|
74
72
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
+
private_constant :PHRASE_RE
|
81
|
+
|
82
|
+
# Return an array of periods, either a single period as in `Period.parse`
|
83
|
+
# from a String phrase in which a `from spec` is introduced with 'from' and,
|
84
|
+
# optionally, a `to spec` is introduced with 'to', or a number of periods if
|
85
|
+
# there is a 'per <chunk>' modifier. A phrase with only a `to spec` is
|
86
|
+
# treated the same as one with only a from spec. If neither 'from' nor 'to'
|
87
|
+
# appear in phrase, treat the whole string as a from spec.
|
80
88
|
#
|
81
89
|
# @example
|
82
|
-
# Period.parse_phrase('from 2014-11 to 2015-3Q') #=> Period('2014-11-01..2015-09-30')
|
83
|
-
# Period.parse_phrase('from 2014-11') #=> Period('2014-11-01..2014-11-30')
|
84
|
-
# Period.parse_phrase('from 2015-3Q') #=> Period('2015-09-01..2015-12-31')
|
85
|
-
# Period.parse_phrase('to 2015-3Q') #=> Period('2015-09-01..2015-12-31')
|
86
|
-
# Period.parse_phrase('2015-3Q')
|
87
|
-
#
|
88
|
-
#
|
89
|
-
#
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
90
|
+
# Period.parse_phrase('from 2014-11 to 2015-3Q') #=> [Period('2014-11-01..2015-09-30')]
|
91
|
+
# Period.parse_phrase('from 2014-11') #=> [Period('2014-11-01..2014-11-30')]
|
92
|
+
# Period.parse_phrase('from 2015-3Q') #=> [Period('2015-09-01..2015-12-31')]
|
93
|
+
# Period.parse_phrase('to 2015-3Q') #=> [Period('2015-09-01..2015-12-31')]
|
94
|
+
# Period.parse_phrase('from 2015-3Q') #=> [Period('2015-09-01..2015-12-31')]
|
95
|
+
# Period.parse_phrase('from 2015 per month') #=> [
|
96
|
+
# Period('2015-01-01..2015-01-31'),
|
97
|
+
# Period('2015-02-01..2015-02-28'),
|
98
|
+
# ...
|
99
|
+
# Period('2015-12-01..2015-12-31')
|
100
|
+
# ]
|
101
|
+
#
|
102
|
+
# @param phrase [String] with 'from <spec> to <spec> [per <chunk>]'
|
103
|
+
# @return [Array<Period>] translated from phrase
|
104
|
+
def self.parse_phrase(phrase, partial_first: false, partial_last: false, round_up_last: false)
|
105
|
+
phrase = phrase.downcase.clean
|
106
|
+
mm = phrase.match(PHRASE_RE)
|
107
|
+
raise ArgumentError, "invalid period phrase: `#{phrase}`" unless mm
|
108
|
+
|
109
|
+
if mm[:from_part] && mm[:to_part].nil?
|
110
|
+
from_part = mm[:from_part]
|
111
|
+
to_part = nil
|
112
|
+
elsif mm[:from_part].nil? && mm[:to_part]
|
113
|
+
from_part = mm[:to_part]
|
114
|
+
to_part = nil
|
115
|
+
else
|
116
|
+
from_part = mm[:from_part]
|
117
|
+
to_part = mm[:to_part]
|
118
|
+
end
|
119
|
+
|
120
|
+
whole_period = parse(from_part, to_part)
|
121
|
+
if mm[:chunk_part].nil?
|
122
|
+
[whole_period]
|
100
123
|
else
|
101
|
-
|
102
|
-
|
124
|
+
whole_period.chunks(size: mm[:chunk_part], partial_first: partial_first,
|
125
|
+
partial_last: partial_last, round_up_last: round_up_last)
|
103
126
|
end
|
104
|
-
parse(from_phrase, to_phrase)
|
105
127
|
end
|
106
128
|
|
107
129
|
# @group Conversion
|
@@ -213,10 +235,15 @@ class Period
|
|
213
235
|
|
214
236
|
# Yield each day in this Period.
|
215
237
|
def each
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
238
|
+
if block_given?
|
239
|
+
d = first
|
240
|
+
while d <= last
|
241
|
+
yield d
|
242
|
+
d += 1.day
|
243
|
+
end
|
244
|
+
self
|
245
|
+
else
|
246
|
+
to_enum(:each)
|
220
247
|
end
|
221
248
|
end
|
222
249
|
|
@@ -297,6 +324,8 @@ class Period
|
|
297
324
|
half: (180..183), year: (365..366)
|
298
325
|
}.freeze
|
299
326
|
|
327
|
+
private_constant :CHUNK_ORDER, :CHUNK_RANGE
|
328
|
+
|
300
329
|
def self.chunk_cmp(chunk1, chunk2)
|
301
330
|
CHUNK_ORDER[chunk1] <=> CHUNK_ORDER[chunk2]
|
302
331
|
end
|
@@ -342,9 +371,7 @@ class Period
|
|
342
371
|
raise ArgumentError, 'chunk is nil' unless chunk
|
343
372
|
|
344
373
|
chunk = chunk.to_sym
|
345
|
-
unless CHUNKS.include?(chunk)
|
346
|
-
raise ArgumentError, "unknown chunk name: #{chunk}"
|
347
|
-
end
|
374
|
+
raise ArgumentError, "unknown chunk name: #{chunk}" unless CHUNKS.include?(chunk)
|
348
375
|
|
349
376
|
date = Date.ensure_date(date)
|
350
377
|
method = "#{chunk}_containing".to_sym
|
@@ -471,20 +498,20 @@ class Period
|
|
471
498
|
end
|
472
499
|
end
|
473
500
|
|
474
|
-
# Return the chunk symbol represented by the number of days given, but allow
|
475
|
-
# deviation from the minimum and maximum number of days for periods larger
|
476
|
-
# than bimonths. The default tolerance is +/-10%, but that can be
|
477
|
-
# reason for allowing a bit of tolerance for the larger
|
478
|
-
# financial statements meant to cover a given calendar
|
479
|
-
# or long by a few days due to such things as
|
480
|
-
# accounting convenience. For example, a bank might
|
481
|
-
# statements approximately every 30 days, but issue them
|
482
|
-
# avoid having the closing date fall on a weekend or
|
483
|
-
# be able to recognize them as "monthly", even
|
484
|
-
# be a few days shorter or longer than any
|
485
|
-
# eliminate this "fudge factor" by setting
|
486
|
-
# the number of days corresponds to none of
|
487
|
-
# return the symbol `:irregular`.
|
501
|
+
# Return the chunk symbol represented by the number of days given, but allow
|
502
|
+
# a deviation from the minimum and maximum number of days for periods larger
|
503
|
+
# than bimonths. The default tolerance is +/-10%, but that can be
|
504
|
+
# adjusted. The reason for allowing a bit of tolerance for the larger
|
505
|
+
# periods is that financial statements meant to cover a given calendar
|
506
|
+
# period are often short or long by a few days due to such things as
|
507
|
+
# weekends, holidays, or accounting convenience. For example, a bank might
|
508
|
+
# issuer "monthly" statements approximately every 30 days, but issue them
|
509
|
+
# earlier or later to avoid having the closing date fall on a weekend or
|
510
|
+
# holiday. We still want to be able to recognize them as "monthly", even
|
511
|
+
# though the period covered might be a few days shorter or longer than any
|
512
|
+
# possible calendar month. You can eliminate this "fudge factor" by setting
|
513
|
+
# the `tolerance_pct` to zero. If the number of days corresponds to none of
|
514
|
+
# the defined calendar periods, return the symbol `:irregular`.
|
488
515
|
#
|
489
516
|
# @example
|
490
517
|
# Period.days_to_chunk(360) #=> :year
|
@@ -493,12 +520,12 @@ class Period
|
|
493
520
|
# Period.days_to_chunk(88, 0) #=> :irregular
|
494
521
|
#
|
495
522
|
# @param days [Integer] the number of days in the period under test
|
496
|
-
# @param tolerance_pct [
|
523
|
+
# @param tolerance_pct [Numeric] the percent deviation allowed, e.g. 10 => 10%
|
497
524
|
# @return [Symbol] symbol for the period corresponding to days number of days
|
498
525
|
def self.days_to_chunk(days, tolerance_pct = 10)
|
499
526
|
result = :irregular
|
500
527
|
CHUNK_RANGE.each_pair do |chunk, rng|
|
501
|
-
if [
|
528
|
+
if %i[semimonth biweek week day].include?(chunk)
|
502
529
|
# Be strict for shorter periods.
|
503
530
|
if rng.cover?(days)
|
504
531
|
result = chunk
|
@@ -589,9 +616,7 @@ class Period
|
|
589
616
|
def chunks(size: :month, partial_first: false, partial_last: false,
|
590
617
|
round_up_last: false)
|
591
618
|
chunk_size = size.to_sym
|
592
|
-
unless CHUNKS.include?(chunk_size)
|
593
|
-
raise ArgumentError, "unknown chunk size '#{chunk_size}'"
|
594
|
-
end
|
619
|
+
raise ArgumentError, "unknown chunk size '#{chunk_size}'" unless CHUNKS.include?(chunk_size)
|
595
620
|
|
596
621
|
containing_period = Period.chunk_containing(first, chunk_size)
|
597
622
|
return [dup] if self == containing_period
|
@@ -613,26 +638,25 @@ class Period
|
|
613
638
|
return result
|
614
639
|
end
|
615
640
|
|
641
|
+
# The first chunk
|
616
642
|
chunk_start = first.dup
|
617
643
|
chunk_end = chunk_start.end_of_chunk(chunk_size)
|
618
644
|
if chunk_start.beginning_of_chunk?(chunk_size) || partial_first
|
619
645
|
# Keep the first chunk if it's whole or partials allowed
|
620
646
|
result << Period.new(chunk_start, chunk_end)
|
621
|
-
chunk_start = chunk_end + 1.day
|
622
|
-
chunk_end = chunk_start.end_of_chunk(chunk_size)
|
623
|
-
else
|
624
|
-
# Discard the partial first or move to next whole chunk
|
625
|
-
chunk_start = chunk_end + 1.day
|
626
|
-
chunk_end = chunk_start.end_of_chunk(chunk_size)
|
627
647
|
end
|
648
|
+
chunk_start = chunk_end + 1.day
|
649
|
+
chunk_end = chunk_start.end_of_chunk(chunk_size)
|
650
|
+
|
628
651
|
# Add Whole chunks
|
629
652
|
while chunk_end <= last
|
630
653
|
result << Period.new(chunk_start, chunk_end)
|
631
654
|
chunk_start = chunk_end + 1.day
|
632
655
|
chunk_end = chunk_start.end_of_chunk(chunk_size)
|
633
656
|
end
|
657
|
+
|
634
658
|
# Possibly append the final chunk to result
|
635
|
-
if chunk_start
|
659
|
+
if chunk_start <= last
|
636
660
|
if round_up_last
|
637
661
|
result << Period.new(chunk_start, chunk_end)
|
638
662
|
elsif partial_last
|
@@ -640,15 +664,15 @@ class Period
|
|
640
664
|
else
|
641
665
|
result
|
642
666
|
end
|
643
|
-
|
667
|
+
end
|
668
|
+
if partial_last && !partial_first && result.empty?
|
644
669
|
# Catch the case where the period is too small to make a whole chunk and
|
645
670
|
# partial_first is false, so it did not get included as the initial
|
646
671
|
# partial chunk, yet a partial_last is allowed, so include the whole
|
647
672
|
# period as a partial chunk.
|
648
673
|
result << Period.new(first, last)
|
649
|
-
else
|
650
|
-
result
|
651
674
|
end
|
675
|
+
result
|
652
676
|
end
|
653
677
|
|
654
678
|
# @group Set operations
|
@@ -692,7 +716,7 @@ class Period
|
|
692
716
|
to_range.superset_of?(other.to_range)
|
693
717
|
end
|
694
718
|
|
695
|
-
# Does this period wholly contain but not coincident with `other`?
|
719
|
+
# Does this period wholly contain but is not coincident with `other`?
|
696
720
|
#
|
697
721
|
# @example
|
698
722
|
# 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.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Daniel E. Doherty
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2022-04-12 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
|
- - ">="
|
@@ -147,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
147
147
|
- !ruby/object:Gem::Version
|
148
148
|
version: '0'
|
149
149
|
requirements: []
|
150
|
-
rubygems_version: 3.
|
150
|
+
rubygems_version: 3.3.3
|
151
151
|
signing_key:
|
152
152
|
specification_version: 4
|
153
153
|
summary: Implements a Period class as a Range of Dates.
|