groupdate 4.1.2 → 6.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
2
  SHA256:
3
- metadata.gz: 28ba8881c2af7869f774eceebed739e851e81d7d28b53473762260e7c4297134
4
- data.tar.gz: e0031f94b52b53255bdbe01c0f72d5c7abcb20d94b02d81bf9e31344a957919a
3
+ metadata.gz: 12cfc48ae509ac20872022dd07429dd7bd8f24a5bfd81dcb16242f3519cb5385
4
+ data.tar.gz: 3079f700f956f141184fbc5bc8244ad90380804cea9f15fef51300985380d13e
5
5
  SHA512:
6
- metadata.gz: cb863625ed13dea87c18b31bf897f0e6d6c00bb02b3c41aaf08a11221cc52a8cbfea5cb4f8fe991d33552b0f9294c9c2c04bfb1c220bdcfb8688dbacc848ceed
7
- data.tar.gz: 805c5290d0652b2f82c3426fb253dc962bc9c2e4903b3edb1aa2451dc252f0c94bbc7737d3c1b8ddd429c5844ca99c8c01b1e872f1efd4980a747eb4cd2c4294
6
+ metadata.gz: 64b4e317731bc0032a914e4f07ca751ee276e41b7a30a2113bff9d9f2437196916c68e5f1f699c2e107861d0022b712b32315132cd0f530bb7caad5f41955e45
7
+ data.tar.gz: ea085f2e6985f4df40f591333935b88f8c59b2be87943f39241254dbbc8ac18cc1aaa2b54030880a804117ecdc0451a65463b07ea758d3fc93d99697a11a5711
data/CHANGELOG.md CHANGED
@@ -1,31 +1,94 @@
1
- ## 4.1.2
1
+ ## 6.0.1 (2022-01-16)
2
+
3
+ - Fixed incorrect results (error before 6.0) with `includes` with Active Record 6.1+
4
+
5
+ ## 6.0.0 (2022-01-15)
6
+
7
+ - Raise `ActiveRecord::UnknownAttributeReference` for non-attribute arguments
8
+ - Raise `ArgumentError` for ranges with string bounds
9
+ - Added `n` option for Redshift
10
+ - Changed SQL to return dates instead of times for day, week, month, quarter, and year
11
+ - Removed `dates` option
12
+ - Dropped support for Ruby < 2.6 and Rails < 5.2
13
+
14
+ ## 5.2.4 (2021-12-15)
15
+
16
+ - Simplified queries for Active Record 7 and MySQL
17
+
18
+ ## 5.2.3 (2021-12-06)
19
+
20
+ - Fixed error and warnings with Active Record 7
21
+
22
+ ## 5.2.2 (2021-02-08)
23
+
24
+ - Added support for `nil..nil` ranges in `range` option
25
+
26
+ ## 5.2.1 (2020-09-09)
27
+
28
+ - Improved error message for invalid ranges
29
+ - Fixed bug with date string ranges
30
+
31
+ ## 5.2.0 (2020-09-07)
32
+
33
+ - Added warning for non-attribute argument
34
+ - Added support for beginless and endless ranges in `range` option
35
+
36
+ ## 5.1.0 (2020-07-30)
37
+
38
+ - Added `n` option to minute and second for custom durations
39
+
40
+ ## 5.0.0 (2020-02-18)
41
+
42
+ - Added support for `week_start` for SQLite
43
+ - Added support for full weekday names
44
+ - Made `day_start` behavior consistent between Active Record and enumerable
45
+ - Made `last` option extend to end of current period
46
+ - Raise error when `day_start` and `week_start` passed to unsupported methods
47
+ - The `day_start` option no longer applies to shorter periods
48
+ - Fixed `inconsistent time zone info` errors around DST with MySQL and PostgreSQL
49
+ - Improved performance of `format` option
50
+ - Removed deprecated positional arguments for time zone and range
51
+ - Dropped support for `mysql` gem (last release was 2013)
52
+
53
+ ## 4.3.0 (2019-12-26)
54
+
55
+ - Fixed error with empty results in Ruby 2.7
56
+ - Fixed deprecation warnings in Ruby 2.7
57
+ - Deprecated positional arguments for time zone and range
58
+
59
+ ## 4.2.0 (2019-10-28)
60
+
61
+ - Added `day_of_year`
62
+ - Dropped support for Rails 4.2
63
+
64
+ ## 4.1.2 (2019-05-26)
2
65
 
3
66
  - Fixed error with empty data and `current: false`
4
67
  - Fixed error in time zone check for Rails < 5.2
5
68
  - Prevent infinite loop with endless ranges
6
69
 
7
- ## 4.1.1
70
+ ## 4.1.1 (2018-12-11)
8
71
 
9
72
  - Made column resolution consistent with `group`
10
73
  - Added support for `alias_attribute`
11
74
 
12
- ## 4.1.0
75
+ ## 4.1.0 (2018-11-04)
13
76
 
14
77
  - Many performance improvements
15
78
  - Added check for consistent time zone info
16
79
  - Fixed error message for invalid queries with MySQL and SQLite
17
80
  - Fixed issue with enumerable methods ignoring nils
18
81
 
19
- ## 4.0.2
82
+ ## 4.0.2 (2018-10-15)
20
83
 
21
84
  - Make `current` option work without `last`
22
85
  - Fixed default value for `maximum`, `minimum`, and `average` (periods with no results now return `nil` instead of `0`, pass `default_value: 0` for previous behavior)
23
86
 
24
- ## 4.0.1
87
+ ## 4.0.1 (2018-05-03)
25
88
 
26
89
  - Fixed incorrect range with `last` option near time change
27
90
 
28
- ## 4.0.0
91
+ ## 4.0.0 (2018-02-21)
29
92
 
30
93
  - Custom calculation methods are supported by default - `groupdate_calculation_methods` is no longer needed
31
94
 
@@ -37,37 +100,37 @@ Breaking changes
37
100
  - `week_start` now affects `day_of_week`
38
101
  - Removed support for `reverse_order` (was never supported in Rails 5)
39
102
 
40
- ## 3.2.1
103
+ ## 3.2.1 (2018-02-21)
41
104
 
42
105
  - Added `minute_of_hour`
43
106
  - Added support for `unscoped`
44
107
 
45
- ## 3.2.0
108
+ ## 3.2.0 (2017-01-30)
46
109
 
47
110
  - Added limited support for SQLite
48
111
 
49
- ## 3.1.1
112
+ ## 3.1.1 (2016-10-25)
50
113
 
51
114
  - Fixed `current: false`
52
115
  - Fixed `last` with `group_by_quarter`
53
116
  - Raise `ArgumentError` when `last` option is not supported
54
117
 
55
- ## 3.1.0
118
+ ## 3.1.0 (2016-10-22)
56
119
 
57
120
  - Better support for date columns with `time_zone: false`
58
121
  - Better date range handling for `range` option
59
122
 
60
- ## 3.0.2
123
+ ## 3.0.2 (2016-08-09)
61
124
 
62
125
  - Fixed `group_by_period` with associations
63
126
  - Fixed `week_start` option for enumerables
64
127
 
65
- ## 3.0.1
128
+ ## 3.0.1 (2016-07-13)
66
129
 
67
130
  - Added support for Redshift
68
131
  - Fix for infinite loop in certain cases for Rails 5
69
132
 
70
- ## 3.0.0
133
+ ## 3.0.0 (2016-05-30)
71
134
 
72
135
  Breaking changes
73
136
 
@@ -75,16 +138,16 @@ Breaking changes
75
138
  - Array and hash methods no longer return the entire series by default. Use `series: true` for the previous behavior.
76
139
  - The `series: false` option now returns the correct types and order, and plays nicely with other options.
77
140
 
78
- ## 2.5.3
141
+ ## 2.5.3 (2016-04-28)
79
142
 
80
143
  - All tests green with `mysql` gem
81
144
  - Added support for decimal day start
82
145
 
83
- ## 2.5.2
146
+ ## 2.5.2 (2016-02-16)
84
147
 
85
148
  - Added `dates` option to return dates for day, week, month, quarter, and year
86
149
 
87
- ## 2.5.1
150
+ ## 2.5.1 (2016-02-03)
88
151
 
89
152
  - Added `group_by_quarter`
90
153
  - Added `default_value` option
@@ -92,13 +155,13 @@ Breaking changes
92
155
  - Raise `ArgumentError` if no field specified
93
156
  - Added support for ActiveRecord 5 beta
94
157
 
95
- ## 2.5.0
158
+ ## 2.5.0 (2015-09-29)
96
159
 
97
160
  - Added `group_by_period` method
98
161
  - Added `current` option
99
162
  - Raise `ArgumentError` if no block given to enumerable
100
163
 
101
- ## 2.4.0
164
+ ## 2.4.0 (2014-12-28)
102
165
 
103
166
  - Added localization
104
167
  - Added `carry_forward` option
@@ -106,77 +169,77 @@ Breaking changes
106
169
  - Fixed issue w/ Brasilia Summer Time
107
170
  - Fixed issues w/ ActiveRecord 4.2
108
171
 
109
- ## 2.3.0
172
+ ## 2.3.0 (2014-08-31)
110
173
 
111
174
  - Raise error when ActiveRecord::Base.default_timezone is not `:utc`
112
175
  - Added `day_of_month`
113
176
  - Added `month_of_year`
114
177
  - Do not quote column name
115
178
 
116
- ## 2.2.1
179
+ ## 2.2.1 (2014-06-23)
117
180
 
118
181
  - Fixed ActiveRecord 3 associations
119
182
 
120
- ## 2.2.0
183
+ ## 2.2.0 (2014-06-22)
121
184
 
122
185
  - Added support for arrays and hashes
123
186
 
124
- ## 2.1.1
187
+ ## 2.1.1 (2014-05-17)
125
188
 
126
189
  - Fixed format option with multiple groups
127
190
  - Better error message if time zone support is missing for MySQL
128
191
 
129
- ## 2.1.0
192
+ ## 2.1.0 (2014-03-16)
130
193
 
131
194
  - Added last option
132
195
  - Added format option
133
196
 
134
- ## 2.0.4
197
+ ## 2.0.4 (2014-03-12)
135
198
 
136
199
  - Added multiple groups
137
200
  - Added order
138
201
  - Subsequent methods no longer modify relation
139
202
 
140
- ## 2.0.3
203
+ ## 2.0.3 (2014-03-11)
141
204
 
142
205
  - Implemented respond_to?
143
206
 
144
- ## 2.0.2
207
+ ## 2.0.2 (2014-03-11)
145
208
 
146
209
  - where, joins, and includes no longer need to be before the group_by method
147
210
 
148
- ## 2.0.1
211
+ ## 2.0.1 (2014-03-07)
149
212
 
150
213
  - Use time zone instead of UTC for results
151
214
 
152
- ## 2.0.0
215
+ ## 2.0.0 (2014-03-07)
153
216
 
154
217
  - Returns entire series by default
155
218
  - Added day_start option
156
219
  - Better interface
157
220
 
158
- ## 1.0.5
221
+ ## 1.0.5 (2014-03-06)
159
222
 
160
223
  - Added global time_zone option
161
224
 
162
- ## 1.0.4
225
+ ## 1.0.4 (2013-07-20)
163
226
 
164
227
  - Added global week_start option
165
228
  - Fixed bug with NULL values and series
166
229
 
167
- ## 1.0.3
230
+ ## 1.0.3 (2013-07-05)
168
231
 
169
232
  - Fixed deprecation warning when used with will_paginate
170
233
  - Fixed bug with DateTime series
171
234
 
172
- ## 1.0.2
235
+ ## 1.0.2 (2013-06-10)
173
236
 
174
237
  - Added :start option for custom week start for group_by_week
175
238
 
176
- ## 1.0.1
239
+ ## 1.0.1 (2013-06-03)
177
240
 
178
241
  - Fixed series for Rails < 3.2 and MySQL
179
242
 
180
- ## 1.0.0
243
+ ## 1.0.0 (2013-05-15)
181
244
 
182
245
  - First major release
data/CONTRIBUTING.md CHANGED
@@ -57,7 +57,7 @@ brew services start mysql
57
57
 
58
58
  # create databases
59
59
  createdb groupdate_test
60
- mysql -u root -e "create database groupdate_test"
60
+ mysqladmin create groupdate_test
61
61
  mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
62
62
 
63
63
  # clone the repo and run the tests
data/LICENSE.txt CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2013-2019 Andrew Kane
1
+ Copyright (c) 2013-2022 Andrew Kane
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -15,14 +15,14 @@ Supports PostgreSQL, MySQL, and Redshift, plus arrays and hashes (and limited su
15
15
 
16
16
  :cupid: Goes hand in hand with [Chartkick](https://www.chartkick.com)
17
17
 
18
- [![Build Status](https://travis-ci.org/ankane/groupdate.svg?branch=master)](https://travis-ci.org/ankane/groupdate)
18
+ [![Build Status](https://github.com/ankane/groupdate/workflows/build/badge.svg?branch=master)](https://github.com/ankane/groupdate/actions)
19
19
 
20
20
  ## Installation
21
21
 
22
22
  Add this line to your application’s Gemfile:
23
23
 
24
24
  ```ruby
25
- gem 'groupdate'
25
+ gem "groupdate"
26
26
  ```
27
27
 
28
28
  For MySQL and SQLite, also follow [these instructions](#additional-instructions).
@@ -32,9 +32,9 @@ For MySQL and SQLite, also follow [these instructions](#additional-instructions)
32
32
  ```ruby
33
33
  User.group_by_day(:created_at).count
34
34
  # {
35
- # Sat, 28 May 2016 => 50,
36
- # Sun, 29 May 2016 => 100,
37
- # Mon, 30 May 2016 => 34
35
+ # Sat, 24 May 2020 => 50,
36
+ # Sun, 25 May 2020 => 100,
37
+ # Mon, 26 May 2020 => 34
38
38
  # }
39
39
  ```
40
40
 
@@ -53,12 +53,14 @@ You can group by:
53
53
 
54
54
  and
55
55
 
56
+ - minute_of_hour
56
57
  - hour_of_day
57
58
  - day_of_week (Sunday = 0, Monday = 1, etc)
58
59
  - day_of_month
60
+ - day_of_year
59
61
  - month_of_year
60
62
 
61
- Use it anywhere you can use `group`. Works with `count`, `sum`, `minimum`, `maximum`, and `average`. For `median`, check out [ActiveMedian](https://github.com/ankane/active_median). For other aggregate functions, including multiple together, check out [CalculateAll](https://github.com/codesnik/calculate-all).
63
+ Use it anywhere you can use `group`. Works with `count`, `sum`, `minimum`, `maximum`, and `average`. For `median` and `percentile`, check out [ActiveMedian](https://github.com/ankane/active_median).
62
64
 
63
65
  ### Time Zones
64
66
 
@@ -73,9 +75,9 @@ or
73
75
  ```ruby
74
76
  User.group_by_week(:created_at, time_zone: "Pacific Time (US & Canada)").count
75
77
  # {
76
- # Sun, 06 Mar 2016 => 70,
77
- # Sun, 13 Mar 2016 => 54,
78
- # Sun, 20 Mar 2016 => 80
78
+ # Sun, 08 Mar 2020 => 70,
79
+ # Sun, 15 Mar 2020 => 54,
80
+ # Sun, 22 Mar 2020 => 80
79
81
  # }
80
82
  ```
81
83
 
@@ -86,13 +88,13 @@ Time zone objects also work. To see a list of available time zones in Rails, run
86
88
  Weeks start on Sunday by default. Change this with:
87
89
 
88
90
  ```ruby
89
- Groupdate.week_start = :mon # first three letters of day
91
+ Groupdate.week_start = :monday
90
92
  ```
91
93
 
92
94
  or
93
95
 
94
96
  ```ruby
95
- User.group_by_week(:created_at, week_start: :mon).count
97
+ User.group_by_week(:created_at, week_start: :monday).count
96
98
  ```
97
99
 
98
100
  ### Day Start
@@ -146,8 +148,8 @@ To get keys in a different format, use:
146
148
  ```ruby
147
149
  User.group_by_month(:created_at, format: "%b %Y").count
148
150
  # {
149
- # "Jan 2015" => 10
150
- # "Feb 2015" => 12
151
+ # "Jan 2020" => 10
152
+ # "Feb 2020" => 12
151
153
  # }
152
154
  ```
153
155
 
@@ -192,6 +194,14 @@ User.group_by_period(params[:period], :created_at, permit: ["day", "week"]).coun
192
194
 
193
195
  Raises an `ArgumentError` for unpermitted periods.
194
196
 
197
+ ### Custom Duration
198
+
199
+ To group by a specific number of minutes or seconds, use:
200
+
201
+ ```ruby
202
+ User.group_by_minute(:created_at, n: 10).count # 10 minutes
203
+ ```
204
+
195
205
  ### Date Columns
196
206
 
197
207
  If grouping on date columns which don’t need time zone conversion, use:
@@ -200,17 +210,12 @@ If grouping on date columns which don’t need time zone conversion, use:
200
210
  User.group_by_week(:created_on, time_zone: false).count
201
211
  ```
202
212
 
203
- ### User Input
213
+ ### Default Scopes
204
214
 
205
- If passing user input as the column, be sure to sanitize it first [like you must](https://rails-sqli.org/) with `group`.
215
+ If you use Postgres and have a default scope that uses `order`, you may get a `column must appear in the GROUP BY clause` error (just like with Active Record’s `group` method). Remove the `order` scope with:
206
216
 
207
217
  ```ruby
208
- column = params[:column]
209
-
210
- # check against permitted columns
211
- raise "Unpermitted column" unless ["column_a", "column_b"].include?(column)
212
-
213
- User.group_by_day(column).count
218
+ User.unscope(:order).group_by_day(:count).count
214
219
  ```
215
220
 
216
221
  ## Arrays and Hashes
@@ -225,24 +230,28 @@ Supports the same options as above
225
230
  users.group_by_day(time_zone: time_zone) { |u| u.created_at }
226
231
  ```
227
232
 
233
+ Get the entire series with:
234
+
235
+ ```ruby
236
+ users.group_by_day(series: true) { |u| u.created_at }
237
+ ```
238
+
228
239
  Count
229
240
 
230
241
  ```ruby
231
- Hash[ users.group_by_day { |u| u.created_at }.map { |k, v| [k, v.size] } ]
242
+ users.group_by_day { |u| u.created_at }.to_h { |k, v| [k, v.count] }
232
243
  ```
233
244
 
234
245
  ## Additional Instructions
235
246
 
236
247
  ### For MySQL
237
248
 
238
- [Time zone support](https://dev.mysql.com/doc/refman/5.7/en/time-zone-support.html) must be installed on the server.
249
+ [Time zone support](https://dev.mysql.com/doc/refman/8.0/en/time-zone-support.html) must be installed on the server.
239
250
 
240
251
  ```sh
241
252
  mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
242
253
  ```
243
254
 
244
- or copy and paste [these statements](https://gist.githubusercontent.com/ankane/1d6b0022173186accbf0/raw/time_zone_support.sql) into a SQL console.
245
-
246
255
  You can confirm it worked with:
247
256
 
248
257
  ```sql
@@ -256,7 +265,7 @@ It should return the time instead of `NULL`.
256
265
  Groupdate has limited support for SQLite.
257
266
 
258
267
  - No time zone support
259
- - No `day_start` or `week_start` options
268
+ - No `day_start` option
260
269
  - No `group_by_quarter` method
261
270
 
262
271
  If your application’s time zone is set to something other than `Etc/UTC` (the default), create an initializer with:
@@ -267,36 +276,20 @@ Groupdate.time_zone = false
267
276
 
268
277
  ## Upgrading
269
278
 
270
- ### 4.0
271
-
272
- Groupdate 4.0 brings a number of improvements. Here are a few to be aware of:
273
-
274
- - `group_by` methods return an `ActiveRecord::Relation` instead of a `Groupdate::Series`
275
- - Invalid options now throw an `ArgumentError`
276
- - `week_start` now affects `day_of_week`
277
- - Custom calculation methods are supported by default
278
-
279
- ### 3.0
279
+ ### 6.0
280
280
 
281
- Groupdate 3.0 brings a number of improvements. Here are a few to be aware of:
281
+ Groupdate 6.0 protects against unsafe input by default. For non-attribute arguments, use:
282
282
 
283
- - `Date` objects are now returned for day, week, month, quarter, and year by default. Use `dates: false` for the previous behavior, or change this globally with `Groupdate.dates = false`.
284
- - Array and hash methods no longer return the entire series by default. Use `series: true` for the previous behavior.
285
- - The `series: false` option now returns the correct type and order, and plays nicely with other options.
286
-
287
- ### 2.0
288
-
289
- Groupdate 2.0 brings a number of improvements. Here are two things to be aware of:
283
+ ```ruby
284
+ User.group_by_day(Arel.sql(known_safe_value)).count
285
+ ```
290
286
 
291
- - the entire series is returned by default
292
- - `ActiveSupport::TimeWithZone` keys are now returned for every database adapter - adapters previously returned `Time` or `String` keys
287
+ Also, the `dates` option has been removed.
293
288
 
294
289
  ## History
295
290
 
296
291
  View the [changelog](https://github.com/ankane/groupdate/blob/master/CHANGELOG.md)
297
292
 
298
- Groupdate follows [Semantic Versioning](https://semver.org/)
299
-
300
293
  ## Contributing
301
294
 
302
295
  Everyone is encouraged to help improve this project. Here are a few ways you can help:
@@ -0,0 +1,51 @@
1
+ module Groupdate
2
+ module Adapters
3
+ class BaseAdapter
4
+ attr_reader :period, :column, :day_start, :week_start, :n_seconds
5
+
6
+ def initialize(relation, column:, period:, time_zone:, time_range:, week_start:, day_start:, n_seconds:)
7
+ @relation = relation
8
+ @column = column
9
+ @period = period
10
+ @time_zone = time_zone
11
+ @time_range = time_range
12
+ @week_start = week_start
13
+ @day_start = day_start
14
+ @n_seconds = n_seconds
15
+
16
+ if ActiveRecord::VERSION::MAJOR >= 7
17
+ if ActiveRecord.default_timezone == :local
18
+ raise Groupdate::Error, "ActiveRecord.default_timezone must be :utc to use Groupdate"
19
+ end
20
+ else
21
+ if relation.default_timezone == :local
22
+ raise Groupdate::Error, "ActiveRecord::Base.default_timezone must be :utc to use Groupdate"
23
+ end
24
+ end
25
+ end
26
+
27
+ def generate
28
+ @relation.group(group_clause).where(*where_clause)
29
+ end
30
+
31
+ private
32
+
33
+ def where_clause
34
+ if @time_range.is_a?(Range)
35
+ if @time_range.end
36
+ op = @time_range.exclude_end? ? "<" : "<="
37
+ if @time_range.begin
38
+ ["#{column} >= ? AND #{column} #{op} ?", @time_range.begin, @time_range.end]
39
+ else
40
+ ["#{column} #{op} ?", @time_range.end]
41
+ end
42
+ else
43
+ ["#{column} >= ?", @time_range.begin]
44
+ end
45
+ else
46
+ ["#{column} IS NOT NULL"]
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,63 @@
1
+ module Groupdate
2
+ module Adapters
3
+ class MySQLAdapter < BaseAdapter
4
+ def group_clause
5
+ time_zone = @time_zone.tzinfo.name
6
+ day_start_column = "CONVERT_TZ(#{column}, '+00:00', ?) - INTERVAL ? second"
7
+
8
+ query =
9
+ case period
10
+ when :minute_of_hour
11
+ ["MINUTE(#{day_start_column})", time_zone, day_start]
12
+ when :hour_of_day
13
+ ["HOUR(#{day_start_column})", time_zone, day_start]
14
+ when :day_of_week
15
+ ["DAYOFWEEK(#{day_start_column}) - 1", time_zone, day_start]
16
+ when :day_of_month
17
+ ["DAYOFMONTH(#{day_start_column})", time_zone, day_start]
18
+ when :day_of_year
19
+ ["DAYOFYEAR(#{day_start_column})", time_zone, day_start]
20
+ when :month_of_year
21
+ ["MONTH(#{day_start_column})", time_zone, day_start]
22
+ when :week
23
+ ["CAST(DATE_FORMAT(#{day_start_column} - INTERVAL ((? + DAYOFWEEK(#{day_start_column})) % 7) DAY, '%Y-%m-%d') AS DATE)", time_zone, day_start, 12 - week_start, time_zone, day_start]
24
+ when :quarter
25
+ ["CAST(CONCAT(YEAR(#{day_start_column}), '-', LPAD(1 + 3 * (QUARTER(#{day_start_column}) - 1), 2, '00'), '-01') AS DATE)", time_zone, day_start, time_zone, day_start]
26
+ when :day, :month, :year
27
+ format =
28
+ case period
29
+ when :day
30
+ "%Y-%m-%d"
31
+ when :month
32
+ "%Y-%m-01"
33
+ else # year
34
+ "%Y-01-01"
35
+ end
36
+
37
+ ["CAST(DATE_FORMAT(#{day_start_column}, ?) AS DATE)", time_zone, day_start, format]
38
+ when :custom
39
+ ["FROM_UNIXTIME((UNIX_TIMESTAMP(#{column}) DIV ?) * ?)", n_seconds, n_seconds]
40
+ else
41
+ format =
42
+ case period
43
+ when :second
44
+ "%Y-%m-%d %H:%i:%S"
45
+ when :minute
46
+ "%Y-%m-%d %H:%i:00"
47
+ else # hour
48
+ "%Y-%m-%d %H:00:00"
49
+ end
50
+
51
+ ["CONVERT_TZ(DATE_FORMAT(#{day_start_column}, ?) + INTERVAL ? second, ?, '+00:00')", time_zone, day_start, format, day_start, time_zone]
52
+ end
53
+
54
+ clean_group_clause(@relation.send(:sanitize_sql_array, query))
55
+ end
56
+
57
+ def clean_group_clause(clause)
58
+ # zero quoted in Active Record 7+
59
+ clause.gsub(/ (\-|\+) INTERVAL 0 second/, "").gsub(/ (\-|\+) INTERVAL '0' second/, "")
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,46 @@
1
+ module Groupdate
2
+ module Adapters
3
+ class PostgreSQLAdapter < BaseAdapter
4
+ def group_clause
5
+ time_zone = @time_zone.tzinfo.name
6
+ day_start_column = "#{column}::timestamptz AT TIME ZONE ? - INTERVAL ?"
7
+ day_start_interval = "#{day_start} second"
8
+
9
+ query =
10
+ case period
11
+ when :minute_of_hour
12
+ ["EXTRACT(MINUTE FROM #{day_start_column})::integer", time_zone, day_start_interval]
13
+ when :hour_of_day
14
+ ["EXTRACT(HOUR FROM #{day_start_column})::integer", time_zone, day_start_interval]
15
+ when :day_of_week
16
+ ["EXTRACT(DOW FROM #{day_start_column})::integer", time_zone, day_start_interval]
17
+ when :day_of_month
18
+ ["EXTRACT(DAY FROM #{day_start_column})::integer", time_zone, day_start_interval]
19
+ when :day_of_year
20
+ ["EXTRACT(DOY FROM #{day_start_column})::integer", time_zone, day_start_interval]
21
+ when :month_of_year
22
+ ["EXTRACT(MONTH FROM #{day_start_column})::integer", time_zone, day_start_interval]
23
+ when :week
24
+ ["(DATE_TRUNC('day', #{day_start_column} - INTERVAL '1 day' * ((? + EXTRACT(DOW FROM #{day_start_column})::integer) % 7)) + INTERVAL ?)::date", time_zone, day_start_interval, 13 - week_start, time_zone, day_start_interval, day_start_interval]
25
+ when :custom
26
+ if @relation.connection.adapter_name == "Redshift"
27
+ ["TIMESTAMP 'epoch' + (FLOOR(EXTRACT(EPOCH FROM #{column}::timestamp) / ?) * ?) * INTERVAL '1 second'", n_seconds, n_seconds]
28
+ else
29
+ ["TO_TIMESTAMP(FLOOR(EXTRACT(EPOCH FROM #{column}::timestamptz) / ?) * ?)", n_seconds, n_seconds]
30
+ end
31
+ when :day, :month, :quarter, :year
32
+ ["DATE_TRUNC(?, #{day_start_column})::date", period, time_zone, day_start_interval]
33
+ else
34
+ # day start is always 0 for seconds, minute, hour
35
+ ["DATE_TRUNC(?, #{day_start_column}) AT TIME ZONE ?", period, time_zone, day_start_interval, time_zone]
36
+ end
37
+
38
+ clean_group_clause(@relation.send(:sanitize_sql_array, query))
39
+ end
40
+
41
+ def clean_group_clause(clause)
42
+ clause.gsub(/ (\-|\+) INTERVAL '0 second'/, "")
43
+ end
44
+ end
45
+ end
46
+ end