lib/groupdate/magic.rb in groupdate2-4.1.5 vs lib/groupdate/magic.rb in groupdate2-5.0.0

- old
+ new

@@ -1,21 +1,45 @@ require "i18n" module Groupdate class Magic + DAYS = [:monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday] + attr_accessor :period, :options, :group_index def initialize(period:, **options) @period = period @options = options - unknown_keywords = options.keys - [:day_start, :time_zone, :dates, :series, :week_start, :format, :locale, :range, :reverse] + validate_keywords + validate_arguments + end + + def validate_keywords + known_keywords = [:time_zone, :dates, :series, :format, :locale, :range, :reverse, :series_label] + + if %i[week day_of_week].include?(period) + known_keywords << :week_start + end + + if %i[day week month quarter year day_of_week hour_of_day day_of_month day_of_year month_of_year].include?(period) + known_keywords << :day_start + else + # prevent Groupdate.day_start from applying + @day_start = 0 + end + + unknown_keywords = options.keys - known_keywords raise ArgumentError, "unknown keywords: #{unknown_keywords.join(", ")}" if unknown_keywords.any? + end - raise Groupdate::Error, "Unrecognized time zone" unless time_zone - raise Groupdate::Error, "Unrecognized :week_start option" if period == :week && !week_start - raise Groupdate::Error, "Cannot use endless range for :range option" if options[:range].is_a?(Range) && !options[:range].end + def validate_arguments + # TODO better messages + raise ArgumentError, "Unrecognized time zone" unless time_zone + raise ArgumentError, "Unrecognized :week_start option" unless week_start + raise ArgumentError, "Cannot use endless range for :range option" if options[:range].is_a?(Range) && !options[:range].end + raise ArgumentError, ":day_start must be between 0 and 24" if (day_start / 3600) < 0 || (day_start / 3600) >= 24 end def time_zone @time_zone ||= begin time_zone = "Etc/UTC" if options[:time_zone] == false @@ -23,17 +47,24 @@ time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone[time_zone] end end def week_start - @week_start ||= [:mon, :tue, :wed, :thu, :fri, :sat, :sun].index((options[:week_start] || options[:start] || Groupdate.week_start).to_sym) + @week_start ||= begin + v = (options[:week_start] || Groupdate.week_start).to_sym + DAYS.index(v) || [:mon, :tue, :wed, :thu, :fri, :sat, :sun].index(v) + end end def day_start @day_start ||= ((options[:day_start] || Groupdate.day_start).to_f * 3600).round end + def series_label + @series_label ||= (options[:series_label].present? ? options[:series_label] : nil) + end + def series_builder @series_builder ||= SeriesBuilder.new( **options, period: period, @@ -45,10 +76,15 @@ def time_range series_builder.time_range end + def self.validate_period(period, permit) + permitted_periods = ((permit || Groupdate::PERIODS).map(&:to_sym) & Groupdate::PERIODS).map(&:to_s) + raise ArgumentError, "Unpermitted period" unless permitted_periods.include?(period.to_s) + end + class Enumerable < Magic def group_by(enum, &_block) group = enum.group_by do |v| v = yield(v) raise ArgumentError, "Not a time" unless v.respond_to?(:to_time) @@ -83,14 +119,14 @@ end def cast_method @cast_method ||= begin case period + when :minute_of_hour, :hour_of_day, :day_of_month, :day_of_year, :month_of_year + lambda { |k| k.to_i } when :day_of_week lambda { |k| (k.to_i - 1 - week_start) % 7 } - when :hour_of_day, :day_of_month, :month_of_year, :minute_of_hour - lambda { |k| k.to_i } else utc = ActiveSupport::TimeZone["UTC"] lambda { |k| (k.is_a?(String) || !k.respond_to?(:to_time) ? utc.parse(k.to_s) : k.to_time).in_time_zone(time_zone) } end end @@ -128,10 +164,24 @@ raise Groupdate::Error, "Database missing time zone support for #{time_zone.tzinfo.name} - see https://github.com/ankane/groupdate#for-mysql" end end end + def perform_series_label(relation, result) + label = options[:series_label] + return result unless label.present? + + result.map do |r| + r.send("#{label}=", cast_series_label(r.send(label))) + r + end + end + + def cast_series_label(original_label) + series_builder.format_series_label(cast_method.call(original_label)) + end + def self.generate_relation(relation, field:, **options) magic = Groupdate::Magic::Relation.new(**options) # generate ActiveRecord relation relation = @@ -140,11 +190,12 @@ column: field, period: magic.period, time_zone: magic.time_zone, time_range: magic.time_range, week_start: magic.week_start, - day_start: magic.day_start + day_start: magic.day_start, + series_label: magic.series_label ).generate # add Groupdate info magic.group_index = relation.group_values.size - 1 (relation.groupdate_values ||= []) << magic @@ -154,9 +205,16 @@ # allow any options to keep flexible for future def self.process_result(relation, result, **options) relation.groupdate_values.reverse.each do |gv| result = gv.perform(relation, result, default_value: options[:default_value]) + end + result + end + + def self.process_series_label(relation, result) + relation.groupdate_values.reverse.each do |gv| + result = gv.perform_series_label(relation, result) end result end end end