lib/groupdate/series_builder.rb in groupdate-5.2.4 vs lib/groupdate/series_builder.rb in groupdate-6.0.0

- old
+ new

@@ -1,11 +1,9 @@ module Groupdate class SeriesBuilder attr_reader :period, :time_zone, :day_start, :week_start, :n_seconds, :options - CHECK_PERIODS = [:day, :week, :month, :quarter, :year] - def initialize(period:, time_zone:, day_start:, week_start:, n_seconds:, **options) @period = period @time_zone = time_zone @week_start = week_start @day_start = day_start @@ -21,55 +19,26 @@ verified_data = {} series.each do |k| verified_data[k] = data.delete(k) end - # this is a fun one - # PostgreSQL and Ruby both return the 2nd hour when converting/parsing a backward DST change - # Other databases and Active Support return the 1st hour (as expected) - # Active Support good: ActiveSupport::TimeZone["America/Los_Angeles"].parse("2013-11-03 01:00:00") - # MySQL good: SELECT CONVERT_TZ('2013-11-03 01:00:00', 'America/Los_Angeles', 'Etc/UTC'); - # Ruby not good: Time.parse("2013-11-03 01:00:00") - # PostgreSQL not good: SELECT '2013-11-03 01:00:00'::timestamp AT TIME ZONE 'America/Los_Angeles'; - # we need to account for this here - if series_default && CHECK_PERIODS.include?(period) - data.each do |k, v| - key = multiple_groups ? k[group_index] : k - # TODO only do this for PostgreSQL - # this may mask some inconsistent time zone errors - # but not sure there's a better approach - if key.hour == (key - 1.hour).hour && series.include?(key - 1.hour) - key -= 1.hour - if multiple_groups - k[group_index] = key - else - k = key - end - verified_data[k] = v - elsif key != round_time(key) - # only need to show what database returned since it will cast in Ruby time zone - raise Groupdate::Error, "Database and Ruby have inconsistent time zone info. Database returned #{key}" - end - end - end - unless entire_series?(series_default) series = series.select { |k| verified_data[k] } end value = 0 - result = Hash[series.map do |k| + result = series.to_h do |k| value = verified_data[k] || (@options[:carry_forward] && value) || default_value key = if multiple_groups k[0...group_index] + [key_format.call(k[group_index])] + k[(group_index + 1)..-1] else key_format.call(k) end [key, value] - end] + end result end def round_time(time) @@ -79,11 +48,11 @@ time = time.to_time.in_time_zone(time_zone) if day_start != 0 # apply day_start to a time object that's not affected by DST - time = change_zone.call(time, utc) + time = time.change(zone: utc) time -= day_start.seconds end time = case period @@ -119,41 +88,26 @@ raise Groupdate::Error, "Invalid period" end if day_start != 0 && time.is_a?(Time) time += day_start.seconds - time = change_zone.call(time, time_zone) + time = time.change(zone: time_zone) end time end - def change_zone - @change_zone ||= begin - if ActiveSupport::VERSION::STRING >= "5.2" - ->(time, zone) { time.change(zone: zone) } - else - # TODO make more efficient - ->(time, zone) { zone.parse(time.strftime("%Y-%m-%d %H:%M:%S")) } - end - end - end - def time_range @time_range ||= begin time_range = options[:range] if time_range.is_a?(Range) # check types [time_range.begin, time_range.end].each do |v| case v when nil, Date, Time # good - when String - # TODO raise error in Groupdate 6 - warn "[groupdate] Range bounds should be Date or Time, not #{v.class.name}. This will raise an error in Groupdate 6" - break else raise ArgumentError, "Range bounds should be Date or Time, not #{v.class.name}" end end @@ -287,11 +241,10 @@ end def key_format @key_format ||= begin locale = options[:locale] || I18n.locale - use_dates = options.key?(:dates) ? options[:dates] : Groupdate.dates if options[:format] if options[:format].respond_to?(:call) options[:format] else @@ -310,10 +263,10 @@ key = Date.new(2014, key, 1).to_time end I18n.localize(key, format: options[:format], locale: locale) end end - elsif [:day, :week, :month, :quarter, :year].include?(period) && use_dates + elsif [:day, :week, :month, :quarter, :year].include?(period) lambda { |k| k.to_date } else lambda { |k| k } end end