br-utils 0.1.0 → 0.2.0
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/.github/workflows/ci.yml +27 -0
- data/README.md +446 -22
- data/lib/brazilian-utils/area-code-utils.rb +60 -0
- data/lib/brazilian-utils/bank-account-utils.rb +82 -0
- data/lib/brazilian-utils/bank-utils.rb +61 -0
- data/lib/brazilian-utils/boleto-utils.rb +139 -0
- data/lib/brazilian-utils/caepf-utils.rb +91 -0
- data/lib/brazilian-utils/cbo-utils.rb +74 -0
- data/lib/brazilian-utils/cei-utils.rb +79 -0
- data/lib/brazilian-utils/cep-utils.rb +34 -1
- data/lib/brazilian-utils/certidao-utils.rb +171 -0
- data/lib/brazilian-utils/cfop-utils.rb +74 -0
- data/lib/brazilian-utils/cnae-utils.rb +104 -0
- data/lib/brazilian-utils/cnh-utils.rb +68 -15
- data/lib/brazilian-utils/cno-utils.rb +76 -0
- data/lib/brazilian-utils/cnpj-utils.rb +126 -13
- data/lib/brazilian-utils/cns-utils.rb +110 -0
- data/lib/brazilian-utils/cpf-utils.rb +14 -1
- data/lib/brazilian-utils/credit-card-utils.rb +47 -0
- data/lib/brazilian-utils/csosn-utils.rb +55 -0
- data/lib/brazilian-utils/cst-utils.rb +103 -0
- data/lib/brazilian-utils/currency-utils.rb +347 -226
- data/lib/brazilian-utils/data/area_codes.json +676 -0
- data/lib/brazilian-utils/data/banks.json +357 -0
- data/lib/brazilian-utils/data/cbo.json +1 -0
- data/lib/brazilian-utils/data/cfop.json +1 -0
- data/lib/brazilian-utils/data/cnae.json +1 -0
- data/lib/brazilian-utils/data/csosn.json +42 -0
- data/lib/brazilian-utils/data/cst.json +294 -0
- data/lib/brazilian-utils/data/legal_nature.json +900 -0
- data/lib/brazilian-utils/data/municipalities.json +1 -0
- data/lib/brazilian-utils/data/ncm.json +1 -0
- data/lib/brazilian-utils/data/states.json +218 -0
- data/lib/brazilian-utils/date-utils.rb +504 -244
- data/lib/brazilian-utils/email-utils.rb +20 -9
- data/lib/brazilian-utils/iban-utils.rb +120 -0
- data/lib/brazilian-utils/ie-utils.rb +84 -0
- data/lib/brazilian-utils/legal-nature-utils.rb +238 -235
- data/lib/brazilian-utils/legal-process-utils.rb +35 -5
- data/lib/brazilian-utils/license-plate-utils.rb +24 -4
- data/lib/brazilian-utils/municipality-utils.rb +58 -0
- data/lib/brazilian-utils/ncm-utils.rb +91 -0
- data/lib/brazilian-utils/nfe-key-utils.rb +158 -0
- data/lib/brazilian-utils/number-utils.rb +123 -0
- data/lib/brazilian-utils/passport-utils.rb +65 -0
- data/lib/brazilian-utils/phone-utils.rb +491 -272
- data/lib/brazilian-utils/pis-utils.rb +16 -1
- data/lib/brazilian-utils/pix-key-utils.rb +70 -0
- data/lib/brazilian-utils/pix-payload-utils.rb +253 -0
- data/lib/brazilian-utils/registro-profissional-utils.rb +112 -0
- data/lib/brazilian-utils/renavam-utils.rb +14 -0
- data/lib/brazilian-utils/state-utils.rb +105 -0
- data/lib/brazilian-utils/text-utils.rb +105 -0
- data/lib/brazilian-utils/vin-utils.rb +46 -0
- data/lib/brazilian-utils/voter-id-utils.rb +70 -19
- metadata +54 -2
- data/.travis.yml +0 -5
|
@@ -1,272 +1,491 @@
|
|
|
1
|
-
module BrazilianUtils
|
|
2
|
-
# Utilities for formatting, validating, and generating Brazilian phone numbers.
|
|
3
|
-
#
|
|
4
|
-
# Brazilian phone numbers come in two types:
|
|
5
|
-
# - Mobile (Celular): 11 digits - DDD (2 digits) + 9 + 8 digits, e.g., "11994029275"
|
|
6
|
-
# - Landline (Fixo): 10 digits - DDD (2 digits) + [2-5] + 7 digits, e.g., "1635014415"
|
|
7
|
-
#
|
|
8
|
-
# DDD (Discagem Direta à Distância) is the area code, ranging from 11 to 99.
|
|
9
|
-
# Mobile numbers always have 9 as the 3rd digit (after DDD).
|
|
10
|
-
# Landline numbers have 2, 3, 4, or 5 as the 3rd digit (after DDD).
|
|
11
|
-
module PhoneUtils
|
|
12
|
-
# Pattern for mobile phone numbers (11 digits: DDD + 9 + 8 digits)
|
|
13
|
-
MOBILE_PATTERN = /^[1-9][1-9][9]\d{8}$/.freeze
|
|
14
|
-
|
|
15
|
-
# Pattern for landline phone numbers (10 digits: DDD + [2-5] + 7 digits)
|
|
16
|
-
LANDLINE_PATTERN = /^[1-9][1-9][2-5]\d{7}$/.freeze
|
|
17
|
-
|
|
18
|
-
# Pattern for international dialing code (+55 or 55)
|
|
19
|
-
INTERNATIONAL_CODE_PATTERN = /\+?55/.freeze
|
|
20
|
-
|
|
21
|
-
#
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
#
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
#
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
#
|
|
55
|
-
#
|
|
56
|
-
#
|
|
57
|
-
#
|
|
58
|
-
#
|
|
59
|
-
#
|
|
60
|
-
# @return [
|
|
61
|
-
#
|
|
62
|
-
# @example
|
|
63
|
-
#
|
|
64
|
-
#
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
#
|
|
78
|
-
#
|
|
79
|
-
#
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
#
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
#
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
#
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
#
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
#
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
#
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
#
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
#
|
|
227
|
-
#
|
|
228
|
-
#
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
#
|
|
244
|
-
#
|
|
245
|
-
#
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
#
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
1
|
+
module BrazilianUtils
|
|
2
|
+
# Utilities for formatting, validating, and generating Brazilian phone numbers.
|
|
3
|
+
#
|
|
4
|
+
# Brazilian phone numbers come in two types:
|
|
5
|
+
# - Mobile (Celular): 11 digits - DDD (2 digits) + 9 + 8 digits, e.g., "11994029275"
|
|
6
|
+
# - Landline (Fixo): 10 digits - DDD (2 digits) + [2-5] + 7 digits, e.g., "1635014415"
|
|
7
|
+
#
|
|
8
|
+
# DDD (Discagem Direta à Distância) is the area code, ranging from 11 to 99.
|
|
9
|
+
# Mobile numbers always have 9 as the 3rd digit (after DDD).
|
|
10
|
+
# Landline numbers have 2, 3, 4, or 5 as the 3rd digit (after DDD).
|
|
11
|
+
module PhoneUtils
|
|
12
|
+
# Pattern for mobile phone numbers (11 digits: DDD + 9 + 8 digits)
|
|
13
|
+
MOBILE_PATTERN = /^[1-9][1-9][9]\d{8}$/.freeze
|
|
14
|
+
|
|
15
|
+
# Pattern for landline phone numbers (10 digits: DDD + [2-5] + 7 digits)
|
|
16
|
+
LANDLINE_PATTERN = /^[1-9][1-9][2-5]\d{7}$/.freeze
|
|
17
|
+
|
|
18
|
+
# Pattern for international dialing code (+55 or 55)
|
|
19
|
+
INTERNATIONAL_CODE_PATTERN = /\+?55/.freeze
|
|
20
|
+
|
|
21
|
+
# Códigos Não Geográficos (Anatel) that take 7 digits.
|
|
22
|
+
SERVICE_CNG_PREFIXES = %w[0300 0303 0500 0800 0900].freeze
|
|
23
|
+
|
|
24
|
+
# 3-digit public-utility numbers designated by Anatel (não exaustivo).
|
|
25
|
+
SERVICE_SHORT_CODES = %w[
|
|
26
|
+
100 101 102 104 105 106 107 108 110 111 116 118 119 120 121 122 123
|
|
27
|
+
125 126 127 128 129 130 131 132 133 135 136 137 138 140 141 144 145
|
|
28
|
+
146 147 148 150 151 152 153 154 155 156 158 159 160 161 162 163 164
|
|
29
|
+
171 172 173 174 175 176 177 178 179 180 181 185 188 189 190 191 192
|
|
30
|
+
193 194 195 196 197 198 199
|
|
31
|
+
].freeze
|
|
32
|
+
|
|
33
|
+
# Removes a leading country code (`+55`, `0055` or a bare `55`) from an
|
|
34
|
+
# already digits-only string, but only when doing so leaves 10 or 11
|
|
35
|
+
# digits (so a DDD of `55`, e.g. Rio Grande do Sul, is not mistaken for
|
|
36
|
+
# the country code).
|
|
37
|
+
#
|
|
38
|
+
# @param digits [String] A digits-only phone number.
|
|
39
|
+
# @return [String] The digits, with the country code removed if applicable.
|
|
40
|
+
#
|
|
41
|
+
# @private
|
|
42
|
+
def self.strip_country_code(digits)
|
|
43
|
+
if digits.start_with?('0055') && [10, 11].include?(digits.length - 4)
|
|
44
|
+
digits[4..-1]
|
|
45
|
+
elsif digits.start_with?('55') && [10, 11].include?(digits.length - 2)
|
|
46
|
+
digits[2..-1]
|
|
47
|
+
else
|
|
48
|
+
digits
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private_class_method :strip_country_code
|
|
53
|
+
|
|
54
|
+
# Removes phone formatting and keeps only digits, capped to 11 digits.
|
|
55
|
+
#
|
|
56
|
+
# A country code (`+55`, `0055` or a bare `55`) is stripped only when 10
|
|
57
|
+
# or 11 digits are left, so an area code of `55` is not mistaken for it.
|
|
58
|
+
#
|
|
59
|
+
# @param value [String, Integer] The value to parse.
|
|
60
|
+
# @return [String] The parsed digits.
|
|
61
|
+
#
|
|
62
|
+
# @example
|
|
63
|
+
# parse("(11) 98888-7777") #=> "11988887777"
|
|
64
|
+
# parse("+55 11 98888-7777") #=> "11988887777"
|
|
65
|
+
# parse("55988887777") #=> "55988887777" (55 read as DDD)
|
|
66
|
+
def self.parse(value)
|
|
67
|
+
return '' unless value.is_a?(String) || value.is_a?(Integer)
|
|
68
|
+
|
|
69
|
+
digits = value.to_s.gsub(/\D/, '')
|
|
70
|
+
return '' if digits.empty?
|
|
71
|
+
|
|
72
|
+
strip_country_code(digits)[0, 11]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Formats a Brazilian phone number.
|
|
76
|
+
#
|
|
77
|
+
# Without `options`, formats as the subscriber number only (`sn` mask,
|
|
78
|
+
# no DDD): e.g. `"988887777"` becomes `"98888-7777"`. Other masks:
|
|
79
|
+
# `:ddd` (`(11) 99402-9275`), `:e164` (`+5511994029275`),
|
|
80
|
+
# `:international` (`+55 11 99402-9275`) and `:service`
|
|
81
|
+
# (`0800 123 4567`).
|
|
82
|
+
#
|
|
83
|
+
# @param phone [String, Integer] A phone number, with or without formatting.
|
|
84
|
+
# @param options [Hash] `:mask` picks the mask (default `:sn`).
|
|
85
|
+
#
|
|
86
|
+
# @return [String] The formatted phone number, or an empty string when
|
|
87
|
+
# there is nothing to format.
|
|
88
|
+
#
|
|
89
|
+
# @example
|
|
90
|
+
# format_phone("988887777") #=> "98888-7777"
|
|
91
|
+
# format_phone("1130000000") #=> "11300-0000"
|
|
92
|
+
# format_phone("11994029275", mask: :ddd) #=> "(11) 99402-9275"
|
|
93
|
+
def self.format_phone(phone, options = {})
|
|
94
|
+
return '' unless phone.is_a?(String) || phone.is_a?(Integer)
|
|
95
|
+
|
|
96
|
+
digits = phone.to_s.gsub(/\D/, '')
|
|
97
|
+
return '' if digits.empty?
|
|
98
|
+
|
|
99
|
+
mask = (options[:mask] || options['mask'] || :sn).to_s
|
|
100
|
+
|
|
101
|
+
case mask
|
|
102
|
+
when 'ddd'
|
|
103
|
+
format_ddd_mask(digits)
|
|
104
|
+
when 'e164'
|
|
105
|
+
format_e164_mask(digits)
|
|
106
|
+
when 'international'
|
|
107
|
+
format_international_mask(digits)
|
|
108
|
+
when 'service'
|
|
109
|
+
format_service_mask(digits)
|
|
110
|
+
else
|
|
111
|
+
format_subscriber_number_mask(digits)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Alias for format_phone
|
|
116
|
+
class << self
|
|
117
|
+
alias format format_phone
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# @private
|
|
121
|
+
def self.format_subscriber_number_mask(digits)
|
|
122
|
+
d = digits[0, [digits.length, 9].min]
|
|
123
|
+
return d if d.length <= 5
|
|
124
|
+
|
|
125
|
+
"#{d[0, 5]}-#{d[5..-1]}"
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
private_class_method :format_subscriber_number_mask
|
|
129
|
+
|
|
130
|
+
# @private
|
|
131
|
+
def self.format_ddd_mask(digits)
|
|
132
|
+
d = strip_country_code(digits)
|
|
133
|
+
return '' unless d.length == 10 || d.length == 11
|
|
134
|
+
|
|
135
|
+
ddd = d[0, 2]
|
|
136
|
+
subscriber = d[2..-1]
|
|
137
|
+
"(#{ddd}) #{subscriber[0..-5]}-#{subscriber[-4..-1]}"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
private_class_method :format_ddd_mask
|
|
141
|
+
|
|
142
|
+
# @private
|
|
143
|
+
def self.format_e164_mask(digits)
|
|
144
|
+
d = strip_country_code(digits)
|
|
145
|
+
return '' unless d.length == 10 || d.length == 11
|
|
146
|
+
|
|
147
|
+
"+55#{d}"
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
private_class_method :format_e164_mask
|
|
151
|
+
|
|
152
|
+
# @private
|
|
153
|
+
def self.format_international_mask(digits)
|
|
154
|
+
d = strip_country_code(digits)
|
|
155
|
+
return '' unless d.length == 10 || d.length == 11
|
|
156
|
+
|
|
157
|
+
ddd = d[0, 2]
|
|
158
|
+
subscriber = d[2..-1]
|
|
159
|
+
"+55 #{ddd} #{subscriber[0..-5]}-#{subscriber[-4..-1]}"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
private_class_method :format_international_mask
|
|
163
|
+
|
|
164
|
+
# @private
|
|
165
|
+
def self.format_service_mask(digits)
|
|
166
|
+
if digits.length == 11 && SERVICE_CNG_PREFIXES.include?(digits[0, 4])
|
|
167
|
+
"#{digits[0, 4]} #{digits[4, 3]} #{digits[7, 4]}"
|
|
168
|
+
elsif digits.length == 8
|
|
169
|
+
"#{digits[0, 4]}-#{digits[4, 4]}"
|
|
170
|
+
else
|
|
171
|
+
digits
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
private_class_method :format_service_mask
|
|
176
|
+
|
|
177
|
+
# Returns if a Brazilian phone number is valid (mobile or landline).
|
|
178
|
+
#
|
|
179
|
+
# A country code (`+55`, `0055` or a bare `55`) is accepted and removed
|
|
180
|
+
# first, as in {parse}.
|
|
181
|
+
#
|
|
182
|
+
# @param phone_number [String] The phone number to validate.
|
|
183
|
+
# @param type [Symbol, String, Hash, nil] :mobile, :landline, "mobile",
|
|
184
|
+
# "landline", or a Hash of options (`:type`, `:mobile_version`).
|
|
185
|
+
# If not specified, checks for either type.
|
|
186
|
+
#
|
|
187
|
+
# @return [Boolean] True if the phone number is valid, false otherwise
|
|
188
|
+
#
|
|
189
|
+
# @example
|
|
190
|
+
# is_valid("11994029275") #=> true (mobile)
|
|
191
|
+
# is_valid("1635014415") #=> true (landline)
|
|
192
|
+
# is_valid("+5511994029275") #=> true (country code stripped first)
|
|
193
|
+
def self.is_valid(phone_number, type = nil)
|
|
194
|
+
return false unless phone_number.is_a?(String)
|
|
195
|
+
|
|
196
|
+
options = type.is_a?(Hash) ? type : { type: type }
|
|
197
|
+
type_str = options[:type] ? options[:type].to_s : nil
|
|
198
|
+
mobile_version = options[:mobile_version] || 1
|
|
199
|
+
|
|
200
|
+
digits = phone_number.to_s.gsub(/\D/, '')
|
|
201
|
+
return false if digits.empty?
|
|
202
|
+
|
|
203
|
+
value = strip_country_code(digits)
|
|
204
|
+
|
|
205
|
+
case type_str
|
|
206
|
+
when 'mobile'
|
|
207
|
+
mobile_number_matches?(value, mobile_version)
|
|
208
|
+
when 'landline'
|
|
209
|
+
landline_number_matches?(value)
|
|
210
|
+
when 'service'
|
|
211
|
+
service_number_matches?(value)
|
|
212
|
+
else
|
|
213
|
+
mobile_number_matches?(value, mobile_version) || landline_number_matches?(value)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
|
|
217
|
+
# Alias for is_valid
|
|
218
|
+
class << self
|
|
219
|
+
alias valid? is_valid
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# Validates if a phone number is a valid Brazilian mobile phone (DDD +
|
|
223
|
+
# 9 digits). A country code is accepted and removed first, as in {parse}.
|
|
224
|
+
#
|
|
225
|
+
# @param value [String] The phone number to validate.
|
|
226
|
+
# @param options [Hash] `:version` 1 (default, subscriber digit 6-9) or
|
|
227
|
+
# 2 (Resolução Anatel nº 749/2022: subscriber digit 7-9, no 700 series).
|
|
228
|
+
# @return [Boolean]
|
|
229
|
+
def self.is_valid_mobile(value, options = {})
|
|
230
|
+
return false unless value.is_a?(String)
|
|
231
|
+
|
|
232
|
+
digits = value.to_s.gsub(/\D/, '')
|
|
233
|
+
return false if digits.empty?
|
|
234
|
+
|
|
235
|
+
version = options[:version] || options['version'] || 1
|
|
236
|
+
mobile_number_matches?(strip_country_code(digits), version)
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
class << self
|
|
240
|
+
alias valid_mobile? is_valid_mobile
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
# Validates if a phone number is a valid Brazilian landline phone (DDD +
|
|
244
|
+
# 8 digits). A country code is accepted and removed first, as in {parse}.
|
|
245
|
+
#
|
|
246
|
+
# @param value [String]
|
|
247
|
+
# @return [Boolean]
|
|
248
|
+
def self.is_valid_landline(value)
|
|
249
|
+
return false unless value.is_a?(String)
|
|
250
|
+
|
|
251
|
+
digits = value.to_s.gsub(/\D/, '')
|
|
252
|
+
return false if digits.empty?
|
|
253
|
+
|
|
254
|
+
landline_number_matches?(strip_country_code(digits))
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
class << self
|
|
258
|
+
alias valid_landline? is_valid_landline
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
# Validates if a phone number is a valid Brazilian service number
|
|
262
|
+
# (Código Não Geográfico or a 3-digit public-utility code).
|
|
263
|
+
#
|
|
264
|
+
# @param value [String]
|
|
265
|
+
# @return [Boolean]
|
|
266
|
+
def self.is_valid_service(value)
|
|
267
|
+
return false unless value.is_a?(String)
|
|
268
|
+
|
|
269
|
+
digits = value.to_s.gsub(/\D/, '')
|
|
270
|
+
return false if digits.empty?
|
|
271
|
+
|
|
272
|
+
service_number_matches?(digits)
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
class << self
|
|
276
|
+
alias valid_service? is_valid_service
|
|
277
|
+
end
|
|
278
|
+
|
|
279
|
+
# Removes common symbols from a Brazilian phone number string.
|
|
280
|
+
#
|
|
281
|
+
# Removes: (, ), -, +, and spaces
|
|
282
|
+
#
|
|
283
|
+
# @param phone_number [String] The phone number to remove symbols from
|
|
284
|
+
#
|
|
285
|
+
# @return [String] A new string with the specified symbols removed
|
|
286
|
+
#
|
|
287
|
+
# @example
|
|
288
|
+
# remove_symbols_phone("(11)99402-9275")
|
|
289
|
+
# #=> "11994029275"
|
|
290
|
+
#
|
|
291
|
+
# remove_symbols_phone("+55 11 99402-9275")
|
|
292
|
+
# #=> "5511994029275"
|
|
293
|
+
#
|
|
294
|
+
# remove_symbols_phone("(16) 3501-4415")
|
|
295
|
+
# #=> "1635014415"
|
|
296
|
+
def self.remove_symbols_phone(phone_number)
|
|
297
|
+
return '' unless phone_number.is_a?(String)
|
|
298
|
+
|
|
299
|
+
phone_number.gsub(/[\(\)\-\+\s]/, '')
|
|
300
|
+
end
|
|
301
|
+
|
|
302
|
+
# Alias for remove_symbols_phone
|
|
303
|
+
class << self
|
|
304
|
+
alias remove_symbols remove_symbols_phone
|
|
305
|
+
alias sieve remove_symbols_phone
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# Generates a valid and random phone number.
|
|
309
|
+
#
|
|
310
|
+
# @param type [Symbol, String, nil] :mobile, :landline, "mobile", or "landline".
|
|
311
|
+
# If not specified, generates either type randomly.
|
|
312
|
+
#
|
|
313
|
+
# @return [String] A randomly generated valid phone number
|
|
314
|
+
#
|
|
315
|
+
# @example
|
|
316
|
+
# generate
|
|
317
|
+
# #=> "2234451215" (random type)
|
|
318
|
+
#
|
|
319
|
+
# generate(:mobile)
|
|
320
|
+
# #=> "11999115895"
|
|
321
|
+
#
|
|
322
|
+
# generate(:landline)
|
|
323
|
+
# #=> "1635317900"
|
|
324
|
+
#
|
|
325
|
+
# generate("mobile")
|
|
326
|
+
# #=> "21987654321"
|
|
327
|
+
def self.generate(type = nil)
|
|
328
|
+
type_str = type.to_s if type
|
|
329
|
+
|
|
330
|
+
case type_str
|
|
331
|
+
when 'mobile'
|
|
332
|
+
generate_mobile_phone
|
|
333
|
+
when 'landline'
|
|
334
|
+
generate_landline_phone
|
|
335
|
+
else
|
|
336
|
+
[method(:generate_mobile_phone), method(:generate_landline_phone)].sample.call
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
# Removes the international dialing code (+55 or 55) from a phone number.
|
|
341
|
+
#
|
|
342
|
+
# Only removes the code if the resulting number has more than 11 digits.
|
|
343
|
+
#
|
|
344
|
+
# @param phone_number [String] The phone number with or without international code
|
|
345
|
+
#
|
|
346
|
+
# @return [String] The phone number without international code, or the same number if no code present
|
|
347
|
+
#
|
|
348
|
+
# @example
|
|
349
|
+
# remove_international_dialing_code("5511994029275")
|
|
350
|
+
# #=> "11994029275"
|
|
351
|
+
#
|
|
352
|
+
# remove_international_dialing_code("+5511994029275")
|
|
353
|
+
# #=> "11994029275"
|
|
354
|
+
#
|
|
355
|
+
# remove_international_dialing_code("1635014415")
|
|
356
|
+
# #=> "1635014415" (no international code)
|
|
357
|
+
#
|
|
358
|
+
# remove_international_dialing_code("+55 11 99402-9275")
|
|
359
|
+
# #=> "+55 11 99402-9275" (has spaces, length check fails)
|
|
360
|
+
def self.remove_international_dialing_code(phone_number)
|
|
361
|
+
return '' unless phone_number.is_a?(String)
|
|
362
|
+
|
|
363
|
+
# Only touch a "clean" digit string (with an optional leading '+') that
|
|
364
|
+
# is longer than 11 digits; anything with spaces/hyphens/etc. is left
|
|
365
|
+
# alone rather than partially stripped.
|
|
366
|
+
digits_part = phone_number.sub(/\A\+/, '')
|
|
367
|
+
|
|
368
|
+
if INTERNATIONAL_CODE_PATTERN.match?(phone_number) &&
|
|
369
|
+
digits_part.match?(/\A\d+\z/) && digits_part.length > 11
|
|
370
|
+
# Anchor to the start so only the leading "+55"/"55" is stripped
|
|
371
|
+
# (a plain #sub would also drop the '+' and could hit an unrelated
|
|
372
|
+
# "55" further into the number, e.g. an RS-state "55" DDD).
|
|
373
|
+
phone_number.sub(/\A\+?55/, '')
|
|
374
|
+
else
|
|
375
|
+
phone_number
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# Returns if a Brazilian mobile number is valid.
|
|
380
|
+
#
|
|
381
|
+
# Mobile pattern: DDD (2 digits 1-9) + 9 + 8 digits (total 11 digits)
|
|
382
|
+
#
|
|
383
|
+
# @param phone_number [String] The mobile number to validate
|
|
384
|
+
# @param version [Integer] 1 (default) or 2, see {is_valid_mobile}.
|
|
385
|
+
#
|
|
386
|
+
# @return [Boolean] True if valid mobile, false otherwise
|
|
387
|
+
#
|
|
388
|
+
# @private
|
|
389
|
+
def self.mobile_number_matches?(phone_number, version = 1)
|
|
390
|
+
return false unless phone_number.is_a?(String)
|
|
391
|
+
return false unless MOBILE_PATTERN.match?(phone_number.strip)
|
|
392
|
+
|
|
393
|
+
subscriber_first_digit = phone_number.strip[3]
|
|
394
|
+
|
|
395
|
+
case version.to_i
|
|
396
|
+
when 2
|
|
397
|
+
return false unless %w[7 8 9].include?(subscriber_first_digit)
|
|
398
|
+
return false if phone_number.strip[3, 3] == '700'
|
|
399
|
+
|
|
400
|
+
true
|
|
401
|
+
else
|
|
402
|
+
true
|
|
403
|
+
end
|
|
404
|
+
end
|
|
405
|
+
|
|
406
|
+
private_class_method :mobile_number_matches?
|
|
407
|
+
|
|
408
|
+
# Returns if a Brazilian landline number is valid.
|
|
409
|
+
#
|
|
410
|
+
# Landline pattern: DDD (2 digits 1-9) + [2-5] + 7 digits (total 10 digits)
|
|
411
|
+
#
|
|
412
|
+
# @param phone_number [String] The landline number to validate
|
|
413
|
+
#
|
|
414
|
+
# @return [Boolean] True if valid landline, false otherwise
|
|
415
|
+
#
|
|
416
|
+
# @private
|
|
417
|
+
def self.landline_number_matches?(phone_number)
|
|
418
|
+
return false unless phone_number.is_a?(String)
|
|
419
|
+
|
|
420
|
+
LANDLINE_PATTERN.match?(phone_number.strip)
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
private_class_method :landline_number_matches?
|
|
424
|
+
|
|
425
|
+
# Returns if a value is a valid Brazilian service number.
|
|
426
|
+
#
|
|
427
|
+
# @param value [String] Digits-only phone number.
|
|
428
|
+
# @return [Boolean]
|
|
429
|
+
#
|
|
430
|
+
# @private
|
|
431
|
+
def self.service_number_matches?(value)
|
|
432
|
+
return false unless value.is_a?(String)
|
|
433
|
+
|
|
434
|
+
v = value.strip
|
|
435
|
+
|
|
436
|
+
return true if v.length == 11 && SERVICE_CNG_PREFIXES.include?(v[0, 4])
|
|
437
|
+
return true if v.length == 8 && %w[300 400].include?(v[0, 3])
|
|
438
|
+
return true if v.length == 3 && SERVICE_SHORT_CODES.include?(v)
|
|
439
|
+
|
|
440
|
+
false
|
|
441
|
+
end
|
|
442
|
+
|
|
443
|
+
private_class_method :service_number_matches?
|
|
444
|
+
|
|
445
|
+
# Generates a valid DDD (area code) number.
|
|
446
|
+
#
|
|
447
|
+
# DDD consists of 2 digits, both ranging from 1-9.
|
|
448
|
+
#
|
|
449
|
+
# @return [String] A 2-digit DDD number
|
|
450
|
+
#
|
|
451
|
+
# @private
|
|
452
|
+
def self.generate_ddd_number
|
|
453
|
+
2.times.map { rand(1..9) }.join
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
private_class_method :generate_ddd_number
|
|
457
|
+
|
|
458
|
+
# Generates a valid and random mobile phone number.
|
|
459
|
+
#
|
|
460
|
+
# Format: DDD + 9 + 8 random digits (total 11 digits)
|
|
461
|
+
#
|
|
462
|
+
# @return [String] A valid mobile phone number
|
|
463
|
+
#
|
|
464
|
+
# @private
|
|
465
|
+
def self.generate_mobile_phone
|
|
466
|
+
ddd = generate_ddd_number
|
|
467
|
+
client_number = 8.times.map { rand(0..9) }.join
|
|
468
|
+
|
|
469
|
+
"#{ddd}9#{client_number}"
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
private_class_method :generate_mobile_phone
|
|
473
|
+
|
|
474
|
+
# Generates a valid and random landline phone number.
|
|
475
|
+
#
|
|
476
|
+
# Format: DDD + [2-5] + 7 random digits (total 10 digits)
|
|
477
|
+
#
|
|
478
|
+
# @return [String] A valid landline phone number
|
|
479
|
+
#
|
|
480
|
+
# @private
|
|
481
|
+
def self.generate_landline_phone
|
|
482
|
+
ddd = generate_ddd_number
|
|
483
|
+
first_digit = rand(2..5)
|
|
484
|
+
remaining_digits = rand(0..9999999).to_s.rjust(7, '0')
|
|
485
|
+
|
|
486
|
+
"#{ddd}#{first_digit}#{remaining_digits}"
|
|
487
|
+
end
|
|
488
|
+
|
|
489
|
+
private_class_method :generate_landline_phone
|
|
490
|
+
end
|
|
491
|
+
end
|