groupdate 4.1.2 → 6.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
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