module Repor module Dimensions class TimeDimension < BinDimension STEPS = %i(seconds minutes hours days weeks months years) BIN_STEPS = (STEPS - [:seconds]).map { |s| s.to_s.singularize } DURATION_PATTERN = /\A\d+ (?:#{STEPS.map{|s| "#{s}?" }.join('|')})\z/ def validate_params! super if params.key?(:bin_width) && !valid_duration?(params[:bin_width]) invalid_param!(:bin_width, "must be a hash of one of #{STEPS} to an integer") end end def bin_width @bin_width ||= if params.key?(:bin_width) custom_bin_width elsif params.key?(:bin_count) && domain > 0 (domain / params[:bin_count].to_f).seconds else default_bin_width end end def bin_start # ensure that each autogenerated bin represents a correctly aligned # day/week/month/year bin_start = super if bin_start.nil? nil elsif step = BIN_STEPS.detect { |step| bin_width == 1.send(step) } bin_start.send(:"beginning_of_#{step}") else bin_start end end private def custom_bin_width if params[:bin_width].is_a?(Hash) params[:bin_width].map { |step, n| n.send(step) }.sum elsif params[:bin_width].is_a?(String) n, step = params[:bin_width].split.map(&:strip) n.to_i.send(step) end end def valid_duration?(d) case d when Hash d.all? { |step, n| step.to_sym.in?(STEPS) && n.is_a?(Fixnum) } when String d =~ DURATION_PATTERN else false end end def default_bin_width case domain when 0 then 1.day when 0..1.minute then 1.second when 0..2.hours then 1.minute when 0..2.days then 1.hour when 0..2.weeks then 1.day when 0..2.months then 1.week when 0..2.years then 1.month else 1.year end end class Bin < BinDimension::Bin def parse(value) Time.zone.parse(value.to_s.gsub('"', '')) end def cast(value) case Repor.database_type when :postgres "CAST(#{super} AS timestamp with time zone)" when :sqlite "DATETIME(#{super})" else "CAST(#{super} AS DATETIME)" end end end end end end