groupdate2 4.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +182 -0
- data/CONTRIBUTING.md +75 -0
- data/LICENSE.txt +22 -0
- data/README.md +16 -0
- data/lib/groupdate/active_record.rb +6 -0
- data/lib/groupdate/enumerable.rb +31 -0
- data/lib/groupdate/magic.rb +164 -0
- data/lib/groupdate/query_methods.rb +25 -0
- data/lib/groupdate/relation.rb +16 -0
- data/lib/groupdate/relation_builder.rb +191 -0
- data/lib/groupdate/series_builder.rb +264 -0
- data/lib/groupdate/sql_server_group_clause.rb +92 -0
- data/lib/groupdate/version.rb +3 -0
- data/lib/groupdate/windows_zones.json +1 -0
- data/lib/groupdate2.rb +32 -0
- metadata +184 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
module Groupdate
|
2
|
+
module QueryMethods
|
3
|
+
Groupdate::PERIODS.each do |period|
|
4
|
+
define_method :"group_by_#{period}" do |field, time_zone = nil, range = nil, **options|
|
5
|
+
Groupdate::Magic::Relation.generate_relation(self,
|
6
|
+
period: period,
|
7
|
+
field: field,
|
8
|
+
time_zone: time_zone,
|
9
|
+
range: range,
|
10
|
+
**options
|
11
|
+
)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def group_by_period(period, field, permit: nil, **options)
|
16
|
+
# to_sym is unsafe on user input, so convert to strings
|
17
|
+
permitted_periods = ((permit || Groupdate::PERIODS).map(&:to_sym) & Groupdate::PERIODS).map(&:to_s)
|
18
|
+
if permitted_periods.include?(period.to_s)
|
19
|
+
send("group_by_#{period}", field, **options)
|
20
|
+
else
|
21
|
+
raise ArgumentError, "Unpermitted period"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
|
3
|
+
module Groupdate
|
4
|
+
module Relation
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
attr_accessor :groupdate_values
|
9
|
+
end
|
10
|
+
|
11
|
+
def calculate(*args, &block)
|
12
|
+
default_value = [:count, :sum].include?(args[0]) ? 0 : nil
|
13
|
+
Groupdate.process_result(self, super, default_value: default_value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,191 @@
|
|
1
|
+
require_relative 'sql_server_group_clause'
|
2
|
+
|
3
|
+
module Groupdate
|
4
|
+
class RelationBuilder
|
5
|
+
include SqlServerGroupClause
|
6
|
+
attr_reader :period, :column, :day_start, :week_start
|
7
|
+
|
8
|
+
def initialize(relation, column:, period:, time_zone:, time_range:, week_start:, day_start:)
|
9
|
+
@relation = relation
|
10
|
+
@column = resolve_column(relation, column)
|
11
|
+
@period = period
|
12
|
+
@time_zone = time_zone
|
13
|
+
@time_range = time_range
|
14
|
+
@week_start = week_start
|
15
|
+
@day_start = day_start
|
16
|
+
|
17
|
+
if relation.default_timezone == :local
|
18
|
+
raise Groupdate::Error, "ActiveRecord::Base.default_timezone must be :utc to use Groupdate"
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def generate
|
23
|
+
@relation.group(group_clause).where(*where_clause)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def group_clause
|
29
|
+
time_zone = @time_zone.tzinfo.name
|
30
|
+
adapter_name = @relation.connection.adapter_name
|
31
|
+
query =
|
32
|
+
case adapter_name
|
33
|
+
when "SQLServer"
|
34
|
+
sql_server_group_clause(time_zone)
|
35
|
+
when "MySQL", "Mysql2", "Mysql2Spatial", 'Mysql2Rgeo'
|
36
|
+
case period
|
37
|
+
when :day_of_week
|
38
|
+
["DAYOFWEEK(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?)) - 1", time_zone]
|
39
|
+
when :hour_of_day
|
40
|
+
["(EXTRACT(HOUR from CONVERT_TZ(#{column}, '+00:00', ?)) + 24 - #{day_start / 3600}) % 24", time_zone]
|
41
|
+
when :minute_of_hour
|
42
|
+
["(EXTRACT(MINUTE from CONVERT_TZ(#{column}, '+00:00', ?)))", time_zone]
|
43
|
+
when :day_of_month
|
44
|
+
["DAYOFMONTH(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?))", time_zone]
|
45
|
+
when :month_of_year
|
46
|
+
["MONTH(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?))", time_zone]
|
47
|
+
when :week
|
48
|
+
["CONVERT_TZ(DATE_FORMAT(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL ((#{7 - week_start} + WEEKDAY(CONVERT_TZ(#{column}, '+00:00', ?) - INTERVAL #{day_start} second)) % 7) DAY) - INTERVAL #{day_start} second, '+00:00', ?), '%Y-%m-%d 00:00:00') + INTERVAL #{day_start} second, ?, '+00:00')", time_zone, time_zone, time_zone]
|
49
|
+
when :quarter
|
50
|
+
["DATE_ADD(CONVERT_TZ(DATE_FORMAT(DATE(CONCAT(EXTRACT(YEAR FROM CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?)), '-', LPAD(1 + 3 * (QUARTER(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?)) - 1), 2, '00'), '-01')), '%Y-%m-%d %H:%i:%S'), ?, '+00:00'), INTERVAL #{day_start} second)", time_zone, time_zone, time_zone]
|
51
|
+
else
|
52
|
+
format =
|
53
|
+
case period
|
54
|
+
when :second
|
55
|
+
"%Y-%m-%d %H:%i:%S"
|
56
|
+
when :minute
|
57
|
+
"%Y-%m-%d %H:%i:00"
|
58
|
+
when :hour
|
59
|
+
"%Y-%m-%d %H:00:00"
|
60
|
+
when :day
|
61
|
+
"%Y-%m-%d 00:00:00"
|
62
|
+
when :month
|
63
|
+
"%Y-%m-01 00:00:00"
|
64
|
+
else # year
|
65
|
+
"%Y-01-01 00:00:00"
|
66
|
+
end
|
67
|
+
|
68
|
+
["DATE_ADD(CONVERT_TZ(DATE_FORMAT(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?), '#{format}'), ?, '+00:00'), INTERVAL #{day_start} second)", time_zone, time_zone]
|
69
|
+
end
|
70
|
+
when "PostgreSQL", "PostGIS"
|
71
|
+
case period
|
72
|
+
when :day_of_week
|
73
|
+
["EXTRACT(DOW from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
|
74
|
+
when :hour_of_day
|
75
|
+
["EXTRACT(HOUR from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
|
76
|
+
when :minute_of_hour
|
77
|
+
["EXTRACT(MINUTE from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
|
78
|
+
when :day_of_month
|
79
|
+
["EXTRACT(DAY from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
|
80
|
+
when :month_of_year
|
81
|
+
["EXTRACT(MONTH from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
|
82
|
+
when :week # start on Sunday, not PostgreSQL default Monday
|
83
|
+
["(DATE_TRUNC('#{period}', (#{column}::timestamptz - INTERVAL '#{week_start} day' - INTERVAL '#{day_start} second') AT TIME ZONE ?) + INTERVAL '#{week_start} day' + INTERVAL '#{day_start} second') AT TIME ZONE ?", time_zone, time_zone]
|
84
|
+
else
|
85
|
+
["(DATE_TRUNC('#{period}', (#{column}::timestamptz - INTERVAL '#{day_start} second') AT TIME ZONE ?) + INTERVAL '#{day_start} second') AT TIME ZONE ?", time_zone, time_zone]
|
86
|
+
end
|
87
|
+
when "SQLite"
|
88
|
+
raise Groupdate::Error, "Time zones not supported for SQLite" unless @time_zone.utc_offset.zero?
|
89
|
+
raise Groupdate::Error, "day_start not supported for SQLite" unless day_start.zero?
|
90
|
+
raise Groupdate::Error, "week_start not supported for SQLite" unless week_start == 6
|
91
|
+
|
92
|
+
if period == :week
|
93
|
+
["strftime('%%Y-%%m-%%d 00:00:00 UTC', #{column}, '-6 days', 'weekday 0')"]
|
94
|
+
else
|
95
|
+
format =
|
96
|
+
case period
|
97
|
+
when :hour_of_day
|
98
|
+
"%H"
|
99
|
+
when :minute_of_hour
|
100
|
+
"%M"
|
101
|
+
when :day_of_week
|
102
|
+
"%w"
|
103
|
+
when :day_of_month
|
104
|
+
"%d"
|
105
|
+
when :month_of_year
|
106
|
+
"%m"
|
107
|
+
when :second
|
108
|
+
"%Y-%m-%d %H:%M:%S UTC"
|
109
|
+
when :minute
|
110
|
+
"%Y-%m-%d %H:%M:00 UTC"
|
111
|
+
when :hour
|
112
|
+
"%Y-%m-%d %H:00:00 UTC"
|
113
|
+
when :day
|
114
|
+
"%Y-%m-%d 00:00:00 UTC"
|
115
|
+
when :month
|
116
|
+
"%Y-%m-01 00:00:00 UTC"
|
117
|
+
when :quarter
|
118
|
+
raise Groupdate::Error, "Quarter not supported for SQLite"
|
119
|
+
else # year
|
120
|
+
"%Y-01-01 00:00:00 UTC"
|
121
|
+
end
|
122
|
+
|
123
|
+
["strftime('#{format.gsub(/%/, '%%')}', #{column})"]
|
124
|
+
end
|
125
|
+
when "Redshift"
|
126
|
+
case period
|
127
|
+
when :day_of_week
|
128
|
+
["EXTRACT(DOW from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
|
129
|
+
when :hour_of_day
|
130
|
+
["EXTRACT(HOUR from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
|
131
|
+
when :minute_of_hour
|
132
|
+
["EXTRACT(MINUTE from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
|
133
|
+
when :day_of_month
|
134
|
+
["EXTRACT(DAY from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
|
135
|
+
when :month_of_year
|
136
|
+
["EXTRACT(MONTH from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
|
137
|
+
when :week # start on Sunday, not Redshift default Monday
|
138
|
+
# Redshift does not return timezone information; it
|
139
|
+
# always says it is in UTC time, so we must convert
|
140
|
+
# back to UTC to play properly with the rest of Groupdate.
|
141
|
+
#
|
142
|
+
["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC(?, CONVERT_TIMEZONE(?, #{column}) - INTERVAL '#{week_start} day' - INTERVAL '#{day_start} second'))::timestamp + INTERVAL '#{week_start} day' + INTERVAL '#{day_start} second'", time_zone, period, time_zone]
|
143
|
+
else
|
144
|
+
["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC(?, CONVERT_TIMEZONE(?, #{column}) - INTERVAL '#{day_start} second'))::timestamp + INTERVAL '#{day_start} second'", time_zone, period, time_zone]
|
145
|
+
end
|
146
|
+
else
|
147
|
+
raise Groupdate::Error, "Connection adapter not supported: #{adapter_name}"
|
148
|
+
end
|
149
|
+
|
150
|
+
if adapter_name == "MySQL" && period == :week
|
151
|
+
query[0] = "CAST(#{query[0]} AS DATETIME)"
|
152
|
+
end
|
153
|
+
|
154
|
+
clause = @relation.send(:sanitize_sql_array, query)
|
155
|
+
|
156
|
+
# cleaner queries in logs
|
157
|
+
clause = clean_group_clause_postgresql(clause)
|
158
|
+
clean_group_clause_mysql(clause)
|
159
|
+
end
|
160
|
+
|
161
|
+
def clean_group_clause_postgresql(clause)
|
162
|
+
clause.gsub(/ (\-|\+) INTERVAL '0 second'/, "")
|
163
|
+
end
|
164
|
+
|
165
|
+
def clean_group_clause_mysql(clause)
|
166
|
+
clause = clause.gsub("DATE_SUB(#{column}, INTERVAL 0 second)", "#{column}")
|
167
|
+
if clause.start_with?("DATE_ADD(") && clause.end_with?(", INTERVAL 0 second)")
|
168
|
+
clause = clause[9..-21]
|
169
|
+
end
|
170
|
+
clause
|
171
|
+
end
|
172
|
+
|
173
|
+
def where_clause
|
174
|
+
if @time_range.is_a?(Range)
|
175
|
+
op = @time_range.exclude_end? ? "<" : "<="
|
176
|
+
["#{column} >= ? AND #{column} #{op} ?", @time_range.first, @time_range.last]
|
177
|
+
else
|
178
|
+
["#{column} IS NOT NULL"]
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
# resolves eagerly
|
183
|
+
# need to convert both where_clause (easy)
|
184
|
+
# and group_clause (not easy) if want to avoid this
|
185
|
+
def resolve_column(relation, column)
|
186
|
+
node = relation.send(:relation).send(:arel_columns, [column]).first
|
187
|
+
node = Arel::Nodes::SqlLiteral.new(node) if node.is_a?(String)
|
188
|
+
relation.connection.visitor.accept(node, Arel::Collectors::SQLString.new).value
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
@@ -0,0 +1,264 @@
|
|
1
|
+
module Groupdate
|
2
|
+
class SeriesBuilder
|
3
|
+
attr_reader :period, :time_zone, :day_start, :week_start, :options
|
4
|
+
|
5
|
+
CHECK_PERIODS = [:day, :week, :month, :quarter, :year]
|
6
|
+
|
7
|
+
def initialize(period:, time_zone:, day_start:, week_start:, **options)
|
8
|
+
@period = period
|
9
|
+
@time_zone = time_zone
|
10
|
+
@week_start = week_start
|
11
|
+
@day_start = day_start
|
12
|
+
@options = options
|
13
|
+
@round_time = {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def generate(data, default_value:, series_default: true, multiple_groups: false, group_index: nil)
|
17
|
+
series = generate_series(data, multiple_groups, group_index)
|
18
|
+
series = handle_multiple(data, series, multiple_groups, group_index)
|
19
|
+
|
20
|
+
unless entire_series?(series_default)
|
21
|
+
series = series.select { |k| data[k] }
|
22
|
+
end
|
23
|
+
|
24
|
+
value = 0
|
25
|
+
result = Hash[series.map do |k|
|
26
|
+
value = data.delete(k) || (@options[:carry_forward] && value) || default_value
|
27
|
+
key =
|
28
|
+
if multiple_groups
|
29
|
+
k[0...group_index] + [key_format.call(k[group_index])] + k[(group_index + 1)..-1]
|
30
|
+
else
|
31
|
+
key_format.call(k)
|
32
|
+
end
|
33
|
+
|
34
|
+
[key, value]
|
35
|
+
end]
|
36
|
+
|
37
|
+
# only check for database
|
38
|
+
# only checks remaining keys to avoid expensive calls to round_time
|
39
|
+
if series_default && CHECK_PERIODS.include?(period)
|
40
|
+
check_consistent_time_zone_info(data, multiple_groups, group_index)
|
41
|
+
end
|
42
|
+
|
43
|
+
result
|
44
|
+
end
|
45
|
+
|
46
|
+
def round_time(time)
|
47
|
+
time = time.to_time.in_time_zone(time_zone)
|
48
|
+
|
49
|
+
# only if day_start != 0 for performance
|
50
|
+
time -= day_start.seconds if day_start != 0
|
51
|
+
|
52
|
+
time =
|
53
|
+
case period
|
54
|
+
when :second
|
55
|
+
time.change(usec: 0)
|
56
|
+
when :minute
|
57
|
+
time.change(sec: 0)
|
58
|
+
when :hour
|
59
|
+
time.change(min: 0)
|
60
|
+
when :day
|
61
|
+
time.beginning_of_day
|
62
|
+
when :week
|
63
|
+
# same logic as MySQL group
|
64
|
+
weekday = (time.wday - 1) % 7
|
65
|
+
(time - ((7 - week_start + weekday) % 7).days).midnight
|
66
|
+
when :month
|
67
|
+
time.beginning_of_month
|
68
|
+
when :quarter
|
69
|
+
time.beginning_of_quarter
|
70
|
+
when :year
|
71
|
+
time.beginning_of_year
|
72
|
+
when :hour_of_day
|
73
|
+
time.hour
|
74
|
+
when :minute_of_hour
|
75
|
+
time.min
|
76
|
+
when :day_of_week
|
77
|
+
(time.wday - 1 - week_start) % 7
|
78
|
+
when :day_of_month
|
79
|
+
time.day
|
80
|
+
when :month_of_year
|
81
|
+
time.month
|
82
|
+
else
|
83
|
+
raise Groupdate::Error, "Invalid period"
|
84
|
+
end
|
85
|
+
|
86
|
+
# only if day_start != 0 for performance
|
87
|
+
time += day_start.seconds if day_start != 0 && time.is_a?(Time)
|
88
|
+
|
89
|
+
time
|
90
|
+
end
|
91
|
+
|
92
|
+
def time_range
|
93
|
+
@time_range ||= begin
|
94
|
+
time_range = options[:range]
|
95
|
+
if time_range.is_a?(Range) && time_range.first.is_a?(Date)
|
96
|
+
# convert range of dates to range of times
|
97
|
+
# use parsing instead of in_time_zone due to Rails < 4
|
98
|
+
last = time_zone.parse(time_range.last.to_s)
|
99
|
+
last += 1.day unless time_range.exclude_end?
|
100
|
+
time_range = Range.new(time_zone.parse(time_range.first.to_s), last, true)
|
101
|
+
elsif !time_range && options[:last]
|
102
|
+
if period == :quarter
|
103
|
+
step = 3.months
|
104
|
+
elsif 1.respond_to?(period)
|
105
|
+
step = 1.send(period)
|
106
|
+
else
|
107
|
+
raise ArgumentError, "Cannot use last option with #{period}"
|
108
|
+
end
|
109
|
+
if step
|
110
|
+
# loop instead of multiply to change start_at - see #151
|
111
|
+
start_at = now
|
112
|
+
(options[:last].to_i - 1).times do
|
113
|
+
start_at -= step
|
114
|
+
end
|
115
|
+
|
116
|
+
time_range =
|
117
|
+
if options[:current] == false
|
118
|
+
round_time(start_at - step)...round_time(now)
|
119
|
+
else
|
120
|
+
round_time(start_at)..now
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
time_range
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def now
|
131
|
+
@now ||= time_zone.now
|
132
|
+
end
|
133
|
+
|
134
|
+
def generate_series(data, multiple_groups, group_index)
|
135
|
+
case period
|
136
|
+
when :day_of_week
|
137
|
+
0..6
|
138
|
+
when :hour_of_day
|
139
|
+
0..23
|
140
|
+
when :minute_of_hour
|
141
|
+
0..59
|
142
|
+
when :day_of_month
|
143
|
+
1..31
|
144
|
+
when :month_of_year
|
145
|
+
1..12
|
146
|
+
else
|
147
|
+
time_range = self.time_range
|
148
|
+
time_range =
|
149
|
+
if time_range.is_a?(Range)
|
150
|
+
time_range
|
151
|
+
else
|
152
|
+
# use first and last values
|
153
|
+
sorted_keys =
|
154
|
+
if multiple_groups
|
155
|
+
data.keys.map { |k| k[group_index] }.sort
|
156
|
+
else
|
157
|
+
data.keys.sort
|
158
|
+
end
|
159
|
+
|
160
|
+
tr = sorted_keys.first..sorted_keys.last
|
161
|
+
if options[:current] == false && sorted_keys.any? && round_time(now) >= tr.last
|
162
|
+
tr = tr.first...round_time(now)
|
163
|
+
end
|
164
|
+
tr
|
165
|
+
end
|
166
|
+
|
167
|
+
if time_range.first
|
168
|
+
series = [round_time(time_range.first)]
|
169
|
+
|
170
|
+
if period == :quarter
|
171
|
+
step = 3.months
|
172
|
+
else
|
173
|
+
step = 1.send(period)
|
174
|
+
end
|
175
|
+
|
176
|
+
last_step = series.last
|
177
|
+
loop do
|
178
|
+
next_step = last_step + step
|
179
|
+
next_step = round_time(next_step) if next_step.hour != day_start # add condition to speed up
|
180
|
+
break unless time_range.cover?(next_step)
|
181
|
+
|
182
|
+
if next_step == last_step
|
183
|
+
last_step += step
|
184
|
+
next
|
185
|
+
end
|
186
|
+
series << next_step
|
187
|
+
last_step = next_step
|
188
|
+
end
|
189
|
+
|
190
|
+
series
|
191
|
+
else
|
192
|
+
[]
|
193
|
+
end
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
def key_format
|
198
|
+
locale = options[:locale] || I18n.locale
|
199
|
+
use_dates = options.key?(:dates) ? options[:dates] : Groupdate.dates
|
200
|
+
|
201
|
+
if options[:format]
|
202
|
+
if options[:format].respond_to?(:call)
|
203
|
+
options[:format]
|
204
|
+
else
|
205
|
+
sunday = time_zone.parse("2014-03-02 00:00:00")
|
206
|
+
lambda do |key|
|
207
|
+
case period
|
208
|
+
when :hour_of_day
|
209
|
+
key = sunday + key.hours + day_start.seconds
|
210
|
+
when :minute_of_hour
|
211
|
+
key = sunday + key.minutes + day_start.seconds
|
212
|
+
when :day_of_week
|
213
|
+
key = sunday + key.days + (week_start + 1).days
|
214
|
+
when :day_of_month
|
215
|
+
key = Date.new(2014, 1, key).to_time
|
216
|
+
when :month_of_year
|
217
|
+
key = Date.new(2014, key, 1).to_time
|
218
|
+
end
|
219
|
+
I18n.localize(key, format: options[:format], locale: locale)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
elsif [:day, :week, :month, :quarter, :year].include?(period) && use_dates
|
223
|
+
lambda { |k| k.to_date }
|
224
|
+
else
|
225
|
+
lambda { |k| k }
|
226
|
+
end
|
227
|
+
end
|
228
|
+
|
229
|
+
def handle_multiple(data, series, multiple_groups, group_index)
|
230
|
+
reverse = options[:reverse]
|
231
|
+
|
232
|
+
if multiple_groups
|
233
|
+
keys = data.keys.map { |k| k[0...group_index] + k[(group_index + 1)..-1] }.uniq
|
234
|
+
series = series.to_a.reverse if reverse
|
235
|
+
keys.flat_map do |k|
|
236
|
+
series.map { |s| k[0...group_index] + [s] + k[group_index..-1] }
|
237
|
+
end
|
238
|
+
elsif reverse
|
239
|
+
series.to_a.reverse
|
240
|
+
else
|
241
|
+
series
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
def check_consistent_time_zone_info(data, multiple_groups, group_index)
|
246
|
+
keys = data.keys
|
247
|
+
if multiple_groups
|
248
|
+
keys.map! { |k| k[group_index] }
|
249
|
+
keys.uniq!
|
250
|
+
end
|
251
|
+
|
252
|
+
keys.each do |key|
|
253
|
+
if key != round_time(key)
|
254
|
+
# only need to show what database returned since it will cast in Ruby time zone
|
255
|
+
raise Groupdate::Error, "Database and Ruby have inconsistent time zone info. Database returned #{key} != #{round_time(key)}"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
end
|
259
|
+
|
260
|
+
def entire_series?(series_default)
|
261
|
+
options.key?(:series) ? options[:series] : series_default
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|