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 +4 -4
- data/CHANGELOG.md +97 -34
- data/CONTRIBUTING.md +1 -1
- data/LICENSE.txt +1 -1
- data/README.md +41 -48
- data/lib/groupdate/adapters/base_adapter.rb +51 -0
- data/lib/groupdate/adapters/mysql_adapter.rb +63 -0
- data/lib/groupdate/adapters/postgresql_adapter.rb +46 -0
- data/lib/groupdate/adapters/sqlite_adapter.rb +51 -0
- data/lib/groupdate/enumerable.rb +9 -15
- data/lib/groupdate/magic.rb +111 -12
- data/lib/groupdate/query_methods.rb +3 -10
- data/lib/groupdate/relation.rb +3 -0
- data/lib/groupdate/series_builder.rb +112 -76
- data/lib/groupdate/version.rb +1 -1
- data/lib/groupdate.rb +29 -7
- metadata +15 -110
- data/lib/groupdate/relation_builder.rb +0 -186
@@ -0,0 +1,51 @@
|
|
1
|
+
module Groupdate
|
2
|
+
module Adapters
|
3
|
+
class SQLiteAdapter < BaseAdapter
|
4
|
+
def group_clause
|
5
|
+
raise Groupdate::Error, "Time zones not supported for SQLite" unless @time_zone.utc_offset.zero?
|
6
|
+
raise Groupdate::Error, "day_start not supported for SQLite" unless day_start.zero?
|
7
|
+
|
8
|
+
query =
|
9
|
+
if period == :week
|
10
|
+
["strftime('%Y-%m-%d', #{column}, '-6 days', ?)", "weekday #{(week_start + 1) % 7}"]
|
11
|
+
elsif period == :custom
|
12
|
+
["datetime((strftime('%s', #{column}) / ?) * ?, 'unixepoch')", n_seconds, n_seconds]
|
13
|
+
else
|
14
|
+
format =
|
15
|
+
case period
|
16
|
+
when :minute_of_hour
|
17
|
+
"%M"
|
18
|
+
when :hour_of_day
|
19
|
+
"%H"
|
20
|
+
when :day_of_week
|
21
|
+
"%w"
|
22
|
+
when :day_of_month
|
23
|
+
"%d"
|
24
|
+
when :day_of_year
|
25
|
+
"%j"
|
26
|
+
when :month_of_year
|
27
|
+
"%m"
|
28
|
+
when :second
|
29
|
+
"%Y-%m-%d %H:%M:%S UTC"
|
30
|
+
when :minute
|
31
|
+
"%Y-%m-%d %H:%M:00 UTC"
|
32
|
+
when :hour
|
33
|
+
"%Y-%m-%d %H:00:00 UTC"
|
34
|
+
when :day
|
35
|
+
"%Y-%m-%d"
|
36
|
+
when :month
|
37
|
+
"%Y-%m-01"
|
38
|
+
when :quarter
|
39
|
+
raise Groupdate::Error, "Quarter not supported for SQLite"
|
40
|
+
else # year
|
41
|
+
"%Y-01-01"
|
42
|
+
end
|
43
|
+
|
44
|
+
["strftime(?, #{column})", format]
|
45
|
+
end
|
46
|
+
|
47
|
+
@relation.send(:sanitize_sql_array, query)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/groupdate/enumerable.rb
CHANGED
@@ -1,31 +1,25 @@
|
|
1
1
|
module Enumerable
|
2
2
|
Groupdate::PERIODS.each do |period|
|
3
|
-
define_method :"group_by_#{period}" do |*args, &block|
|
3
|
+
define_method :"group_by_#{period}" do |*args, **options, &block|
|
4
4
|
if block
|
5
|
-
|
5
|
+
raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0)" if args.any?
|
6
|
+
Groupdate::Magic::Enumerable.group_by(self, period, options, &block)
|
6
7
|
elsif respond_to?(:scoping)
|
7
|
-
scoping { @klass.
|
8
|
+
scoping { @klass.group_by_period(period, *args, **options, &block) }
|
8
9
|
else
|
9
10
|
raise ArgumentError, "no block given"
|
10
11
|
end
|
11
12
|
end
|
12
13
|
end
|
13
14
|
|
14
|
-
def group_by_period(*args, &block)
|
15
|
+
def group_by_period(period, *args, **options, &block)
|
15
16
|
if block || !respond_to?(:scoping)
|
16
|
-
|
17
|
-
options = args[1] || {}
|
17
|
+
raise ArgumentError, "wrong number of arguments (given #{args.size + 1}, expected 1)" if args.any?
|
18
18
|
|
19
|
-
|
20
|
-
#
|
21
|
-
permitted_periods = ((options.delete(:permit) || Groupdate::PERIODS).map(&:to_sym) & Groupdate::PERIODS).map(&:to_s)
|
22
|
-
if permitted_periods.include?(period.to_s)
|
23
|
-
send("group_by_#{period}", options, &block)
|
24
|
-
else
|
25
|
-
raise ArgumentError, "Unpermitted period"
|
26
|
-
end
|
19
|
+
Groupdate::Magic.validate_period(period, options.delete(:permit))
|
20
|
+
send("group_by_#{period}", **options, &block)
|
27
21
|
else
|
28
|
-
scoping { @klass.
|
22
|
+
scoping { @klass.group_by_period(period, *args, **options, &block) }
|
29
23
|
end
|
30
24
|
end
|
31
25
|
end
|
data/lib/groupdate/magic.rb
CHANGED
@@ -2,18 +2,52 @@ require "i18n"
|
|
2
2
|
|
3
3
|
module Groupdate
|
4
4
|
class Magic
|
5
|
-
|
5
|
+
DAYS = [:monday, :tuesday, :wednesday, :thursday, :friday, :saturday, :sunday]
|
6
|
+
|
7
|
+
attr_accessor :period, :options, :group_index, :n_seconds
|
6
8
|
|
7
9
|
def initialize(period:, **options)
|
8
10
|
@period = period
|
9
11
|
@options = options
|
10
12
|
|
11
|
-
|
13
|
+
validate_keywords
|
14
|
+
validate_arguments
|
15
|
+
|
16
|
+
if options[:n]
|
17
|
+
raise ArgumentError, "n must be a positive integer" if !options[:n].is_a?(Integer) || options[:n] < 1
|
18
|
+
@period = :custom
|
19
|
+
@n_seconds = options[:n].to_i
|
20
|
+
@n_seconds *= 60 if period == :minute
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def validate_keywords
|
25
|
+
known_keywords = [:time_zone, :series, :format, :locale, :range, :reverse]
|
26
|
+
|
27
|
+
if %i[week day_of_week].include?(period)
|
28
|
+
known_keywords << :week_start
|
29
|
+
end
|
30
|
+
|
31
|
+
if %i[day week month quarter year day_of_week hour_of_day day_of_month day_of_year month_of_year].include?(period)
|
32
|
+
known_keywords << :day_start
|
33
|
+
else
|
34
|
+
# prevent Groupdate.day_start from applying
|
35
|
+
@day_start = 0
|
36
|
+
end
|
37
|
+
|
38
|
+
if %i[second minute].include?(period)
|
39
|
+
known_keywords << :n
|
40
|
+
end
|
41
|
+
|
42
|
+
unknown_keywords = options.keys - known_keywords
|
12
43
|
raise ArgumentError, "unknown keywords: #{unknown_keywords.join(", ")}" if unknown_keywords.any?
|
44
|
+
end
|
13
45
|
|
14
|
-
|
15
|
-
|
16
|
-
raise
|
46
|
+
def validate_arguments
|
47
|
+
# TODO better messages
|
48
|
+
raise ArgumentError, "Unrecognized time zone" unless time_zone
|
49
|
+
raise ArgumentError, "Unrecognized :week_start option" unless week_start
|
50
|
+
raise ArgumentError, ":day_start must be between 0 and 24" if (day_start / 3600) < 0 || (day_start / 3600) >= 24
|
17
51
|
end
|
18
52
|
|
19
53
|
def time_zone
|
@@ -25,21 +59,38 @@ module Groupdate
|
|
25
59
|
end
|
26
60
|
|
27
61
|
def week_start
|
28
|
-
@week_start ||=
|
62
|
+
@week_start ||= begin
|
63
|
+
v = (options[:week_start] || Groupdate.week_start).to_sym
|
64
|
+
DAYS.index(v) || [:mon, :tue, :wed, :thu, :fri, :sat, :sun].index(v)
|
65
|
+
end
|
29
66
|
end
|
30
67
|
|
31
68
|
def day_start
|
32
69
|
@day_start ||= ((options[:day_start] || Groupdate.day_start).to_f * 3600).round
|
33
70
|
end
|
34
71
|
|
72
|
+
def range
|
73
|
+
@range ||= begin
|
74
|
+
time_range = options[:range]
|
75
|
+
|
76
|
+
if time_range.is_a?(Range) && time_range.begin.nil? && time_range.end.nil?
|
77
|
+
nil
|
78
|
+
else
|
79
|
+
time_range
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
35
84
|
def series_builder
|
36
85
|
@series_builder ||=
|
37
86
|
SeriesBuilder.new(
|
38
87
|
**options,
|
39
88
|
period: period,
|
40
89
|
time_zone: time_zone,
|
90
|
+
range: range,
|
41
91
|
day_start: day_start,
|
42
|
-
week_start: week_start
|
92
|
+
week_start: week_start,
|
93
|
+
n_seconds: n_seconds
|
43
94
|
)
|
44
95
|
end
|
45
96
|
|
@@ -47,6 +98,11 @@ module Groupdate
|
|
47
98
|
series_builder.time_range
|
48
99
|
end
|
49
100
|
|
101
|
+
def self.validate_period(period, permit)
|
102
|
+
permitted_periods = ((permit || Groupdate::PERIODS).map(&:to_sym) & Groupdate::PERIODS).map(&:to_s)
|
103
|
+
raise ArgumentError, "Unpermitted period" unless permitted_periods.include?(period.to_s)
|
104
|
+
end
|
105
|
+
|
50
106
|
class Enumerable < Magic
|
51
107
|
def group_by(enum, &_block)
|
52
108
|
group = enum.group_by do |v|
|
@@ -85,10 +141,20 @@ module Groupdate
|
|
85
141
|
def cast_method
|
86
142
|
@cast_method ||= begin
|
87
143
|
case period
|
144
|
+
when :minute_of_hour, :hour_of_day, :day_of_month, :day_of_year, :month_of_year
|
145
|
+
lambda { |k| k.to_i }
|
88
146
|
when :day_of_week
|
89
147
|
lambda { |k| (k.to_i - 1 - week_start) % 7 }
|
90
|
-
when :
|
91
|
-
|
148
|
+
when :day, :week, :month, :quarter, :year
|
149
|
+
# TODO keep as date
|
150
|
+
if day_start != 0
|
151
|
+
day_start_hour = day_start / 3600
|
152
|
+
day_start_min = (day_start % 3600) / 60
|
153
|
+
day_start_sec = (day_start % 3600) % 60
|
154
|
+
lambda { |k| k.in_time_zone(time_zone).change(hour: day_start_hour, min: day_start_min, sec: day_start_sec) }
|
155
|
+
else
|
156
|
+
lambda { |k| k.in_time_zone(time_zone) }
|
157
|
+
end
|
92
158
|
else
|
93
159
|
utc = ActiveSupport::TimeZone["UTC"]
|
94
160
|
lambda { |k| (k.is_a?(String) || !k.respond_to?(:to_time) ? utc.parse(k.to_s) : k.to_time).in_time_zone(time_zone) }
|
@@ -133,16 +199,25 @@ module Groupdate
|
|
133
199
|
def self.generate_relation(relation, field:, **options)
|
134
200
|
magic = Groupdate::Magic::Relation.new(**options)
|
135
201
|
|
202
|
+
adapter_name = relation.connection.adapter_name
|
203
|
+
adapter = Groupdate.adapters[adapter_name]
|
204
|
+
raise Groupdate::Error, "Connection adapter not supported: #{adapter_name}" unless adapter
|
205
|
+
|
206
|
+
# very important
|
207
|
+
column = validate_column(field)
|
208
|
+
column = resolve_column(relation, column)
|
209
|
+
|
136
210
|
# generate ActiveRecord relation
|
137
211
|
relation =
|
138
|
-
|
212
|
+
adapter.new(
|
139
213
|
relation,
|
140
|
-
column:
|
214
|
+
column: column,
|
141
215
|
period: magic.period,
|
142
216
|
time_zone: magic.time_zone,
|
143
217
|
time_range: magic.time_range,
|
144
218
|
week_start: magic.week_start,
|
145
|
-
day_start: magic.day_start
|
219
|
+
day_start: magic.day_start,
|
220
|
+
n_seconds: magic.n_seconds
|
146
221
|
).generate
|
147
222
|
|
148
223
|
# add Groupdate info
|
@@ -152,6 +227,30 @@ module Groupdate
|
|
152
227
|
relation
|
153
228
|
end
|
154
229
|
|
230
|
+
class << self
|
231
|
+
# basic version of Active Record disallow_raw_sql!
|
232
|
+
# symbol = column (safe), Arel node = SQL (safe), other = untrusted
|
233
|
+
# matches table.column and column
|
234
|
+
def validate_column(column)
|
235
|
+
unless column.is_a?(Symbol) || column.is_a?(Arel::Nodes::SqlLiteral)
|
236
|
+
column = column.to_s
|
237
|
+
unless /\A\w+(\.\w+)?\z/i.match(column)
|
238
|
+
raise ActiveRecord::UnknownAttributeReference, "Query method called with non-attribute argument(s): #{column.inspect}. Use Arel.sql() for known-safe values."
|
239
|
+
end
|
240
|
+
end
|
241
|
+
column
|
242
|
+
end
|
243
|
+
|
244
|
+
# resolves eagerly
|
245
|
+
# need to convert both where_clause (easy)
|
246
|
+
# and group_clause (not easy) if want to avoid this
|
247
|
+
def resolve_column(relation, column)
|
248
|
+
node = relation.send(:relation).send(:arel_columns, [column]).first
|
249
|
+
node = Arel::Nodes::SqlLiteral.new(node) if node.is_a?(String)
|
250
|
+
relation.connection.visitor.accept(node, Arel::Collectors::SQLString.new).value
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
155
254
|
# allow any options to keep flexible for future
|
156
255
|
def self.process_result(relation, result, **options)
|
157
256
|
relation.groupdate_values.reverse.each do |gv|
|
@@ -1,25 +1,18 @@
|
|
1
1
|
module Groupdate
|
2
2
|
module QueryMethods
|
3
3
|
Groupdate::PERIODS.each do |period|
|
4
|
-
define_method :"group_by_#{period}" do |field,
|
4
|
+
define_method :"group_by_#{period}" do |field, **options|
|
5
5
|
Groupdate::Magic::Relation.generate_relation(self,
|
6
6
|
period: period,
|
7
7
|
field: field,
|
8
|
-
time_zone: time_zone,
|
9
|
-
range: range,
|
10
8
|
**options
|
11
9
|
)
|
12
10
|
end
|
13
11
|
end
|
14
12
|
|
15
13
|
def group_by_period(period, field, permit: nil, **options)
|
16
|
-
|
17
|
-
|
18
|
-
if permitted_periods.include?(period.to_s)
|
19
|
-
send("group_by_#{period}", field, **options)
|
20
|
-
else
|
21
|
-
raise ArgumentError, "Unpermitted period"
|
22
|
-
end
|
14
|
+
Groupdate::Magic.validate_period(period, permit)
|
15
|
+
send("group_by_#{period}", field, **options)
|
23
16
|
end
|
24
17
|
end
|
25
18
|
end
|
data/lib/groupdate/relation.rb
CHANGED
@@ -9,6 +9,9 @@ module Groupdate
|
|
9
9
|
end
|
10
10
|
|
11
11
|
def calculate(*args, &block)
|
12
|
+
# prevent calculate from being called twice
|
13
|
+
return super if ActiveRecord::VERSION::STRING.to_f >= 6.1 && has_include?(args[1])
|
14
|
+
|
12
15
|
default_value = [:count, :sum].include?(args[0]) ? 0 : nil
|
13
16
|
Groupdate.process_result(self, super, default_value: default_value)
|
14
17
|
end
|
@@ -1,29 +1,33 @@
|
|
1
1
|
module Groupdate
|
2
2
|
class SeriesBuilder
|
3
|
-
attr_reader :period, :time_zone, :day_start, :week_start, :options
|
3
|
+
attr_reader :period, :time_zone, :day_start, :week_start, :n_seconds, :options
|
4
4
|
|
5
|
-
|
6
|
-
|
7
|
-
def initialize(period:, time_zone:, day_start:, week_start:, **options)
|
5
|
+
def initialize(period:, time_zone:, day_start:, week_start:, n_seconds:, **options)
|
8
6
|
@period = period
|
9
7
|
@time_zone = time_zone
|
10
8
|
@week_start = week_start
|
11
9
|
@day_start = day_start
|
10
|
+
@n_seconds = n_seconds
|
12
11
|
@options = options
|
13
|
-
@
|
12
|
+
@week_start_key = Groupdate::Magic::DAYS[@week_start] if @week_start
|
14
13
|
end
|
15
14
|
|
16
15
|
def generate(data, default_value:, series_default: true, multiple_groups: false, group_index: nil)
|
17
16
|
series = generate_series(data, multiple_groups, group_index)
|
18
17
|
series = handle_multiple(data, series, multiple_groups, group_index)
|
19
18
|
|
19
|
+
verified_data = {}
|
20
|
+
series.each do |k|
|
21
|
+
verified_data[k] = data.delete(k)
|
22
|
+
end
|
23
|
+
|
20
24
|
unless entire_series?(series_default)
|
21
|
-
series = series.select { |k|
|
25
|
+
series = series.select { |k| verified_data[k] }
|
22
26
|
end
|
23
27
|
|
24
28
|
value = 0
|
25
|
-
result =
|
26
|
-
value =
|
29
|
+
result = series.to_h do |k|
|
30
|
+
value = verified_data[k] || (@options[:carry_forward] && value) || default_value
|
27
31
|
key =
|
28
32
|
if multiple_groups
|
29
33
|
k[0...group_index] + [key_format.call(k[group_index])] + k[(group_index + 1)..-1]
|
@@ -32,22 +36,23 @@ module Groupdate
|
|
32
36
|
end
|
33
37
|
|
34
38
|
[key, value]
|
35
|
-
end]
|
36
|
-
|
37
|
-
# only check for database
|
38
|
-
# only checks remaining keys to avoid expensive calls to round_time
|
39
|
-
if series_default && CHECK_PERIODS.include?(period)
|
40
|
-
check_consistent_time_zone_info(data, multiple_groups, group_index)
|
41
39
|
end
|
42
40
|
|
43
41
|
result
|
44
42
|
end
|
45
43
|
|
46
44
|
def round_time(time)
|
45
|
+
if period == :custom
|
46
|
+
return time_zone.at((time.to_time.to_i / n_seconds) * n_seconds)
|
47
|
+
end
|
48
|
+
|
47
49
|
time = time.to_time.in_time_zone(time_zone)
|
48
50
|
|
49
|
-
|
50
|
-
|
51
|
+
if day_start != 0
|
52
|
+
# apply day_start to a time object that's not affected by DST
|
53
|
+
time = time.change(zone: utc)
|
54
|
+
time -= day_start.seconds
|
55
|
+
end
|
51
56
|
|
52
57
|
time =
|
53
58
|
case period
|
@@ -60,9 +65,7 @@ module Groupdate
|
|
60
65
|
when :day
|
61
66
|
time.beginning_of_day
|
62
67
|
when :week
|
63
|
-
|
64
|
-
weekday = (time.wday - 1) % 7
|
65
|
-
(time - ((7 - week_start + weekday) % 7).days).midnight
|
68
|
+
time.beginning_of_week(@week_start_key)
|
66
69
|
when :month
|
67
70
|
time.beginning_of_month
|
68
71
|
when :quarter
|
@@ -74,17 +77,21 @@ module Groupdate
|
|
74
77
|
when :minute_of_hour
|
75
78
|
time.min
|
76
79
|
when :day_of_week
|
77
|
-
|
80
|
+
time.days_to_week_start(@week_start_key)
|
78
81
|
when :day_of_month
|
79
82
|
time.day
|
80
83
|
when :month_of_year
|
81
84
|
time.month
|
85
|
+
when :day_of_year
|
86
|
+
time.yday
|
82
87
|
else
|
83
88
|
raise Groupdate::Error, "Invalid period"
|
84
89
|
end
|
85
90
|
|
86
|
-
|
87
|
-
|
91
|
+
if day_start != 0 && time.is_a?(Time)
|
92
|
+
time += day_start.seconds
|
93
|
+
time = time.change(zone: time_zone)
|
94
|
+
end
|
88
95
|
|
89
96
|
time
|
90
97
|
end
|
@@ -92,15 +99,36 @@ module Groupdate
|
|
92
99
|
def time_range
|
93
100
|
@time_range ||= begin
|
94
101
|
time_range = options[:range]
|
95
|
-
|
96
|
-
|
97
|
-
#
|
98
|
-
|
99
|
-
|
100
|
-
|
102
|
+
|
103
|
+
if time_range.is_a?(Range)
|
104
|
+
# check types
|
105
|
+
[time_range.begin, time_range.end].each do |v|
|
106
|
+
case v
|
107
|
+
when nil, Date, Time
|
108
|
+
# good
|
109
|
+
else
|
110
|
+
raise ArgumentError, "Range bounds should be Date or Time, not #{v.class.name}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
start = time_range.begin
|
115
|
+
start = start.in_time_zone(time_zone) if start
|
116
|
+
|
117
|
+
exclude_end = time_range.exclude_end?
|
118
|
+
|
119
|
+
finish = time_range.end
|
120
|
+
finish = finish.in_time_zone(time_zone) if finish
|
121
|
+
if time_range.end.is_a?(Date) && !exclude_end
|
122
|
+
finish += 1.day
|
123
|
+
exclude_end = true
|
124
|
+
end
|
125
|
+
|
126
|
+
time_range = Range.new(start, finish, exclude_end)
|
101
127
|
elsif !time_range && options[:last]
|
102
128
|
if period == :quarter
|
103
129
|
step = 3.months
|
130
|
+
elsif period == :custom
|
131
|
+
step = n_seconds
|
104
132
|
elsif 1.respond_to?(period)
|
105
133
|
step = 1.send(period)
|
106
134
|
else
|
@@ -117,7 +145,8 @@ module Groupdate
|
|
117
145
|
if options[:current] == false
|
118
146
|
round_time(start_at - step)...round_time(now)
|
119
147
|
else
|
120
|
-
|
148
|
+
# extend to end of current period
|
149
|
+
round_time(start_at)...(round_time(now) + step)
|
121
150
|
end
|
122
151
|
end
|
123
152
|
end
|
@@ -141,12 +170,14 @@ module Groupdate
|
|
141
170
|
0..59
|
142
171
|
when :day_of_month
|
143
172
|
1..31
|
173
|
+
when :day_of_year
|
174
|
+
1..366
|
144
175
|
when :month_of_year
|
145
176
|
1..12
|
146
177
|
else
|
147
178
|
time_range = self.time_range
|
148
179
|
time_range =
|
149
|
-
if time_range.is_a?(Range)
|
180
|
+
if time_range.is_a?(Range) && time_range.begin && time_range.end
|
150
181
|
time_range
|
151
182
|
else
|
152
183
|
# use first and last values
|
@@ -157,26 +188,41 @@ module Groupdate
|
|
157
188
|
data.keys.sort
|
158
189
|
end
|
159
190
|
|
160
|
-
|
161
|
-
|
162
|
-
|
191
|
+
if time_range.is_a?(Range)
|
192
|
+
if sorted_keys.any?
|
193
|
+
if time_range.begin
|
194
|
+
time_range.begin..sorted_keys.last
|
195
|
+
else
|
196
|
+
Range.new(sorted_keys.first, time_range.end, time_range.exclude_end?)
|
197
|
+
end
|
198
|
+
else
|
199
|
+
nil..nil
|
200
|
+
end
|
201
|
+
else
|
202
|
+
tr = sorted_keys.first..sorted_keys.last
|
203
|
+
if options[:current] == false && sorted_keys.any? && round_time(now) >= tr.last
|
204
|
+
tr = tr.first...round_time(now)
|
205
|
+
end
|
206
|
+
tr
|
163
207
|
end
|
164
|
-
tr
|
165
208
|
end
|
166
209
|
|
167
|
-
if time_range.
|
168
|
-
series = [round_time(time_range.
|
210
|
+
if time_range.begin
|
211
|
+
series = [round_time(time_range.begin)]
|
169
212
|
|
170
213
|
if period == :quarter
|
171
214
|
step = 3.months
|
215
|
+
elsif period == :custom
|
216
|
+
step = n_seconds
|
172
217
|
else
|
173
218
|
step = 1.send(period)
|
174
219
|
end
|
175
220
|
|
176
221
|
last_step = series.last
|
222
|
+
day_start_hour = day_start / 3600
|
177
223
|
loop do
|
178
224
|
next_step = last_step + step
|
179
|
-
next_step = round_time(next_step) if next_step.hour !=
|
225
|
+
next_step = round_time(next_step) if next_step.hour != day_start_hour # add condition to speed up
|
180
226
|
break unless time_range.cover?(next_step)
|
181
227
|
|
182
228
|
if next_step == last_step
|
@@ -195,34 +241,35 @@ module Groupdate
|
|
195
241
|
end
|
196
242
|
|
197
243
|
def key_format
|
198
|
-
|
199
|
-
|
244
|
+
@key_format ||= begin
|
245
|
+
locale = options[:locale] || I18n.locale
|
200
246
|
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
247
|
+
if options[:format]
|
248
|
+
if options[:format].respond_to?(:call)
|
249
|
+
options[:format]
|
250
|
+
else
|
251
|
+
sunday = time_zone.parse("2014-03-02 00:00:00")
|
252
|
+
lambda do |key|
|
253
|
+
case period
|
254
|
+
when :hour_of_day
|
255
|
+
key = sunday + key.hours + day_start.seconds
|
256
|
+
when :minute_of_hour
|
257
|
+
key = sunday + key.minutes + day_start.seconds
|
258
|
+
when :day_of_week
|
259
|
+
key = sunday + key.days + (week_start + 1).days
|
260
|
+
when :day_of_month
|
261
|
+
key = Date.new(2014, 1, key).to_time
|
262
|
+
when :month_of_year
|
263
|
+
key = Date.new(2014, key, 1).to_time
|
264
|
+
end
|
265
|
+
I18n.localize(key, format: options[:format], locale: locale)
|
218
266
|
end
|
219
|
-
I18n.localize(key, format: options[:format], locale: locale)
|
220
267
|
end
|
268
|
+
elsif [:day, :week, :month, :quarter, :year].include?(period)
|
269
|
+
lambda { |k| k.to_date }
|
270
|
+
else
|
271
|
+
lambda { |k| k }
|
221
272
|
end
|
222
|
-
elsif [:day, :week, :month, :quarter, :year].include?(period) && use_dates
|
223
|
-
lambda { |k| k.to_date }
|
224
|
-
else
|
225
|
-
lambda { |k| k }
|
226
273
|
end
|
227
274
|
end
|
228
275
|
|
@@ -242,23 +289,12 @@ module Groupdate
|
|
242
289
|
end
|
243
290
|
end
|
244
291
|
|
245
|
-
def check_consistent_time_zone_info(data, multiple_groups, group_index)
|
246
|
-
keys = data.keys
|
247
|
-
if multiple_groups
|
248
|
-
keys.map! { |k| k[group_index] }
|
249
|
-
keys.uniq!
|
250
|
-
end
|
251
|
-
|
252
|
-
keys.each do |key|
|
253
|
-
if key != round_time(key)
|
254
|
-
# only need to show what database returned since it will cast in Ruby time zone
|
255
|
-
raise Groupdate::Error, "Database and Ruby have inconsistent time zone info. Database returned #{key}"
|
256
|
-
end
|
257
|
-
end
|
258
|
-
end
|
259
|
-
|
260
292
|
def entire_series?(series_default)
|
261
293
|
options.key?(:series) ? options[:series] : series_default
|
262
294
|
end
|
295
|
+
|
296
|
+
def utc
|
297
|
+
@utc ||= ActiveSupport::TimeZone["Etc/UTC"]
|
298
|
+
end
|
263
299
|
end
|
264
300
|
end
|
data/lib/groupdate/version.rb
CHANGED