groupdate2 4.1.5
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 +7 -0
- data/CHANGELOG.md +182 -0
- data/CONTRIBUTING.md +75 -0
- data/LICENSE.txt +22 -0
- data/README.md +16 -0
- data/lib/groupdate/active_record.rb +6 -0
- data/lib/groupdate/enumerable.rb +31 -0
- data/lib/groupdate/magic.rb +164 -0
- data/lib/groupdate/query_methods.rb +25 -0
- data/lib/groupdate/relation.rb +16 -0
- data/lib/groupdate/relation_builder.rb +191 -0
- data/lib/groupdate/series_builder.rb +264 -0
- data/lib/groupdate/sql_server_group_clause.rb +92 -0
- data/lib/groupdate/version.rb +3 -0
- data/lib/groupdate/windows_zones.json +1 -0
- data/lib/groupdate2.rb +32 -0
- metadata +184 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 1a798d7e082e32adf30f0fd07348f8fec6369006
|
4
|
+
data.tar.gz: 6fc8861fc493d524bc29b8be51ef030887d38dd4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3c3459a9aa602d466dac261078dcd3dda23481e57b9cf1cf644699353f18e03af3924d0fda5f45887a6fad83d715ea7a0fc07f5772b30b0d4c34159fa0092bd3
|
7
|
+
data.tar.gz: fe47222def4bec06307c1930aff1c81b2bc6dd2819e9413d4df7e766345834d80271fa899e06a3f6655b53337e3864c68a32b2351f99432760f46731ec7b6c7d
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,182 @@
|
|
1
|
+
## 4.1.2
|
2
|
+
|
3
|
+
- Fixed error with empty data and `current: false`
|
4
|
+
- Fixed error in time zone check for Rails < 5.2
|
5
|
+
- Prevent infinite loop with endless ranges
|
6
|
+
|
7
|
+
## 4.1.1
|
8
|
+
|
9
|
+
- Made column resolution consistent with `group`
|
10
|
+
- Added support for `alias_attribute`
|
11
|
+
|
12
|
+
## 4.1.0
|
13
|
+
|
14
|
+
- Many performance improvements
|
15
|
+
- Added check for consistent time zone info
|
16
|
+
- Fixed error message for invalid queries with MySQL and SQLite
|
17
|
+
- Fixed issue with enumerable methods ignoring nils
|
18
|
+
|
19
|
+
## 4.0.2
|
20
|
+
|
21
|
+
- Make `current` option work without `last`
|
22
|
+
- Fixed default value for `maximum`, `minimum`, and `average` (periods with no results now return `nil` instead of `0`, pass `default_value: 0` for previous behavior)
|
23
|
+
|
24
|
+
## 4.0.1
|
25
|
+
|
26
|
+
- Fixed incorrect range with `last` option near time change
|
27
|
+
|
28
|
+
## 4.0.0
|
29
|
+
|
30
|
+
- Custom calculation methods are supported by default - `groupdate_calculation_methods` is no longer needed
|
31
|
+
|
32
|
+
Breaking changes
|
33
|
+
|
34
|
+
- Dropped support for Rails < 4.2
|
35
|
+
- Invalid options now throw an `ArgumentError`
|
36
|
+
- `group_by` methods return an `ActiveRecord::Relation` instead of a `Groupdate::Series`
|
37
|
+
- `week_start` now affects `day_of_week`
|
38
|
+
- Removed support for `reverse_order` (was never supported in Rails 5)
|
39
|
+
|
40
|
+
## 3.2.1
|
41
|
+
|
42
|
+
- Added `minute_of_hour`
|
43
|
+
- Added support for `unscoped`
|
44
|
+
|
45
|
+
## 3.2.0
|
46
|
+
|
47
|
+
- Added limited support for SQLite
|
48
|
+
|
49
|
+
## 3.1.1
|
50
|
+
|
51
|
+
- Fixed `current: false`
|
52
|
+
- Fixed `last` with `group_by_quarter`
|
53
|
+
- Raise `ArgumentError` when `last` option is not supported
|
54
|
+
|
55
|
+
## 3.1.0
|
56
|
+
|
57
|
+
- Better support for date columns with `time_zone: false`
|
58
|
+
- Better date range handling for `range` option
|
59
|
+
|
60
|
+
## 3.0.2
|
61
|
+
|
62
|
+
- Fixed `group_by_period` with associations
|
63
|
+
- Fixed `week_start` option for enumerables
|
64
|
+
|
65
|
+
## 3.0.1
|
66
|
+
|
67
|
+
- Added support for Redshift
|
68
|
+
- Fix for infinite loop in certain cases for Rails 5
|
69
|
+
|
70
|
+
## 3.0.0
|
71
|
+
|
72
|
+
Breaking changes
|
73
|
+
|
74
|
+
- `Date` objects are now returned for day, week, month, quarter, and year by default. Use `dates: false` for the previous behavior, or change this globally with `Groupdate.dates = false`.
|
75
|
+
- Array and hash methods no longer return the entire series by default. Use `series: true` for the previous behavior.
|
76
|
+
- The `series: false` option now returns the correct types and order, and plays nicely with other options.
|
77
|
+
|
78
|
+
## 2.5.3
|
79
|
+
|
80
|
+
- All tests green with `mysql` gem
|
81
|
+
- Added support for decimal day start
|
82
|
+
|
83
|
+
## 2.5.2
|
84
|
+
|
85
|
+
- Added `dates` option to return dates for day, week, month, quarter, and year
|
86
|
+
|
87
|
+
## 2.5.1
|
88
|
+
|
89
|
+
- Added `group_by_quarter`
|
90
|
+
- Added `default_value` option
|
91
|
+
- Accept symbol for `format` option
|
92
|
+
- Raise `ArgumentError` if no field specified
|
93
|
+
- Added support for ActiveRecord 5 beta
|
94
|
+
|
95
|
+
## 2.5.0
|
96
|
+
|
97
|
+
- Added `group_by_period` method
|
98
|
+
- Added `current` option
|
99
|
+
- Raise `ArgumentError` if no block given to enumerable
|
100
|
+
|
101
|
+
## 2.4.0
|
102
|
+
|
103
|
+
- Added localization
|
104
|
+
- Added `carry_forward` option
|
105
|
+
- Added `series: false` option for arrays and hashes
|
106
|
+
- Fixed issue w/ Brasilia Summer Time
|
107
|
+
- Fixed issues w/ ActiveRecord 4.2
|
108
|
+
|
109
|
+
## 2.3.0
|
110
|
+
|
111
|
+
- Raise error when ActiveRecord::Base.default_timezone is not `:utc`
|
112
|
+
- Added `day_of_month`
|
113
|
+
- Added `month_of_year`
|
114
|
+
- Do not quote column name
|
115
|
+
|
116
|
+
## 2.2.1
|
117
|
+
|
118
|
+
- Fixed ActiveRecord 3 associations
|
119
|
+
|
120
|
+
## 2.2.0
|
121
|
+
|
122
|
+
- Added support for arrays and hashes
|
123
|
+
|
124
|
+
## 2.1.1
|
125
|
+
|
126
|
+
- Fixed format option with multiple groups
|
127
|
+
- Better error message if time zone support is missing for MySQL
|
128
|
+
|
129
|
+
## 2.1.0
|
130
|
+
|
131
|
+
- Added last option
|
132
|
+
- Added format option
|
133
|
+
|
134
|
+
## 2.0.4
|
135
|
+
|
136
|
+
- Added multiple groups
|
137
|
+
- Added order
|
138
|
+
- Subsequent methods no longer modify relation
|
139
|
+
|
140
|
+
## 2.0.3
|
141
|
+
|
142
|
+
- Implemented respond_to?
|
143
|
+
|
144
|
+
## 2.0.2
|
145
|
+
|
146
|
+
- where, joins, and includes no longer need to be before the group_by method
|
147
|
+
|
148
|
+
## 2.0.1
|
149
|
+
|
150
|
+
- Use time zone instead of UTC for results
|
151
|
+
|
152
|
+
## 2.0.0
|
153
|
+
|
154
|
+
- Returns entire series by default
|
155
|
+
- Added day_start option
|
156
|
+
- Better interface
|
157
|
+
|
158
|
+
## 1.0.5
|
159
|
+
|
160
|
+
- Added global time_zone option
|
161
|
+
|
162
|
+
## 1.0.4
|
163
|
+
|
164
|
+
- Added global week_start option
|
165
|
+
- Fixed bug with NULL values and series
|
166
|
+
|
167
|
+
## 1.0.3
|
168
|
+
|
169
|
+
- Fixed deprecation warning when used with will_paginate
|
170
|
+
- Fixed bug with DateTime series
|
171
|
+
|
172
|
+
## 1.0.2
|
173
|
+
|
174
|
+
- Added :start option for custom week start for group_by_week
|
175
|
+
|
176
|
+
## 1.0.1
|
177
|
+
|
178
|
+
- Fixed series for Rails < 3.2 and MySQL
|
179
|
+
|
180
|
+
## 1.0.0
|
181
|
+
|
182
|
+
- First major release
|
data/CONTRIBUTING.md
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
# Contributing
|
2
|
+
|
3
|
+
First, thanks for wanting to contribute. You’re awesome! :heart:
|
4
|
+
|
5
|
+
## Help
|
6
|
+
|
7
|
+
We’re not able to provide support through GitHub Issues. If you’re looking for help with your code, try posting on [Stack Overflow](https://stackoverflow.com/).
|
8
|
+
|
9
|
+
All features should be documented. If you don’t see a feature in the docs, assume it doesn’t exist.
|
10
|
+
|
11
|
+
## Bugs
|
12
|
+
|
13
|
+
Think you’ve discovered a bug?
|
14
|
+
|
15
|
+
1. Search existing issues to see if it’s been reported.
|
16
|
+
2. Try the `master` branch to make sure it hasn’t been fixed.
|
17
|
+
|
18
|
+
```rb
|
19
|
+
gem "groupdate", github: "ankane/groupdate"
|
20
|
+
```
|
21
|
+
|
22
|
+
If the above steps don’t help, create an issue. Include:
|
23
|
+
|
24
|
+
- Detailed steps to reproduce
|
25
|
+
- Complete backtraces for exceptions
|
26
|
+
|
27
|
+
## New Features
|
28
|
+
|
29
|
+
If you’d like to discuss a new feature, create an issue and start the title with `[Idea]`.
|
30
|
+
|
31
|
+
## Pull Requests
|
32
|
+
|
33
|
+
Fork the project and create a pull request. A few tips:
|
34
|
+
|
35
|
+
- Keep changes to a minimum. If you have multiple features or fixes, submit multiple pull requests.
|
36
|
+
- Follow the existing style. The code should read like it’s written by a single person.
|
37
|
+
- Add one or more tests if possible. Make sure existing tests pass with:
|
38
|
+
|
39
|
+
```sh
|
40
|
+
bundle exec rake test
|
41
|
+
```
|
42
|
+
|
43
|
+
Feel free to open an issue to get feedback on your idea before spending too much time on it.
|
44
|
+
|
45
|
+
## Dev Setup
|
46
|
+
|
47
|
+
On Mac:
|
48
|
+
|
49
|
+
```sh
|
50
|
+
# install and run PostgreSQL
|
51
|
+
brew install postgresql
|
52
|
+
brew services start postgresql
|
53
|
+
|
54
|
+
# install and run MySQL
|
55
|
+
brew install mysql
|
56
|
+
brew services start mysql
|
57
|
+
|
58
|
+
# create databases
|
59
|
+
createdb groupdate_test
|
60
|
+
mysql -u root -e "create database groupdate_test"
|
61
|
+
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root mysql
|
62
|
+
|
63
|
+
# clone the repo and run the tests
|
64
|
+
git clone https://github.com/ankane/groupdate.git
|
65
|
+
cd groupdate
|
66
|
+
bundle install
|
67
|
+
bundle exec rake test
|
68
|
+
|
69
|
+
# run a single test file
|
70
|
+
ruby test/postgresql_test.rb
|
71
|
+
```
|
72
|
+
|
73
|
+
---
|
74
|
+
|
75
|
+
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/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013-2019 Andrew Kane
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# Groupdate2
|
2
|
+
groupdate2 is an enhanced version of the beautiful [Groupdate](https://github.com/ankane/groupdate) gem.
|
3
|
+
It adds MS SQL Server (2016 and above) support by using `(AT TIME ZONE)`.
|
4
|
+
|
5
|
+
In JRuby, it relies on `activerecord-sqlserver-adapter` `jdbc-mode` branch, which is not ideal. The plan is using `activerecord-jdbcsqlserver-adapter` when JRuby 9.2+ is supported.
|
6
|
+
|
7
|
+
# Version
|
8
|
+
## 4.1.x
|
9
|
+
This version corresponds to groupdate 4.1.2 with SQL Server support.
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
Add this line to your application’s Gemfile:
|
13
|
+
```ruby
|
14
|
+
gem 'groupdate2'
|
15
|
+
```
|
16
|
+
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Enumerable
|
2
|
+
Groupdate::PERIODS.each do |period|
|
3
|
+
define_method :"group_by_#{period}" do |*args, &block|
|
4
|
+
if block
|
5
|
+
Groupdate::Magic::Enumerable.group_by(self, period, args[0] || {}, &block)
|
6
|
+
elsif respond_to?(:scoping)
|
7
|
+
scoping { @klass.send(:"group_by_#{period}", *args, &block) }
|
8
|
+
else
|
9
|
+
raise ArgumentError, "no block given"
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def group_by_period(*args, &block)
|
15
|
+
if block || !respond_to?(:scoping)
|
16
|
+
period = args[0]
|
17
|
+
options = args[1] || {}
|
18
|
+
|
19
|
+
options = options.dup
|
20
|
+
# to_sym is unsafe on user input, so convert to strings
|
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
|
27
|
+
else
|
28
|
+
scoping { @klass.send(:group_by_period, *args, &block) }
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,164 @@
|
|
1
|
+
require "i18n"
|
2
|
+
|
3
|
+
module Groupdate
|
4
|
+
class Magic
|
5
|
+
attr_accessor :period, :options, :group_index
|
6
|
+
|
7
|
+
def initialize(period:, **options)
|
8
|
+
@period = period
|
9
|
+
@options = options
|
10
|
+
|
11
|
+
unknown_keywords = options.keys - [:day_start, :time_zone, :dates, :series, :week_start, :format, :locale, :range, :reverse]
|
12
|
+
raise ArgumentError, "unknown keywords: #{unknown_keywords.join(", ")}" if unknown_keywords.any?
|
13
|
+
|
14
|
+
raise Groupdate::Error, "Unrecognized time zone" unless time_zone
|
15
|
+
raise Groupdate::Error, "Unrecognized :week_start option" if period == :week && !week_start
|
16
|
+
raise Groupdate::Error, "Cannot use endless range for :range option" if options[:range].is_a?(Range) && !options[:range].end
|
17
|
+
end
|
18
|
+
|
19
|
+
def time_zone
|
20
|
+
@time_zone ||= begin
|
21
|
+
time_zone = "Etc/UTC" if options[:time_zone] == false
|
22
|
+
time_zone ||= options[:time_zone] || Groupdate.time_zone || (Groupdate.time_zone == false && "Etc/UTC") || Time.zone || "Etc/UTC"
|
23
|
+
time_zone.is_a?(ActiveSupport::TimeZone) ? time_zone : ActiveSupport::TimeZone[time_zone]
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def week_start
|
28
|
+
@week_start ||= [:mon, :tue, :wed, :thu, :fri, :sat, :sun].index((options[:week_start] || options[:start] || Groupdate.week_start).to_sym)
|
29
|
+
end
|
30
|
+
|
31
|
+
def day_start
|
32
|
+
@day_start ||= ((options[:day_start] || Groupdate.day_start).to_f * 3600).round
|
33
|
+
end
|
34
|
+
|
35
|
+
def series_builder
|
36
|
+
@series_builder ||=
|
37
|
+
SeriesBuilder.new(
|
38
|
+
**options,
|
39
|
+
period: period,
|
40
|
+
time_zone: time_zone,
|
41
|
+
day_start: day_start,
|
42
|
+
week_start: week_start
|
43
|
+
)
|
44
|
+
end
|
45
|
+
|
46
|
+
def time_range
|
47
|
+
series_builder.time_range
|
48
|
+
end
|
49
|
+
|
50
|
+
class Enumerable < Magic
|
51
|
+
def group_by(enum, &_block)
|
52
|
+
group = enum.group_by do |v|
|
53
|
+
v = yield(v)
|
54
|
+
raise ArgumentError, "Not a time" unless v.respond_to?(:to_time)
|
55
|
+
series_builder.round_time(v)
|
56
|
+
end
|
57
|
+
series_builder.generate(group, default_value: [], series_default: false)
|
58
|
+
end
|
59
|
+
|
60
|
+
def self.group_by(enum, period, options, &block)
|
61
|
+
Groupdate::Magic::Enumerable.new(period: period, **options).group_by(enum, &block)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
class Relation < Magic
|
66
|
+
def initialize(**options)
|
67
|
+
super(**options.reject { |k, _| [:default_value, :carry_forward, :last, :current].include?(k) })
|
68
|
+
@options = options
|
69
|
+
end
|
70
|
+
|
71
|
+
def perform(relation, result, default_value:)
|
72
|
+
multiple_groups = relation.group_values.size > 1
|
73
|
+
|
74
|
+
check_nils(result, multiple_groups, relation)
|
75
|
+
result = cast_result(result, multiple_groups)
|
76
|
+
|
77
|
+
series_builder.generate(
|
78
|
+
result,
|
79
|
+
default_value: options.key?(:default_value) ? options[:default_value] : default_value,
|
80
|
+
multiple_groups: multiple_groups,
|
81
|
+
group_index: group_index
|
82
|
+
)
|
83
|
+
end
|
84
|
+
|
85
|
+
def cast_method
|
86
|
+
@cast_method ||= begin
|
87
|
+
case period
|
88
|
+
when :day_of_week
|
89
|
+
lambda { |k| (k.to_i - 1 - week_start) % 7 }
|
90
|
+
when :hour_of_day, :day_of_month, :month_of_year, :minute_of_hour
|
91
|
+
lambda { |k| k.to_i }
|
92
|
+
else
|
93
|
+
utc = ActiveSupport::TimeZone["UTC"]
|
94
|
+
lambda { |k| (k.is_a?(String) || !k.respond_to?(:to_time) ? utc.parse(k.to_s) : k.to_time).in_time_zone(time_zone) }
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def cast_result(result, multiple_groups)
|
100
|
+
new_result = {}
|
101
|
+
result.each do |k, v|
|
102
|
+
if multiple_groups
|
103
|
+
k[group_index] = cast_method.call(k[group_index])
|
104
|
+
else
|
105
|
+
k = cast_method.call(k)
|
106
|
+
end
|
107
|
+
new_result[k] = v
|
108
|
+
end
|
109
|
+
new_result
|
110
|
+
end
|
111
|
+
|
112
|
+
def time_zone_support?(relation)
|
113
|
+
if relation.connection.adapter_name =~ /mysql/i
|
114
|
+
# need to call klass for Rails < 5.2
|
115
|
+
sql = relation.klass.send(:sanitize_sql_array, ["SELECT CONVERT_TZ(NOW(), '+00:00', ?)", time_zone.tzinfo.name])
|
116
|
+
!relation.connection.select_all(sql).first.values.first.nil?
|
117
|
+
else
|
118
|
+
true
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
def check_nils(result, multiple_groups, relation)
|
123
|
+
has_nils = multiple_groups ? (result.keys.first && result.keys.first[group_index].nil?) : result.key?(nil)
|
124
|
+
if has_nils
|
125
|
+
if time_zone_support?(relation)
|
126
|
+
raise Groupdate::Error, "Invalid query - be sure to use a date or time column"
|
127
|
+
else
|
128
|
+
raise Groupdate::Error, "Database missing time zone support for #{time_zone.tzinfo.name} - see https://github.com/ankane/groupdate#for-mysql"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def self.generate_relation(relation, field:, **options)
|
134
|
+
magic = Groupdate::Magic::Relation.new(**options)
|
135
|
+
|
136
|
+
# generate ActiveRecord relation
|
137
|
+
relation =
|
138
|
+
RelationBuilder.new(
|
139
|
+
relation,
|
140
|
+
column: field,
|
141
|
+
period: magic.period,
|
142
|
+
time_zone: magic.time_zone,
|
143
|
+
time_range: magic.time_range,
|
144
|
+
week_start: magic.week_start,
|
145
|
+
day_start: magic.day_start
|
146
|
+
).generate
|
147
|
+
|
148
|
+
# add Groupdate info
|
149
|
+
magic.group_index = relation.group_values.size - 1
|
150
|
+
(relation.groupdate_values ||= []) << magic
|
151
|
+
|
152
|
+
relation
|
153
|
+
end
|
154
|
+
|
155
|
+
# allow any options to keep flexible for future
|
156
|
+
def self.process_result(relation, result, **options)
|
157
|
+
relation.groupdate_values.reverse.each do |gv|
|
158
|
+
result = gv.perform(relation, result, default_value: options[:default_value])
|
159
|
+
end
|
160
|
+
result
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|