groupdate 4.0.0 → 4.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: d6b498ed092c226ed3e7eeec42f68ff8a4b092c1
4
- data.tar.gz: a43c5abd7af5dcc458825c8e151184fc30d98626
2
+ SHA256:
3
+ metadata.gz: edfe1e903c4ce720f5dfe74be0088937aa52b69f82dd88732802f49764205314
4
+ data.tar.gz: 85a0642b059de80a94ee621c028f117296e1baa09d93232ed6e97680b9d5fb25
5
5
  SHA512:
6
- metadata.gz: c6379307ca9ef9c5d3435490bb61cea9d8afd15abd9c106aabbf55bb2e4d3d20f2dcd5f2e5e8e12cf338cb48c113d81ea2265e7085bce643b88a0011bd73c7f4
7
- data.tar.gz: a1c6d9161d0051153b313b5501d3043df99de23105748e336a3c2446edeb5acfb3708be638c95cb23811205840f2dd2799f571124f369dbfbe562df70259087d
6
+ metadata.gz: f2c7f1f9d5cd73b00b89005a17d62897494deb51164ccb75cbf5b6917ce389cd2847c410f271c75118c74b17677d6fabb1ef0c327063c5b7de527ad46604e66d
7
+ data.tar.gz: c0a4f216150c83fa1cca7e8f10775b619da2b654b4a85e6f148bead6fe8fe82b1fd45b1741f89b7873e7c9c1ca6494f528b6cfbf41b4dda723b37c88b9d22cc2
File without changes
data/.travis.yml CHANGED
@@ -1,10 +1,10 @@
1
1
  language: ruby
2
2
  rvm:
3
3
  - 2.4.2
4
- - jruby-9.1.5.0
4
+ - jruby-9.1.9.0
5
5
  gemfile:
6
6
  - Gemfile
7
- - test/gemfiles/activerecord52.gemfile
7
+ - test/gemfiles/activerecord51.gemfile
8
8
  - test/gemfiles/activerecord50.gemfile
9
9
  - test/gemfiles/activerecord42.gemfile
10
10
  sudo: false
@@ -20,9 +20,7 @@ notifications:
20
20
  on_failure: change
21
21
  matrix:
22
22
  allow_failures:
23
- - rvm: 2.4.2
24
- gemfile: test/gemfiles/activerecord52.gemfile
25
- - rvm: jruby-9.1.5.0
26
- gemfile: test/gemfiles/activerecord52.gemfile
27
- - rvm: jruby-9.1.5.0
28
- gemfile: test/gemfiles/activerecord42.gemfile
23
+ - rvm: jruby-9.1.9.0
24
+ gemfile: Gemfile
25
+ - rvm: jruby-9.1.9.0
26
+ gemfile: test/gemfiles/activerecord51.gemfile
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 4.0.1
2
+
3
+ - Fixed incorrect range with `last` option near time change
4
+
1
5
  ## 4.0.0
2
6
 
3
7
  - Custom calculation methods are supported by default - `groupdate_calculation_methods` is no longer needed
data/CONTRIBUTING.md CHANGED
@@ -21,7 +21,10 @@ Think you’ve discovered an issue?
21
21
  gem "groupdate", github: "ankane/groupdate"
22
22
  ```
23
23
 
24
- If the above steps don’t help, create an issue. Include the complete backtrace for exceptions.
24
+ If the above steps don’t help, create an issue. Include:
25
+
26
+ - Detailed steps to reproduce
27
+ - Complete backtraces for exceptions
25
28
 
26
29
  ## Pull Requests
27
30
 
data/Gemfile CHANGED
@@ -3,4 +3,10 @@ source "https://rubygems.org"
3
3
  # Specify your gem's dependencies in groupdate.gemspec
4
4
  gemspec
5
5
 
6
- gem "activerecord", "5.1.4" # "~> 5.1.0"
6
+ gem "activerecord", "~> 5.2.0"
7
+
8
+ if defined?(JRUBY_VERSION)
9
+ gem "activerecord-jdbcpostgresql-adapter", git: "https://github.com/jruby/activerecord-jdbc-adapter.git"
10
+ gem "activerecord-jdbcmysql-adapter", git: "https://github.com/jruby/activerecord-jdbc-adapter.git"
11
+ gem "activerecord-jdbcsqlite3-adapter", git: "https://github.com/jruby/activerecord-jdbc-adapter.git"
12
+ end
data/README.md CHANGED
@@ -218,7 +218,7 @@ gem 'groupdate'
218
218
 
219
219
  #### For MySQL
220
220
 
221
- [Time zone support](http://dev.mysql.com/doc/refman/5.6/en/time-zone-support.html) must be installed on the server.
221
+ [Time zone support](https://dev.mysql.com/doc/refman/5.7/en/time-zone-support.html) must be installed on the server.
222
222
 
223
223
  ```sh
224
224
  mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
@@ -278,7 +278,7 @@ Groupdate 2.0 brings a number of improvements. Here are two things to be aware
278
278
 
279
279
  View the [changelog](https://github.com/ankane/groupdate/blob/master/CHANGELOG.md)
280
280
 
281
- Groupdate follows [Semantic Versioning](http://semver.org/)
281
+ Groupdate follows [Semantic Versioning](https://semver.org/)
282
282
 
283
283
  ## Contributing
284
284
 
data/groupdate.gemspec CHANGED
@@ -17,6 +17,8 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ["lib"]
19
19
 
20
+ spec.required_ruby_version = ">= 2.2.0"
21
+
20
22
  spec.add_dependency "activesupport", ">= 4.2"
21
23
 
22
24
  spec.add_development_dependency "bundler"
@@ -30,7 +32,7 @@ Gem::Specification.new do |spec|
30
32
  spec.add_development_dependency "activerecord-jdbcsqlite3-adapter"
31
33
  else
32
34
  spec.add_development_dependency "pg", "< 1"
33
- spec.add_development_dependency "mysql2"
35
+ spec.add_development_dependency "mysql2", "< 0.5"
34
36
  spec.add_development_dependency "sqlite3"
35
37
  end
36
38
  end
data/lib/groupdate.rb CHANGED
@@ -1,6 +1,8 @@
1
1
  require "active_support/core_ext/module/attribute_accessors"
2
2
  require "active_support/time"
3
3
  require "groupdate/version"
4
+ require "groupdate/relation_builder"
5
+ require "groupdate/series_builder"
4
6
  require "groupdate/magic"
5
7
 
6
8
  module Groupdate
@@ -2,7 +2,7 @@ require "i18n"
2
2
 
3
3
  module Groupdate
4
4
  class Magic
5
- attr_accessor :period, :options
5
+ attr_accessor :period, :options, :group_index
6
6
 
7
7
  def initialize(period:, **options)
8
8
  @period = period
@@ -15,8 +15,6 @@ module Groupdate
15
15
  raise Groupdate::Error, "Unrecognized :week_start option" if period == :week && !week_start
16
16
  end
17
17
 
18
- protected
19
-
20
18
  def time_zone
21
19
  @time_zone ||= begin
22
20
  time_zone = "Etc/UTC" if options[:time_zone] == false
@@ -33,199 +31,25 @@ module Groupdate
33
31
  @day_start ||= ((options[:day_start] || Groupdate.day_start).to_f * 3600).round
34
32
  end
35
33
 
36
- def time_range
37
- @time_range ||= begin
38
- time_range = options[:range]
39
- if time_range.is_a?(Range) && time_range.first.is_a?(Date)
40
- # convert range of dates to range of times
41
- # use parsing instead of in_time_zone due to Rails < 4
42
- last = time_zone.parse(time_range.last.to_s)
43
- last += 1.day unless time_range.exclude_end?
44
- time_range = Range.new(time_zone.parse(time_range.first.to_s), last, true)
45
- elsif !time_range && options[:last]
46
- if period == :quarter
47
- step = 3.months
48
- elsif 1.respond_to?(period)
49
- step = 1.send(period)
50
- else
51
- raise ArgumentError, "Cannot use last option with #{period}"
52
- end
53
- if step
54
- now = Time.now
55
- # loop instead of multiply to change start_at - see #151
56
- start_at = now
57
- (options[:last].to_i - 1).times do
58
- start_at -= step
59
- end
60
-
61
- time_range =
62
- if options[:current] == false
63
- round_time(start_at - step)...round_time(now)
64
- else
65
- round_time(start_at)..now
66
- end
67
- end
68
- end
69
- time_range
70
- end
34
+ def series_builder
35
+ @series_builder ||=
36
+ SeriesBuilder.new(
37
+ **options,
38
+ period: period,
39
+ time_zone: time_zone,
40
+ day_start: day_start,
41
+ week_start: week_start
42
+ )
71
43
  end
72
44
 
73
- def series(count, default_value, multiple_groups = false, reverse = false, series_default = true)
74
- reverse = !reverse if options[:reverse]
75
-
76
- series =
77
- case period
78
- when :day_of_week
79
- 0..6
80
- when :hour_of_day
81
- 0..23
82
- when :minute_of_hour
83
- 0..59
84
- when :day_of_month
85
- 1..31
86
- when :month_of_year
87
- 1..12
88
- else
89
- time_range = self.time_range
90
- time_range =
91
- if time_range.is_a?(Range)
92
- time_range
93
- else
94
- # use first and last values
95
- sorted_keys =
96
- if multiple_groups
97
- count.keys.map { |k| k[@group_index] }.sort
98
- else
99
- count.keys.sort
100
- end
101
- sorted_keys.first..sorted_keys.last
102
- end
103
-
104
- if time_range.first
105
- series = [round_time(time_range.first)]
106
-
107
- if period == :quarter
108
- step = 3.months
109
- else
110
- step = 1.send(period)
111
- end
112
-
113
- last_step = series.last
114
- while (next_step = round_time(last_step + step)) && time_range.cover?(next_step)
115
- if next_step == last_step
116
- last_step += step
117
- next
118
- end
119
- series << next_step
120
- last_step = next_step
121
- end
122
-
123
- series
124
- else
125
- []
126
- end
127
- end
128
-
129
- series =
130
- if multiple_groups
131
- keys = count.keys.map { |k| k[0...@group_index] + k[(@group_index + 1)..-1] }.uniq
132
- series = series.to_a.reverse if reverse
133
- keys.flat_map do |k|
134
- series.map { |s| k[0...@group_index] + [s] + k[@group_index..-1] }
135
- end
136
- else
137
- series
138
- end
139
-
140
- # reversed above if multiple groups
141
- series = series.to_a.reverse if !multiple_groups && reverse
142
-
143
- locale = options[:locale] || I18n.locale
144
- use_dates = options.key?(:dates) ? options[:dates] : Groupdate.dates
145
- key_format =
146
- if options[:format]
147
- if options[:format].respond_to?(:call)
148
- options[:format]
149
- else
150
- sunday = time_zone.parse("2014-03-02 00:00:00")
151
- lambda do |key|
152
- case period
153
- when :hour_of_day
154
- key = sunday + key.hours + day_start.seconds
155
- when :minute_of_hour
156
- key = sunday + key.minutes + day_start.seconds
157
- when :day_of_week
158
- key = sunday + key.days + (week_start + 1).days
159
- when :day_of_month
160
- key = Date.new(2014, 1, key).to_time
161
- when :month_of_year
162
- key = Date.new(2014, key, 1).to_time
163
- end
164
- I18n.localize(key, format: options[:format], locale: locale)
165
- end
166
- end
167
- elsif [:day, :week, :month, :quarter, :year].include?(period) && use_dates
168
- lambda { |k| k.to_date }
169
- else
170
- lambda { |k| k }
171
- end
172
-
173
- use_series = options.key?(:series) ? options[:series] : series_default
174
- if use_series == false
175
- series = series.select { |k| count[k] }
176
- end
177
-
178
- value = 0
179
- Hash[series.map do |k|
180
- value = count[k] || (@options[:carry_forward] && value) || default_value
181
- [multiple_groups ? k[0...@group_index] + [key_format.call(k[@group_index])] + k[(@group_index + 1)..-1] : key_format.call(k), value]
182
- end]
183
- end
184
-
185
- def round_time(time)
186
- time = time.to_time.in_time_zone(time_zone) - day_start.seconds
187
-
188
- time =
189
- case period
190
- when :second
191
- time.change(usec: 0)
192
- when :minute
193
- time.change(sec: 0)
194
- when :hour
195
- time.change(min: 0)
196
- when :day
197
- time.beginning_of_day
198
- when :week
199
- # same logic as MySQL group
200
- weekday = (time.wday - 1) % 7
201
- (time - ((7 - week_start + weekday) % 7).days).midnight
202
- when :month
203
- time.beginning_of_month
204
- when :quarter
205
- time.beginning_of_quarter
206
- when :year
207
- time.beginning_of_year
208
- when :hour_of_day
209
- time.hour
210
- when :minute_of_hour
211
- time.min
212
- when :day_of_week
213
- (time.wday - 1 - week_start) % 7
214
- when :day_of_month
215
- time.day
216
- when :month_of_year
217
- time.month
218
- else
219
- raise Groupdate::Error, "Invalid period"
220
- end
221
-
222
- time.is_a?(Time) ? time + day_start.seconds : time
45
+ def time_range
46
+ series_builder.time_range
223
47
  end
224
48
 
225
49
  class Enumerable < Magic
226
50
  def group_by(enum, &_block)
227
- group = enum.group_by { |v| v = yield(v); v ? round_time(v) : nil }
228
- series(group, [], false, false, false)
51
+ group = enum.group_by { |v| v = yield(v); v ? series_builder.round_time(v) : nil }
52
+ series_builder.generate(group, default_value: [], series_default: false)
229
53
  end
230
54
 
231
55
  def self.group_by(enum, period, options, &block)
@@ -239,191 +63,60 @@ module Groupdate
239
63
  @options = options
240
64
  end
241
65
 
242
- def relation(column, relation)
243
- if relation.default_timezone == :local
244
- raise Groupdate::Error, "ActiveRecord::Base.default_timezone must be :utc to use Groupdate"
245
- end
246
-
247
- time_zone = self.time_zone.tzinfo.name
248
-
249
- adapter_name = relation.connection.adapter_name
250
- query =
251
- case adapter_name
252
- when "MySQL", "Mysql2", "Mysql2Spatial", 'Mysql2Rgeo'
253
- case period
254
- when :day_of_week
255
- ["DAYOFWEEK(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?)) - 1", time_zone]
256
- when :hour_of_day
257
- ["(EXTRACT(HOUR from CONVERT_TZ(#{column}, '+00:00', ?)) + 24 - #{day_start / 3600}) % 24", time_zone]
258
- when :minute_of_hour
259
- ["(EXTRACT(MINUTE from CONVERT_TZ(#{column}, '+00:00', ?)))", time_zone]
260
- when :day_of_month
261
- ["DAYOFMONTH(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?))", time_zone]
262
- when :month_of_year
263
- ["MONTH(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?))", time_zone]
264
- when :week
265
- ["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]
266
- when :quarter
267
- ["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]
268
- else
269
- format =
270
- case period
271
- when :second
272
- "%Y-%m-%d %H:%i:%S"
273
- when :minute
274
- "%Y-%m-%d %H:%i:00"
275
- when :hour
276
- "%Y-%m-%d %H:00:00"
277
- when :day
278
- "%Y-%m-%d 00:00:00"
279
- when :month
280
- "%Y-%m-01 00:00:00"
281
- else # year
282
- "%Y-01-01 00:00:00"
283
- end
284
-
285
- ["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]
286
- end
287
- when "PostgreSQL", "PostGIS"
288
- case period
289
- when :day_of_week
290
- ["EXTRACT(DOW from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
291
- when :hour_of_day
292
- ["EXTRACT(HOUR from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
293
- when :minute_of_hour
294
- ["EXTRACT(MINUTE from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
295
- when :day_of_month
296
- ["EXTRACT(DAY from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
297
- when :month_of_year
298
- ["EXTRACT(MONTH from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
299
- when :week # start on Sunday, not PostgreSQL default Monday
300
- ["(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]
301
- else
302
- ["(DATE_TRUNC('#{period}', (#{column}::timestamptz - INTERVAL '#{day_start} second') AT TIME ZONE ?) + INTERVAL '#{day_start} second') AT TIME ZONE ?", time_zone, time_zone]
303
- end
304
- when "SQLite"
305
- raise Groupdate::Error, "Time zones not supported for SQLite" unless self.time_zone.utc_offset.zero?
306
- raise Groupdate::Error, "day_start not supported for SQLite" unless day_start.zero?
307
- raise Groupdate::Error, "week_start not supported for SQLite" unless week_start == 6
308
-
309
- if period == :week
310
- ["strftime('%%Y-%%m-%%d 00:00:00 UTC', #{column}, '-6 days', 'weekday 0')"]
311
- else
312
- format =
313
- case period
314
- when :hour_of_day
315
- "%H"
316
- when :minute_of_hour
317
- "%M"
318
- when :day_of_week
319
- "%w"
320
- when :day_of_month
321
- "%d"
322
- when :month_of_year
323
- "%m"
324
- when :second
325
- "%Y-%m-%d %H:%M:%S UTC"
326
- when :minute
327
- "%Y-%m-%d %H:%M:00 UTC"
328
- when :hour
329
- "%Y-%m-%d %H:00:00 UTC"
330
- when :day
331
- "%Y-%m-%d 00:00:00 UTC"
332
- when :month
333
- "%Y-%m-01 00:00:00 UTC"
334
- when :quarter
335
- raise Groupdate::Error, "Quarter not supported for SQLite"
336
- else # year
337
- "%Y-01-01 00:00:00 UTC"
338
- end
339
-
340
- ["strftime('#{format.gsub(/%/, '%%')}', #{column})"]
341
- end
342
- when "Redshift"
343
- case period
344
- when :day_of_week
345
- ["EXTRACT(DOW from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
346
- when :hour_of_day
347
- ["EXTRACT(HOUR from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
348
- when :minute_of_hour
349
- ["EXTRACT(MINUTE from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
350
- when :day_of_month
351
- ["EXTRACT(DAY from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
352
- when :month_of_year
353
- ["EXTRACT(MONTH from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
354
- when :week # start on Sunday, not Redshift default Monday
355
- # Redshift does not return timezone information; it
356
- # always says it is in UTC time, so we must convert
357
- # back to UTC to play properly with the rest of Groupdate.
358
- #
359
- ["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]
360
- else
361
- ["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC(?, CONVERT_TIMEZONE(?, #{column}) - INTERVAL '#{day_start} second'))::timestamp + INTERVAL '#{day_start} second'", time_zone, period, time_zone]
362
- end
363
- else
364
- raise Groupdate::Error, "Connection adapter not supported: #{adapter_name}"
365
- end
66
+ def perform(relation, result)
67
+ multiple_groups = relation.group_values.size > 1
366
68
 
367
- if adapter_name == "MySQL" && period == :week
368
- query[0] = "CAST(#{query[0]} AS DATETIME)"
369
- end
69
+ check_time_zone_support(result, multiple_groups)
70
+ result = cast_result(result, multiple_groups)
370
71
 
371
- group_str = relation.send(:sanitize_sql_array, query)
72
+ series_builder.generate(
73
+ result,
74
+ default_value: options.key?(:default_value) ? options[:default_value] : 0,
75
+ multiple_groups: multiple_groups,
76
+ group_index: group_index
77
+ )
78
+ end
372
79
 
373
- # cleaner queries in logs
374
- # Postgres
375
- group_str = group_str.gsub(/ (\-|\+) INTERVAL '0 second'/, "")
376
- # MySQL
377
- group_str = group_str.gsub("DATE_SUB(#{column}, INTERVAL 0 second)", "#{column}")
378
- if group_str.start_with?("DATE_ADD(") && group_str.end_with?(", INTERVAL 0 second)")
379
- group_str = group_str[9..-21]
80
+ def cast_method
81
+ case period
82
+ when :day_of_week
83
+ lambda { |k| (k.to_i - 1 - week_start) % 7 }
84
+ when :hour_of_day, :day_of_month, :month_of_year, :minute_of_hour
85
+ lambda { |k| k.to_i }
86
+ else
87
+ utc = ActiveSupport::TimeZone["UTC"]
88
+ lambda { |k| (k.is_a?(String) || !k.respond_to?(:to_time) ? utc.parse(k.to_s) : k.to_time).in_time_zone(time_zone) }
380
89
  end
381
-
382
- group = relation.group(group_str)
383
- relation =
384
- if time_range.is_a?(Range)
385
- # doesn't matter whether we include the end of a ... range - it will be excluded later
386
- group.where("#{column} >= ? AND #{column} <= ?", time_range.first, time_range.last)
387
- else
388
- group.where("#{column} IS NOT NULL")
389
- end
390
-
391
- # TODO do not change object state
392
- @group_index = group.group_values.size - 1
393
-
394
- relation
395
90
  end
396
91
 
397
- def perform(relation, result)
398
- multiple_groups = relation.group_values.size > 1
399
-
400
- cast_method =
401
- case period
402
- when :day_of_week
403
- lambda { |k| (k.to_i - 1 - week_start) % 7 }
404
- when :hour_of_day, :day_of_month, :month_of_year, :minute_of_hour
405
- lambda { |k| k.to_i }
406
- else
407
- utc = ActiveSupport::TimeZone["UTC"]
408
- lambda { |k| (k.is_a?(String) || !k.respond_to?(:to_time) ? utc.parse(k.to_s) : k.to_time).in_time_zone(time_zone) }
409
- end
92
+ def cast_result(result, multiple_groups)
93
+ Hash[result.map { |k, v| [multiple_groups ? k[0...group_index] + [cast_method.call(k[group_index])] + k[(group_index + 1)..-1] : cast_method.call(k), v] }]
94
+ end
410
95
 
411
- missing_time_zone_support = multiple_groups ? (result.keys.first && result.keys.first[@group_index].nil?) : result.key?(nil)
96
+ def check_time_zone_support(result, multiple_groups)
97
+ missing_time_zone_support = multiple_groups ? (result.keys.first && result.keys.first[group_index].nil?) : result.key?(nil)
412
98
  if missing_time_zone_support
413
99
  raise Groupdate::Error, "Be sure to install time zone support - https://github.com/ankane/groupdate#for-mysql"
414
100
  end
415
- result = Hash[result.map { |k, v| [multiple_groups ? k[0...@group_index] + [cast_method.call(k[@group_index])] + k[(@group_index + 1)..-1] : cast_method.call(k), v] }]
416
-
417
- series(result, (options.key?(:default_value) ? options[:default_value] : 0), multiple_groups, @reverse)
418
101
  end
419
102
 
420
103
  def self.generate_relation(relation, field:, **options)
421
104
  magic = Groupdate::Magic::Relation.new(**options)
422
105
 
423
106
  # generate ActiveRecord relation
424
- relation = magic.relation(field, relation)
107
+ relation =
108
+ RelationBuilder.new(
109
+ relation,
110
+ column: field,
111
+ period: magic.period,
112
+ time_zone: magic.time_zone,
113
+ time_range: magic.time_range,
114
+ week_start: magic.week_start,
115
+ day_start: magic.day_start
116
+ ).generate
425
117
 
426
118
  # add Groupdate info
119
+ magic.group_index = relation.group_values.size - 1
427
120
  (relation.groupdate_values ||= []) << magic
428
121
 
429
122
  relation
@@ -0,0 +1,177 @@
1
+ module Groupdate
2
+ class RelationBuilder
3
+ attr_reader :period, :column, :day_start, :week_start
4
+
5
+ def initialize(relation, column:, period:, time_zone:, time_range:, week_start:, day_start:)
6
+ @relation = relation
7
+ @column = column
8
+ @period = period
9
+ @time_zone = time_zone
10
+ @time_range = time_range
11
+ @week_start = week_start
12
+ @day_start = day_start
13
+
14
+ if relation.default_timezone == :local
15
+ raise Groupdate::Error, "ActiveRecord::Base.default_timezone must be :utc to use Groupdate"
16
+ end
17
+ end
18
+
19
+ def generate
20
+ @relation.group(group_clause).where(*where_clause)
21
+ end
22
+
23
+ private
24
+
25
+ def group_clause
26
+ time_zone = @time_zone.tzinfo.name
27
+ adapter_name = @relation.connection.adapter_name
28
+ query =
29
+ case adapter_name
30
+ when "MySQL", "Mysql2", "Mysql2Spatial", 'Mysql2Rgeo'
31
+ case period
32
+ when :day_of_week
33
+ ["DAYOFWEEK(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?)) - 1", time_zone]
34
+ when :hour_of_day
35
+ ["(EXTRACT(HOUR from CONVERT_TZ(#{column}, '+00:00', ?)) + 24 - #{day_start / 3600}) % 24", time_zone]
36
+ when :minute_of_hour
37
+ ["(EXTRACT(MINUTE from CONVERT_TZ(#{column}, '+00:00', ?)))", time_zone]
38
+ when :day_of_month
39
+ ["DAYOFMONTH(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?))", time_zone]
40
+ when :month_of_year
41
+ ["MONTH(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?))", time_zone]
42
+ when :week
43
+ ["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]
44
+ when :quarter
45
+ ["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]
46
+ else
47
+ format =
48
+ case period
49
+ when :second
50
+ "%Y-%m-%d %H:%i:%S"
51
+ when :minute
52
+ "%Y-%m-%d %H:%i:00"
53
+ when :hour
54
+ "%Y-%m-%d %H:00:00"
55
+ when :day
56
+ "%Y-%m-%d 00:00:00"
57
+ when :month
58
+ "%Y-%m-01 00:00:00"
59
+ else # year
60
+ "%Y-01-01 00:00:00"
61
+ end
62
+
63
+ ["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]
64
+ end
65
+ when "PostgreSQL", "PostGIS"
66
+ case period
67
+ when :day_of_week
68
+ ["EXTRACT(DOW from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
69
+ when :hour_of_day
70
+ ["EXTRACT(HOUR from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
71
+ when :minute_of_hour
72
+ ["EXTRACT(MINUTE from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
73
+ when :day_of_month
74
+ ["EXTRACT(DAY from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
75
+ when :month_of_year
76
+ ["EXTRACT(MONTH from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
77
+ when :week # start on Sunday, not PostgreSQL default Monday
78
+ ["(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]
79
+ else
80
+ ["(DATE_TRUNC('#{period}', (#{column}::timestamptz - INTERVAL '#{day_start} second') AT TIME ZONE ?) + INTERVAL '#{day_start} second') AT TIME ZONE ?", time_zone, time_zone]
81
+ end
82
+ when "SQLite"
83
+ raise Groupdate::Error, "Time zones not supported for SQLite" unless @time_zone.utc_offset.zero?
84
+ raise Groupdate::Error, "day_start not supported for SQLite" unless day_start.zero?
85
+ raise Groupdate::Error, "week_start not supported for SQLite" unless week_start == 6
86
+
87
+ if period == :week
88
+ ["strftime('%%Y-%%m-%%d 00:00:00 UTC', #{column}, '-6 days', 'weekday 0')"]
89
+ else
90
+ format =
91
+ case period
92
+ when :hour_of_day
93
+ "%H"
94
+ when :minute_of_hour
95
+ "%M"
96
+ when :day_of_week
97
+ "%w"
98
+ when :day_of_month
99
+ "%d"
100
+ when :month_of_year
101
+ "%m"
102
+ when :second
103
+ "%Y-%m-%d %H:%M:%S UTC"
104
+ when :minute
105
+ "%Y-%m-%d %H:%M:00 UTC"
106
+ when :hour
107
+ "%Y-%m-%d %H:00:00 UTC"
108
+ when :day
109
+ "%Y-%m-%d 00:00:00 UTC"
110
+ when :month
111
+ "%Y-%m-01 00:00:00 UTC"
112
+ when :quarter
113
+ raise Groupdate::Error, "Quarter not supported for SQLite"
114
+ else # year
115
+ "%Y-01-01 00:00:00 UTC"
116
+ end
117
+
118
+ ["strftime('#{format.gsub(/%/, '%%')}', #{column})"]
119
+ end
120
+ when "Redshift"
121
+ case period
122
+ when :day_of_week
123
+ ["EXTRACT(DOW from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
124
+ when :hour_of_day
125
+ ["EXTRACT(HOUR from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
126
+ when :minute_of_hour
127
+ ["EXTRACT(MINUTE from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
128
+ when :day_of_month
129
+ ["EXTRACT(DAY from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
130
+ when :month_of_year
131
+ ["EXTRACT(MONTH from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
132
+ when :week # start on Sunday, not Redshift default Monday
133
+ # Redshift does not return timezone information; it
134
+ # always says it is in UTC time, so we must convert
135
+ # back to UTC to play properly with the rest of Groupdate.
136
+ #
137
+ ["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]
138
+ else
139
+ ["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC(?, CONVERT_TIMEZONE(?, #{column}) - INTERVAL '#{day_start} second'))::timestamp + INTERVAL '#{day_start} second'", time_zone, period, time_zone]
140
+ end
141
+ else
142
+ raise Groupdate::Error, "Connection adapter not supported: #{adapter_name}"
143
+ end
144
+
145
+ if adapter_name == "MySQL" && period == :week
146
+ query[0] = "CAST(#{query[0]} AS DATETIME)"
147
+ end
148
+
149
+ clause = @relation.send(:sanitize_sql_array, query)
150
+
151
+ # cleaner queries in logs
152
+ clause = clean_group_clause_postgresql(clause)
153
+ clean_group_clause_mysql(clause)
154
+ end
155
+
156
+ def clean_group_clause_postgresql(clause)
157
+ clause.gsub(/ (\-|\+) INTERVAL '0 second'/, "")
158
+ end
159
+
160
+ def clean_group_clause_mysql(clause)
161
+ clause = clause.gsub("DATE_SUB(#{column}, INTERVAL 0 second)", "#{column}")
162
+ if clause.start_with?("DATE_ADD(") && clause.end_with?(", INTERVAL 0 second)")
163
+ clause = clause[9..-21]
164
+ end
165
+ clause
166
+ end
167
+
168
+ def where_clause
169
+ if @time_range.is_a?(Range)
170
+ # doesn't matter whether we include the end of a ... range - it will be excluded later
171
+ ["#{column} >= ? AND #{column} <= ?", @time_range.first, @time_range.last]
172
+ else
173
+ ["#{column} IS NOT NULL"]
174
+ end
175
+ end
176
+ end
177
+ end
@@ -0,0 +1,220 @@
1
+ module Groupdate
2
+ class SeriesBuilder
3
+ attr_reader :period, :time_zone, :day_start, :week_start, :options
4
+
5
+ def initialize(period:, time_zone:, day_start:, week_start:, **options)
6
+ @period = period
7
+ @time_zone = time_zone
8
+ @week_start = week_start
9
+ @day_start = day_start
10
+ @options = options
11
+ end
12
+
13
+ def generate(data, default_value:, series_default: true, multiple_groups: false, group_index: nil)
14
+ series = generate_series(data, multiple_groups, group_index)
15
+ series = handle_multiple(data, series, multiple_groups, group_index)
16
+
17
+ unless entire_series?(series_default)
18
+ series = series.select { |k| data[k] }
19
+ end
20
+
21
+ value = 0
22
+ Hash[series.map do |k|
23
+ value = data[k] || (@options[:carry_forward] && value) || default_value
24
+ key =
25
+ if multiple_groups
26
+ k[0...group_index] + [key_format.call(k[group_index])] + k[(group_index + 1)..-1]
27
+ else
28
+ key_format.call(k)
29
+ end
30
+
31
+ [key, value]
32
+ end]
33
+ end
34
+
35
+ def round_time(time)
36
+ time = time.to_time.in_time_zone(time_zone) - day_start.seconds
37
+
38
+ time =
39
+ case period
40
+ when :second
41
+ time.change(usec: 0)
42
+ when :minute
43
+ time.change(sec: 0)
44
+ when :hour
45
+ time.change(min: 0)
46
+ when :day
47
+ time.beginning_of_day
48
+ when :week
49
+ # same logic as MySQL group
50
+ weekday = (time.wday - 1) % 7
51
+ (time - ((7 - week_start + weekday) % 7).days).midnight
52
+ when :month
53
+ time.beginning_of_month
54
+ when :quarter
55
+ time.beginning_of_quarter
56
+ when :year
57
+ time.beginning_of_year
58
+ when :hour_of_day
59
+ time.hour
60
+ when :minute_of_hour
61
+ time.min
62
+ when :day_of_week
63
+ (time.wday - 1 - week_start) % 7
64
+ when :day_of_month
65
+ time.day
66
+ when :month_of_year
67
+ time.month
68
+ else
69
+ raise Groupdate::Error, "Invalid period"
70
+ end
71
+
72
+ time.is_a?(Time) ? time + day_start.seconds : time
73
+ end
74
+
75
+ def time_range
76
+ @time_range ||= begin
77
+ time_range = options[:range]
78
+ if time_range.is_a?(Range) && time_range.first.is_a?(Date)
79
+ # convert range of dates to range of times
80
+ # use parsing instead of in_time_zone due to Rails < 4
81
+ last = time_zone.parse(time_range.last.to_s)
82
+ last += 1.day unless time_range.exclude_end?
83
+ time_range = Range.new(time_zone.parse(time_range.first.to_s), last, true)
84
+ elsif !time_range && options[:last]
85
+ if period == :quarter
86
+ step = 3.months
87
+ elsif 1.respond_to?(period)
88
+ step = 1.send(period)
89
+ else
90
+ raise ArgumentError, "Cannot use last option with #{period}"
91
+ end
92
+ if step
93
+ now = time_zone.now
94
+ # loop instead of multiply to change start_at - see #151
95
+ start_at = now
96
+ (options[:last].to_i - 1).times do
97
+ start_at -= step
98
+ end
99
+
100
+ time_range =
101
+ if options[:current] == false
102
+ round_time(start_at - step)...round_time(now)
103
+ else
104
+ round_time(start_at)..now
105
+ end
106
+ end
107
+ end
108
+ time_range
109
+ end
110
+ end
111
+
112
+ private
113
+
114
+ def generate_series(data, multiple_groups, group_index)
115
+ case period
116
+ when :day_of_week
117
+ 0..6
118
+ when :hour_of_day
119
+ 0..23
120
+ when :minute_of_hour
121
+ 0..59
122
+ when :day_of_month
123
+ 1..31
124
+ when :month_of_year
125
+ 1..12
126
+ else
127
+ time_range = self.time_range
128
+ time_range =
129
+ if time_range.is_a?(Range)
130
+ time_range
131
+ else
132
+ # use first and last values
133
+ sorted_keys =
134
+ if multiple_groups
135
+ data.keys.map { |k| k[group_index] }.sort
136
+ else
137
+ data.keys.sort
138
+ end
139
+ sorted_keys.first..sorted_keys.last
140
+ end
141
+
142
+ if time_range.first
143
+ series = [round_time(time_range.first)]
144
+
145
+ if period == :quarter
146
+ step = 3.months
147
+ else
148
+ step = 1.send(period)
149
+ end
150
+
151
+ last_step = series.last
152
+ while (next_step = round_time(last_step + step)) && time_range.cover?(next_step)
153
+ if next_step == last_step
154
+ last_step += step
155
+ next
156
+ end
157
+ series << next_step
158
+ last_step = next_step
159
+ end
160
+
161
+ series
162
+ else
163
+ []
164
+ end
165
+ end
166
+ end
167
+
168
+ def key_format
169
+ locale = options[:locale] || I18n.locale
170
+ use_dates = options.key?(:dates) ? options[:dates] : Groupdate.dates
171
+
172
+ if options[:format]
173
+ if options[:format].respond_to?(:call)
174
+ options[:format]
175
+ else
176
+ sunday = time_zone.parse("2014-03-02 00:00:00")
177
+ lambda do |key|
178
+ case period
179
+ when :hour_of_day
180
+ key = sunday + key.hours + day_start.seconds
181
+ when :minute_of_hour
182
+ key = sunday + key.minutes + day_start.seconds
183
+ when :day_of_week
184
+ key = sunday + key.days + (week_start + 1).days
185
+ when :day_of_month
186
+ key = Date.new(2014, 1, key).to_time
187
+ when :month_of_year
188
+ key = Date.new(2014, key, 1).to_time
189
+ end
190
+ I18n.localize(key, format: options[:format], locale: locale)
191
+ end
192
+ end
193
+ elsif [:day, :week, :month, :quarter, :year].include?(period) && use_dates
194
+ lambda { |k| k.to_date }
195
+ else
196
+ lambda { |k| k }
197
+ end
198
+ end
199
+
200
+ def handle_multiple(data, series, multiple_groups, group_index)
201
+ reverse = options[:reverse]
202
+
203
+ if multiple_groups
204
+ keys = data.keys.map { |k| k[0...group_index] + k[(group_index + 1)..-1] }.uniq
205
+ series = series.to_a.reverse if reverse
206
+ keys.flat_map do |k|
207
+ series.map { |s| k[0...group_index] + [s] + k[group_index..-1] }
208
+ end
209
+ elsif reverse
210
+ series.to_a.reverse
211
+ else
212
+ series
213
+ end
214
+ end
215
+
216
+ def entire_series?(series_default)
217
+ options.key?(:series) ? options[:series] : series_default
218
+ end
219
+ end
220
+ end
@@ -1,3 +1,3 @@
1
1
  module Groupdate
2
- VERSION = "4.0.0"
2
+ VERSION = "4.0.1"
3
3
  end
@@ -4,3 +4,9 @@ source 'https://rubygems.org'
4
4
  gemspec path: "../../"
5
5
 
6
6
  gem "activerecord", "~> 4.2.0"
7
+
8
+ if defined?(JRUBY_VERSION)
9
+ gem "activerecord-jdbcpostgresql-adapter", "~> 1.3.24"
10
+ gem "activerecord-jdbcmysql-adapter", "~> 1.3.24"
11
+ gem "activerecord-jdbcsqlite3-adapter", "~> 1.3.24"
12
+ end
@@ -4,3 +4,9 @@ source 'https://rubygems.org'
4
4
  gemspec path: "../../"
5
5
 
6
6
  gem "activerecord", "~> 5.0.0"
7
+
8
+ if defined?(JRUBY_VERSION)
9
+ gem "activerecord-jdbcpostgresql-adapter", "~> 50.0"
10
+ gem "activerecord-jdbcmysql-adapter", "~> 50.0"
11
+ gem "activerecord-jdbcsqlite3-adapter", "~> 50.0"
12
+ end
@@ -0,0 +1,12 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in groupdate.gemspec
4
+ gemspec path: "../../"
5
+
6
+ gem "activerecord", "~> 5.1.0"
7
+
8
+ if defined?(JRUBY_VERSION)
9
+ gem "activerecord-jdbcpostgresql-adapter" # no 51.0 yet
10
+ gem "activerecord-jdbcmysql-adapter", "~> 51.0"
11
+ gem "activerecord-jdbcsqlite3-adapter", "~> 51.0"
12
+ end
data/test/test_helper.rb CHANGED
@@ -201,7 +201,7 @@ module TestDatabase
201
201
 
202
202
  def test_last_date
203
203
  Time.zone = pt
204
- today = Date.today
204
+ today = Time.zone.now.to_date
205
205
  create_user today.to_s
206
206
  this_month = pt.parse(today.to_s).beginning_of_month
207
207
  last_month = this_month - 1.month
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: groupdate
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.0
4
+ version: 4.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Andrew Kane
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-02-22 00:00:00.000000000 Z
11
+ date: 2018-05-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -98,16 +98,16 @@ dependencies:
98
98
  name: mysql2
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
- - - ">="
101
+ - - "<"
102
102
  - !ruby/object:Gem::Version
103
- version: '0'
103
+ version: '0.5'
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
- - - ">="
108
+ - - "<"
109
109
  - !ruby/object:Gem::Version
110
- version: '0'
110
+ version: '0.5'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: sqlite3
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -129,12 +129,12 @@ executables: []
129
129
  extensions: []
130
130
  extra_rdoc_files: []
131
131
  files:
132
+ - ".github/ISSUE_TEMPLATE.md"
132
133
  - ".gitignore"
133
134
  - ".travis.yml"
134
135
  - CHANGELOG.md
135
136
  - CONTRIBUTING.md
136
137
  - Gemfile
137
- - ISSUE_TEMPLATE.md
138
138
  - LICENSE.txt
139
139
  - README.md
140
140
  - Rakefile
@@ -145,11 +145,13 @@ files:
145
145
  - lib/groupdate/magic.rb
146
146
  - lib/groupdate/query_methods.rb
147
147
  - lib/groupdate/relation.rb
148
+ - lib/groupdate/relation_builder.rb
149
+ - lib/groupdate/series_builder.rb
148
150
  - lib/groupdate/version.rb
149
151
  - test/enumerable_test.rb
150
152
  - test/gemfiles/activerecord42.gemfile
151
153
  - test/gemfiles/activerecord50.gemfile
152
- - test/gemfiles/activerecord52.gemfile
154
+ - test/gemfiles/activerecord51.gemfile
153
155
  - test/gemfiles/redshift.gemfile
154
156
  - test/mysql_test.rb
155
157
  - test/postgresql_test.rb
@@ -168,7 +170,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
168
170
  requirements:
169
171
  - - ">="
170
172
  - !ruby/object:Gem::Version
171
- version: '0'
173
+ version: 2.2.0
172
174
  required_rubygems_version: !ruby/object:Gem::Requirement
173
175
  requirements:
174
176
  - - ">="
@@ -176,7 +178,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
176
178
  version: '0'
177
179
  requirements: []
178
180
  rubyforge_project:
179
- rubygems_version: 2.6.13
181
+ rubygems_version: 2.7.6
180
182
  signing_key:
181
183
  specification_version: 4
182
184
  summary: The simplest way to group temporal data
@@ -184,7 +186,7 @@ test_files:
184
186
  - test/enumerable_test.rb
185
187
  - test/gemfiles/activerecord42.gemfile
186
188
  - test/gemfiles/activerecord50.gemfile
187
- - test/gemfiles/activerecord52.gemfile
189
+ - test/gemfiles/activerecord51.gemfile
188
190
  - test/gemfiles/redshift.gemfile
189
191
  - test/mysql_test.rb
190
192
  - test/postgresql_test.rb
@@ -1,6 +0,0 @@
1
- source 'https://rubygems.org'
2
-
3
- # Specify your gem's dependencies in groupdate.gemspec
4
- gemspec path: "../../"
5
-
6
- gem "activerecord", "~> 5.2.0.rc1"