groupdate2 4.1.5 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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