groupdate 4.3.0 → 5.2.2
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 +31 -0
- data/LICENSE.txt +1 -1
- data/README.md +27 -36
- data/lib/groupdate.rb +28 -4
- data/lib/groupdate/adapters/base_adapter.rb +70 -0
- data/lib/groupdate/adapters/mysql_adapter.rb +56 -0
- data/lib/groupdate/adapters/postgresql_adapter.rb +44 -0
- data/lib/groupdate/adapters/redshift_adapter.rb +39 -0
- data/lib/groupdate/adapters/sqlite_adapter.rb +51 -0
- data/lib/groupdate/enumerable.rb +8 -15
- data/lib/groupdate/magic.rb +72 -11
- data/lib/groupdate/query_methods.rb +3 -12
- data/lib/groupdate/series_builder.rb +150 -71
- data/lib/groupdate/version.rb +1 -1
- metadata +13 -107
- data/lib/groupdate/relation_builder.rb +0 -194
@@ -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 00:00:00 UTC', #{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 00:00:00 UTC"
|
36
|
+
when :month
|
37
|
+
"%Y-%m-01 00:00:00 UTC"
|
38
|
+
when :quarter
|
39
|
+
raise Groupdate::Error, "Quarter not supported for SQLite"
|
40
|
+
else # year
|
41
|
+
"%Y-01-01 00:00:00 UTC"
|
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
@@ -2,11 +2,10 @@ module Enumerable
|
|
2
2
|
Groupdate::PERIODS.each do |period|
|
3
3
|
define_method :"group_by_#{period}" do |*args, **options, &block|
|
4
4
|
if block
|
5
|
-
|
6
|
-
|
7
|
-
Groupdate::Magic::Enumerable.group_by(self, period, (args[0] || {}).merge(options), &block)
|
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)
|
8
7
|
elsif respond_to?(:scoping)
|
9
|
-
scoping { @klass.
|
8
|
+
scoping { @klass.group_by_period(period, *args, **options, &block) }
|
10
9
|
else
|
11
10
|
raise ArgumentError, "no block given"
|
12
11
|
end
|
@@ -15,18 +14,12 @@ module Enumerable
|
|
15
14
|
|
16
15
|
def group_by_period(period, *args, **options, &block)
|
17
16
|
if block || !respond_to?(:scoping)
|
18
|
-
#
|
19
|
-
|
20
|
-
|
21
|
-
#
|
22
|
-
permitted_periods = ((options.delete(:permit) || Groupdate::PERIODS).map(&:to_sym) & Groupdate::PERIODS).map(&:to_s)
|
23
|
-
if permitted_periods.include?(period.to_s)
|
24
|
-
send("group_by_#{period}", **options, &block)
|
25
|
-
else
|
26
|
-
raise ArgumentError, "Unpermitted period"
|
27
|
-
end
|
17
|
+
raise ArgumentError, "wrong number of arguments (given #{args.size + 1}, expected 1)" if args.any?
|
18
|
+
|
19
|
+
Groupdate::Magic.validate_period(period, options.delete(:permit))
|
20
|
+
send("group_by_#{period}", **options, &block)
|
28
21
|
else
|
29
|
-
scoping { @klass.
|
22
|
+
scoping { @klass.group_by_period(period, *args, **options, &block) }
|
30
23
|
end
|
31
24
|
end
|
32
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, :dates, :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,10 @@ 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 :hour_of_day, :day_of_month, :day_of_year, :month_of_year, :minute_of_hour
|
91
|
-
lambda { |k| k.to_i }
|
92
148
|
else
|
93
149
|
utc = ActiveSupport::TimeZone["UTC"]
|
94
150
|
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 +189,21 @@ module Groupdate
|
|
133
189
|
def self.generate_relation(relation, field:, **options)
|
134
190
|
magic = Groupdate::Magic::Relation.new(**options)
|
135
191
|
|
192
|
+
adapter_name = relation.connection.adapter_name
|
193
|
+
adapter = Groupdate.adapters[adapter_name]
|
194
|
+
raise Groupdate::Error, "Connection adapter not supported: #{adapter_name}" unless adapter
|
195
|
+
|
136
196
|
# generate ActiveRecord relation
|
137
197
|
relation =
|
138
|
-
|
198
|
+
adapter.new(
|
139
199
|
relation,
|
140
200
|
column: field,
|
141
201
|
period: magic.period,
|
142
202
|
time_zone: magic.time_zone,
|
143
203
|
time_range: magic.time_range,
|
144
204
|
week_start: magic.week_start,
|
145
|
-
day_start: magic.day_start
|
205
|
+
day_start: magic.day_start,
|
206
|
+
n_seconds: magic.n_seconds
|
146
207
|
).generate
|
147
208
|
|
148
209
|
# add Groupdate info
|
@@ -1,27 +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,
|
5
|
-
warn "[groupdate] positional arguments for time zone and range are deprecated" if time_zone || range
|
6
|
-
|
4
|
+
define_method :"group_by_#{period}" do |field, **options|
|
7
5
|
Groupdate::Magic::Relation.generate_relation(self,
|
8
6
|
period: period,
|
9
7
|
field: field,
|
10
|
-
time_zone: time_zone,
|
11
|
-
range: range,
|
12
8
|
**options
|
13
9
|
)
|
14
10
|
end
|
15
11
|
end
|
16
12
|
|
17
13
|
def group_by_period(period, field, permit: nil, **options)
|
18
|
-
|
19
|
-
|
20
|
-
if permitted_periods.include?(period.to_s)
|
21
|
-
send("group_by_#{period}", field, **options)
|
22
|
-
else
|
23
|
-
raise ArgumentError, "Unpermitted period"
|
24
|
-
end
|
14
|
+
Groupdate::Magic.validate_period(period, permit)
|
15
|
+
send("group_by_#{period}", field, **options)
|
25
16
|
end
|
26
17
|
end
|
27
18
|
end
|
@@ -1,29 +1,64 @@
|
|
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
5
|
CHECK_PERIODS = [:day, :week, :month, :quarter, :year]
|
6
6
|
|
7
|
-
def initialize(period:, time_zone:, day_start:, week_start:, **options)
|
7
|
+
def initialize(period:, time_zone:, day_start:, week_start:, n_seconds:, **options)
|
8
8
|
@period = period
|
9
9
|
@time_zone = time_zone
|
10
10
|
@week_start = week_start
|
11
11
|
@day_start = day_start
|
12
|
+
@n_seconds = n_seconds
|
12
13
|
@options = options
|
13
|
-
@
|
14
|
+
@week_start_key = Groupdate::Magic::DAYS[@week_start] if @week_start
|
14
15
|
end
|
15
16
|
|
16
17
|
def generate(data, default_value:, series_default: true, multiple_groups: false, group_index: nil)
|
17
18
|
series = generate_series(data, multiple_groups, group_index)
|
18
19
|
series = handle_multiple(data, series, multiple_groups, group_index)
|
19
20
|
|
21
|
+
verified_data = {}
|
22
|
+
series.each do |k|
|
23
|
+
verified_data[k] = data.delete(k)
|
24
|
+
end
|
25
|
+
|
26
|
+
# this is a fun one
|
27
|
+
# PostgreSQL and Ruby both return the 2nd hour when converting/parsing a backward DST change
|
28
|
+
# Other databases and Active Support return the 1st hour (as expected)
|
29
|
+
# Active Support good: ActiveSupport::TimeZone["America/Los_Angeles"].parse("2013-11-03 01:00:00")
|
30
|
+
# MySQL good: SELECT CONVERT_TZ('2013-11-03 01:00:00', 'America/Los_Angeles', 'Etc/UTC');
|
31
|
+
# Ruby not good: Time.parse("2013-11-03 01:00:00")
|
32
|
+
# PostgreSQL not good: SELECT '2013-11-03 01:00:00'::timestamp AT TIME ZONE 'America/Los_Angeles';
|
33
|
+
# we need to account for this here
|
34
|
+
if series_default && CHECK_PERIODS.include?(period)
|
35
|
+
data.each do |k, v|
|
36
|
+
key = multiple_groups ? k[group_index] : k
|
37
|
+
# TODO only do this for PostgreSQL
|
38
|
+
# this may mask some inconsistent time zone errors
|
39
|
+
# but not sure there's a better approach
|
40
|
+
if key.hour == (key - 1.hour).hour && series.include?(key - 1.hour)
|
41
|
+
key -= 1.hour
|
42
|
+
if multiple_groups
|
43
|
+
k[group_index] = key
|
44
|
+
else
|
45
|
+
k = key
|
46
|
+
end
|
47
|
+
verified_data[k] = v
|
48
|
+
elsif key != round_time(key)
|
49
|
+
# only need to show what database returned since it will cast in Ruby time zone
|
50
|
+
raise Groupdate::Error, "Database and Ruby have inconsistent time zone info. Database returned #{key}"
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
20
55
|
unless entire_series?(series_default)
|
21
|
-
series = series.select { |k|
|
56
|
+
series = series.select { |k| verified_data[k] }
|
22
57
|
end
|
23
58
|
|
24
59
|
value = 0
|
25
60
|
result = Hash[series.map do |k|
|
26
|
-
value =
|
61
|
+
value = verified_data[k] || (@options[:carry_forward] && value) || default_value
|
27
62
|
key =
|
28
63
|
if multiple_groups
|
29
64
|
k[0...group_index] + [key_format.call(k[group_index])] + k[(group_index + 1)..-1]
|
@@ -34,20 +69,21 @@ module Groupdate
|
|
34
69
|
[key, value]
|
35
70
|
end]
|
36
71
|
|
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
|
-
end
|
42
|
-
|
43
72
|
result
|
44
73
|
end
|
45
74
|
|
46
75
|
def round_time(time)
|
76
|
+
if period == :custom
|
77
|
+
return time_zone.at((time.to_time.to_i / n_seconds) * n_seconds)
|
78
|
+
end
|
79
|
+
|
47
80
|
time = time.to_time.in_time_zone(time_zone)
|
48
81
|
|
49
|
-
|
50
|
-
|
82
|
+
if day_start != 0
|
83
|
+
# apply day_start to a time object that's not affected by DST
|
84
|
+
time = change_zone.call(time, utc)
|
85
|
+
time -= day_start.seconds
|
86
|
+
end
|
51
87
|
|
52
88
|
time =
|
53
89
|
case period
|
@@ -60,9 +96,7 @@ module Groupdate
|
|
60
96
|
when :day
|
61
97
|
time.beginning_of_day
|
62
98
|
when :week
|
63
|
-
|
64
|
-
weekday = (time.wday - 1) % 7
|
65
|
-
(time - ((7 - week_start + weekday) % 7).days).midnight
|
99
|
+
time.beginning_of_week(@week_start_key)
|
66
100
|
when :month
|
67
101
|
time.beginning_of_month
|
68
102
|
when :quarter
|
@@ -74,7 +108,7 @@ module Groupdate
|
|
74
108
|
when :minute_of_hour
|
75
109
|
time.min
|
76
110
|
when :day_of_week
|
77
|
-
|
111
|
+
time.days_to_week_start(@week_start_key)
|
78
112
|
when :day_of_month
|
79
113
|
time.day
|
80
114
|
when :month_of_year
|
@@ -85,24 +119,62 @@ module Groupdate
|
|
85
119
|
raise Groupdate::Error, "Invalid period"
|
86
120
|
end
|
87
121
|
|
88
|
-
|
89
|
-
|
122
|
+
if day_start != 0 && time.is_a?(Time)
|
123
|
+
time += day_start.seconds
|
124
|
+
time = change_zone.call(time, time_zone)
|
125
|
+
end
|
90
126
|
|
91
127
|
time
|
92
128
|
end
|
93
129
|
|
130
|
+
def change_zone
|
131
|
+
@change_zone ||= begin
|
132
|
+
if ActiveSupport::VERSION::STRING >= "5.2"
|
133
|
+
->(time, zone) { time.change(zone: zone) }
|
134
|
+
else
|
135
|
+
# TODO make more efficient
|
136
|
+
->(time, zone) { zone.parse(time.strftime("%Y-%m-%d %H:%M:%S")) }
|
137
|
+
end
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
94
141
|
def time_range
|
95
142
|
@time_range ||= begin
|
96
143
|
time_range = options[:range]
|
97
|
-
|
98
|
-
|
99
|
-
#
|
100
|
-
|
101
|
-
|
102
|
-
|
144
|
+
|
145
|
+
if time_range.is_a?(Range)
|
146
|
+
# check types
|
147
|
+
[time_range.begin, time_range.end].each do |v|
|
148
|
+
case v
|
149
|
+
when nil, Date, Time
|
150
|
+
# good
|
151
|
+
when String
|
152
|
+
# TODO raise error in Groupdate 6
|
153
|
+
warn "[groupdate] Range bounds should be Date or Time, not #{v.class.name}. This will raise an error in Groupdate 6"
|
154
|
+
break
|
155
|
+
else
|
156
|
+
raise ArgumentError, "Range bounds should be Date or Time, not #{v.class.name}"
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
start = time_range.begin
|
161
|
+
start = start.in_time_zone(time_zone) if start
|
162
|
+
|
163
|
+
exclude_end = time_range.exclude_end?
|
164
|
+
|
165
|
+
finish = time_range.end
|
166
|
+
finish = finish.in_time_zone(time_zone) if finish
|
167
|
+
if time_range.end.is_a?(Date) && !exclude_end
|
168
|
+
finish += 1.day
|
169
|
+
exclude_end = true
|
170
|
+
end
|
171
|
+
|
172
|
+
time_range = Range.new(start, finish, exclude_end)
|
103
173
|
elsif !time_range && options[:last]
|
104
174
|
if period == :quarter
|
105
175
|
step = 3.months
|
176
|
+
elsif period == :custom
|
177
|
+
step = n_seconds
|
106
178
|
elsif 1.respond_to?(period)
|
107
179
|
step = 1.send(period)
|
108
180
|
else
|
@@ -119,7 +191,8 @@ module Groupdate
|
|
119
191
|
if options[:current] == false
|
120
192
|
round_time(start_at - step)...round_time(now)
|
121
193
|
else
|
122
|
-
|
194
|
+
# extend to end of current period
|
195
|
+
round_time(start_at)...(round_time(now) + step)
|
123
196
|
end
|
124
197
|
end
|
125
198
|
end
|
@@ -150,7 +223,7 @@ module Groupdate
|
|
150
223
|
else
|
151
224
|
time_range = self.time_range
|
152
225
|
time_range =
|
153
|
-
if time_range.is_a?(Range)
|
226
|
+
if time_range.is_a?(Range) && time_range.begin && time_range.end
|
154
227
|
time_range
|
155
228
|
else
|
156
229
|
# use first and last values
|
@@ -161,11 +234,23 @@ module Groupdate
|
|
161
234
|
data.keys.sort
|
162
235
|
end
|
163
236
|
|
164
|
-
|
165
|
-
|
166
|
-
|
237
|
+
if time_range.is_a?(Range)
|
238
|
+
if sorted_keys.any?
|
239
|
+
if time_range.begin
|
240
|
+
time_range.begin..sorted_keys.last
|
241
|
+
else
|
242
|
+
Range.new(sorted_keys.first, time_range.end, time_range.exclude_end?)
|
243
|
+
end
|
244
|
+
else
|
245
|
+
nil..nil
|
246
|
+
end
|
247
|
+
else
|
248
|
+
tr = sorted_keys.first..sorted_keys.last
|
249
|
+
if options[:current] == false && sorted_keys.any? && round_time(now) >= tr.last
|
250
|
+
tr = tr.first...round_time(now)
|
251
|
+
end
|
252
|
+
tr
|
167
253
|
end
|
168
|
-
tr
|
169
254
|
end
|
170
255
|
|
171
256
|
if time_range.begin
|
@@ -173,14 +258,17 @@ module Groupdate
|
|
173
258
|
|
174
259
|
if period == :quarter
|
175
260
|
step = 3.months
|
261
|
+
elsif period == :custom
|
262
|
+
step = n_seconds
|
176
263
|
else
|
177
264
|
step = 1.send(period)
|
178
265
|
end
|
179
266
|
|
180
267
|
last_step = series.last
|
268
|
+
day_start_hour = day_start / 3600
|
181
269
|
loop do
|
182
270
|
next_step = last_step + step
|
183
|
-
next_step = round_time(next_step) if next_step.hour !=
|
271
|
+
next_step = round_time(next_step) if next_step.hour != day_start_hour # add condition to speed up
|
184
272
|
break unless time_range.cover?(next_step)
|
185
273
|
|
186
274
|
if next_step == last_step
|
@@ -199,34 +287,36 @@ module Groupdate
|
|
199
287
|
end
|
200
288
|
|
201
289
|
def key_format
|
202
|
-
|
203
|
-
|
290
|
+
@key_format ||= begin
|
291
|
+
locale = options[:locale] || I18n.locale
|
292
|
+
use_dates = options.key?(:dates) ? options[:dates] : Groupdate.dates
|
204
293
|
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
294
|
+
if options[:format]
|
295
|
+
if options[:format].respond_to?(:call)
|
296
|
+
options[:format]
|
297
|
+
else
|
298
|
+
sunday = time_zone.parse("2014-03-02 00:00:00")
|
299
|
+
lambda do |key|
|
300
|
+
case period
|
301
|
+
when :hour_of_day
|
302
|
+
key = sunday + key.hours + day_start.seconds
|
303
|
+
when :minute_of_hour
|
304
|
+
key = sunday + key.minutes + day_start.seconds
|
305
|
+
when :day_of_week
|
306
|
+
key = sunday + key.days + (week_start + 1).days
|
307
|
+
when :day_of_month
|
308
|
+
key = Date.new(2014, 1, key).to_time
|
309
|
+
when :month_of_year
|
310
|
+
key = Date.new(2014, key, 1).to_time
|
311
|
+
end
|
312
|
+
I18n.localize(key, format: options[:format], locale: locale)
|
222
313
|
end
|
223
|
-
I18n.localize(key, format: options[:format], locale: locale)
|
224
314
|
end
|
315
|
+
elsif [:day, :week, :month, :quarter, :year].include?(period) && use_dates
|
316
|
+
lambda { |k| k.to_date }
|
317
|
+
else
|
318
|
+
lambda { |k| k }
|
225
319
|
end
|
226
|
-
elsif [:day, :week, :month, :quarter, :year].include?(period) && use_dates
|
227
|
-
lambda { |k| k.to_date }
|
228
|
-
else
|
229
|
-
lambda { |k| k }
|
230
320
|
end
|
231
321
|
end
|
232
322
|
|
@@ -246,23 +336,12 @@ module Groupdate
|
|
246
336
|
end
|
247
337
|
end
|
248
338
|
|
249
|
-
def check_consistent_time_zone_info(data, multiple_groups, group_index)
|
250
|
-
keys = data.keys
|
251
|
-
if multiple_groups
|
252
|
-
keys.map! { |k| k[group_index] }
|
253
|
-
keys.uniq!
|
254
|
-
end
|
255
|
-
|
256
|
-
keys.each do |key|
|
257
|
-
if key != round_time(key)
|
258
|
-
# only need to show what database returned since it will cast in Ruby time zone
|
259
|
-
raise Groupdate::Error, "Database and Ruby have inconsistent time zone info. Database returned #{key}"
|
260
|
-
end
|
261
|
-
end
|
262
|
-
end
|
263
|
-
|
264
339
|
def entire_series?(series_default)
|
265
340
|
options.key?(:series) ? options[:series] : series_default
|
266
341
|
end
|
342
|
+
|
343
|
+
def utc
|
344
|
+
@utc ||= ActiveSupport::TimeZone["Etc/UTC"]
|
345
|
+
end
|
267
346
|
end
|
268
347
|
end
|