lib/fat_period/period.rb in fat_period-1.2.1 vs lib/fat_period/period.rb in fat_period-1.3.0

- old
+ new

@@ -68,39 +68,63 @@ 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. + PHRASE_RE = %r{\A + # One or both of from and to parts + ((from\s+(?<from_part>[-_a-z0-9]+)\s*)? + (to\s+(?<to_part>[-_a-z0-9]+))?) + # Wholly optional chunk part + (\s+per\s+(?<chunk_part>\w+))?\z}xi + + # Return an array of periods, either a single period as in `Period.parse` + # from a String phrase in which a `from spec` is introduced with 'from' and, + # optionally, a `to spec` is introduced with 'to', or a number of periods if + # there is a 'per <chunk>' modifier. 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. # # @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('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('from 2015-3Q') #=> [Period('2015-09-01..2015-12-31')] + # Period.parse_phrase('from 2015 per month') #=> [ + # Period('2015-01-01..2015-01-31'), + # Period('2015-02-01..2015-02-28'), + # ... + # Period('2015-12-01..2015-12-31') + # ] # - # @param phrase [String] with 'from <spec> to <spec>' - # @return [Period] translated from phrase - def self.parse_phrase(phrase) - phrase = phrase.clean - case phrase - when /\Afrom (.*) to (.*)\z/ - from_phrase = $1 - to_phrase = $2 - when /\Afrom (.*)\z/, /\Ato (.*)\z/ - from_phrase = $1 - to_phrase = nil + # @param phrase [String] with 'from <spec> to <spec> [per <chunk>]' + # @return [Array<Period>] translated from phrase + def self.parse_phrase(phrase, + partial_first: false, partial_last: false, round_up_last: false) + phrase = phrase.downcase.clean + mm = phrase.match(PHRASE_RE) + raise ArgumentError, "invalid period phrase: `#{phrase}`" unless mm + + if mm[:from_part] && mm[:to_part].nil? + from_part = mm[:from_part] + to_part = nil + elsif mm[:from_part].nil? && mm[:to_part] + from_part = mm[:to_part] + to_part = nil else - from_phrase = phrase - to_phrase = nil + from_part = mm[:from_part] + to_part = mm[:to_part] end - parse(from_phrase, to_phrase) + + whole_period = parse(from_part, to_part) + if mm[:chunk_part].nil? + [whole_period] + else + whole_period.chunks(size: mm[:chunk_part], partial_first: partial_first, + partial_last: partial_last, round_up_last: round_up_last) + end end # @group Conversion # Convert this Period to a Range. @@ -604,25 +628,28 @@ [] end return result end + # The first chunk chunk_start = first.dup chunk_end = chunk_start.end_of_chunk(chunk_size) if chunk_start.beginning_of_chunk?(chunk_size) || partial_first # Keep the first chunk if it's whole or partials allowed result << Period.new(chunk_start, chunk_end) end chunk_start = chunk_end + 1.day chunk_end = chunk_start.end_of_chunk(chunk_size) + # Add Whole chunks while chunk_end <= last result << Period.new(chunk_start, chunk_end) chunk_start = chunk_end + 1.day chunk_end = chunk_start.end_of_chunk(chunk_size) end + # Possibly append the final chunk to result - if chunk_start < last + if chunk_start <= last if round_up_last result << Period.new(chunk_start, chunk_end) elsif partial_last result << Period.new(chunk_start, last) else