groupdate 3.2.0 → 3.2.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![Build Status](https://travis-ci.org/ankane/groupdate.svg?branch=master)](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
|