<commit_before># -*- coding: utf-8 -*-
class Period
  include Enumerable
  include Comparable

  attr_accessor :first, :last

  def initialize(first, last)
    case first
    when String
      begin
        first = Date.parse(first)
      rescue ArgumentError => ex
        if ex.message =~ /invalid date/
          raise ArgumentError, "you gave an invalid date '#{first}'"
        else
          raise
        end
      end
    when Date
      first = first
    else
      raise ArgumentError, "use Date or String to initialize Period"
    end

    case last
    when String
      begin
        last = Date.parse(last)
      rescue ArgumentError => ex
        if ex.message =~ /invalid date/
          raise ArgumentError, "you gave an invalid date '#{last}'"
        else
          raise
        end
      end
    when Date
      last = last
    else
      raise ArgumentError, "use Date or String to initialize Period"
    end

    @first = first
    @last = last
    if @first > @last
      raise ArgumentError, "Period's first date is later than its last date"
    end
  end

  # Return a period based on two date specs (see Date.parse_spec), a '''from'
  # and a 'to' spec.  If the to-spec is not given or is nil, the from-spec is
  # used for both the from- and to-spec.  If no from-spec is given, return
  # today as the period.
  def self.parse_spec(from = 'today', to = nil)
    to ||= from
    from ||= to
    Period.new(Date.parse_spec(from, :from), Date.parse_spec(to, :to))
  end

  # Possibly useful class method to take an array of periods and join all the
  # contiguous ones, then return an array of the disjoint periods not
  # contiguous to one another.  An array of periods with no gaps should return
  # an array of only one period spanning all the given periods.

  # Return an array of periods that represent the concatenation of all
  # adjacent periods in the given periods.
  # def self.meld_periods(*periods)
  #   melded_periods = []
  #   while (this_period = periods.pop)
  #     melded_periods.each do |mp|
  #       if mp.overlaps?(this_period)
  #         melded_periods.delete(mp)
  #         melded_periods << mp.union(this_period)
  #         break
  #       elsif mp.contiguous?(this_period)
  #         melded_periods.delete(mp)
  #         melded_periods << mp.join(this_period)
  #         break
  #       end
  #     end
  #   end
  #   melded_periods
  # end

  # TO_DATE = Period.new(Date::BOT, Date.current)
  # FOREVER = Period.new(Date::BOT, Date::EOT)

  def each
    d = first
    while d <= last
      yield d
      d = d + 1.day
    end
  end

  def self.chunk_syms
    [:day, :week, :biweek, :semimonth, :month, :bimonth,
     :quarter, :year, :irregular]
  end

  def self.chunk_sym_to_days(sym)
    case sym
    when :day
      1
    when :week
      7
    when :biweek
      14
    when :semimonth
      15
    when :month
      30
    when :bimonth
      60
    when :quarter
      90
    when :year
      365
    when :irregular
      30
    else
      raise ArgumentError, "unknown chunk sym '#{sym}'"
    end
  end

  # The largest number of days possible in each chunk
  def self.chunk_sym_to_max_days(sym)
    case sym
    when :semimonth
      16
    when :month
      31
    when :bimonth
      62
    when :quarter
      92
    when :year
      366
    else
      chunk_sym_to_days(sym)
    end
  end

  # This is only used for inferring statement frequency based on the number
  # of days between statements, so it will not consider all possible chunks,
  # only :year, :quarter, :month, and :week.  And distinguishing between
  # :semimonth and :biweek is impossible anyway.  Since statement dates can
  # bounce around quite a bit in my experience, this is really fuzzy.  For
  # example, one of my banks does monthly statements "around" the 10th of
  # the month, but the 10th can get pushed off by holidays, weekends, etc.,
  # so a "quarter" here is much broader than the calendar definition.  Ditto
  # for the others, but since statements are most likely monthly, we default
  # to :month.
  def self.days_to_chunk_sym(days)
    case days
    when 356..376
      :year
    when 86..96
      :quarter
    when 26..33
      :month
    when 7
      :week
    when 1
      :day
    else
      :irregular
    end
  end

  def size
    to_range.size
  end

  def length
    size
  end

  def to_range
    (first..last)
  end

  def ==(other)
    first == other.first && last == other.last
  end

  def <=>(other)
    [first, size] <=> [other.first, other.size]
  end

  def to_s
    if first.beginning_of_year? && last.end_of_year? && first.year == last.year
      "#{first.year}"
    elsif first.beginning_of_quarter? &&
        last.end_of_quarter? &&<commit_msg>Make Comparable <=> compare first and last; add case equality.<commit_after># -*- coding: utf-8 -*-
class Period
  include Enumerable
  include Comparable

  attr_accessor :first, :last

  def initialize(first, last)
    case first
    when String
      begin
        first = Date.parse(first)
      rescue ArgumentError => ex
        if ex.message =~ /invalid date/
          raise ArgumentError, "you gave an invalid date '#{first}'"
        else
          raise
        end
      end
    when Date
      first = first
    else
      raise ArgumentError, "use Date or String to initialize Period"
    end

    case last
    when String
      begin
        last = Date.parse(last)
      rescue ArgumentError => ex
        if ex.message =~ /invalid date/
          raise ArgumentError, "you gave an invalid date '#{last}'"
        else
          raise
        end
      end
    when Date
      last = last
    else
      raise ArgumentError, "use Date or String to initialize Period"
    end

    @first = first
    @last = last
    if @first > @last
      raise ArgumentError, "Period's first date is later than its last date"
    end
  end

  # Comparable base: periods are equal only if their first and last dates are
  # equal.  Sorting will be by first date, then last, so periods starting on
  # the same date will sort by last date, thus, from smallest to largest in
  # size.
  def <=>(other)
    [first, last] <=> [other.first, other.last]
  end

  # Comparable does not include this.
  def !=(other)
    !(self == other)
  end

  # Enumerable base.  Yield each day in the period.
  def each
    d = first
    while d <= last
      yield d
      d = d + 1.day
    end
  end

  # Case equality checks for inclusion of date in period.
  def ===(date)
    self.contains?(date)
  end

  # Return a period based on two date specs (see Date.parse_spec), a '''from'
  # and a 'to' spec.  If the to-spec is not given or is nil, the from-spec is
  # used for both the from- and to-spec.  If no from-spec is given, return
  # today as the period.
  def self.parse_spec(from = 'today', to = nil)
    to ||= from
    from ||= to
    Period.new(Date.parse_spec(from, :from), Date.parse_spec(to, :to))
  end

  # Possibly useful class method to take an array of periods and join all the
  # contiguous ones, then return an array of the disjoint periods not
  # contiguous to one another.  An array of periods with no gaps should return
  # an array of only one period spanning all the given periods.

  # Return an array of periods that represent the concatenation of all
  # adjacent periods in the given periods.
  # def self.meld_periods(*periods)
  #   melded_periods = []
  #   while (this_period = periods.pop)
  #     melded_periods.each do |mp|
  #       if mp.overlaps?(this_period)
  #         melded_periods.delete(mp)
  #         melded_periods << mp.union(this_period)
  #         break
  #       elsif mp.contiguous?(this_period)
  #         melded_periods.delete(mp)
  #         melded_periods << mp.join(this_period)
  #         break
  #       end
  #     end
  #   end
  #   melded_periods
  # end

  # TO_DATE = Period.new(Date::BOT, Date.current)
  # FOREVER = Period.new(Date::BOT, Date::EOT)

  def self.chunk_syms
    [:day, :week, :biweek, :semimonth, :month, :bimonth,
     :quarter, :year, :irregular]
  end

  def self.chunk_sym_to_days(sym)
    case sym
    when :day
      1
    when :week
      7
    when :biweek
      14
    when :semimonth
      15
    when :month
      30
    when :bimonth
      60
    when :quarter
      90
    when :year
      365
    when :irregular
      30
    else
      raise ArgumentError, "unknown chunk sym '#{sym}'"
    end
  end

  # The largest number of days possible in each chunk
  def self.chunk_sym_to_max_days(sym)
    case sym
    when :semimonth
      16
    when :month
      31
    when :bimonth
      62
    when :quarter
      92
    when :year
      366
    else
      chunk_sym_to_days(sym)
    end
  end

  # This is only used for inferring statement frequency based on the number
  # of days between statements, so it will not consider all possible chunks,
  # only :year, :quarter, :month, and :week.  And distinguishing between
  # :semimonth and :biweek is impossible anyway.  Since statement dates can
  # bounce around quite a bit in my experience, this is really fuzzy.  For
  # example, one of my banks does monthly statements "around" the 10th of
  # the month, but the 10th can get pushed off by holidays, weekends, etc.,
  # so a "quarter" here is much broader than the calendar definition.  Ditto
  # for the others, but since statements are most likely monthly, we default
  # to :month.
  def self.days_to_chunk_sym(days)
    case days
    when 356..376
      :year
    when 86..96
      :quarter
    when 26..33
      :month
    when 7
      :week
    when 1
      :day
    else
      :irregular
    end
  end

  def size
    to_range.size
  end

  def length
    size
  end

  def to_range
    (first..last)
  end

  def to_s
    if first.beginning_of_year? && last.end_of_year? && first.year == last.year
      "#{first.year}"
    elsif first.beginning_of_quarter? &&
        last.end_of_quarter? &&
{'fraction_non_alphanumeric': 0.07990729544538493, 'fraction_numerical': 0.01007658202337767, 'mean_word_length': 2.2150955620343376, 'pattern_counts': {'":': 0, '<': 18, '<?xml version=': 0, '>': 14, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '199713', 'n_tokens_mistral': 3239, 'n_tokens_neox': 3079, 'n_words': 1151}