fat_period 1.0.3 → 1.2.1
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 -3
- data/fat_period.gemspec +1 -1
- data/lib/fat_period/period.rb +101 -114
- data/lib/fat_period/version.rb +1 -1
- metadata +9 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 609274b0338a91caf02d5fbf0ed37b4d2621dea51e93a393f9bde86e5287b0ad
|
4
|
+
data.tar.gz: cddf0aa8c7087d0705917319090dab8fea710579b697cc79d231f0257a80271d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3da42e489a2dc3a1d8b42191e589e675a71466048a70b1b2d04f430b2f3f34685f0c137194952a7c4ec33c117cf227b1143670409353351af9b8531492efc93b
|
7
|
+
data.tar.gz: e91123fa17ff8a9f47553652a302316418aaf2f5fe5eee587b45ecb9b27799ad20b39b9e1af350e4453ee69f19f4a8cd6a595ed82df2247c5bc8084c664768de
|
data/.travis.yml
CHANGED
data/fat_period.gemspec
CHANGED
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,37 +27,10 @@ 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
|
-
|
33
|
-
|
34
|
-
|
35
|
-
begin
|
36
|
-
@first = Date.parse(first.to_s)
|
37
|
-
rescue ArgumentError => e
|
38
|
-
if e.message =~ /invalid date/
|
39
|
-
raise ArgumentError, "invalid date '#{first}'"
|
40
|
-
end
|
41
|
-
|
42
|
-
raise
|
43
|
-
end
|
44
|
-
else
|
45
|
-
raise ArgumentError, 'use Date or String to initialize Period'
|
46
|
-
end
|
30
|
+
@first = Date.ensure_date(first).freeze
|
31
|
+
@last = Date.ensure_date(last).freeze
|
32
|
+
freeze
|
47
33
|
|
48
|
-
if last.is_a?(Date)
|
49
|
-
@last = last
|
50
|
-
elsif last.respond_to?(:to_s)
|
51
|
-
begin
|
52
|
-
@last = Date.parse(last.to_s)
|
53
|
-
rescue ArgumentError => e
|
54
|
-
if e.message =~ /invalid date/
|
55
|
-
raise ArgumentError, "you gave an invalid date '#{last}'"
|
56
|
-
end
|
57
|
-
|
58
|
-
raise
|
59
|
-
end
|
60
|
-
else
|
61
|
-
raise ArgumentError, 'use Date or String to initialize Period'
|
62
|
-
end
|
63
34
|
return unless @first > @last
|
64
35
|
|
65
36
|
raise ArgumentError, "Period's first date is later than its last date"
|
@@ -116,14 +87,13 @@ class Period
|
|
116
87
|
# @return [Period] translated from phrase
|
117
88
|
def self.parse_phrase(phrase)
|
118
89
|
phrase = phrase.clean
|
119
|
-
|
90
|
+
case phrase
|
91
|
+
when /\Afrom (.*) to (.*)\z/
|
120
92
|
from_phrase = $1
|
121
93
|
to_phrase = $2
|
122
|
-
|
94
|
+
when /\Afrom (.*)\z/, /\Ato (.*)\z/
|
123
95
|
from_phrase = $1
|
124
96
|
to_phrase = nil
|
125
|
-
elsif phrase =~ /\Ato (.*)\z/
|
126
|
-
from_phrase = $1
|
127
97
|
else
|
128
98
|
from_phrase = phrase
|
129
99
|
to_phrase = nil
|
@@ -208,6 +178,20 @@ class Period
|
|
208
178
|
!(self == other)
|
209
179
|
end
|
210
180
|
|
181
|
+
# Return the hash value for this Period. Make Period's with identical
|
182
|
+
# values test eql? so that they may be used as hash keys.
|
183
|
+
#
|
184
|
+
# @return [Integer]
|
185
|
+
def hash
|
186
|
+
(first.hash | last.hash)
|
187
|
+
end
|
188
|
+
|
189
|
+
def eql?(other)
|
190
|
+
return nil unless other.is_a?(Period)
|
191
|
+
|
192
|
+
hash == other.hash
|
193
|
+
end
|
194
|
+
|
211
195
|
# Return whether this Period contains the given date.
|
212
196
|
#
|
213
197
|
# @param date [Date] date to test
|
@@ -297,6 +281,12 @@ class Period
|
|
297
281
|
CHUNKS = %i[day week biweek semimonth month bimonth quarter
|
298
282
|
half year irregular].freeze
|
299
283
|
|
284
|
+
CHUNK_ORDER = {}
|
285
|
+
CHUNKS.each_with_index do |c, i|
|
286
|
+
CHUNK_ORDER[c] = i
|
287
|
+
end
|
288
|
+
CHUNK_ORDER.freeze
|
289
|
+
|
300
290
|
# An Array of Ranges for the number of days that can be covered by each chunk.
|
301
291
|
CHUNK_RANGE = {
|
302
292
|
day: (1..1), week: (7..7), biweek: (14..14), semimonth: (15..16),
|
@@ -304,6 +294,10 @@ class Period
|
|
304
294
|
half: (180..183), year: (365..366)
|
305
295
|
}.freeze
|
306
296
|
|
297
|
+
def self.chunk_cmp(chunk1, chunk2)
|
298
|
+
CHUNK_ORDER[chunk1] <=> CHUNK_ORDER[chunk2]
|
299
|
+
end
|
300
|
+
|
307
301
|
# Return a period representing a chunk containing a given Date.
|
308
302
|
def self.day_containing(date)
|
309
303
|
Period.new(date, date)
|
@@ -341,6 +335,17 @@ class Period
|
|
341
335
|
Period.new(date.beginning_of_year, date.end_of_year)
|
342
336
|
end
|
343
337
|
|
338
|
+
def self.chunk_containing(date, chunk)
|
339
|
+
raise ArgumentError, 'chunk is nil' unless chunk
|
340
|
+
|
341
|
+
chunk = chunk.to_sym
|
342
|
+
raise ArgumentError, "unknown chunk name: #{chunk}" unless CHUNKS.include?(chunk)
|
343
|
+
|
344
|
+
date = Date.ensure_date(date)
|
345
|
+
method = "#{chunk}_containing".to_sym
|
346
|
+
send(method, date)
|
347
|
+
end
|
348
|
+
|
344
349
|
# Return a Period representing a chunk containing today.
|
345
350
|
def self.this_day
|
346
351
|
day_containing(Date.current)
|
@@ -461,20 +466,20 @@ class Period
|
|
461
466
|
end
|
462
467
|
end
|
463
468
|
|
464
|
-
# Return the chunk symbol represented by the number of days given, but allow
|
465
|
-
# deviation from the minimum and maximum number of days for periods larger
|
466
|
-
# than bimonths. The default tolerance is +/-10%, but that can be
|
467
|
-
# reason for allowing a bit of tolerance for the larger
|
468
|
-
# financial statements meant to cover a given calendar
|
469
|
-
# or long by a few days due to such things as
|
470
|
-
# accounting convenience. For example, a bank might
|
471
|
-
# statements approximately every 30 days, but issue them
|
472
|
-
# avoid having the closing date fall on a weekend or
|
473
|
-
# be able to recognize them as "monthly", even
|
474
|
-
# be a few days shorter or longer than any
|
475
|
-
# eliminate this "fudge factor" by setting
|
476
|
-
# the number of days corresponds to none of
|
477
|
-
# return the symbol `:irregular`.
|
469
|
+
# Return the chunk symbol represented by the number of days given, but allow
|
470
|
+
# a deviation from the minimum and maximum number of days for periods larger
|
471
|
+
# than bimonths. The default tolerance is +/-10%, but that can be
|
472
|
+
# adjusted. The reason for allowing a bit of tolerance for the larger
|
473
|
+
# periods is that financial statements meant to cover a given calendar
|
474
|
+
# period are often short or long by a few days due to such things as
|
475
|
+
# weekends, holidays, or accounting convenience. For example, a bank might
|
476
|
+
# issuer "monthly" statements approximately every 30 days, but issue them
|
477
|
+
# earlier or later to avoid having the closing date fall on a weekend or
|
478
|
+
# holiday. We still want to be able to recognize them as "monthly", even
|
479
|
+
# though the period covered might be a few days shorter or longer than any
|
480
|
+
# possible calendar month. You can eliminate this "fudge factor" by setting
|
481
|
+
# the `tolerance_pct` to zero. If the number of days corresponds to none of
|
482
|
+
# the defined calendar periods, return the symbol `:irregular`.
|
478
483
|
#
|
479
484
|
# @example
|
480
485
|
# Period.days_to_chunk(360) #=> :year
|
@@ -483,12 +488,12 @@ class Period
|
|
483
488
|
# Period.days_to_chunk(88, 0) #=> :irregular
|
484
489
|
#
|
485
490
|
# @param days [Integer] the number of days in the period under test
|
486
|
-
# @param tolerance_pct [
|
491
|
+
# @param tolerance_pct [Numeric] the percent deviation allowed, e.g. 10 => 10%
|
487
492
|
# @return [Symbol] symbol for the period corresponding to days number of days
|
488
493
|
def self.days_to_chunk(days, tolerance_pct = 10)
|
489
494
|
result = :irregular
|
490
495
|
CHUNK_RANGE.each_pair do |chunk, rng|
|
491
|
-
if [
|
496
|
+
if %i[semimonth biweek week day].include?(chunk)
|
492
497
|
# Be strict for shorter periods.
|
493
498
|
if rng.cover?(days)
|
494
499
|
result = chunk
|
@@ -578,77 +583,59 @@ class Period
|
|
578
583
|
# @return [Array<Period>] periods that subdivide self into chunks of size, `size`
|
579
584
|
def chunks(size: :month, partial_first: false, partial_last: false,
|
580
585
|
round_up_last: false)
|
581
|
-
|
582
|
-
unless CHUNKS.include?(
|
583
|
-
raise ArgumentError, "unknown chunk size '#{size}'"
|
584
|
-
end
|
586
|
+
chunk_size = size.to_sym
|
587
|
+
raise ArgumentError, "unknown chunk size '#{chunk_size}'" unless CHUNKS.include?(chunk_size)
|
585
588
|
|
586
|
-
|
587
|
-
|
589
|
+
containing_period = Period.chunk_containing(first, chunk_size)
|
590
|
+
return [dup] if self == containing_period
|
588
591
|
|
589
|
-
|
590
|
-
|
592
|
+
# Period too small for even a single chunk and is wholly-contained by a
|
593
|
+
# single chunk.
|
594
|
+
result = []
|
595
|
+
if proper_subset_of?(containing_period)
|
596
|
+
result =
|
597
|
+
if partial_first || partial_last
|
598
|
+
if round_up_last
|
599
|
+
[containing_period]
|
600
|
+
else
|
601
|
+
[dup]
|
602
|
+
end
|
603
|
+
else
|
604
|
+
[]
|
605
|
+
end
|
606
|
+
return result
|
591
607
|
end
|
592
608
|
|
593
|
-
result = []
|
594
609
|
chunk_start = first.dup
|
595
|
-
|
596
|
-
|
597
|
-
|
598
|
-
|
599
|
-
|
600
|
-
|
601
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
chunk_end = chunk_start.end_of_quarter
|
612
|
-
when :bimonth
|
613
|
-
unless partial_first
|
614
|
-
chunk_start += 1.day until chunk_start.beginning_of_bimonth?
|
615
|
-
end
|
616
|
-
chunk_end = (chunk_start.end_of_month + 1.day).end_of_month
|
617
|
-
when :month
|
618
|
-
unless partial_first
|
619
|
-
chunk_start += 1.day until chunk_start.beginning_of_month?
|
620
|
-
end
|
621
|
-
chunk_end = chunk_start.end_of_month
|
622
|
-
when :semimonth
|
623
|
-
unless partial_first
|
624
|
-
chunk_start += 1.day until chunk_start.beginning_of_semimonth?
|
625
|
-
end
|
626
|
-
chunk_end = chunk_start.end_of_semimonth
|
627
|
-
when :biweek
|
628
|
-
unless partial_first
|
629
|
-
chunk_start += 1.day until chunk_start.beginning_of_biweek?
|
630
|
-
end
|
631
|
-
chunk_end = chunk_start.end_of_biweek
|
632
|
-
when :week
|
633
|
-
unless partial_first
|
634
|
-
chunk_start += 1.day until chunk_start.beginning_of_week?
|
635
|
-
end
|
636
|
-
chunk_end = chunk_start.end_of_week
|
637
|
-
when :day
|
638
|
-
chunk_end = chunk_start
|
639
|
-
else
|
640
|
-
raise ArgumentError, "invalid chunk size '#{size}'"
|
641
|
-
end
|
642
|
-
if chunk_end <= last
|
643
|
-
result << Period.new(chunk_start, chunk_end)
|
644
|
-
elsif round_up_last
|
610
|
+
chunk_end = chunk_start.end_of_chunk(chunk_size)
|
611
|
+
if chunk_start.beginning_of_chunk?(chunk_size) || partial_first
|
612
|
+
# Keep the first chunk if it's whole or partials allowed
|
613
|
+
result << Period.new(chunk_start, chunk_end)
|
614
|
+
end
|
615
|
+
chunk_start = chunk_end + 1.day
|
616
|
+
chunk_end = chunk_start.end_of_chunk(chunk_size)
|
617
|
+
# Add Whole chunks
|
618
|
+
while chunk_end <= last
|
619
|
+
result << Period.new(chunk_start, chunk_end)
|
620
|
+
chunk_start = chunk_end + 1.day
|
621
|
+
chunk_end = chunk_start.end_of_chunk(chunk_size)
|
622
|
+
end
|
623
|
+
# Possibly append the final chunk to result
|
624
|
+
if chunk_start < last
|
625
|
+
if round_up_last
|
645
626
|
result << Period.new(chunk_start, chunk_end)
|
646
627
|
elsif partial_last
|
647
628
|
result << Period.new(chunk_start, last)
|
648
629
|
else
|
649
|
-
|
630
|
+
result
|
650
631
|
end
|
651
|
-
|
632
|
+
end
|
633
|
+
if partial_last && !partial_first && result.empty?
|
634
|
+
# Catch the case where the period is too small to make a whole chunk and
|
635
|
+
# partial_first is false, so it did not get included as the initial
|
636
|
+
# partial chunk, yet a partial_last is allowed, so include the whole
|
637
|
+
# period as a partial chunk.
|
638
|
+
result << Period.new(first, last)
|
652
639
|
end
|
653
640
|
result
|
654
641
|
end
|
@@ -694,7 +681,7 @@ class Period
|
|
694
681
|
to_range.superset_of?(other.to_range)
|
695
682
|
end
|
696
683
|
|
697
|
-
# Does this period wholly contain but not coincident with `other`?
|
684
|
+
# Does this period wholly contain but is not coincident with `other`?
|
698
685
|
#
|
699
686
|
# @example
|
700
687
|
# 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.2.1
|
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: 2021-12-27 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -100,15 +100,15 @@ dependencies:
|
|
100
100
|
requirements:
|
101
101
|
- - ">="
|
102
102
|
- !ruby/object:Gem::Version
|
103
|
-
version:
|
103
|
+
version: 4.8.3
|
104
104
|
type: :runtime
|
105
105
|
prerelease: false
|
106
106
|
version_requirements: !ruby/object:Gem::Requirement
|
107
107
|
requirements:
|
108
108
|
- - ">="
|
109
109
|
- !ruby/object:Gem::Version
|
110
|
-
version:
|
111
|
-
description:
|
110
|
+
version: 4.8.3
|
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: []
|