groupdate 4.0.0 → 4.0.1

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 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"