groupdate2 4.1.5 → 5.0.0

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.
@@ -1,25 +1,18 @@
1
1
  module Groupdate
2
2
  module QueryMethods
3
3
  Groupdate::PERIODS.each do |period|
4
- define_method :"group_by_#{period}" do |field, time_zone = nil, range = nil, **options|
4
+ define_method :"group_by_#{period}" do |field, **options|
5
5
  Groupdate::Magic::Relation.generate_relation(self,
6
6
  period: period,
7
7
  field: field,
8
- time_zone: time_zone,
9
- range: range,
10
8
  **options
11
9
  )
12
10
  end
13
11
  end
14
12
 
15
13
  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
14
+ Groupdate::Magic.validate_period(period, permit)
15
+ send("group_by_#{period}", field, **options)
23
16
  end
24
17
  end
25
18
  end
@@ -13,4 +13,11 @@ module Groupdate
13
13
  Groupdate.process_result(self, super, default_value: default_value)
14
14
  end
15
15
  end
16
+
17
+ module RelationRecords
18
+ def records
19
+ super
20
+ Groupdate.process_series_label(self, @records)
21
+ end
22
+ end
16
23
  end
@@ -3,9 +3,9 @@ require_relative 'sql_server_group_clause'
3
3
  module Groupdate
4
4
  class RelationBuilder
5
5
  include SqlServerGroupClause
6
- attr_reader :period, :column, :day_start, :week_start
6
+ attr_reader :period, :column, :day_start, :week_start, :series_label
7
7
 
8
- def initialize(relation, column:, period:, time_zone:, time_range:, week_start:, day_start:)
8
+ def initialize(relation, column:, period:, time_zone:, time_range:, week_start:, day_start:, series_label:)
9
9
  @relation = relation
10
10
  @column = resolve_column(relation, column)
11
11
  @period = period
@@ -13,6 +13,7 @@ module Groupdate
13
13
  @time_range = time_range
14
14
  @week_start = week_start
15
15
  @day_start = day_start
16
+ @series_label = series_label
16
17
 
17
18
  if relation.default_timezone == :local
18
19
  raise Groupdate::Error, "ActiveRecord::Base.default_timezone must be :utc to use Groupdate"
@@ -20,7 +21,10 @@ module Groupdate
20
21
  end
21
22
 
22
23
  def generate
23
- @relation.group(group_clause).where(*where_clause)
24
+ group_by = group_clause
25
+ @relation = @relation.group(group_by).where(*where_clause)
26
+ @relation.select_values += ["#{group_by} AS #{series_label}"] if series_label
27
+ @relation
24
28
  end
25
29
 
26
30
  private
@@ -32,22 +36,26 @@ module Groupdate
32
36
  case adapter_name
33
37
  when "SQLServer"
34
38
  sql_server_group_clause(time_zone)
35
- when "MySQL", "Mysql2", "Mysql2Spatial", 'Mysql2Rgeo'
39
+ when "Mysql2", "Mysql2Spatial", "Mysql2Rgeo"
40
+ day_start_column = "CONVERT_TZ(#{column}, '+00:00', ?) - INTERVAL ? second"
41
+
36
42
  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
43
  when :minute_of_hour
42
- ["(EXTRACT(MINUTE from CONVERT_TZ(#{column}, '+00:00', ?)))", time_zone]
44
+ ["MINUTE(#{day_start_column})", time_zone, day_start]
45
+ when :hour_of_day
46
+ ["HOUR(#{day_start_column})", time_zone, day_start]
47
+ when :day_of_week
48
+ ["DAYOFWEEK(#{day_start_column}) - 1", time_zone, day_start]
43
49
  when :day_of_month
44
- ["DAYOFMONTH(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?))", time_zone]
50
+ ["DAYOFMONTH(#{day_start_column})", time_zone, day_start]
51
+ when :day_of_year
52
+ ["DAYOFYEAR(#{day_start_column})", time_zone, day_start]
45
53
  when :month_of_year
46
- ["MONTH(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?))", time_zone]
54
+ ["MONTH(#{day_start_column})", time_zone, day_start]
47
55
  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]
56
+ ["CONVERT_TZ(DATE_FORMAT(#{day_start_column} - INTERVAL ((? + DAYOFWEEK(#{day_start_column})) % 7) DAY, '%Y-%m-%d 00:00:00') + INTERVAL ? second, ?, '+00:00')", time_zone, day_start, 12 - week_start, time_zone, day_start, day_start, time_zone]
49
57
  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]
58
+ ["CONVERT_TZ(DATE_FORMAT(DATE(CONCAT(YEAR(#{day_start_column}), '-', LPAD(1 + 3 * (QUARTER(#{day_start_column}) - 1), 2, '00'), '-01')), '%Y-%m-%d %H:%i:%S') + INTERVAL ? second, ?, '+00:00')", time_zone, day_start, time_zone, day_start, day_start, time_zone]
51
59
  else
52
60
  format =
53
61
  case period
@@ -65,43 +73,54 @@ module Groupdate
65
73
  "%Y-01-01 00:00:00"
66
74
  end
67
75
 
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]
76
+ ["CONVERT_TZ(DATE_FORMAT(#{day_start_column}, ?) + INTERVAL ? second, ?, '+00:00')", time_zone, day_start, format, day_start, time_zone]
69
77
  end
70
78
  when "PostgreSQL", "PostGIS"
79
+ day_start_column = "#{column}::timestamptz AT TIME ZONE ? - INTERVAL ?"
80
+ day_start_interval = "#{day_start} second"
81
+
71
82
  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
83
  when :minute_of_hour
77
- ["EXTRACT(MINUTE from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
84
+ ["EXTRACT(MINUTE FROM #{day_start_column})::integer", time_zone, day_start_interval]
85
+ when :hour_of_day
86
+ ["EXTRACT(HOUR FROM #{day_start_column})::integer", time_zone, day_start_interval]
87
+ when :day_of_week
88
+ ["EXTRACT(DOW FROM #{day_start_column})::integer", time_zone, day_start_interval]
78
89
  when :day_of_month
79
- ["EXTRACT(DAY from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
90
+ ["EXTRACT(DAY FROM #{day_start_column})::integer", time_zone, day_start_interval]
91
+ when :day_of_year
92
+ ["EXTRACT(DOY FROM #{day_start_column})::integer", time_zone, day_start_interval]
80
93
  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]
94
+ ["EXTRACT(MONTH FROM #{day_start_column})::integer", time_zone, day_start_interval]
95
+ when :week
96
+ ["(DATE_TRUNC('day', #{day_start_column} - INTERVAL '1 day' * ((? + EXTRACT(DOW FROM #{day_start_column})::integer) % 7)) + INTERVAL ?) AT TIME ZONE ?", time_zone, day_start_interval, 13 - week_start, time_zone, day_start_interval, day_start_interval, time_zone]
84
97
  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]
98
+ if day_start == 0
99
+ # prettier
100
+ ["DATE_TRUNC(?, #{day_start_column}) AT TIME ZONE ?", period, time_zone, day_start_interval, time_zone]
101
+ else
102
+ ["(DATE_TRUNC(?, #{day_start_column}) + INTERVAL ?) AT TIME ZONE ?", period, time_zone, day_start_interval, day_start_interval, time_zone]
103
+ end
86
104
  end
87
105
  when "SQLite"
88
106
  raise Groupdate::Error, "Time zones not supported for SQLite" unless @time_zone.utc_offset.zero?
89
107
  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
108
 
92
109
  if period == :week
93
- ["strftime('%%Y-%%m-%%d 00:00:00 UTC', #{column}, '-6 days', 'weekday 0')"]
110
+ ["strftime('%Y-%m-%d 00:00:00 UTC', #{column}, '-6 days', ?)", "weekday #{(week_start + 1) % 7}"]
94
111
  else
95
112
  format =
96
113
  case period
97
- when :hour_of_day
98
- "%H"
99
114
  when :minute_of_hour
100
115
  "%M"
116
+ when :hour_of_day
117
+ "%H"
101
118
  when :day_of_week
102
119
  "%w"
103
120
  when :day_of_month
104
121
  "%d"
122
+ when :day_of_year
123
+ "%j"
105
124
  when :month_of_year
106
125
  "%m"
107
126
  when :second
@@ -120,37 +139,38 @@ module Groupdate
120
139
  "%Y-01-01 00:00:00 UTC"
121
140
  end
122
141
 
123
- ["strftime('#{format.gsub(/%/, '%%')}', #{column})"]
142
+ ["strftime(?, #{column})", format]
124
143
  end
125
144
  when "Redshift"
145
+ day_start_column = "CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL ?"
146
+ day_start_interval = "#{day_start} second"
147
+
126
148
  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
149
  when :minute_of_hour
132
- ["EXTRACT(MINUTE from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
150
+ ["EXTRACT(MINUTE from #{day_start_column})::integer", time_zone, day_start_interval]
151
+ when :hour_of_day
152
+ ["EXTRACT(HOUR from #{day_start_column})::integer", time_zone, day_start_interval]
153
+ when :day_of_week
154
+ ["EXTRACT(DOW from #{day_start_column})::integer", time_zone, day_start_interval]
133
155
  when :day_of_month
134
- ["EXTRACT(DAY from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
156
+ ["EXTRACT(DAY from #{day_start_column})::integer", time_zone, day_start_interval]
157
+ when :day_of_year
158
+ ["EXTRACT(DOY from #{day_start_column})::integer", time_zone, day_start_interval]
135
159
  when :month_of_year
136
- ["EXTRACT(MONTH from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
160
+ ["EXTRACT(MONTH from #{day_start_column})::integer", time_zone, day_start_interval]
137
161
  when :week # start on Sunday, not Redshift default Monday
138
162
  # Redshift does not return timezone information; it
139
163
  # always says it is in UTC time, so we must convert
140
164
  # 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]
165
+ week_start_interval = "#{week_start} day"
166
+ ["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC('week', #{day_start_column} - INTERVAL ?) + INTERVAL ? + INTERVAL ?)::timestamp", time_zone, time_zone, day_start_interval, week_start_interval, week_start_interval, day_start_interval]
143
167
  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]
168
+ ["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC(?, #{day_start_column}) + INTERVAL ?)::timestamp", time_zone, period, time_zone, day_start_interval, day_start_interval]
145
169
  end
146
170
  else
147
171
  raise Groupdate::Error, "Connection adapter not supported: #{adapter_name}"
148
172
  end
149
173
 
150
- if adapter_name == "MySQL" && period == :week
151
- query[0] = "CAST(#{query[0]} AS DATETIME)"
152
- end
153
-
154
174
  clause = @relation.send(:sanitize_sql_array, query)
155
175
 
156
176
  # cleaner queries in logs
@@ -163,11 +183,7 @@ module Groupdate
163
183
  end
164
184
 
165
185
  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
186
+ clause.gsub(/ (\-|\+) INTERVAL 0 second/, "")
171
187
  end
172
188
 
173
189
  def where_clause
@@ -11,19 +11,55 @@ module Groupdate
11
11
  @day_start = day_start
12
12
  @options = options
13
13
  @round_time = {}
14
+ @week_start_key = Groupdate::Magic::DAYS[@week_start] if @week_start
14
15
  end
15
16
 
16
17
  def generate(data, default_value:, series_default: true, multiple_groups: false, group_index: nil)
17
18
  series = generate_series(data, multiple_groups, group_index)
18
19
  series = handle_multiple(data, series, multiple_groups, group_index)
19
20
 
21
+ verified_data = {}
22
+ series.each do |k|
23
+ verified_data[k] = data.delete(k)
24
+ end
25
+
26
+ # this is a fun one
27
+ # PostgreSQL and Ruby both return the 2nd hour when converting/parsing a backward DST change
28
+ # Other databases and Active Support return the 1st hour (as expected)
29
+ # Active Support good: ActiveSupport::TimeZone["America/Los_Angeles"].parse("2013-11-03 01:00:00")
30
+ # MySQL good: SELECT CONVERT_TZ('2013-11-03 01:00:00', 'America/Los_Angeles', 'Etc/UTC');
31
+ # SQLServer good: SELECT CAST('2013-11-03 01:00:00' AS DATETIME2(3)) AT TIME ZONE 'Pacific Standard Time' AT TIME ZONE 'UTC'
32
+ # Ruby not good: Time.parse("2013-11-03 01:00:00")
33
+ # PostgreSQL not good: SELECT '2013-11-03 01:00:00'::timestamp AT TIME ZONE 'America/Los_Angeles';
34
+ # we need to account for this here
35
+ if series_default && CHECK_PERIODS.include?(period)
36
+ data.each do |k, v|
37
+ key = multiple_groups ? k[group_index] : k
38
+ # TODO only do this for PostgreSQL
39
+ # this may mask some inconsistent time zone errors
40
+ # but not sure there's a better approach
41
+ if key.hour == (key - 1.hour).hour && series.include?(key - 1.hour)
42
+ key -= 1.hour
43
+ if multiple_groups
44
+ k[group_index] = key
45
+ else
46
+ k = key
47
+ end
48
+ verified_data[k] = v
49
+ elsif key != round_time(key)
50
+ # only need to show what database returned since it will cast in Ruby time zone
51
+ # raise Groupdate::Error, "Database and Ruby have inconsistent time zone info. Database returned #{key}: #{round_time(key)}"
52
+ end
53
+ end
54
+ end
55
+
20
56
  unless entire_series?(series_default)
21
- series = series.select { |k| data[k] }
57
+ series = series.select { |k| verified_data[k] }
22
58
  end
23
59
 
24
60
  value = 0
25
61
  result = Hash[series.map do |k|
26
- value = data.delete(k) || (@options[:carry_forward] && value) || default_value
62
+ value = verified_data[k] || (@options[:carry_forward] && value) || default_value
27
63
  key =
28
64
  if multiple_groups
29
65
  k[0...group_index] + [key_format.call(k[group_index])] + k[(group_index + 1)..-1]
@@ -34,20 +70,22 @@ module Groupdate
34
70
  [key, value]
35
71
  end]
36
72
 
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
73
  result
44
74
  end
45
75
 
76
+ def format_series_label(series_label)
77
+ series_label = round_time(series_label) if series_label.respond_to?(:to_time)
78
+ key_format.call(series_label)
79
+ end
80
+
46
81
  def round_time(time)
47
82
  time = time.to_time.in_time_zone(time_zone)
48
83
 
49
- # only if day_start != 0 for performance
50
- time -= day_start.seconds if day_start != 0
84
+ if day_start != 0
85
+ # apply day_start to a time object that's not affected by DST
86
+ time = change_zone.call(time, utc)
87
+ time -= day_start.seconds
88
+ end
51
89
 
52
90
  time =
53
91
  case period
@@ -60,9 +98,7 @@ module Groupdate
60
98
  when :day
61
99
  time.beginning_of_day
62
100
  when :week
63
- # same logic as MySQL group
64
- weekday = (time.wday - 1) % 7
65
- (time - ((7 - week_start + weekday) % 7).days).midnight
101
+ time.beginning_of_week(@week_start_key)
66
102
  when :month
67
103
  time.beginning_of_month
68
104
  when :quarter
@@ -74,30 +110,44 @@ module Groupdate
74
110
  when :minute_of_hour
75
111
  time.min
76
112
  when :day_of_week
77
- (time.wday - 1 - week_start) % 7
113
+ time.days_to_week_start(@week_start_key)
78
114
  when :day_of_month
79
115
  time.day
80
116
  when :month_of_year
81
117
  time.month
118
+ when :day_of_year
119
+ time.yday
82
120
  else
83
121
  raise Groupdate::Error, "Invalid period"
84
122
  end
85
123
 
86
- # only if day_start != 0 for performance
87
- time += day_start.seconds if day_start != 0 && time.is_a?(Time)
124
+ if day_start != 0 && time.is_a?(Time)
125
+ time += day_start.seconds
126
+ time = change_zone.call(time, time_zone)
127
+ end
88
128
 
89
129
  time
90
130
  end
91
131
 
132
+ def change_zone
133
+ @change_zone ||= begin
134
+ if ActiveSupport::VERSION::STRING >= "5.2"
135
+ ->(time, zone) { time.change(zone: zone) }
136
+ else
137
+ # TODO make more efficient
138
+ ->(time, zone) { zone.parse(time.strftime("%Y-%m-%d %H:%M:%S")) }
139
+ end
140
+ end
141
+ end
142
+
92
143
  def time_range
93
144
  @time_range ||= begin
94
145
  time_range = options[:range]
95
146
  if time_range.is_a?(Range) && time_range.first.is_a?(Date)
96
147
  # 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)
148
+ last = time_range.last.in_time_zone(time_zone)
99
149
  last += 1.day unless time_range.exclude_end?
100
- time_range = Range.new(time_zone.parse(time_range.first.to_s), last, true)
150
+ time_range = Range.new(time_range.first.in_time_zone(time_zone), last, true)
101
151
  elsif !time_range && options[:last]
102
152
  if period == :quarter
103
153
  step = 3.months
@@ -117,7 +167,8 @@ module Groupdate
117
167
  if options[:current] == false
118
168
  round_time(start_at - step)...round_time(now)
119
169
  else
120
- round_time(start_at)..now
170
+ # extend to end of current period
171
+ round_time(start_at)...(round_time(now) + step)
121
172
  end
122
173
  end
123
174
  end
@@ -141,6 +192,8 @@ module Groupdate
141
192
  0..59
142
193
  when :day_of_month
143
194
  1..31
195
+ when :day_of_year
196
+ 1..366
144
197
  when :month_of_year
145
198
  1..12
146
199
  else
@@ -164,8 +217,8 @@ module Groupdate
164
217
  tr
165
218
  end
166
219
 
167
- if time_range.first
168
- series = [round_time(time_range.first)]
220
+ if time_range.begin
221
+ series = [round_time(time_range.begin)]
169
222
 
170
223
  if period == :quarter
171
224
  step = 3.months
@@ -174,9 +227,10 @@ module Groupdate
174
227
  end
175
228
 
176
229
  last_step = series.last
230
+ day_start_hour = day_start / 3600
177
231
  loop do
178
232
  next_step = last_step + step
179
- next_step = round_time(next_step) if next_step.hour != day_start # add condition to speed up
233
+ next_step = round_time(next_step) if next_step.hour != day_start_hour # add condition to speed up
180
234
  break unless time_range.cover?(next_step)
181
235
 
182
236
  if next_step == last_step
@@ -195,34 +249,36 @@ module Groupdate
195
249
  end
196
250
 
197
251
  def key_format
198
- locale = options[:locale] || I18n.locale
199
- use_dates = options.key?(:dates) ? options[:dates] : Groupdate.dates
252
+ @key_format ||= begin
253
+ locale = options[:locale] || I18n.locale
254
+ use_dates = options.key?(:dates) ? options[:dates] : Groupdate.dates
200
255
 
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
256
+ if options[:format]
257
+ if options[:format].respond_to?(:call)
258
+ options[:format]
259
+ else
260
+ sunday = time_zone.parse("2014-03-02 00:00:00")
261
+ lambda do |key|
262
+ case period
263
+ when :hour_of_day
264
+ key = sunday + key.hours + day_start.seconds
265
+ when :minute_of_hour
266
+ key = sunday + key.minutes + day_start.seconds
267
+ when :day_of_week
268
+ key = sunday + key.days + (week_start + 1).days
269
+ when :day_of_month
270
+ key = Date.new(2014, 1, key).to_time
271
+ when :month_of_year
272
+ key = Date.new(2014, key, 1).to_time
273
+ end
274
+ I18n.localize(key, format: options[:format], locale: locale)
218
275
  end
219
- I18n.localize(key, format: options[:format], locale: locale)
220
276
  end
277
+ elsif [:day, :week, :month, :quarter, :year].include?(period) && use_dates
278
+ lambda { |k| k.to_date }
279
+ else
280
+ lambda { |k| k }
221
281
  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
282
  end
227
283
  end
228
284
 
@@ -242,23 +298,12 @@ module Groupdate
242
298
  end
243
299
  end
244
300
 
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
301
  def entire_series?(series_default)
261
302
  options.key?(:series) ? options[:series] : series_default
262
303
  end
304
+
305
+ def utc
306
+ @utc ||= ActiveSupport::TimeZone["Etc/UTC"]
307
+ end
263
308
  end
264
309
  end