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
- 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.7.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 =
{ 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' }, de: { code: '49', lengths: [10, 11], format: '%s %s', groups: [4, 7], name: 'Germany' }, fr: { code: '33', lengths: [9], format: '%s %s %s %s %s', groups: [1, 2, 2, 2, 2], name: 'France' }, au: { code: '61', lengths: [9], format: '%s %s %s', groups: [4, 3, 3], name: 'Australia' }, jp: { code: '81', lengths: [10, 11], format: '%s-%s-%s', groups: [2, 4, 4], name: 'Japan' }, in: { code: '91', lengths: [10], format: '%s %s', groups: [5, 5], name: 'India' }, 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' }, be: { code: '32', lengths: [8, 9], format: '%s %s %s', groups: [3, 3, 3], name: 'Belgium' }, ch: { code: '41', lengths: [9], format: '%s %s %s', groups: [3, 3, 3], name: 'Switzerland' }, at: { code: '43', lengths: [10, 11], format: '%s %s', groups: [4, 7], name: 'Austria' }, se: { code: '46', lengths: [9, 10], format: '%s-%s %s', groups: [3, 3, 4], name: 'Sweden' }, 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' }, 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' }, 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' }, kr: { code: '82', lengths: [10, 11], format: '%s-%s-%s', groups: [3, 4, 4], name: 'South Korea' }, 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' }, 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' }, ke: { code: '254', lengths: [9, 10], format: '%s %s', groups: [3, 7], name: 'Kenya' }, eg: { code: '20', lengths: [10], format: '%s %s %s', groups: [3, 4, 3], name: 'Egypt' }, ar: { code: '54', lengths: [10], format: '%s %s-%s', groups: [3, 3, 4], name: 'Argentina' }, 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
- .parse(input, country: nil) ⇒ Object
- .valid?(input, country: nil) ⇒ Boolean
- .valid_shortcode?(input, country: :us) ⇒ Boolean
- .vanity_to_digits(input) ⇒ Object
Class Method Details
.parse(input, country: nil) ⇒ Object
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/philiprehberger/phone.rb', line 132 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) 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
155 156 157 158 159 |
# File 'lib/philiprehberger/phone.rb', line 155 def self.valid?(input, country: nil) parse(input, country: country).valid? rescue ParseError false end |
.valid_shortcode?(input, country: :us) ⇒ Boolean
165 166 167 |
# File 'lib/philiprehberger/phone.rb', line 165 def self.valid_shortcode?(input, country: :us) ShortcodeValidation.valid_shortcode?(input, country: country) end |
.vanity_to_digits(input) ⇒ Object
161 162 163 |
# File 'lib/philiprehberger/phone.rb', line 161 def self.vanity_to_digits(input) VanityConversion.vanity_to_digits(input) end |