30

I have a database with millions of phone numbers with free-for-all formatting. Ie, the UI does not enforce any constraints and the users are typing in whatever they want.

What I'm looking for is a Java API that can make a best-effort to convert these into a consistent format. Ideally, the API would take the free text value and a country code and produce a valid international phone number or throw an exception.

For example, a phone number in the system might look like any of the following:

(555) 478-1123
555-478-1123    
555.478.1123
5554781123

Given the country of US, the API would produce the value "+1 (555) 478-1123" for all these. The exact format does not matter, as long as it's consistent.

There are also numbers in the system without area codes, such as "478-1123". In that case, I would expect a NoAreaCodeException, or something similar.

There could also be data such as "abc", which should also throw exceptions.

Of course, there are countless variations of the examples I have posted, as well as the enormous complication of international phone numbers, which have quite complicated validation rules. This is why I would not consider rolling my own.

Has anyone seen such an API?

2
  • I wanted to add a follow-up question to this, but I asked a related question of my own to avoid hijacking your question. stackoverflow.com/questions/501368/… Commented Feb 1, 2009 at 19:00
  • 1
    Quite trolling Shevrin, first of all - having an api for something formalizes and abstracts the code pattern to something that can be reasoned in more general terms than "loop over these characters, insert space here, ... etc.". Secondly applying correct internationalized formatting to phone numbers is not a trivial task. Commented Oct 27, 2010 at 7:36

12 Answers 12

43

You could write your own (for US phone # format):

  • Strip any non-numeric characters from the string
  • Check that the remaining string is ten characters long
  • Put parentheses around the first three characters and a dash between the sixth and seventh character.
  • Prepend "+1 " to the string


Update:

Google recently released libphonenumber for parsing, formatting, storing and validating international phone numbers.

Sign up to request clarification or add additional context in comments.

7 Comments

That would work for US phone numbers. I was hoping for a generic international solution as well.
I understand. You'd have to implement a separate format method for each country you're interested in that uses a different phone number format.
This is basically what I ended up doing. You download some sample code for US and UK phone numbers from my blog: bitkickers.blogspot.com/2009/02/…
libphonenumber will probably work but the API looks very poorly designed.
libphonenumber requires that you already know the country code to be able to do anything useful.
|
23

You could try this Java phone number formatting library https://github.com/googlei18n/libphonenumber

It has data for hundreds of countries and formats.

Comments

10

Simple regex parser

/**
 * @param pPhoneNumber
 * @return true if the phone number is correct
 */
private boolean isPhoneNumberCorrect(String pPhoneNumber) {

    Pattern pattern = Pattern
            .compile("((\\+[1-9]{3,4}|0[1-9]{4}|00[1-9]{3})\\-?)?\\d{8,20}");
    Matcher matcher = pattern.matcher(pPhoneNumber);

    if (matcher.matches()) return true;


    return false;
}

Format

I made this according to my needs, and it accepts numbers:

  1. CountryCode-Number
  2. Number

Country Codes:

They may have a: +, or either one or two zeros. Then, it may be followed by a -.

Accepts:

  • +456
  • 00456
  • +1234
  • 01234

All above may or may not followed by a -

Rejects:

  • 0456
    • it should be:
      • 00456 or+456 or04444

Number

A simple number with 8-20 digits.

Accepts:

  • 00456-12345678
  • +457-12345678
  • +45712345678
  • 0045712345678
  • 99999999

Extend it?

Feel free, so you may include support for . or '(' separators. Just make sure you escape them, e.g. for ( use \(.

Comments

3

I don't know of such an API but it looks like could be done by using regular expressions. Probably you can't convert all numbers to a valid format but most of them.

1 Comment

Here is what scared me away from trying to roll my own. Take a look at the phone number rules for just Australia: en.wikipedia.org/wiki/%2B61
3

The recent versions of http://code.google.com/p/libphonenumber/ have added metadata for many new countries and added a lot more detail for some of the countries previously listed.

The current source code version is r74 and the .jar file is at version 2.6. Previous .jar files were compiled for Java 1.6, but as of libphonenumber version 2.5.1 onwards they are now compiled for Java 1.5 and above.

Don't forget there is also a direct port of the code to JavaScript. It can be found in the source code tree at http://code.google.com/p/libphonenumber/source/browse/#svn%2Ftrunk%2Fjavascript

Bug reports are welcome. Updates to metadata are actively encouraged, as even the official government-published area code lists for many countries are either incomplete or out of date.

Comments

3

Don't re-invent the wheel; use an API, e.g. http://libphonenumber.googlecode.com/ This API gives you nice formatting, too. Example:

String number = "(555) 478-1123";
PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance();
try {
  Phonenumber.PhoneNumber phoneNumber = phoneNumberUtil.parse(number, Locale.US.getCountry());
} catch (NumberParseException e) {
  // error handling
}

You could even use the phoneNumber object to nicely format it a valid phone number before saving it to the DB or whatever.

Comments

2

For French number which look like "01 44 55 66 77", we can use the following logic.

DecimalFormatSymbols dfs = new DecimalFormatSymbols();
dfs.setGroupingSeparator(' ');  // sometimes '.' is used
DecimalFormat decfmt = new DecimalFormat("0,0", dfs);  // enable grouping
decfmt.setMinimumIntegerDigits(10);  // we always have 10 digits
decfmt.setGroupingSize(2);  // necessary in order to group digits by 2 orders
System.out.println(decfmt.format(144556677));  // outputs "01 44 55 66 77"

Once this could be done, with google's phone number API the others mentioned, we can parse these sequences easily and reformat them into other forms such as "+33 1 44 55 66 77" like the following:

Iterable<PhoneNumberMatch> numbers = PhoneNumberUtil.getInstance().findNumbers(textWithPhoneNums, "FR");
for(Iterator<PhoneNumberMatch> iterator = numbers.iterator(); iterator.hasNext(); ){
    PhoneNumberMatch pnm = iterator.next();
    PhoneNumber number = pnm.number();
    System.out.println(PhoneNumberUtil.getInstance().formatOutOfCountryCallingNumber(number, null));
}

Comments

1

I don't think there is a way of recognizing the lack of an area code unless your numbers are all from one country (presumably the USA), as each country has its own rules for things like area codes.

I'd start looking for detailed information here, here, and here - if there are APIs to handle it (in Java or otherwise), they might be linked to there as well.

1 Comment

Yeah, this is why I was thinking the API should take a country code parameter.
1

There are commercial programs that format and validate international telephone numbers, like this one which even checks for valid area codes in some countries. For North America, the NANPA provides some resources for validating area codes.

1 Comment

That PERL API is the best I have seen so far. I would not personally use it because it's commercial and non-Java, but it would be excellent for some projects.
0

The best i found was javax.telephony, to be found here: http://java.sun.com/products/javaphone/

It has an Address class, but sadly that class did not solve your problem :( Well, maybe you can find a solution by digging deeper into it.

Apart of that, my first idea was to use regex. However, that seems to be a kind of bad solution to this specific problem.

1 Comment

This would be a cool piece of functionality to include in the Java Phone API spec, but I agree it does not do this right now.
0

My own needs were very simple. I just needed to take a 7 or 10-digit number and put separators (a dash, period, some string of characters, etc.) between the area code, exchange, and exchange number. Any value passed into the method that is not all digits or is not a length of 7 or 10 is simply returned. A null value returns an empty string and a null value for the separator is treated like an empty string. My code:

import java.util.regex.Matcher;
import java.util.regex.Pattern;
// ...
private String formatPhoneNumber(String phnumber, String separator) {
  phnumber = (phnumber == null) ? "" : phnumber;
  if ((phnumber.length() != 7) && (phnumber.length() != 10)) { return phnumber; }

  // If we get here, 'phnumber' is for sure either 7 or 10 chars long

  separator = (separator == null) ? "" : separator;
  Pattern p = Pattern.compile("([0-9]*)");
  Matcher m = p.matcher(phnumber);
  if (m.matches()) {
   if (phnumber.length() == 7) {
     return phnumber.substring(0, 3) + separator + phnumber.substring(4);
    } else {
     return phnumber.substring(0, 3) + separator + phnumber.substring(3, 6)  
            + separator + phnumber.substring(6);
    }
  // If we get here, it means 1 or more of the chars in 'phnumber'
  // is not a digit and so 'phnumber' is returned just as passed in.
  return phnumber;
}

Comments

0

I have created a Helper class using libphonenumber, But it is still on possibilities i.e (users mostly saves the local numbers in local contacts as local format) I mean without country code since it is local number but will save the International numbers with country code. and this helper works for both of these scenario's if the number is in global format it will simply keep it as it is while converts the local numbers to internal format. Below is the Code and Usage

class PhoneNumberHelper {

    companion object {
        fun correctNumber(number: String, context: Context): String? {
            val code = StorageAdapter.get(context).userCountryCode
            return validateNumber(number, code)
        }

        private fun validateNumber(number: String, mUserCountryCode: Int): String? {
            return Utils.formatNumber(Utils.removeDelimetersFromNumber(number), mUserCountryCode)

        }

        fun formatNumber(destinationNumber: String, countryCode: Int): String? {
            try {
                val phoneUtil = PhoneNumberUtil.getInstance()
                val regionCode = phoneUtil.getRegionCodeForCountryCode(countryCode)

                var formattedNumber = formatNumber(destinationNumber, regionCode)
                if (TextUtils.isEmpty(formattedNumber)) {
                    formattedNumber = destinationNumber
                }
                return formattedNumber
            } catch (exp: Exception) {
                Log.e("formatNumber", exp.toString())
            }

            return destinationNumber
        }


        fun formatNumber(destinationNumber: String, regionCode: String): String? {

            if (TextUtils.isEmpty(regionCode)) {
                return null
            }

            var number: String? = null
            try {

                val phoneUtil = PhoneNumberUtil.getInstance()
                val phoneNumber = phoneUtil.parse(destinationNumber, regionCode)

                if (phoneUtil.isValidNumber(phoneNumber)) {

                    /*
                 * E164 format is as per international format but no
                 * formatting applied e.g. no spaces in between
                 */

                    number = phoneUtil.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164)
                    number = number!!.replace("+", "00")
                }


            } catch (e: Exception) {
                // number would be returned as null if it catches here
            }

            return number
        }
    }


}

Here Is how You will use it:

var globalnumber = PhoneNumberHelper.correctNumber(contact.mobile, context)

Clarification:

val code = StorageAdapter.get(context).userCountryCode

This is the Country code you should Store at during Signup. e.g. 0044 or +44

Don't forget to Include Dependency for libphone :

implementation 'com.googlecode.libphonenumber:libphonenumber:8.8.0'

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.