lib/fat_period/period.rb in fat_period-2.0.0 vs lib/fat_period/period.rb in fat_period-2.1.0
- old
+ new
@@ -28,13 +28,11 @@
def initialize(first, last)
@first = Date.ensure_date(first).freeze
@last = Date.ensure_date(last).freeze
freeze
- return unless @first > @last
-
- raise ArgumentError, "Period's first date is later than its last date"
+ raise ArgumentError, "Period's first date is later than its last date" if @first > @last
end
# These need to come after initialize is defined
# Period from commercial beginning of time to commercial end of time.
@@ -64,39 +62,77 @@
first = Date.parse_spec(from, :from)
second = Date.parse_spec(to, :to)
Period.new(first, second) if first && second
end
- # Return a period as in `Period.parse` from a String phrase in which the from
- # spec is introduced with 'from' and, optionally, the to spec is introduced
- # with 'to'. A phrase with only a to spec is treated the same as one with
- # only a from spec. If neither 'from' nor 'to' appear in phrase, treat the
- # whole string as a from spec.
+ # Return a Period either from a given String or other type that can
+ # reasonably converted to a Period.
#
# @example
- # Period.parse_phrase('from 2014-11 to 2015-3Q') #=> Period('2014-11-01..2015-09-30')
- # Period.parse_phrase('from 2014-11') #=> Period('2014-11-01..2014-11-30')
- # Period.parse_phrase('from 2015-3Q') #=> Period('2015-09-01..2015-12-31')
- # Period.parse_phrase('to 2015-3Q') #=> Period('2015-09-01..2015-12-31')
- # Period.parse_phrase('2015-3Q') #=> Period('2015-09-01..2015-12-31')
+ # Period.ensure('2014-11').inspect #=> Period('2014-11-01..2014-11-30')
+ # pd = Period.parse('2011')
+ # Period.ensure(pd).inspect #=> Period('2011-01-01..2011-12-31')
#
- # @param phrase [String] with 'from <spec> to <spec>'
+ # @param prd [String|Period] or any candidate for conversion to Period
+ # @return Period correspondign to prd parameter
+ def self.ensure(prd)
+ prd.to_period if prd.respond_to?(:to_period)
+ case prd
+ when String
+ if prd.match(/from|to/i)
+ Period.parse_phrase(prd).first
+ else
+ Period.parse(prd)
+ end
+ when Period
+ prd
+ end
+ end
+
+ # Return an Array of Periods from a String phrase in which the from spec is
+ # introduced with 'from' and, optionally, the to spec is introduced with
+ # 'to' and optionally a 'per' clause is introduced by 'per'. A phrase with
+ # only a to spec is treated the same as one with only a from spec. If
+ # neither 'from' nor 'to' appear in phrase, treat the string before any
+ # per-clause as a from spec.
+ #
+ # @example
+ # Period.parse_phrase('from 2014-11 to 2015-3Q') #=> [Period('2014-11-01..2015-09-30')]
+ # Period.parse_phrase('from 2014-11') #=> [Period('2014-11-01..2014-11-30')]
+ # Period.parse_phrase('from 2015-3Q') #=> [Period('2015-09-01..2015-12-31')]
+ # Period.parse_phrase('to 2015-3Q') #=> [Period('2015-09-01..2015-12-31')]
+ # Period.parse_phrase('2015-3Q') #=> [Period('2015-09-01..2015-12-31')]
+ # Period.parse_phrase('to 2015-3Q per week') #=> [Period('2015-09-01..2015-09-04')...]
+ # Period.parse_phrase('2015-3Q per month') #=> [Period('2015-09-01..2015-09-30')...]
+ #
+ # @param phrase [String] with 'from <spec> [to <spec>] [per chunk]'
# @return [Period] translated from phrase
- def self.parse_phrase(phrase)
+ def self.parse_phrase(phrase, partial_first: true, partial_last: true, round_up_last: false)
phrase = phrase.clean
case phrase
- when /\Afrom (.*) to (.*)\z/
+ when /\Afrom\s+([^\s]+)\s+to\s+([^\s]+)(\s+per\s+[^\s]+)?\z/i
from_phrase = $1
to_phrase = $2
- when /\Afrom (.*)\z/, /\Ato (.*)\z/
+ when /\Afrom\s+([^\s]+)(\s+per\s+[^\s]+)?\z/, /\Ato\s+([^\s]+)(\s+per\s+[^\s]+)?\z/i
from_phrase = $1
to_phrase = nil
- else
- from_phrase = phrase
+ when /\A([^\s]+)(\s+per\s+[^\s]+)?\z/
+ from_phrase = $1
to_phrase = nil
+ else
+ raise ArgumentError, "unintelligible period phrase: '#{phrase}''"
end
- parse(from_phrase, to_phrase)
+ # Return an Array of periods divided by chunks if any.
+ whole_period = parse(from_phrase, to_phrase)
+ if phrase =~ /per\s+(?<chunk>[a-z_]+)/i
+ chunk_size = Regexp.last_match[:chunk].downcase.to_sym
+ raise ArgumentError, "invalid chunk size #{chunk_size}" unless CHUNKS.include?(chunk_size)
+
+ whole_period.chunks(size: chunk_size, partial_first:, partial_last:, round_up_last:)
+ else
+ [whole_period]
+ end
end
# @group Conversion
# Convert this Period to a Range.
@@ -181,11 +217,11 @@
def hash
(first.hash | last.hash)
end
def eql?(other)
- return unless other.is_a?(Period)
+ return false unless other.is_a?(Period)
hash == other.hash
end
# Return whether this Period contains the given date.
@@ -282,10 +318,9 @@
month
bimonth
quarter
half
year
- irregular
].freeze
CHUNK_ORDER = {}
CHUNKS.each_with_index do |c, i|
CHUNK_ORDER[c] = i