groupdate 3.2.0 → 3.2.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 +4 -4
- data/.travis.yml +10 -5
- data/CHANGELOG.md +5 -0
- data/CONTRIBUTING.md +67 -0
- data/Gemfile +1 -1
- data/ISSUE_TEMPLATE.md +7 -0
- data/README.md +33 -6
- data/groupdate.gemspec +3 -4
- data/lib/groupdate.rb +1 -1
- data/lib/groupdate/magic.rb +79 -60
- data/lib/groupdate/series.rb +4 -0
- data/lib/groupdate/version.rb +1 -1
- data/test/gemfiles/activerecord50.gemfile +6 -0
- data/test/gemfiles/activerecord52.gemfile +6 -0
- data/test/sqlite_test.rb +5 -0
- data/test/test_helper.rb +43 -2
- metadata +22 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f98770b6255920a055b3d7ab6c9bf36227580077
|
4
|
+
data.tar.gz: 4d7d1eb90c872e35ba89ad0eead165841f1e0506
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4aca35fb45ab76e77535404eb6123efc25bcdf4b2414b7255f8dd62e87e4b1d567eaf367807f93ac9ce18f5a571d752ea4d3dc2c47cc141413ebd49eeb656b31
|
7
|
+
data.tar.gz: 3f4ade3ccaab2acc62a413ca93ff618b1d89c2daa0c4d341b895b76db055dd6c0b208826c12696eb4c3f83900afcea69560f6c9bdf8d34c15e3c781264785e1b
|
data/.travis.yml
CHANGED
@@ -1,12 +1,11 @@
|
|
1
1
|
language: ruby
|
2
2
|
rvm:
|
3
|
-
- 2.2
|
3
|
+
- 2.4.2
|
4
4
|
- jruby-9.1.5.0
|
5
5
|
gemfile:
|
6
6
|
- Gemfile
|
7
|
-
- test/gemfiles/
|
8
|
-
- test/gemfiles/
|
9
|
-
- test/gemfiles/activerecord41.gemfile
|
7
|
+
- test/gemfiles/activerecord52.gemfile
|
8
|
+
- test/gemfiles/activerecord50.gemfile
|
10
9
|
- test/gemfiles/activerecord42.gemfile
|
11
10
|
sudo: false
|
12
11
|
script: RUBYOPT=W0 bundle exec rake test
|
@@ -15,11 +14,17 @@ before_install:
|
|
15
14
|
- mysql -e 'create database groupdate_test;'
|
16
15
|
- mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
|
17
16
|
- psql -c 'create database groupdate_test;' -U postgres
|
17
|
+
env:
|
18
|
+
- TRAVIS=t
|
18
19
|
notifications:
|
19
20
|
email:
|
20
21
|
on_success: never
|
21
22
|
on_failure: change
|
22
23
|
matrix:
|
23
24
|
allow_failures:
|
25
|
+
- rvm: 2.4.2
|
26
|
+
gemfile: test/gemfiles/activerecord52.gemfile
|
24
27
|
- rvm: jruby-9.1.5.0
|
25
|
-
gemfile:
|
28
|
+
gemfile: test/gemfiles/activerecord52.gemfile
|
29
|
+
- rvm: jruby-9.1.5.0
|
30
|
+
gemfile: test/gemfiles/activerecord42.gemfile
|
data/CHANGELOG.md
CHANGED
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
First, thanks for wanting to contribute. You’re awesome! :heart:
|
4
|
+
|
5
|
+
## Questions
|
6
|
+
|
7
|
+
Use [Stack Overflow](https://stackoverflow.com/) with the tag `groupdate`.
|
8
|
+
|
9
|
+
## Feature Requests
|
10
|
+
|
11
|
+
Create an issue. Start the title with `[Idea]`.
|
12
|
+
|
13
|
+
## Issues
|
14
|
+
|
15
|
+
Think you’ve discovered an issue?
|
16
|
+
|
17
|
+
1. Search existing issues to see if it’s been reported.
|
18
|
+
2. Try the `master` branch to make sure it hasn’t been fixed.
|
19
|
+
|
20
|
+
```rb
|
21
|
+
gem "groupdate", github: "ankane/groupdate"
|
22
|
+
```
|
23
|
+
|
24
|
+
If the above steps don’t help, create an issue. Include the complete backtrace for exceptions.
|
25
|
+
|
26
|
+
## Pull Requests
|
27
|
+
|
28
|
+
Fork the project and create a pull request. A few tips:
|
29
|
+
|
30
|
+
- Keep changes to a minimum. If you have multiple features or fixes, submit multiple pull requests.
|
31
|
+
- Follow the existing style. The code should read like it’s written by a single person.
|
32
|
+
- Add one or more tests if possible. Make sure existing tests pass with:
|
33
|
+
|
34
|
+
```sh
|
35
|
+
bundle exec rake test
|
36
|
+
```
|
37
|
+
|
38
|
+
Feel free to open an issue to get feedback on your idea before spending too much time on it.
|
39
|
+
|
40
|
+
## Dev Setup
|
41
|
+
|
42
|
+
On Mac:
|
43
|
+
|
44
|
+
```sh
|
45
|
+
# install and run PostgreSQL
|
46
|
+
brew install postgresql
|
47
|
+
brew services start postgresql
|
48
|
+
|
49
|
+
# install and run MySQL
|
50
|
+
brew install mysql
|
51
|
+
brew services start mysql
|
52
|
+
|
53
|
+
# create databases
|
54
|
+
createdb groupdate_test
|
55
|
+
mysql -u root -e "create database groupdate_test"
|
56
|
+
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
|
57
|
+
|
58
|
+
# clone the repo and run the tests
|
59
|
+
git clone https://github.com/ankane/groupdate.git
|
60
|
+
cd groupdate
|
61
|
+
bundle install
|
62
|
+
bundle exec rake test
|
63
|
+
```
|
64
|
+
|
65
|
+
---
|
66
|
+
|
67
|
+
This contributing guide is released under [CCO](https://creativecommons.org/publicdomain/zero/1.0/) (public domain). Use it for your own project without attribution.
|
data/Gemfile
CHANGED
data/ISSUE_TEMPLATE.md
ADDED
data/README.md
CHANGED
@@ -11,16 +11,12 @@ The simplest way to group by:
|
|
11
11
|
|
12
12
|
:cake: Get the entire series - **the other best part**
|
13
13
|
|
14
|
-
Supports PostgreSQL, MySQL, and Redshift, plus arrays and hashes
|
14
|
+
Supports PostgreSQL, MySQL, and Redshift, plus arrays and hashes (and limited support for [SQLite](#for-sqlite))
|
15
15
|
|
16
|
-
|
16
|
+
:cupid: Goes hand in hand with [Chartkick](https://www.chartkick.com)
|
17
17
|
|
18
18
|
[](https://travis-ci.org/ankane/groupdate)
|
19
19
|
|
20
|
-
:cupid: Goes hand in hand with [Chartkick](http://ankane.github.io/chartkick/)
|
21
|
-
|
22
|
-
**Groupdate 3.0 was just released!** See [instructions for upgrading](#30). If you use Chartkick with Groupdate, we recommend Chartkick 2.0 and above.
|
23
|
-
|
24
20
|
## Get Started
|
25
21
|
|
26
22
|
```ruby
|
@@ -212,6 +208,37 @@ Count
|
|
212
208
|
Hash[ users.group_by_day { |u| u.created_at }.map { |k, v| [k, v.size] } ]
|
213
209
|
```
|
214
210
|
|
211
|
+
## Custom Calculation Methods
|
212
|
+
|
213
|
+
Groupdate knows all of the calculations defined by `ActiveRecord` like `count`,
|
214
|
+
`sum`, or `average`. However you may have your own class level calculation
|
215
|
+
methods that you need to tell Groupdate about. All you have to do is define the
|
216
|
+
class method `groupdate_calculation_methods` returning an array of the method
|
217
|
+
names as symbols.
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
class User < ApplicationRecord
|
221
|
+
def self.groupdate_calculation_methods
|
222
|
+
[:total_sign_ins]
|
223
|
+
end
|
224
|
+
|
225
|
+
def self.total_sign_ins
|
226
|
+
all.sum(:sign_ins)
|
227
|
+
end
|
228
|
+
end
|
229
|
+
```
|
230
|
+
|
231
|
+
Then you can use your custom calculation method:
|
232
|
+
|
233
|
+
```ruby
|
234
|
+
User.group_by_week(:created_at).total_sign_ins
|
235
|
+
```
|
236
|
+
|
237
|
+
Note that even if your method uses one of the calculations from `ActiveRecord`,
|
238
|
+
you'll still need to add it to the `groupdate_calculation_methods` array to have
|
239
|
+
it return the Hash of dates to values. Otherwise it will return a
|
240
|
+
`Groupdate::Series` object.
|
241
|
+
|
215
242
|
## Installation
|
216
243
|
|
217
244
|
Add this line to your application’s Gemfile:
|
data/groupdate.gemspec
CHANGED
@@ -8,7 +8,6 @@ Gem::Specification.new do |spec|
|
|
8
8
|
spec.version = Groupdate::VERSION
|
9
9
|
spec.authors = ["Andrew Kane"]
|
10
10
|
spec.email = ["andrew@chartkick.com"]
|
11
|
-
spec.description = "The simplest way to group temporal data"
|
12
11
|
spec.summary = "The simplest way to group temporal data"
|
13
12
|
spec.homepage = "https://github.com/ankane/groupdate"
|
14
13
|
spec.license = "MIT"
|
@@ -20,7 +19,7 @@ Gem::Specification.new do |spec|
|
|
20
19
|
|
21
20
|
spec.add_dependency "activesupport", ">= 3"
|
22
21
|
|
23
|
-
spec.add_development_dependency "bundler"
|
22
|
+
spec.add_development_dependency "bundler"
|
24
23
|
spec.add_development_dependency "rake"
|
25
24
|
spec.add_development_dependency "minitest"
|
26
25
|
spec.add_development_dependency "activerecord"
|
@@ -30,8 +29,8 @@ Gem::Specification.new do |spec|
|
|
30
29
|
spec.add_development_dependency "activerecord-jdbcmysql-adapter"
|
31
30
|
spec.add_development_dependency "activerecord-jdbcsqlite3-adapter"
|
32
31
|
else
|
33
|
-
spec.add_development_dependency "pg"
|
34
|
-
spec.add_development_dependency "mysql2"
|
32
|
+
spec.add_development_dependency "pg", "< 1"
|
33
|
+
spec.add_development_dependency "mysql2"
|
35
34
|
spec.add_development_dependency "sqlite3"
|
36
35
|
end
|
37
36
|
end
|
data/lib/groupdate.rb
CHANGED
@@ -6,7 +6,7 @@ require "groupdate/magic"
|
|
6
6
|
module Groupdate
|
7
7
|
class Error < RuntimeError; end
|
8
8
|
|
9
|
-
PERIODS = [:second, :minute, :hour, :day, :week, :month, :quarter, :year, :day_of_week, :hour_of_day, :day_of_month, :month_of_year]
|
9
|
+
PERIODS = [:second, :minute, :hour, :day, :week, :month, :quarter, :year, :day_of_week, :hour_of_day, :minute_of_hour, :day_of_month, :month_of_year]
|
10
10
|
# backwards compatibility for anyone who happened to use it
|
11
11
|
FIELDS = PERIODS
|
12
12
|
METHODS = PERIODS.map { |v| :"group_by_#{v}" } + [:group_by_period]
|
data/lib/groupdate/magic.rb
CHANGED
@@ -2,15 +2,15 @@ require "i18n"
|
|
2
2
|
|
3
3
|
module Groupdate
|
4
4
|
class Magic
|
5
|
-
attr_accessor :
|
5
|
+
attr_accessor :period, :options
|
6
6
|
|
7
|
-
def initialize(
|
8
|
-
@
|
7
|
+
def initialize(period, options)
|
8
|
+
@period = period
|
9
9
|
@options = options
|
10
10
|
|
11
11
|
raise Groupdate::Error, "Unrecognized time zone" unless time_zone
|
12
12
|
|
13
|
-
raise Groupdate::Error, "Unrecognized :week_start option" if
|
13
|
+
raise Groupdate::Error, "Unrecognized :week_start option" if period == :week && !week_start
|
14
14
|
end
|
15
15
|
|
16
16
|
def group_by(enum, &_block)
|
@@ -28,13 +28,15 @@ module Groupdate
|
|
28
28
|
adapter_name = relation.connection.adapter_name
|
29
29
|
query =
|
30
30
|
case adapter_name
|
31
|
-
when "MySQL", "Mysql2", "Mysql2Spatial"
|
32
|
-
case
|
31
|
+
when "MySQL", "Mysql2", "Mysql2Spatial", 'Mysql2Rgeo'
|
32
|
+
case period
|
33
33
|
when :day_of_week # Sunday = 0, Monday = 1, etc
|
34
34
|
# use CONCAT for consistent return type (String)
|
35
35
|
["DAYOFWEEK(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?)) - 1", time_zone]
|
36
36
|
when :hour_of_day
|
37
37
|
["(EXTRACT(HOUR from CONVERT_TZ(#{column}, '+00:00', ?)) + 24 - #{day_start / 3600}) % 24", time_zone]
|
38
|
+
when :minute_of_hour
|
39
|
+
["(EXTRACT(MINUTE from CONVERT_TZ(#{column}, '+00:00', ?)))", time_zone]
|
38
40
|
when :day_of_month
|
39
41
|
["DAYOFMONTH(CONVERT_TZ(DATE_SUB(#{column}, INTERVAL #{day_start} second), '+00:00', ?))", time_zone]
|
40
42
|
when :month_of_year
|
@@ -45,7 +47,7 @@ module Groupdate
|
|
45
47
|
["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
48
|
else
|
47
49
|
format =
|
48
|
-
case
|
50
|
+
case period
|
49
51
|
when :second
|
50
52
|
"%Y-%m-%d %H:%i:%S"
|
51
53
|
when :minute
|
@@ -63,62 +65,68 @@ module Groupdate
|
|
63
65
|
["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
66
|
end
|
65
67
|
when "PostgreSQL", "PostGIS"
|
66
|
-
case
|
68
|
+
case period
|
67
69
|
when :day_of_week
|
68
70
|
["EXTRACT(DOW from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
|
69
71
|
when :hour_of_day
|
70
72
|
["EXTRACT(HOUR from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
|
73
|
+
when :minute_of_hour
|
74
|
+
["EXTRACT(MINUTE from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
|
71
75
|
when :day_of_month
|
72
76
|
["EXTRACT(DAY from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
|
73
77
|
when :month_of_year
|
74
78
|
["EXTRACT(MONTH from #{column}::timestamptz AT TIME ZONE ? - INTERVAL '#{day_start} second')::integer", time_zone]
|
75
79
|
when :week # start on Sunday, not PostgreSQL default Monday
|
76
|
-
["(DATE_TRUNC('#{
|
80
|
+
["(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]
|
77
81
|
else
|
78
|
-
["(DATE_TRUNC('#{
|
82
|
+
["(DATE_TRUNC('#{period}', (#{column}::timestamptz - INTERVAL '#{day_start} second') AT TIME ZONE ?) + INTERVAL '#{day_start} second') AT TIME ZONE ?", time_zone, time_zone]
|
79
83
|
end
|
80
84
|
when "SQLite"
|
81
85
|
raise Groupdate::Error, "Time zones not supported for SQLite" unless self.time_zone.utc_offset.zero?
|
82
86
|
raise Groupdate::Error, "day_start not supported for SQLite" unless day_start.zero?
|
83
87
|
raise Groupdate::Error, "week_start not supported for SQLite" unless week_start == 6
|
84
88
|
|
85
|
-
if
|
89
|
+
if period == :week
|
86
90
|
["strftime('%%Y-%%m-%%d 00:00:00 UTC', #{column}, '-6 days', 'weekday 0')"]
|
87
91
|
else
|
88
92
|
format =
|
89
|
-
case
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
93
|
+
case period
|
94
|
+
when :hour_of_day
|
95
|
+
"%H"
|
96
|
+
when :minute_of_hour
|
97
|
+
"%M"
|
98
|
+
when :day_of_week
|
99
|
+
"%w"
|
100
|
+
when :day_of_month
|
101
|
+
"%d"
|
102
|
+
when :month_of_year
|
103
|
+
"%m"
|
104
|
+
when :second
|
105
|
+
"%Y-%m-%d %H:%M:%S UTC"
|
106
|
+
when :minute
|
107
|
+
"%Y-%m-%d %H:%M:00 UTC"
|
108
|
+
when :hour
|
109
|
+
"%Y-%m-%d %H:00:00 UTC"
|
110
|
+
when :day
|
111
|
+
"%Y-%m-%d 00:00:00 UTC"
|
112
|
+
when :month
|
113
|
+
"%Y-%m-01 00:00:00 UTC"
|
114
|
+
when :quarter
|
115
|
+
raise Groupdate::Error, "Quarter not supported for SQLite"
|
116
|
+
else # year
|
117
|
+
"%Y-01-01 00:00:00 UTC"
|
118
|
+
end
|
113
119
|
|
114
120
|
["strftime('#{format.gsub(/%/, '%%')}', #{column})"]
|
115
121
|
end
|
116
122
|
when "Redshift"
|
117
|
-
case
|
123
|
+
case period
|
118
124
|
when :day_of_week # Sunday = 0, Monday = 1, etc.
|
119
125
|
["EXTRACT(DOW from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
|
120
126
|
when :hour_of_day
|
121
127
|
["EXTRACT(HOUR from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
|
128
|
+
when :minute_of_hour
|
129
|
+
["EXTRACT(MINUTE from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
|
122
130
|
when :day_of_month
|
123
131
|
["EXTRACT(DAY from CONVERT_TIMEZONE(?, #{column}::timestamp) - INTERVAL '#{day_start} second')::integer", time_zone]
|
124
132
|
when :month_of_year
|
@@ -128,19 +136,19 @@ module Groupdate
|
|
128
136
|
# always says it is in UTC time, so we must convert
|
129
137
|
# back to UTC to play properly with the rest of Groupdate.
|
130
138
|
#
|
131
|
-
["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,
|
139
|
+
["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]
|
132
140
|
else
|
133
|
-
["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC(?, CONVERT_TIMEZONE(?, #{column}) - INTERVAL '#{day_start} second'))::timestamp + INTERVAL '#{day_start} second'", time_zone,
|
141
|
+
["CONVERT_TIMEZONE(?, 'Etc/UTC', DATE_TRUNC(?, CONVERT_TIMEZONE(?, #{column}) - INTERVAL '#{day_start} second'))::timestamp + INTERVAL '#{day_start} second'", time_zone, period, time_zone]
|
134
142
|
end
|
135
143
|
else
|
136
144
|
raise Groupdate::Error, "Connection adapter not supported: #{adapter_name}"
|
137
145
|
end
|
138
146
|
|
139
|
-
if adapter_name == "MySQL" &&
|
147
|
+
if adapter_name == "MySQL" && period == :week
|
140
148
|
query[0] = "CAST(#{query[0]} AS DATETIME)"
|
141
149
|
end
|
142
150
|
|
143
|
-
group = relation.group(Groupdate::OrderHack.new(relation.send(:sanitize_sql_array, query),
|
151
|
+
group = relation.group(Groupdate::OrderHack.new(relation.send(:sanitize_sql_array, query), period, time_zone))
|
144
152
|
relation =
|
145
153
|
if time_range.is_a?(Range)
|
146
154
|
# doesn't matter whether we include the end of a ... range - it will be excluded later
|
@@ -162,7 +170,7 @@ module Groupdate
|
|
162
170
|
order = relation.order_values.first
|
163
171
|
if order.is_a?(String)
|
164
172
|
parts = order.split(" ")
|
165
|
-
reverse_order = (parts.size == 2 && (parts[0].to_sym ==
|
173
|
+
reverse_order = (parts.size == 2 && (parts[0].to_sym == period || (activerecord42? && parts[0] == "#{relation.quoted_table_name}.#{relation.quoted_primary_key}")) && parts[1].to_s.downcase == "desc")
|
166
174
|
if reverse_order
|
167
175
|
reverse = !reverse
|
168
176
|
relation = relation.reorder(relation.order_values[1..-1])
|
@@ -172,8 +180,8 @@ module Groupdate
|
|
172
180
|
multiple_groups = relation.group_values.size > 1
|
173
181
|
|
174
182
|
cast_method =
|
175
|
-
case
|
176
|
-
when :day_of_week, :hour_of_day, :day_of_month, :month_of_year
|
183
|
+
case period
|
184
|
+
when :day_of_week, :hour_of_day, :day_of_month, :month_of_year, :minute_of_hour
|
177
185
|
lambda { |k| k.to_i }
|
178
186
|
else
|
179
187
|
utc = ActiveSupport::TimeZone["UTC"]
|
@@ -181,13 +189,18 @@ module Groupdate
|
|
181
189
|
end
|
182
190
|
|
183
191
|
result = relation.send(method, *args, &block)
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
192
|
+
if result.is_a?(Hash)
|
193
|
+
missing_time_zone_support = multiple_groups ? (result.keys.first && result.keys.first[@group_index].nil?) : result.key?(nil)
|
194
|
+
if missing_time_zone_support
|
195
|
+
raise Groupdate::Error, "Be sure to install time zone support - https://github.com/ankane/groupdate#for-mysql"
|
196
|
+
end
|
197
|
+
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] }]
|
189
198
|
|
190
|
-
|
199
|
+
series(result, (options.key?(:default_value) ? options[:default_value] : 0), multiple_groups, reverse)
|
200
|
+
else
|
201
|
+
# for ActiveRecord::Calculations methods that don't call calculate, like pluck
|
202
|
+
result
|
203
|
+
end
|
191
204
|
end
|
192
205
|
|
193
206
|
protected
|
@@ -218,12 +231,12 @@ module Groupdate
|
|
218
231
|
last += 1.day unless time_range.exclude_end?
|
219
232
|
time_range = Range.new(time_zone.parse(time_range.first.to_s), last, true)
|
220
233
|
elsif !time_range && options[:last]
|
221
|
-
if
|
234
|
+
if period == :quarter
|
222
235
|
step = 3.months
|
223
|
-
elsif 1.respond_to?(
|
224
|
-
step = 1.send(
|
236
|
+
elsif 1.respond_to?(period)
|
237
|
+
step = 1.send(period)
|
225
238
|
else
|
226
|
-
raise ArgumentError, "Cannot use last option with #{
|
239
|
+
raise ArgumentError, "Cannot use last option with #{period}"
|
227
240
|
end
|
228
241
|
if step
|
229
242
|
now = Time.now
|
@@ -249,11 +262,13 @@ module Groupdate
|
|
249
262
|
reverse = !reverse if options[:reverse]
|
250
263
|
|
251
264
|
series =
|
252
|
-
case
|
265
|
+
case period
|
253
266
|
when :day_of_week
|
254
267
|
0..6
|
255
268
|
when :hour_of_day
|
256
269
|
0..23
|
270
|
+
when :minute_of_hour
|
271
|
+
0..59
|
257
272
|
when :day_of_month
|
258
273
|
1..31
|
259
274
|
when :month_of_year
|
@@ -277,10 +292,10 @@ module Groupdate
|
|
277
292
|
if time_range.first
|
278
293
|
series = [round_time(time_range.first)]
|
279
294
|
|
280
|
-
if
|
295
|
+
if period == :quarter
|
281
296
|
step = 3.months
|
282
297
|
else
|
283
|
-
step = 1.send(
|
298
|
+
step = 1.send(period)
|
284
299
|
end
|
285
300
|
|
286
301
|
last_step = series.last
|
@@ -322,9 +337,11 @@ module Groupdate
|
|
322
337
|
else
|
323
338
|
sunday = time_zone.parse("2014-03-02 00:00:00")
|
324
339
|
lambda do |key|
|
325
|
-
case
|
340
|
+
case period
|
326
341
|
when :hour_of_day
|
327
342
|
key = sunday + key.hours + day_start.seconds
|
343
|
+
when :minute_of_hour
|
344
|
+
key = sunday + key.minutes + day_start.seconds
|
328
345
|
when :day_of_week
|
329
346
|
key = sunday + key.days
|
330
347
|
when :day_of_month
|
@@ -335,7 +352,7 @@ module Groupdate
|
|
335
352
|
I18n.localize(key, format: options[:format], locale: locale)
|
336
353
|
end
|
337
354
|
end
|
338
|
-
elsif [:day, :week, :month, :quarter, :year].include?(
|
355
|
+
elsif [:day, :week, :month, :quarter, :year].include?(period) && use_dates
|
339
356
|
lambda { |k| k.to_date }
|
340
357
|
else
|
341
358
|
lambda { |k| k }
|
@@ -357,7 +374,7 @@ module Groupdate
|
|
357
374
|
time = time.to_time.in_time_zone(time_zone) - day_start.seconds
|
358
375
|
|
359
376
|
time =
|
360
|
-
case
|
377
|
+
case period
|
361
378
|
when :second
|
362
379
|
time.change(usec: 0)
|
363
380
|
when :minute
|
@@ -378,6 +395,8 @@ module Groupdate
|
|
378
395
|
time.beginning_of_year
|
379
396
|
when :hour_of_day
|
380
397
|
time.hour
|
398
|
+
when :minute_of_hour
|
399
|
+
time.min
|
381
400
|
when :day_of_week
|
382
401
|
time.wday
|
383
402
|
when :day_of_month
|
@@ -385,7 +404,7 @@ module Groupdate
|
|
385
404
|
when :month_of_year
|
386
405
|
time.month
|
387
406
|
else
|
388
|
-
raise Groupdate::Error, "Invalid
|
407
|
+
raise Groupdate::Error, "Invalid period"
|
389
408
|
end
|
390
409
|
|
391
410
|
time.is_a?(Time) ? time + day_start.seconds : time
|
data/lib/groupdate/series.rb
CHANGED
data/lib/groupdate/version.rb
CHANGED
data/test/sqlite_test.rb
CHANGED
@@ -17,6 +17,11 @@ class TestSqlite < Minitest::Test
|
|
17
17
|
skip
|
18
18
|
end
|
19
19
|
|
20
|
+
def test_zeros_datetime
|
21
|
+
skip if ENV["TRAVIS"]
|
22
|
+
super
|
23
|
+
end
|
24
|
+
|
20
25
|
def call_method(method, field, options)
|
21
26
|
if method == :quarter || options[:time_zone] || options[:day_start] || options[:week_start] || Groupdate.week_start != :sun || (Time.zone && options[:time_zone] != false)
|
22
27
|
error = assert_raises(Groupdate::Error) { super }
|
data/test/test_helper.rb
CHANGED
@@ -413,6 +413,19 @@ module TestDatabase
|
|
413
413
|
end
|
414
414
|
end
|
415
415
|
|
416
|
+
# unscope
|
417
|
+
|
418
|
+
def test_unscope
|
419
|
+
assert_equal User.all, User.group_by_day(:created_at).unscoped.all
|
420
|
+
end
|
421
|
+
|
422
|
+
# pluck
|
423
|
+
|
424
|
+
def test_pluck
|
425
|
+
create_user "2014-05-01"
|
426
|
+
assert_equal [0], User.group_by_hour_of_day(:created_at).pluck(0)
|
427
|
+
end
|
428
|
+
|
416
429
|
private
|
417
430
|
|
418
431
|
def call_method(method, field, options)
|
@@ -431,8 +444,7 @@ module TestDatabase
|
|
431
444
|
# hack for Redshift adapter, which doesn't return id on creation...
|
432
445
|
user = User.last if user.id.nil?
|
433
446
|
|
434
|
-
|
435
|
-
user.update_attributes(created_at: nil, created_on: nil) if created_at.nil? && is_redshift?
|
447
|
+
user.update_columns(created_at: nil, created_on: nil) if created_at.nil?
|
436
448
|
|
437
449
|
user
|
438
450
|
end
|
@@ -741,6 +753,16 @@ module TestGroupdate
|
|
741
753
|
assert_result :hour_of_day, 0, "2013-01-01 10:00:00", true, day_start: 2
|
742
754
|
end
|
743
755
|
|
756
|
+
# minute of hour
|
757
|
+
|
758
|
+
def test_minute_of_hour_end_of_hour
|
759
|
+
assert_result :minute_of_hour, 59, "2017-02-09 23:59:59"
|
760
|
+
end
|
761
|
+
|
762
|
+
def test_minute_of_hour_beginning_of_hour
|
763
|
+
assert_result :minute_of_hour, 0, "2017-02-09 00:00:00"
|
764
|
+
end
|
765
|
+
|
744
766
|
# day of week
|
745
767
|
|
746
768
|
def test_day_of_week_end_of_day
|
@@ -941,6 +963,15 @@ module TestGroupdate
|
|
941
963
|
assert_equal expected, call_method(:hour_of_day, :created_at, {series: true})
|
942
964
|
end
|
943
965
|
|
966
|
+
def test_zeros_minute_of_hour
|
967
|
+
create_user "2017-02-09 20:05:00 UTC"
|
968
|
+
expected = {}
|
969
|
+
60.times do |n|
|
970
|
+
expected[n] = n == 5 ? 1 : 0
|
971
|
+
end
|
972
|
+
assert_equal expected, call_method(:minute_of_hour, :created_at, {series: true})
|
973
|
+
end
|
974
|
+
|
944
975
|
def test_zeros_day_of_month
|
945
976
|
create_user "1978-12-18"
|
946
977
|
expected = {}
|
@@ -1045,6 +1076,16 @@ module TestGroupdate
|
|
1045
1076
|
assert_format :hour_of_day, "12 am", "%-l %P", day_start: 2
|
1046
1077
|
end
|
1047
1078
|
|
1079
|
+
def test_format_minute_of_hour
|
1080
|
+
create_user "2017-02-09"
|
1081
|
+
assert_format :minute_of_hour, "0", "%-M"
|
1082
|
+
end
|
1083
|
+
|
1084
|
+
def test_format_minute_of_hour_day_start
|
1085
|
+
create_user "2017-02-09"
|
1086
|
+
assert_format :minute_of_hour, "0", "%-M", day_start: 2
|
1087
|
+
end
|
1088
|
+
|
1048
1089
|
def test_format_day_of_week
|
1049
1090
|
create_user "2014-03-01"
|
1050
1091
|
assert_format :day_of_week, "Sat", "%a"
|
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: 3.2.
|
4
|
+
version: 3.2.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:
|
11
|
+
date: 2018-02-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -28,16 +28,16 @@ dependencies:
|
|
28
28
|
name: bundler
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
|
-
- - "
|
31
|
+
- - ">="
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version: '
|
33
|
+
version: '0'
|
34
34
|
type: :development
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
|
-
- - "
|
38
|
+
- - ">="
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version: '
|
40
|
+
version: '0'
|
41
41
|
- !ruby/object:Gem::Dependency
|
42
42
|
name: rake
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
@@ -84,30 +84,30 @@ dependencies:
|
|
84
84
|
name: pg
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
86
86
|
requirements:
|
87
|
-
- - "
|
87
|
+
- - "<"
|
88
88
|
- !ruby/object:Gem::Version
|
89
|
-
version: '
|
89
|
+
version: '1'
|
90
90
|
type: :development
|
91
91
|
prerelease: false
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
93
93
|
requirements:
|
94
|
-
- - "
|
94
|
+
- - "<"
|
95
95
|
- !ruby/object:Gem::Version
|
96
|
-
version: '
|
96
|
+
version: '1'
|
97
97
|
- !ruby/object:Gem::Dependency
|
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'
|
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'
|
111
111
|
- !ruby/object:Gem::Dependency
|
112
112
|
name: sqlite3
|
113
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -122,7 +122,7 @@ dependencies:
|
|
122
122
|
- - ">="
|
123
123
|
- !ruby/object:Gem::Version
|
124
124
|
version: '0'
|
125
|
-
description:
|
125
|
+
description:
|
126
126
|
email:
|
127
127
|
- andrew@chartkick.com
|
128
128
|
executables: []
|
@@ -132,7 +132,9 @@ files:
|
|
132
132
|
- ".gitignore"
|
133
133
|
- ".travis.yml"
|
134
134
|
- CHANGELOG.md
|
135
|
+
- CONTRIBUTING.md
|
135
136
|
- Gemfile
|
137
|
+
- ISSUE_TEMPLATE.md
|
136
138
|
- LICENSE.txt
|
137
139
|
- README.md
|
138
140
|
- Rakefile
|
@@ -152,6 +154,8 @@ files:
|
|
152
154
|
- test/gemfiles/activerecord40.gemfile
|
153
155
|
- test/gemfiles/activerecord41.gemfile
|
154
156
|
- test/gemfiles/activerecord42.gemfile
|
157
|
+
- test/gemfiles/activerecord50.gemfile
|
158
|
+
- test/gemfiles/activerecord52.gemfile
|
155
159
|
- test/gemfiles/redshift.gemfile
|
156
160
|
- test/mysql_test.rb
|
157
161
|
- test/postgresql_test.rb
|
@@ -178,7 +182,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
178
182
|
version: '0'
|
179
183
|
requirements: []
|
180
184
|
rubyforge_project:
|
181
|
-
rubygems_version: 2.6.
|
185
|
+
rubygems_version: 2.6.13
|
182
186
|
signing_key:
|
183
187
|
specification_version: 4
|
184
188
|
summary: The simplest way to group temporal data
|
@@ -189,6 +193,8 @@ test_files:
|
|
189
193
|
- test/gemfiles/activerecord40.gemfile
|
190
194
|
- test/gemfiles/activerecord41.gemfile
|
191
195
|
- test/gemfiles/activerecord42.gemfile
|
196
|
+
- test/gemfiles/activerecord50.gemfile
|
197
|
+
- test/gemfiles/activerecord52.gemfile
|
192
198
|
- test/gemfiles/redshift.gemfile
|
193
199
|
- test/mysql_test.rb
|
194
200
|
- test/postgresql_test.rb
|