class Pagy::Calendar

Paginate a Time period by units (year, month, week or day)

def bump_month(time, months = 1)

Add 1 or more months to local time
def bump_month(time, months = 1)
  months += months(time)
  year  = months / 12
  month = months % 12
  month.zero? ? new_time(year - 1, 12) : new_time(year, month)
end

def initialize(vars) # rubocop:disable Lint/MissingSuper

rubocop:disable Lint/MissingSuper
Merge and validate the options, do some simple arithmetic and set a few instance variables
def initialize(vars) # rubocop:disable Lint/MissingSuper
  normalize_vars(vars)
  setup_vars(page: 1, week_offset: 0)
  setup_unit_vars
  setup_params_var
  raise OverflowError.new(self, :page, "in 1..#{@last}", @page) if @page > @last
  @prev = (@page - 1 unless @page == 1)
  @next = @page == @last ? (1 if @vars[:cycle]) : @page + 1
end

def label(**opts)

(it can pass along the I18n gem opts when it's used with the i18n extra)
The label for the current page
def label(**opts)
  label_for(@page, **opts)
end

def label_for(page, **opts)

(it can pass along the I18n gem opts when it's used with the i18n extra)
Generate a label for each page, with the specific `Time` period it refers to
def label_for(page, **opts)
  snap = snap(page.to_i)
  time = case @unit
         when :year  then new_time(@initial.year + snap)
         when :month then bump_month(@initial, snap)
         when :week  then @initial + (snap * WEEK)
         when :day   then @initial + (snap * DAY)
         else raise InternalError, "expected @unit to be in [:year, :month, :week, :day]; got #{@unit.inspect}"
         end
  opts[:format] ||= @vars[:"#{@unit}_format"]
  localize(time, **opts)
end

def localize(time, **opts)

(overridden by the i18n extra when localization is required)
Apply the strftime format to the time
def localize(time, **opts)
  time.strftime(opts[:format])
end

def months(time)

Months in local time
def months(time)
  (time.year * 12) + time.month
end

def new_time(year, month = 1, day = 1)

Create a new local time at the beginning of the day
def new_time(year, month = 1, day = 1)
  Time.new(year, month, day, 0, 0, 0, @utc_offset)
end

def setup_day_vars(min, max)

Setup the calendar vars when the unit is :day
def setup_day_vars(min, max)
  @initial  = new_time(min.year, min.month, min.day)
  @final    = new_time(max.year, max.month, max.day) + DAY
  @pages    = @last = (@final - @initial).to_i / DAY
  @utc_from = (@initial + (snap * DAY)).utc
  @utc_to   = @utc_from + DAY
end

def setup_month_vars(min, max)

Setup the calendar vars when the unit is :month
def setup_month_vars(min, max)
  @initial  = new_time(min.year, min.month)
  @final    = bump_month(max)
  @pages    = @last = months(@final) - months(@initial)
  @utc_from = bump_month(@initial, snap).utc
  @utc_to   = bump_month(@initial, snap + 1).utc
end

def setup_unit_vars

def setup_unit_vars
  (units = %i[year month week day]).each do |unit|
    raise VariableError.new(self, :format, 'to be a strftime format', @vars[:"#{unit}_format"]) \
          unless @vars[:"#{unit}_format"].is_a?(String)
  end
  raise VariableError.new(self, :unit, "to be in #{units.inspect}", @unit) \
        unless units.include?(@unit = @vars[:unit])
  raise VariableError.new(self, :order, 'to be in [:asc, :desc]', @order) \
        unless %i[asc desc].include?(@order = @vars[:order])
  min, max = @vars[:local_minmax]
  raise VariableError.new(self, :local_minmax, 'to be a an Array of min and max local Time instances', @vars[:local_minmax]) \
        unless min.is_a?(Time) && max.is_a?(Time) && !min.utc? && !max.utc? && min <= max \
               && (@utc_offset = min.utc_offset) == max.utc_offset
  send :"setup_#{@unit}_vars", min, max
end

def setup_week_vars(min, max)

Setup the calendar vars when the unit is :week
def setup_week_vars(min, max)
  @initial  = week_start(min)
  @final    = week_start(max) + WEEK
  @pages    = @last = (@final - @initial).to_i / WEEK
  @utc_from = (@initial + (snap * WEEK)).utc
  @utc_to   = @utc_from + WEEK
end

def setup_year_vars(min, max)

Setup the calendar vars when the unit is :year
def setup_year_vars(min, max)
  @initial  = new_time(min.year)
  @final    = new_time(max.year + 1)
  @pages    = @last = @final.year - @initial.year
  @utc_from = new_time(@initial.year + snap).utc
  @utc_to   = new_time(@initial.year + snap + 1).utc
end

def snap(page = @page)

without actually reordering anything in the internal structure
Simple trick to snap the page into its ordered position,
def snap(page = @page)
  @order == :asc ? page - 1 : @pages - page
end

def week_start(time)

Return the start of the week for local time
def week_start(time)
  start = time - (((time.wday - @week_offset) * DAY) % WEEK)
  new_time(start.year, start.month, start.day)
end