# See Pagy::Countless API documentation: https://ddnexus.github.io/pagy/docs/api/calendar# frozen_string_literal: truerequire'active_support'require'active_support/core_ext/time'require'active_support/core_ext/date_and_time/calculations'require'active_support/core_ext/numeric/time'require'active_support/core_ext/integer/time'require'pagy'classPagy# :nodoc:# Base class for time units subclasses (Year, Quarter, Month, Week, Day)classCalendar<Pagy# Specific out of range errorclassOutOfRangeError<VariableError;end# List of units in desc order of duration. It can be used for custom units.UNITS=%i[year quarter month week day]# rubocop:disable Style/MutableConstantattr_reader:order,:from,:to# Merge and validate the options, do some simple arithmetic and set a few instance variablesdefinitialize(vars)# rubocop:disable Lint/MissingSuperraiseInternalError,'Pagy::Calendar is a base class; use one of its subclasses'ifinstance_of?(Pagy::Calendar)vars=self.class::DEFAULT.merge(vars)# subclass specific defaultnormalize_vars(vars)# general defaultsetup_vars(page: 1)setup_unit_varssetup_params_varraiseOverflowError.new(self,:page,"in 1..#{@last}",@page)if@page>@last@prev=(@page-1unless@page==1)@next=@page==@last?(1if@vars[:cycle]):@page+1end# The label for the current page (it can pass along the I18n gem opts when it's used with the i18n extra)deflabel(opts={})label_for(@page,opts)end# The label for any page (it can pass along the I18n gem opts when it's used with the i18n extra)deflabel_for(page,opts={})opts[:format]||=@vars[:format]localize(starting_time_for(page.to_i),opts)# page could be a stringendprotected# The page that includes time# In case of out of range time, the :fit_time option avoids the outOfRangeError# and returns the closest page to the passed time argument (first or last page)defpage_at(time,**opts)fit_time=timefit_final=@final-1unlesstime.between?(@initial,fit_final)raiseOutOfRangeError.new(self,:time,"between #{@initial} and #{fit_final}",time)unlessopts[:fit_time]iftime<@finalfit_time=@initialordinal='first'elsefit_time=fit_finalordinal='last'endWarning.warn"Pagy::Calendar#page_at: Rescued #{time} out of range by returning the #{ordinal} page."endoffset=page_offset_at(fit_time)# offset starts from 0@order==:asc?offset+1:@pages-offsetend# Base class method for the setup of the unit variables (subclasses must implement it and call super)defsetup_unit_varsraiseVariableError.new(self,:format,'to be a strftime format',@vars[:format])unless@vars[:format].is_a?(String)raiseVariableError.new(self,:order,'to be in [:asc, :desc]',@order)\unless%i[asc desc].include?(@order=@vars[:order])@starting,@ending=@vars[:period]raiseVariableError.new(self,:period,'to be a an Array of min and max TimeWithZone instances',@vars[:period])\unless@starting.is_a?(ActiveSupport::TimeWithZone)\&&@ending.is_a?(ActiveSupport::TimeWithZone)&&@starting<=@endingend# Apply the strftime format to the time (overridden by the i18n extra when localization is required)deflocalize(time,opts)time.strftime(opts[:format])end# Number of time units to offset from the @initial time, in order to get the ordered starting time for the page.# Used in starting_time_for(page) where page starts from 1 (e.g. page to starting_time means subtracting 1)deftime_offset_for(page)@order==:asc?page-1:@pages-pageend# Period of the active page (used internally for nested units)defactive_period[[@starting,@from].max,[@to-1,@ending].min]# -1 sec: include only last unit dayend# :nocov:# This method must be implemented by the unit subclassdefstarting_time_for(*)raiseNoMethodError,'the starting_time_for method must be implemented by the unit subclass'end# This method must be implemented by the unit subclassdefpage_offset_at(*)raiseNoMethodError,'the page_offset_at method must be implemented by the unit subclass'end# :nocov:class<<self# Create a subclass instance by unit name (internal use)defcreate(unit,vars)raiseInternalError,"unit must be in #{UNITS.inspect}; got #{unit}"unlessUNITS.include?(unit)name=unit.to_sname[0]=name[0].capitalizeObject.const_get("Pagy::Calendar::#{name}").new(vars)endendend# Require the subclass files in UNITS (no custom unit at this point yet)Calendar::UNITS.each{|unit|require"pagy/calendar/#{unit}"}end