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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7a36af9ab31a0faffb0b9128748deb78fa97b8cb56581bcf3b47b8a2a8776d29
4
- data.tar.gz: '089e00bbce43fe66642ef419c9824fab84ab3a6b36aecc586aeb383662c10aa2'
3
+ metadata.gz: a1b3b656730fd4a24f6ec1ae3c14a2828aee91a6ddf18ce8a4481e31e2d55ad3
4
+ data.tar.gz: eb16ac79598e7c0054bac2ba57a9a5d8abce25cb8e810cbea9ac59ea4c8ead33
5
5
  SHA512:
6
- metadata.gz: 83ebcd8b723bd5f95845acf7ecd831293f88ccd15b31caec79d23e1e4bf308d49b02c43556ff85eaebbe9b31d28c683c5cb5dec1418ebfa2f2758790206d731c
7
- data.tar.gz: 7e19c54d5e179b20ff715e28b2ae191ada78ee75553832b07b48b3bba82ea617494208de44ae18900f45258ffb0de7be60fd5beadf200a7182921f690aebd9a7
6
+ metadata.gz: 82b0fb1e076cf644da168edbfadfe44a060dc8ebdc43cdf450280a32466fc31a33aee79591a209d480cf192373cfcd8fe808f46e70e0c8186ff9fb10bdf84c66
7
+ data.tar.gz: d1a151a0b003ba1bcaa54562928ab42075044ae04bbabca202001c56b1fd697779666c870541f023dddf64e248dd1a1ee2e5eeea5484e2c6f0ee392af469e874
data/.travis.yml CHANGED
@@ -3,4 +3,4 @@ rvm:
3
3
  - 2.5
4
4
  - 2.6
5
5
  - 2.7
6
- - ruby-head
6
+ - 3.0
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
@@ -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
- # Return a period as in `Period.parse` from a String phrase in which the from
76
- # spec is introduced with 'from' and, optionally, the to spec is introduced
77
- # with 'to'. A phrase with only a to spec is treated the same as one with
78
- # only a from spec. If neither 'from' nor 'to' appear in phrase, treat the
79
- # whole string as a from spec.
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') #=> Period('2015-09-01..2015-12-31')
87
- #
88
- # @param phrase [String] with 'from <spec> to <spec>'
89
- # @return [Period] translated from phrase
90
- def self.parse_phrase(phrase)
91
- phrase = phrase.clean
92
- if phrase =~ /\Afrom (.*) to (.*)\z/
93
- from_phrase = $1
94
- to_phrase = $2
95
- elsif phrase =~ /\Afrom (.*)\z/
96
- from_phrase = $1
97
- to_phrase = nil
98
- elsif phrase =~ /\Ato (.*)\z/
99
- from_phrase = $1
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
- from_phrase = phrase
102
- to_phrase = nil
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
- d = first
217
- while d <= last
218
- yield d
219
- d += 1.day
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 a
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 adjusted. The
477
- # reason for allowing a bit of tolerance for the larger periods is that
478
- # financial statements meant to cover a given calendar period are often short
479
- # or long by a few days due to such things as weekends, holidays, or
480
- # accounting convenience. For example, a bank might issuer "monthly"
481
- # statements approximately every 30 days, but issue them earlier or later to
482
- # avoid having the closing date fall on a weekend or holiday. We still want to
483
- # be able to recognize them as "monthly", even though the period covered might
484
- # be a few days shorter or longer than any possible calendar month. You can
485
- # eliminate this "fudge factor" by setting the `tolerance_pct` to zero. If
486
- # the number of days corresponds to none of the defined calendar periods,
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 [Numberic] the percent deviation allowed, e.g. 10 => 10%
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 [:semimonth, :biweek, :week, :day].include?(chunk)
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 < last
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
- elsif partial_last
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
@@ -1,3 +1,3 @@
1
1
  module FatPeriod
2
- VERSION = '1.2.0'.freeze
2
+ VERSION = '1.3.1'.freeze
3
3
  end
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.2.0
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: 2020-04-13 00:00:00.000000000 Z
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: pry
56
+ name: debug
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
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: '0'
68
+ version: 1.0.0
69
69
  - !ruby/object:Gem::Dependency
70
- name: pry-doc
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-byebug
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.0.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.