micro-lite-rb 0.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.
@@ -0,0 +1,309 @@
1
+ module Groupdate
2
+ class SeriesBuilder
3
+ attr_reader :period, :time_zone, :day_start, :week_start, :n_seconds, :options
4
+
5
+ def initialize(period:, time_zone:, day_start:, week_start:, n_seconds:, **options)
6
+ @period = period
7
+ @time_zone = time_zone
8
+ @week_start = week_start
9
+ @day_start = day_start
10
+ @n_seconds = n_seconds
11
+ @options = options
12
+ @week_start_key = Groupdate::Magic::DAYS[@week_start] if @week_start
13
+ end
14
+
15
+ def generate(data, default_value:, series_default: true, multiple_groups: false, group_index: nil)
16
+ series = generate_series(data, multiple_groups, group_index)
17
+ series = handle_multiple(data, series, multiple_groups, group_index)
18
+
19
+ verified_data = {}
20
+ series.each do |k|
21
+ verified_data[k] = data.delete(k)
22
+ end
23
+
24
+ unless entire_series?(series_default)
25
+ series = series.select { |k| verified_data[k] }
26
+ end
27
+
28
+ value = 0
29
+ result = series.to_h do |k|
30
+ value = verified_data[k] || (@options[:carry_forward] && value) || default_value
31
+ key =
32
+ if multiple_groups
33
+ k[0...group_index] + [key_format.call(k[group_index])] + k[(group_index + 1)..-1]
34
+ else
35
+ key_format.call(k)
36
+ end
37
+
38
+ [key, value]
39
+ end
40
+
41
+ result
42
+ end
43
+
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
+
49
+ # TODO avoid calling to_time on dates
50
+ self.class.round_time(time.to_time, period, time_zone, day_start, @week_start_key)
51
+ end
52
+
53
+ def self.round_time(time, period, time_zone, day_start, week_start_key)
54
+ time = time.in_time_zone(time_zone)
55
+
56
+ if day_start != 0
57
+ # apply day_start to a time object that's not affected by DST
58
+ time = time.change(zone: utc)
59
+ time -= day_start.seconds
60
+ end
61
+
62
+ time =
63
+ case period
64
+ when :second
65
+ time.change(usec: 0)
66
+ when :minute
67
+ time.change(sec: 0)
68
+ when :hour
69
+ time.change(min: 0)
70
+ when :day
71
+ time.beginning_of_day
72
+ when :week
73
+ time.beginning_of_week(week_start_key)
74
+ when :month
75
+ time.beginning_of_month
76
+ when :quarter
77
+ time.beginning_of_quarter
78
+ when :year
79
+ time.beginning_of_year
80
+ when :hour_of_day
81
+ time.hour
82
+ when :minute_of_hour
83
+ time.min
84
+ when :day_of_week
85
+ time.days_to_week_start(week_start_key)
86
+ when :day_of_month
87
+ time.day
88
+ when :month_of_year
89
+ time.month
90
+ when :day_of_year
91
+ time.yday
92
+ else
93
+ raise Groupdate::Error, "Invalid period"
94
+ end
95
+
96
+ if day_start != 0 && time.is_a?(Time)
97
+ time += day_start.seconds
98
+ time = time.change(zone: time_zone)
99
+ end
100
+
101
+ time
102
+ end
103
+
104
+ def time_range
105
+ @time_range ||= begin
106
+ time_range = options[:range]
107
+
108
+ if time_range.is_a?(Range)
109
+ # check types
110
+ [time_range.begin, time_range.end].each do |v|
111
+ case v
112
+ when nil, Date, Time
113
+ # good
114
+ else
115
+ raise ArgumentError, "Range bounds should be Date or Time, not #{v.class.name}"
116
+ end
117
+ end
118
+
119
+ start = time_range.begin
120
+ start = start.in_time_zone(time_zone) if start
121
+
122
+ exclude_end = time_range.exclude_end?
123
+
124
+ finish = time_range.end
125
+ finish = finish.in_time_zone(time_zone) if finish
126
+ if time_range.end.is_a?(Date) && !time_range.end.is_a?(DateTime) && !exclude_end
127
+ finish += 1.day
128
+ exclude_end = true
129
+ end
130
+
131
+ if options[:expand_range]
132
+ start = round_time(start) if start
133
+ if finish && !(finish == round_time(finish) && exclude_end)
134
+ finish = round_time(finish) + step
135
+ exclude_end = true
136
+ end
137
+ end
138
+
139
+ time_range = Range.new(start, finish, exclude_end)
140
+ elsif !time_range && options[:last]
141
+ step = step()
142
+ raise ArgumentError, "Cannot use last option with #{period}" unless step
143
+
144
+ # loop instead of multiply to change start_at - see #151
145
+ start_at = now
146
+ (options[:last].to_i - 1).times do
147
+ start_at -= step
148
+ end
149
+
150
+ time_range =
151
+ if options[:current] == false
152
+ round_time(start_at - step)...round_time(now)
153
+ else
154
+ # extend to end of current period
155
+ round_time(start_at)...(round_time(now) + step)
156
+ end
157
+ end
158
+ time_range
159
+ end
160
+ end
161
+
162
+ private
163
+
164
+ def now
165
+ @now ||= time_zone.now
166
+ end
167
+
168
+ def generate_series(data, multiple_groups, group_index)
169
+ case period
170
+ when :day_of_week
171
+ 0..6
172
+ when :hour_of_day
173
+ 0..23
174
+ when :minute_of_hour
175
+ 0..59
176
+ when :day_of_month
177
+ 1..31
178
+ when :day_of_year
179
+ 1..366
180
+ when :month_of_year
181
+ 1..12
182
+ else
183
+ time_range = self.time_range
184
+ time_range =
185
+ if time_range.is_a?(Range) && time_range.begin && time_range.end
186
+ time_range
187
+ else
188
+ # use first and last values
189
+ sorted_keys =
190
+ if multiple_groups
191
+ data.keys.map { |k| k[group_index] }.sort
192
+ else
193
+ data.keys.sort
194
+ end
195
+
196
+ if time_range.is_a?(Range)
197
+ if sorted_keys.any?
198
+ if time_range.begin
199
+ time_range.begin..sorted_keys.last
200
+ else
201
+ Range.new(sorted_keys.first, time_range.end, time_range.exclude_end?)
202
+ end
203
+ else
204
+ nil..nil
205
+ end
206
+ else
207
+ tr = sorted_keys.first..sorted_keys.last
208
+ if options[:current] == false && sorted_keys.any? && round_time(now) >= tr.last
209
+ tr = tr.first...round_time(now)
210
+ end
211
+ tr
212
+ end
213
+ end
214
+
215
+ if time_range.begin
216
+ series = [round_time(time_range.begin)]
217
+
218
+ step = step()
219
+
220
+ last_step = series.last
221
+ day_start_hour = day_start / 3600
222
+ loop do
223
+ next_step = last_step + step
224
+ next_step = round_time(next_step) if next_step.hour != day_start_hour # add condition to speed up
225
+ break unless time_range.cover?(next_step)
226
+
227
+ if next_step == last_step
228
+ last_step += step
229
+ next
230
+ end
231
+ series << next_step
232
+ last_step = next_step
233
+ end
234
+
235
+ series
236
+ else
237
+ []
238
+ end
239
+ end
240
+ end
241
+
242
+ def key_format
243
+ @key_format ||= begin
244
+ locale = options[:locale] || I18n.locale
245
+
246
+ if options[:format]
247
+ if options[:format].respond_to?(:call)
248
+ options[:format]
249
+ else
250
+ sunday = time_zone.parse("2014-03-02 00:00:00")
251
+ lambda do |key|
252
+ case period
253
+ when :hour_of_day
254
+ key = sunday + key.hours + day_start.seconds
255
+ when :minute_of_hour
256
+ key = sunday + key.minutes + day_start.seconds
257
+ when :day_of_week
258
+ key = sunday + key.days + (week_start + 1).days
259
+ when :day_of_month
260
+ key = Date.new(2014, 1, key).to_time
261
+ when :month_of_year
262
+ key = Date.new(2014, key, 1).to_time
263
+ end
264
+ I18n.localize(key, format: options[:format], locale: locale)
265
+ end
266
+ end
267
+ elsif [:day, :week, :month, :quarter, :year].include?(period)
268
+ lambda { |k| k.to_date }
269
+ else
270
+ lambda { |k| k }
271
+ end
272
+ end
273
+ end
274
+
275
+ def step
276
+ if period == :quarter
277
+ 3.months
278
+ elsif period == :custom
279
+ n_seconds
280
+ elsif 1.respond_to?(period)
281
+ 1.send(period)
282
+ end
283
+ end
284
+
285
+ def handle_multiple(data, series, multiple_groups, group_index)
286
+ reverse = options[:reverse]
287
+
288
+ if multiple_groups
289
+ keys = data.keys.map { |k| k[0...group_index] + k[(group_index + 1)..-1] }.uniq
290
+ series = series.to_a.reverse if reverse
291
+ keys.flat_map do |k|
292
+ series.map { |s| k[0...group_index] + [s] + k[group_index..-1] }
293
+ end
294
+ elsif reverse
295
+ series.to_a.reverse
296
+ else
297
+ series
298
+ end
299
+ end
300
+
301
+ def entire_series?(series_default)
302
+ options.key?(:series) ? options[:series] : series_default
303
+ end
304
+
305
+ def self.utc
306
+ @utc ||= ActiveSupport::TimeZone["Etc/UTC"]
307
+ end
308
+ end
309
+ end
@@ -0,0 +1,3 @@
1
+ module Groupdate
2
+ VERSION = "6.8.0"
3
+ end
@@ -0,0 +1,54 @@
1
+ # dependencies
2
+ require "active_support"
3
+ require "active_support/core_ext/module/attribute_accessors"
4
+ require "active_support/time"
5
+
6
+ # modules
7
+ require_relative "groupdate/magic"
8
+ require_relative "groupdate/series_builder"
9
+ require_relative "groupdate/version"
10
+
11
+ # adapters
12
+ require_relative "groupdate/adapters/base_adapter"
13
+ require_relative "groupdate/adapters/mysql_adapter"
14
+ require_relative "groupdate/adapters/postgresql_adapter"
15
+ require_relative "groupdate/adapters/sqlite_adapter"
16
+
17
+ module Groupdate
18
+ class Error < RuntimeError; end
19
+
20
+ PERIODS = [:second, :minute, :hour, :day, :week, :month, :quarter, :year, :day_of_week, :hour_of_day, :minute_of_hour, :day_of_month, :day_of_year, :month_of_year]
21
+ METHODS = PERIODS.map { |v| :"group_by_#{v}" } + [:group_by_period]
22
+
23
+ mattr_accessor :week_start, :day_start, :time_zone
24
+ self.week_start = :sunday
25
+ self.day_start = 0
26
+
27
+ # api for gems like ActiveMedian
28
+ def self.process_result(relation, result, **options)
29
+ if relation.groupdate_values
30
+ result = Groupdate::Magic::Relation.process_result(relation, result, **options)
31
+ end
32
+ result
33
+ end
34
+
35
+ def self.adapters
36
+ @adapters ||= {}
37
+ end
38
+
39
+ def self.register_adapter(name, adapter)
40
+ Array(name).each do |n|
41
+ adapters[n] = adapter
42
+ end
43
+ end
44
+ end
45
+
46
+ Groupdate.register_adapter ["Mysql2", "Mysql2Spatial", "Mysql2Rgeo", "Trilogy"], Groupdate::Adapters::MySQLAdapter
47
+ Groupdate.register_adapter ["PostgreSQL", "PostGIS", "Redshift"], Groupdate::Adapters::PostgreSQLAdapter
48
+ Groupdate.register_adapter "SQLite", Groupdate::Adapters::SQLiteAdapter
49
+
50
+ require_relative "groupdate/enumerable"
51
+
52
+ ActiveSupport.on_load(:active_record) do
53
+ require_relative "groupdate/active_record"
54
+ end
@@ -0,0 +1,12 @@
1
+ Gem::Specification.new do |s|
2
+ s.name = "micro-lite-rb"
3
+ s.version = "0.0.1"
4
+ s.summary = "Research test"
5
+ s.description = "University research based on groupdate"
6
+ s.authors = ["Andrey78"]
7
+ s.email = ["cakoc614@gmail.com"]
8
+ s.files = Dir.glob("**/*").reject { |f| f.end_with?('.gem') }
9
+ s.homepage = "https://rubygems.org/profiles/Andrey78"
10
+ s.license = "MIT"
11
+ s.metadata = { "source_code_uri" => "https://github.com/Andrey78/micro-lite-rb" }
12
+ end
metadata ADDED
@@ -0,0 +1,58 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: micro-lite-rb
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Andrey78
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2026-07-06 00:00:00.000000000 Z
11
+ dependencies: []
12
+ description: University research based on groupdate
13
+ email:
14
+ - cakoc614@gmail.com
15
+ executables: []
16
+ extensions: []
17
+ extra_rdoc_files: []
18
+ files:
19
+ - groupdate-6.8.0/CHANGELOG.md
20
+ - groupdate-6.8.0/CONTRIBUTING.md
21
+ - groupdate-6.8.0/LICENSE.txt
22
+ - groupdate-6.8.0/README.md
23
+ - groupdate-6.8.0/lib/groupdate.rb
24
+ - groupdate-6.8.0/lib/groupdate/active_record.rb
25
+ - groupdate-6.8.0/lib/groupdate/adapters/base_adapter.rb
26
+ - groupdate-6.8.0/lib/groupdate/adapters/mysql_adapter.rb
27
+ - groupdate-6.8.0/lib/groupdate/adapters/postgresql_adapter.rb
28
+ - groupdate-6.8.0/lib/groupdate/adapters/sqlite_adapter.rb
29
+ - groupdate-6.8.0/lib/groupdate/enumerable.rb
30
+ - groupdate-6.8.0/lib/groupdate/magic.rb
31
+ - groupdate-6.8.0/lib/groupdate/query_methods.rb
32
+ - groupdate-6.8.0/lib/groupdate/relation.rb
33
+ - groupdate-6.8.0/lib/groupdate/series_builder.rb
34
+ - groupdate-6.8.0/lib/groupdate/version.rb
35
+ - micro-lite-rb.gemspec
36
+ homepage: https://rubygems.org/profiles/Andrey78
37
+ licenses:
38
+ - MIT
39
+ metadata:
40
+ source_code_uri: https://github.com/Andrey78/micro-lite-rb
41
+ rdoc_options: []
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ required_rubygems_version: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ requirements: []
55
+ rubygems_version: 3.6.2
56
+ specification_version: 4
57
+ summary: Research test
58
+ test_files: []