Module: Philiprehberger::Phone

Defined in:
lib/philiprehberger/phone.rb,
lib/philiprehberger/phone/vanity.rb,
lib/philiprehberger/phone/carrier.rb,
lib/philiprehberger/phone/version.rb,
lib/philiprehberger/phone/area_code.rb,
lib/philiprehberger/phone/countries.rb,
lib/philiprehberger/phone/shortcode.rb,
lib/philiprehberger/phone/phone_type.rb

Defined Under Namespace

Modules: AreaCodeLookup, CarrierLookup, PhoneTypeDetection, ShortcodeValidation, VanityConversion Classes: ParseError, PhoneNumber

Constant Summary collapse

FORMATS =
{
  e164: :e164,
  national: :formatted,
  international: :international
}.freeze
VANITY_MAP =
{
  'A' => '2', 'B' => '2', 'C' => '2',
  'D' => '3', 'E' => '3', 'F' => '3',
  'G' => '4', 'H' => '4', 'I' => '4',
  'J' => '5', 'K' => '5', 'L' => '5',
  'M' => '6', 'N' => '6', 'O' => '6',
  'P' => '7', 'Q' => '7', 'R' => '7', 'S' => '7',
  'T' => '8', 'U' => '8', 'V' => '8',
  'W' => '9', 'X' => '9', 'Y' => '9', 'Z' => '9'
}.freeze
CARRIER_PREFIXES =

Carrier identification based on prefix ranges These are representative ranges; real carrier data changes frequently

{
  us: {
    'AT&T' => %w[
      200 201 202 203 205 206 207 208 209 210
      212 213 214 215 217 219 224 225 228 229
      231 234 239 240 248 251 252 253 254 256
      260 262 267 269 270 276 278 281
    ],
    'Verizon' => %w[
      301 302 303 304 305 307 308 310 312 313
      314 315 316 317 318 319 320 321 323 325
      330 331 334 336 337 339 340 346 347 351
      352 360 361 364 380 385 386
    ],
    'T-Mobile' => %w[
      401 402 404 405 406 407 408 409 410 412
      413 414 415 417 419 423 424 425 430 432
      434 435 440 442 443 445 447 458 463 469
      470 475 478 479 480 484
    ]
  },
  ca: {
    'Rogers' => %w[416 647 437],
    'Bell' => %w[613 514 819],
    'Telus' => %w[604 778 250],
    'Freedom' => %w[343 365]
  },
  gb: {
    'EE' => %w[74],
    'Three' => %w[73],
    'Vodafone' => %w[77],
    'O2' => %w[75]
  },
  de: {
    'Telekom' => %w[151 160 170 171],
    'Vodafone' => %w[152 162 172 173],
    'O2' => %w[155 157 163 176 177 178 179]
  }
}.freeze
VERSION =
'0.10.0'
AREA_CODES =

Area code metadata for US, CA, GB, and DE

{
  us: {
    '212' => 'New York, NY',
    '213' => 'Los Angeles, CA',
    '214' => 'Dallas, TX',
    '215' => 'Philadelphia, PA',
    '216' => 'Cleveland, OH',
    '224' => 'Northern Illinois',
    '301' => 'Maryland',
    '302' => 'Delaware',
    '303' => 'Denver, CO',
    '305' => 'Miami, FL',
    '310' => 'Los Angeles, CA',
    '312' => 'Chicago, IL',
    '313' => 'Detroit, MI',
    '314' => 'St. Louis, MO',
    '323' => 'Los Angeles, CA',
    '347' => 'New York, NY',
    '404' => 'Atlanta, GA',
    '408' => 'San Jose, CA',
    '410' => 'Baltimore, MD',
    '412' => 'Pittsburgh, PA',
    '415' => 'San Francisco, CA',
    '425' => 'Bellevue, WA',
    '470' => 'Atlanta, GA',
    '480' => 'Phoenix, AZ',
    '503' => 'Portland, OR',
    '504' => 'New Orleans, LA',
    '510' => 'Oakland, CA',
    '512' => 'Austin, TX',
    '513' => 'Cincinnati, OH',
    '515' => 'Des Moines, IA',
    '516' => 'Long Island, NY',
    '551' => 'Northern New Jersey',
    '555' => 'Fictional/Reserved',
    '602' => 'Phoenix, AZ',
    '612' => 'Minneapolis, MN',
    '617' => 'Boston, MA',
    '619' => 'San Diego, CA',
    '626' => 'Pasadena, CA',
    '650' => 'San Mateo, CA',
    '702' => 'Las Vegas, NV',
    '703' => 'Northern Virginia',
    '704' => 'Charlotte, NC',
    '713' => 'Houston, TX',
    '714' => 'Orange County, CA',
    '718' => 'New York, NY',
    '720' => 'Denver, CO',
    '737' => 'Austin, TX',
    '747' => 'Los Angeles, CA',
    '773' => 'Chicago, IL',
    '801' => 'Salt Lake City, UT',
    '802' => 'Vermont',
    '808' => 'Hawaii',
    '818' => 'Los Angeles, CA',
    '832' => 'Houston, TX',
    '847' => 'Northern Illinois',
    '858' => 'San Diego, CA',
    '862' => 'Northern New Jersey',
    '901' => 'Memphis, TN',
    '917' => 'New York, NY',
    '919' => 'Raleigh, NC',
    '925' => 'East Bay, CA',
    '929' => 'New York, NY',
    '949' => 'Orange County, CA',
    '972' => 'Dallas, TX'
  },
  ca: {
    '204' => 'Manitoba',
    '226' => 'Southwestern Ontario',
    '236' => 'British Columbia',
    '249' => 'Northern Ontario',
    '250' => 'British Columbia',
    '289' => 'Greater Toronto Area',
    '306' => 'Saskatchewan',
    '343' => 'Eastern Ontario',
    '365' => 'Greater Toronto Area',
    '403' => 'Alberta (South)',
    '416' => 'Toronto, ON',
    '418' => 'Quebec City, QC',
    '431' => 'Manitoba',
    '437' => 'Toronto, ON',
    '438' => 'Montreal, QC',
    '450' => 'Greater Montreal',
    '506' => 'New Brunswick',
    '514' => 'Montreal, QC',
    '519' => 'Southwestern Ontario',
    '548' => 'Southwestern Ontario',
    '579' => 'Quebec',
    '581' => 'Quebec',
    '587' => 'Alberta',
    '604' => 'Vancouver, BC',
    '613' => 'Ottawa, ON',
    '639' => 'Saskatchewan',
    '647' => 'Toronto, ON',
    '672' => 'British Columbia',
    '705' => 'Northern Ontario',
    '709' => 'Newfoundland',
    '778' => 'British Columbia',
    '780' => 'Alberta (North)',
    '807' => 'Northwestern Ontario',
    '819' => 'Quebec (West)',
    '867' => 'Northern Territories',
    '873' => 'Quebec',
    '902' => 'Nova Scotia/PEI',
    '905' => 'Greater Toronto Area'
  },
  gb: {
    '20' => 'London',
    '21' => 'Birmingham',
    '23' => 'Southampton/Portsmouth',
    '24' => 'Coventry',
    '28' => 'Northern Ireland',
    '29' => 'Cardiff',
    '113' => 'Leeds',
    '114' => 'Sheffield',
    '115' => 'Nottingham',
    '116' => 'Leicester',
    '117' => 'Bristol',
    '118' => 'Reading',
    '121' => 'Birmingham',
    '131' => 'Edinburgh',
    '141' => 'Glasgow',
    '151' => 'Liverpool',
    '161' => 'Manchester',
    '171' => 'London (Historic)',
    '191' => 'Newcastle'
  },
  de: {
    '30' => 'Berlin',
    '40' => 'Hamburg',
    '69' => 'Frankfurt',
    '89' => 'Munich',
    '211' => 'Dusseldorf',
    '221' => 'Cologne',
    '228' => 'Bonn',
    '231' => 'Dortmund',
    '241' => 'Aachen',
    '251' => 'Munster',
    '341' => 'Leipzig',
    '351' => 'Dresden',
    '361' => 'Erfurt',
    '371' => 'Chemnitz',
    '381' => 'Rostock',
    '391' => 'Magdeburg',
    '511' => 'Hannover',
    '521' => 'Bielefeld',
    '551' => 'Gottingen',
    '611' => 'Wiesbaden',
    '621' => 'Mannheim',
    '711' => 'Stuttgart',
    '721' => 'Karlsruhe',
    '911' => 'Nuremberg'
  }
}.freeze
COUNTRIES =

trunk_prefix: true marks countries that use a national trunk 0 (dropped when composing E.164). It is intentionally absent for NANP (+1) and for numbering plans where a leading 0 is significant (e.g. Italy, South Africa).

{
  us: { code: '1', lengths: [10], format: '(%s) %s-%s', groups: [3, 3, 4], name: 'United States' },
  ca: { code: '1', lengths: [10], format: '(%s) %s-%s', groups: [3, 3, 4], name: 'Canada' },
  gb: { code: '44', lengths: [10, 11], format: '%s %s', groups: [4, 6], name: 'United Kingdom', trunk_prefix: true },
  de: { code: '49', lengths: [10, 11], format: '%s %s', groups: [4, 7], name: 'Germany', trunk_prefix: true },
  fr: { code: '33', lengths: [9], format: '%s %s %s %s %s', groups: [1, 2, 2, 2, 2], name: 'France', trunk_prefix: true },
  au: { code: '61', lengths: [9], format: '%s %s %s', groups: [4, 3, 3], name: 'Australia', trunk_prefix: true },
  jp: { code: '81', lengths: [10, 11], format: '%s-%s-%s', groups: [2, 4, 4], name: 'Japan', trunk_prefix: true },
  in: { code: '91', lengths: [10], format: '%s %s', groups: [5, 5], name: 'India', trunk_prefix: true },
  br: { code: '55', lengths: [10, 11], format: '(%s) %s-%s', groups: [2, 5, 4], name: 'Brazil' },
  mx: { code: '52', lengths: [10], format: '%s %s %s', groups: [3, 3, 4], name: 'Mexico' },
  es: { code: '34', lengths: [9], format: '%s %s %s', groups: [3, 3, 3], name: 'Spain' },
  it: { code: '39', lengths: [9, 10], format: '%s %s', groups: [3, 7], name: 'Italy' },
  nl: { code: '31', lengths: [9], format: '%s %s', groups: [3, 6], name: 'Netherlands', trunk_prefix: true },
  be: { code: '32', lengths: [8, 9], format: '%s %s %s', groups: [3, 3, 3], name: 'Belgium', trunk_prefix: true },
  ch: { code: '41', lengths: [9], format: '%s %s %s', groups: [3, 3, 3], name: 'Switzerland', trunk_prefix: true },
  at: { code: '43', lengths: [10, 11], format: '%s %s', groups: [4, 7], name: 'Austria', trunk_prefix: true },
  se: { code: '46', lengths: [9, 10], format: '%s-%s %s', groups: [3, 3, 4], name: 'Sweden', trunk_prefix: true },
  no: { code: '47', lengths: [8], format: '%s %s %s', groups: [3, 2, 3], name: 'Norway' },
  dk: { code: '45', lengths: [8], format: '%s %s %s %s', groups: [2, 2, 2, 2], name: 'Denmark' },
  fi: { code: '358', lengths: [9, 10], format: '%s %s', groups: [3, 7], name: 'Finland', trunk_prefix: true },
  pl: { code: '48', lengths: [9], format: '%s %s %s', groups: [3, 3, 3], name: 'Poland' },
  pt: { code: '351', lengths: [9], format: '%s %s %s', groups: [3, 3, 3], name: 'Portugal' },
  ie: { code: '353', lengths: [9, 10], format: '%s %s', groups: [3, 7], name: 'Ireland', trunk_prefix: true },
  ru: { code: '7', lengths: [10], format: '(%s) %s-%s-%s', groups: [3, 3, 2, 2], name: 'Russia' },
  cn: { code: '86', lengths: [11], format: '%s %s %s', groups: [3, 4, 4], name: 'China', trunk_prefix: true },
  kr: { code: '82', lengths: [10, 11], format: '%s-%s-%s', groups: [3, 4, 4], name: 'South Korea', trunk_prefix: true },
  sg: { code: '65', lengths: [8], format: '%s %s', groups: [4, 4], name: 'Singapore' },
  nz: { code: '64', lengths: [8, 9, 10], format: '%s %s', groups: [3, 7], name: 'New Zealand', trunk_prefix: true },
  za: { code: '27', lengths: [9], format: '%s %s %s', groups: [3, 3, 3], name: 'South Africa' },
  ng: { code: '234', lengths: [10, 11], format: '%s %s %s', groups: [3, 4, 4], name: 'Nigeria', trunk_prefix: true },
  ke: { code: '254', lengths: [9, 10], format: '%s %s', groups: [3, 7], name: 'Kenya', trunk_prefix: true },
  eg: { code: '20', lengths: [10], format: '%s %s %s', groups: [3, 4, 3], name: 'Egypt', trunk_prefix: true },
  ar: { code: '54', lengths: [10], format: '%s %s-%s', groups: [3, 3, 4], name: 'Argentina', trunk_prefix: true },
  cl: { code: '56', lengths: [9], format: '%s %s %s', groups: [3, 3, 3], name: 'Chile' },
  co: { code: '57', lengths: [10], format: '%s %s %s', groups: [3, 3, 4], name: 'Colombia' },
  pe: { code: '51', lengths: [9], format: '%s %s %s', groups: [3, 3, 3], name: 'Peru' }
}.freeze
COUNTRY_CODE_MAP =
COUNTRIES.each_with_object({}) do |(sym, data), map|
  map[data[:code]] ||= []
  map[data[:code]] << sym
end.freeze
SHORTCODE_RULES =

SMS shortcode validation rules per country

{
  us: { lengths: [5, 6] },
  ca: { lengths: [5, 6] },
  gb: { lengths: [5, 6] },
  de: { lengths: [4, 5] },
  fr: { lengths: [5] },
  au: { lengths: [6] },
  in: { lengths: [5, 6] },
  br: { lengths: [5] },
  mx: { lengths: [5] },
  jp: { lengths: [4, 5] },
  kr: { lengths: [4] },
  it: { lengths: [5] },
  es: { lengths: [5, 6] }
}.freeze
PHONE_TYPE_PATTERNS =

Phone type detection based on prefix patterns per country

{
  us: {
    toll_free: /\A(800|888|877|866|855|844|833)\d{7}\z/,
    premium: /\A(900|976)\d{7}\z/,
    mobile: /\A[2-9]\d{2}[2-9]\d{6}\z/
  },
  ca: {
    toll_free: /\A(800|888|877|866|855|844|833)\d{7}\z/,
    premium: /\A(900|976)\d{7}\z/,
    mobile: /\A[2-9]\d{2}[2-9]\d{6}\z/
  },
  gb: {
    toll_free: /\A(800|808)\d{6,7}\z/,
    premium: /\A(90[0-9]|91[0-9])\d{7}\z/,
    mobile: /\A7[1-9]\d{8}\z/,
    landline: /\A(1|2)\d{8,9}\z/
  },
  de: {
    toll_free: /\A800\d{7}\z/,
    premium: /\A(900|906)\d{6,7}\z/,
    mobile: /\A(15|16|17)\d{8,9}\z/,
    landline: /\A(2|3|4|5|6|7|8|9)\d{8,9}\z/
  },
  fr: {
    toll_free: /\A800\d{6}\z/,
    premium: /\A8[1-9]\d{7}\z/,
    mobile: /\A[67]\d{8}\z/,
    landline: /\A[1-5]\d{8}\z/
  },
  au: {
    toll_free: /\A(1800)\d{6}\z/,
    premium: /\A(190[0-9])\d{6}\z/,
    mobile: /\A4\d{8}\z/,
    landline: /\A[2378]\d{8}\z/
  },
  jp: {
    toll_free: /\A(120|800)\d{7}\z/,
    mobile: /\A[789]0\d{8}\z/,
    landline: /\A[1-6]\d{8,9}\z/
  },
  in: {
    toll_free: /\A(1800)\d{6,7}\z/,
    mobile: /\A[6-9]\d{9}\z/,
    landline: /\A[1-5]\d{9}\z/
  },
  br: {
    toll_free: /\A(0800)\d{7}\z/,
    mobile: /\A\d{2}9\d{8}\z/,
    landline: /\A\d{2}[2-5]\d{7}\z/
  },
  mx: {
    mobile: /\A[1-9]\d{9}\z/
  },
  es: {
    toll_free: /\A(800|900)\d{6}\z/,
    mobile: /\A[67]\d{8}\z/,
    landline: /\A[89]\d{8}\z/
  },
  it: {
    toll_free: /\A800\d{6}\z/,
    mobile: /\A3\d{8,9}\z/,
    landline: /\A0\d{8,9}\z/
  },
  ru: {
    toll_free: /\A(800)\d{7}\z/,
    mobile: /\A9\d{9}\z/,
    landline: /\A[3-8]\d{9}\z/
  },
  cn: {
    mobile: /\A1[3-9]\d{9}\z/,
    landline: /\A[2-9]\d{9,10}\z/
  },
  kr: {
    toll_free: /\A(80)\d{7,8}\z/,
    mobile: /\A(10|11|16|17|18|19)\d{7,8}\z/,
    landline: /\A(2|3[1-3]|4[1-4]|5[1-5]|6[1-4])\d{7,8}\z/
  },
  nl: {
    toll_free: /\A(800|900)\d{4,7}\z/,
    premium: /\A(906|909)\d{4,7}\z/,
    mobile: /\A6\d{8}\z/,
    landline: /\A[1-5]\d{7,8}\z/
  },
  be: {
    toll_free: /\A(800)\d{5}\z/,
    premium: /\A(900|70)\d{5,6}\z/,
    mobile: /\A4\d{7,8}\z/,
    landline: /\A[1-9]\d{6,7}\z/
  },
  ch: {
    toll_free: /\A(800)\d{6}\z/,
    premium: /\A(900|901)\d{6}\z/,
    mobile: /\A7[5-9]\d{7}\z/,
    landline: /\A[2-6]\d{7,8}\z/
  },
  at: {
    toll_free: /\A(800)\d{6,7}\z/,
    premium: /\A(900|901)\d{6,7}\z/,
    mobile: /\A(650|660|664|676|680|681|688|699)\d{6,7}\z/,
    landline: /\A[1-5]\d{8,9}\z/
  },
  se: {
    toll_free: /\A(20)\d{6,7}\z/,
    premium: /\A(900|939)\d{5,6}\z/,
    mobile: /\A7\d{8}\z/,
    landline: /\A[1-6]\d{7,8}\z/
  },
  no: {
    toll_free: /\A(800)\d{5}\z/,
    premium: /\A(820|829)\d{5}\z/,
    mobile: /\A[49]\d{7}\z/,
    landline: /\A[2-3]\d{7}\z/
  },
  dk: {
    toll_free: /\A(80)\d{6}\z/,
    premium: /\A(90)\d{6}\z/,
    mobile: /\A[2-4]\d{7}\z/,
    landline: /\A[3-9]\d{7}\z/
  },
  fi: {
    toll_free: /\A(800)\d{6,7}\z/,
    premium: /\A(600)\d{6,7}\z/,
    mobile: /\A(4[0-9]|50)\d{6,7}\z/,
    landline: /\A[1-3]\d{7,8}\z/
  },
  pl: {
    toll_free: /\A(800)\d{6}\z/,
    premium: /\A(70)\d{7}\z/,
    mobile: /\A[5-7]\d{8}\z/,
    landline: /\A[1-4]\d{7,8}\z/
  },
  pt: {
    toll_free: /\A(800)\d{6}\z/,
    premium: /\A(760)\d{6}\z/,
    mobile: /\A9[1-3,6]\d{7}\z/,
    landline: /\A2\d{8}\z/
  },
  ie: {
    toll_free: /\A(1800)\d{5,6}\z/,
    premium: /\A(15[12])\d{6,7}\z/,
    mobile: /\A8[35-9]\d{7}\z/,
    landline: /\A[1-6]\d{7,8}\z/
  },
  sg: {
    toll_free: /\A(1800)\d{4}\z/,
    mobile: /\A[89]\d{7}\z/,
    landline: /\A6\d{7}\z/
  },
  nz: {
    toll_free: /\A(800)\d{5,7}\z/,
    premium: /\A(900)\d{5,7}\z/,
    mobile: /\A2[0-9]\d{6,7}\z/,
    landline: /\A[3-9]\d{6,7}\z/
  },
  za: {
    toll_free: /\A(800)\d{6}\z/,
    premium: /\A(86[01])\d{6}\z/,
    mobile: /\A[67]\d{8}\z/,
    landline: /\A[1-5]\d{8}\z/
  },
  ng: {
    toll_free: /\A(800)\d{7,8}\z/,
    mobile: /\A[789]0\d{7,8}\z/,
    landline: /\A[1-9]\d{8,9}\z/
  },
  ke: {
    toll_free: /\A(800)\d{6}\z/,
    mobile: /\A7\d{8}\z/,
    landline: /\A[2-6]\d{7,8}\z/
  },
  eg: {
    toll_free: /\A(800)\d{7}\z/,
    mobile: /\A1[0-2]\d{8}\z/,
    landline: /\A[2-5]\d{8}\z/
  },
  ar: {
    toll_free: /\A(800)\d{7}\z/,
    mobile: /\A9\d{9}\z/,
    landline: /\A[1-8]\d{8,9}\z/
  },
  cl: {
    toll_free: /\A(800)\d{6}\z/,
    mobile: /\A9\d{8}\z/,
    landline: /\A[2-7]\d{7,8}\z/
  },
  co: {
    toll_free: /\A(800)\d{7}\z/,
    mobile: /\A3\d{9}\z/,
    landline: /\A[1-8]\d{8,9}\z/
  },
  pe: {
    toll_free: /\A(800)\d{5,6}\z/,
    mobile: /\A9\d{8}\z/,
    landline: /\A[1-8]\d{7,8}\z/
  }
}.freeze

Class Method Summary collapse

Class Method Details

.country_of(input, country: nil) ⇒ Symbol?

Return the detected country symbol for input, or nil if the input cannot be associated with a known country. Never raises.

Parameters:

  • input (String)

    phone number in any recognized form

  • country (Symbol, nil) (defaults to: nil)

    optional country hint

Returns:

  • (Symbol, nil)


224
225
226
227
228
# File 'lib/philiprehberger/phone.rb', line 224

def self.country_of(input, country: nil)
  parse(input, country: country).country
rescue ParseError
  nil
end

.format(input, format:, country: nil) ⇒ String

Parse and format an input in one call.

Parameters:

  • input (String)

    phone number in any recognized form

  • format (Symbol)

    :e164, :national, or :international

  • country (Symbol, nil) (defaults to: nil)

    country hint for parsing

Returns:

  • (String)

    formatted phone number

Raises:

  • (ArgumentError)

    for unknown format symbols

  • (ParseError)

    when the input cannot be parsed



244
245
246
247
248
249
# File 'lib/philiprehberger/phone.rb', line 244

def self.format(input, format:, country: nil)
  method_name = FORMATS[format] or
    raise ArgumentError, "Unknown format: #{format.inspect} (expected :e164, :national, or :international)"

  parse(input, country: country).public_send(method_name)
end

.parse(input, country: nil) ⇒ Object



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
# File 'lib/philiprehberger/phone.rb', line 186

def self.parse(input, country: nil)
  cleaned = input.to_s.strip
  return PhoneNumber.new(country_code: '', national: '', country: nil) if cleaned.empty?

  has_plus = cleaned.start_with?('+')
  digits = cleaned.gsub(/[^\d]/, '')

  if has_plus
    cc, national, sym = detect_country_from_digits(digits)
  elsif country
    data = COUNTRIES[country]
    raise ParseError, "unknown country: #{country}" unless data

    cc = data[:code]
    national = digits.delete_prefix(cc)
    # Nationally-formatted input for trunk-prefix countries keeps a leading
    # `0` (e.g. GB "020 7946 0958"); strip it so the E.164 is well-formed.
    national = national.delete_prefix('0') if data[:trunk_prefix]
    sym = country
  else
    cc, national, sym = detect_country_from_digits(digits)
  end

  PhoneNumber.new(country_code: cc, national: national, country: sym)
end

.valid?(input, country: nil) ⇒ Boolean

Returns:

  • (Boolean)


212
213
214
215
216
# File 'lib/philiprehberger/phone.rb', line 212

def self.valid?(input, country: nil)
  parse(input, country: country).valid?
rescue ParseError
  false
end

.valid_shortcode?(input, country: :us) ⇒ Boolean

Returns:

  • (Boolean)


255
256
257
# File 'lib/philiprehberger/phone.rb', line 255

def self.valid_shortcode?(input, country: :us)
  ShortcodeValidation.valid_shortcode?(input, country: country)
end

.vanity_to_digits(input) ⇒ Object



251
252
253
# File 'lib/philiprehberger/phone.rb', line 251

def self.vanity_to_digits(input)
  VanityConversion.vanity_to_digits(input)
end