Module: BrazilianUtils::PhoneUtils

Defined in:
lib/brazilian-utils/phone-utils.rb

Overview

Utilities for formatting, validating, and generating Brazilian phone numbers.

Brazilian phone numbers come in two types:

  • Mobile (Celular): 11 digits - DDD (2 digits) + 9 + 8 digits, e.g., "11994029275"
  • Landline (Fixo): 10 digits - DDD (2 digits) + [2-5] + 7 digits, e.g., "1635014415"

DDD (Discagem Direta à Distância) is the area code, ranging from 11 to 99. Mobile numbers always have 9 as the 3rd digit (after DDD). Landline numbers have 2, 3, 4, or 5 as the 3rd digit (after DDD).

Constant Summary collapse

MOBILE_PATTERN =

Pattern for mobile phone numbers (11 digits: DDD + 9 + 8 digits)

/^[1-9][1-9][9]\d{8}$/.freeze
LANDLINE_PATTERN =

Pattern for landline phone numbers (10 digits: DDD + [2-5] + 7 digits)

/^[1-9][1-9][2-5]\d{7}$/.freeze
INTERNATIONAL_CODE_PATTERN =

Pattern for international dialing code (+55 or 55)

/\+?55/.freeze
SERVICE_CNG_PREFIXES =

Códigos Não Geográficos (Anatel) that take 7 digits.

%w[0300 0303 0500 0800 0900].freeze
SERVICE_SHORT_CODES =

3-digit public-utility numbers designated by Anatel (não exaustivo).

%w[
  100 101 102 104 105 106 107 108 110 111 116 118 119 120 121 122 123
  125 126 127 128 129 130 131 132 133 135 136 137 138 140 141 144 145
  146 147 148 150 151 152 153 154 155 156 158 159 160 161 162 163 164
  171 172 173 174 175 176 177 178 179 180 181 185 188 189 190 191 192
  193 194 195 196 197 198 199
].freeze

Class Method Summary collapse

Class Method Details

.format_phone(phone, options = {}) ⇒ String Also known as: format

Formats a Brazilian phone number.

Without options, formats as the subscriber number only (sn mask, no DDD): e.g. "988887777" becomes "98888-7777". Other masks: :ddd ((11) 99402-9275), :e164 (+5511994029275), :international (+55 11 99402-9275) and :service (0800 123 4567).

Examples:

format_phone("988887777")     #=> "98888-7777"
format_phone("1130000000")    #=> "11300-0000"
format_phone("11994029275", mask: :ddd) #=> "(11) 99402-9275"

Parameters:

  • phone (String, Integer) —

    A phone number, with or without formatting.

  • options (Hash) (defaults to: {}) —

    :mask picks the mask (default :sn).

Returns:

  • (String) —

    The formatted phone number, or an empty string when there is nothing to format.



93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/brazilian-utils/phone-utils.rb', line 93

def self.format_phone(phone, options = {})
  return '' unless phone.is_a?(String) || phone.is_a?(Integer)

  digits = phone.to_s.gsub(/\D/, '')
  return '' if digits.empty?

  mask = (options[:mask] || options['mask'] || :sn).to_s

  case mask
  when 'ddd'
    format_ddd_mask(digits)
  when 'e164'
    format_e164_mask(digits)
  when 'international'
    format_international_mask(digits)
  when 'service'
    format_service_mask(digits)
  else
    format_subscriber_number_mask(digits)
  end
end

.generate(type = nil) ⇒ String

Generates a valid and random phone number.

Examples:

generate
#=> "2234451215" (random type)

generate(:mobile)
#=> "11999115895"

generate(:landline)
#=> "1635317900"

generate("mobile")
#=> "21987654321"

Parameters:

  • type (Symbol, String, nil) (defaults to: nil) —

    :mobile, :landline, "mobile", or "landline". If not specified, generates either type randomly.

Returns:

  • (String) —

    A randomly generated valid phone number



327
328
329
330
331
332
333
334
335
336
337
338
# File 'lib/brazilian-utils/phone-utils.rb', line 327

def self.generate(type = nil)
  type_str = type.to_s if type

  case type_str
  when 'mobile'
    generate_mobile_phone
  when 'landline'
    generate_landline_phone
  else
    [method(:generate_mobile_phone), method(:generate_landline_phone)].sample.call
  end
end

.is_valid(phone_number, type = nil) ⇒ Boolean Also known as: valid?

Returns if a Brazilian phone number is valid (mobile or landline).

A country code (+55, 0055 or a bare 55) is accepted and removed first, as in parse.

Examples:

is_valid("11994029275")   #=> true (mobile)
is_valid("1635014415")    #=> true (landline)
is_valid("+5511994029275") #=> true (country code stripped first)

Parameters:

  • phone_number (String) —

    The phone number to validate.

  • type (Symbol, String, Hash, nil) (defaults to: nil) —

    :mobile, :landline, "mobile", "landline", or a Hash of options (:type, :mobile_version). If not specified, checks for either type.

Returns:

  • (Boolean) —

    True if the phone number is valid, false otherwise



193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
# File 'lib/brazilian-utils/phone-utils.rb', line 193

def self.is_valid(phone_number, type = nil)
  return false unless phone_number.is_a?(String)

  options = type.is_a?(Hash) ? type : { type: type }
  type_str = options[:type] ? options[:type].to_s : nil
  mobile_version = options[:mobile_version] || 1

  digits = phone_number.to_s.gsub(/\D/, '')
  return false if digits.empty?

  value = strip_country_code(digits)

  case type_str
  when 'mobile'
    mobile_number_matches?(value, mobile_version)
  when 'landline'
    landline_number_matches?(value)
  when 'service'
    service_number_matches?(value)
  else
    mobile_number_matches?(value, mobile_version) || landline_number_matches?(value)
  end
end

.is_valid_landline(value) ⇒ Boolean Also known as: valid_landline?

Validates if a phone number is a valid Brazilian landline phone (DDD + 8 digits). A country code is accepted and removed first, as in parse.

Parameters:

  • value (String)

Returns:

  • (Boolean)


248
249
250
251
252
253
254
255
# File 'lib/brazilian-utils/phone-utils.rb', line 248

def self.is_valid_landline(value)
  return false unless value.is_a?(String)

  digits = value.to_s.gsub(/\D/, '')
  return false if digits.empty?

  landline_number_matches?(strip_country_code(digits))
end

.is_valid_mobile(value, options = {}) ⇒ Boolean Also known as: valid_mobile?

Validates if a phone number is a valid Brazilian mobile phone (DDD + 9 digits). A country code is accepted and removed first, as in parse.

Parameters:

  • value (String) —

    The phone number to validate.

  • options (Hash) (defaults to: {}) —

    :version 1 (default, subscriber digit 6-9) or 2 (Resolução Anatel nº 749/2022: subscriber digit 7-9, no 700 series).

Returns:

  • (Boolean)


229
230
231
232
233
234
235
236
237
# File 'lib/brazilian-utils/phone-utils.rb', line 229

def self.is_valid_mobile(value, options = {})
  return false unless value.is_a?(String)

  digits = value.to_s.gsub(/\D/, '')
  return false if digits.empty?

  version = options[:version] || options['version'] || 1
  mobile_number_matches?(strip_country_code(digits), version)
end

.is_valid_service(value) ⇒ Boolean Also known as: valid_service?

Validates if a phone number is a valid Brazilian service number (Código Não Geográfico or a 3-digit public-utility code).

Parameters:

  • value (String)

Returns:

  • (Boolean)


266
267
268
269
270
271
272
273
# File 'lib/brazilian-utils/phone-utils.rb', line 266

def self.is_valid_service(value)
  return false unless value.is_a?(String)

  digits = value.to_s.gsub(/\D/, '')
  return false if digits.empty?

  service_number_matches?(digits)
end

.parse(value) ⇒ String

Removes phone formatting and keeps only digits, capped to 11 digits.

A country code (+55, 0055 or a bare 55) is stripped only when 10 or 11 digits are left, so an area code of 55 is not mistaken for it.

Examples:

parse("(11) 98888-7777")     #=> "11988887777"
parse("+55 11 98888-7777")   #=> "11988887777"
parse("55988887777")         #=> "55988887777" (55 read as DDD)

Parameters:

  • value (String, Integer) —

    The value to parse.

Returns:

  • (String) —

    The parsed digits.



66
67
68
69
70
71
72
73
# File 'lib/brazilian-utils/phone-utils.rb', line 66

def self.parse(value)
  return '' unless value.is_a?(String) || value.is_a?(Integer)

  digits = value.to_s.gsub(/\D/, '')
  return '' if digits.empty?

  strip_country_code(digits)[0, 11]
end

.remove_international_dialing_code(phone_number) ⇒ String

Removes the international dialing code (+55 or 55) from a phone number.

Only removes the code if the resulting number has more than 11 digits.

Examples:

remove_international_dialing_code("5511994029275")
#=> "11994029275"

remove_international_dialing_code("+5511994029275")
#=> "11994029275"

remove_international_dialing_code("1635014415")
#=> "1635014415" (no international code)

remove_international_dialing_code("+55 11 99402-9275")
#=> "+55 11 99402-9275" (has spaces, length check fails)

Parameters:

  • phone_number (String) —

    The phone number with or without international code

Returns:

  • (String) —

    The phone number without international code, or the same number if no code present



360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
# File 'lib/brazilian-utils/phone-utils.rb', line 360

def self.remove_international_dialing_code(phone_number)
  return '' unless phone_number.is_a?(String)

  # Only touch a "clean" digit string (with an optional leading '+') that
  # is longer than 11 digits; anything with spaces/hyphens/etc. is left
  # alone rather than partially stripped.
  digits_part = phone_number.sub(/\A\+/, '')

  if INTERNATIONAL_CODE_PATTERN.match?(phone_number) &&
     digits_part.match?(/\A\d+\z/) && digits_part.length > 11
    # Anchor to the start so only the leading "+55"/"55" is stripped
    # (a plain #sub would also drop the '+' and could hit an unrelated
    # "55" further into the number, e.g. an RS-state "55" DDD).
    phone_number.sub(/\A\+?55/, '')
  else
    phone_number
  end
end

.remove_symbols_phone(phone_number) ⇒ String Also known as: remove_symbols, sieve

Removes common symbols from a Brazilian phone number string.

Removes: (, ), -, +, and spaces

Examples:

remove_symbols_phone("(11)99402-9275")
#=> "11994029275"

remove_symbols_phone("+55 11 99402-9275")
#=> "5511994029275"

remove_symbols_phone("(16) 3501-4415")
#=> "1635014415"

Parameters:

  • phone_number (String) —

    The phone number to remove symbols from

Returns:

  • (String) —

    A new string with the specified symbols removed



296
297
298
299
300
# File 'lib/brazilian-utils/phone-utils.rb', line 296

def self.remove_symbols_phone(phone_number)
  return '' unless phone_number.is_a?(String)

  phone_number.gsub(/[\(\)\-\+\s]/, '')
end