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