diff --git a/java/resources/com/google/i18n/phonenumbers/src/PhoneNumberMetaData.xml b/java/resources/com/google/i18n/phonenumbers/src/PhoneNumberMetaData.xml index 862dd9a33..d714e48a8 100644 --- a/java/resources/com/google/i18n/phonenumbers/src/PhoneNumberMetaData.xml +++ b/java/resources/com/google/i18n/phonenumbers/src/PhoneNumberMetaData.xml @@ -3626,7 +3626,7 @@ + nationalPrefix="8" nationalPrefixFormattingRule="$NP $FG"> 0) { + * areaCode = nationalSignificantNumber.substring(0, areaCodeLength); + * subscriberNumber = nationalSignificantNumber.substring(areaCodeLength); + * } else { + * areaCode = ""; + * subscriberNumber = nationalSignificantNumber; + * } + * + * N.B.: area code is a very ambiguous concept, so the I18N team generally recommends against + * using it for most purposes, but recommends using the more general national_number instead. Read + * the following carefully before deciding to use this method: + * + * - geographical area codes change over time, and this method honors those changes; therefore, + * it doesn't guarantee the stability of the result it produces. + * - subscriber numbers may not be diallable from all devices (notably mobile devices, which + * typically requires the full national_number to be dialled in most countries). + * - most non-geographical numbers have no area codes. + * - some geographical numbers have no area codes. + * + * @param number the PhoneNumber object for which clients want to know the length of the area + * code in the national_number field. + * @return the length of area code of the PhoneNumber object passed in. + */ + public int getLengthOfGeographicalAreaCode(PhoneNumber number) { + String regionCode = getRegionCodeForNumber(number); + if (regionCode == null || regionCode.equalsIgnoreCase("ZZ")) { + return 0; + } + PhoneMetadata metadata = getMetadataForRegion(regionCode); + // For NANPA countries, national prefix is the same as country code, but it is not stored in + // the metadata. + if (!metadata.hasNationalPrefix() && !isNANPACountry(regionCode)) { + return 0; + } + + PhoneNumberType type = getNumberTypeHelper(String.valueOf(number.getNationalNumber()), + metadata); + // Most numbers other than the two types below have to be dialled in full. + if (type != PhoneNumberType.FIXED_LINE && type != PhoneNumberType.FIXED_LINE_OR_MOBILE) { + return 0; + } + + PhoneNumber copiedProto; + if (number.hasExtension()) { + // We don't want to alter the proto given to us, but we don't want to include the extension + // when we format it, so we copy it and clear the extension here. + PhoneNumber.Builder protoBuilder = PhoneNumber.newBuilder(); + protoBuilder.mergeFrom(number); + protoBuilder.clearExtension(); + copiedProto = protoBuilder.build(); + } else { + copiedProto = number; + } + + String nationalSignificantNumber = format(copiedProto, + PhoneNumberUtil.PhoneNumberFormat.INTERNATIONAL); + Pattern nonDigitPattern = Pattern.compile("(\\D+)"); + String[] numberGroups = nonDigitPattern.split(nationalSignificantNumber); + // The pattern will start with "+COUNTRY_CODE " so the first group will always be the empty + // string (before the + symbol) and the second group will be the country code. The third group + // will be area code if it is not the last group. + if (numberGroups.length <= 3) { + return 0; + } + // Note all countries that use leading zero in national number don't use national prefix, so + // they won't have an area code, which means clients don't need to worry about appending the + // leading zero to the geographical area code they derive from the length we return here. + return numberGroups[2].length(); + } + /** * Normalizes a string of characters representing a phone number by replacing all characters found * in the accompanying map with the values therein, and stripping all other characters if @@ -494,7 +579,7 @@ public class PhoneNumberUtil { * @param removeNonMatches indicates whether characters that are not able to be replaced * should be stripped from the number. If this is false, they * will be left unchanged in the number. - * @return the normalized string version of the phone number + * @return the normalized string version of the phone number */ private static String normalizeHelper(String number, Map normalizationReplacements, @@ -582,11 +667,11 @@ public class PhoneNumberUtil { * * @param number the phone number to be formatted * @param numberFormat the format the phone number should be formatted into - * @return the formatted phone number + * @return the formatted phone number */ public String format(PhoneNumber number, PhoneNumberFormat numberFormat) { int countryCode = number.getCountryCode(); - String nationalSignificantNumber = getUnformattedNationalNumber(number); + String nationalSignificantNumber = getNationalSignificantNumber(number); if (numberFormat == PhoneNumberFormat.E164) { // Early exit for E164 case since no formatting of the national number needs to be applied. // Extensions are not formatted. @@ -617,7 +702,7 @@ public class PhoneNumberUtil { * @param number the phone number to be formatted * @param numberFormat the format the phone number should be formatted into * @param userDefinedFormats formatting rules specified by clients - * @return the formatted phone number + * @return the formatted phone number */ public String formatByPattern(PhoneNumber number, PhoneNumberFormat numberFormat, @@ -627,7 +712,7 @@ public class PhoneNumberUtil { // share a country code is contained by only one country for performance reasons. For example, // for NANPA countries it will be contained in the metadata for US. String regionCode = getRegionCodeForCountryCode(countryCode); - String nationalSignificantNumber = getUnformattedNationalNumber(number); + String nationalSignificantNumber = getNationalSignificantNumber(number); if (!isValidRegionCode(regionCode, countryCode, nationalSignificantNumber)) { return nationalSignificantNumber; } @@ -671,7 +756,7 @@ public class PhoneNumberUtil { * @param number the phone number to be formatted * @param countryCallingFrom the ISO 3166-1 two-letter country code that denotes the foreign * country where the call is being placed - * @return the formatted phone number + * @return the formatted phone number */ public String formatOutOfCountryCallingNumber(PhoneNumber number, String countryCallingFrom) { @@ -720,7 +805,7 @@ public class PhoneNumberUtil { } else { regionCode = getRegionCodeForCountryCode(countryCode); } - String nationalSignificantNumber = getUnformattedNationalNumber(number); + String nationalSignificantNumber = getNationalSignificantNumber(number); if (!isValidRegionCode(regionCode, countryCode, nationalSignificantNumber)) { return nationalSignificantNumber; } @@ -750,7 +835,15 @@ public class PhoneNumberUtil { formattedExtension); } - static String getUnformattedNationalNumber(PhoneNumber number) { + + /** + * Gets the national significant number of the a phone number. Note a national significant number + * doesn't contain a national prefix or any formatting. + * + * @param number the PhoneNumber object for which the national significant number is needed + * @return the national significant number of the PhoneNumber object passed in + */ + public static String getNationalSignificantNumber(PhoneNumber number) { // The leading zero in the national (significant) number of an Italian phone number has a // special meaning. Unlike the rest of the world, it indicates the number is a landline // number. There have been plans to migrate landline numbers to start with the digit two since @@ -921,7 +1014,7 @@ public class PhoneNumberUtil { */ public PhoneNumberType getNumberType(PhoneNumber number) { String regionCode = getRegionCodeForNumber(number); - String nationalSignificantNumber = getUnformattedNationalNumber(number); + String nationalSignificantNumber = getNationalSignificantNumber(number); if (!isValidRegionCode(regionCode, number.getCountryCode(), nationalSignificantNumber)) { return PhoneNumberType.UNKNOWN; } @@ -1007,7 +1100,7 @@ public class PhoneNumberUtil { public boolean isValidNumber(PhoneNumber number) { String regionCode = getRegionCodeForNumber(number); return isValidRegionCode(regionCode, number.getCountryCode(), - getUnformattedNationalNumber(number)) + getNationalSignificantNumber(number)) && isValidNumberForRegion(number, regionCode); } @@ -1031,7 +1124,7 @@ public class PhoneNumberUtil { } PhoneMetadata metadata = getMetadataForRegion(regionCode); PhoneNumberDesc generalNumDesc = metadata.getGeneralDesc(); - String nationalSignificantNumber = getUnformattedNationalNumber(number); + String nationalSignificantNumber = getNationalSignificantNumber(number); // For countries where we don't have metadata for PhoneNumberDesc, we treat any number passed // in as a valid number if its national significant number is between the minimum and maximum @@ -1058,7 +1151,9 @@ public class PhoneNumberUtil { case NANPA_COUNTRY_CODE: // Override this and try the US case first, since it is more likely than other countries, // for performance reasons. - if (isValidNumberForRegion(number, "US")) { + String nationalNumber = getNationalSignificantNumber(number); + if (getNumberTypeHelper(nationalNumber, + getMetadataForRegion("US")) != PhoneNumberType.UNKNOWN) { return "US"; } Set nanpaExceptUS = new HashSet(nanpaCountries); @@ -1165,7 +1260,7 @@ public class PhoneNumberUtil { * @return a ValidationResult object which indicates whether the number is possible */ public ValidationResult isPossibleNumberWithReason(PhoneNumber number) { - String nationalNumber = getUnformattedNationalNumber(number); + String nationalNumber = getNationalSignificantNumber(number); int countryCode = number.getCountryCode(); // Note: For Russian Fed and NANPA numbers, we just use the rules from the default region (US or // Russia) since the getRegionCodeForNumber will not work if the number is possible but not @@ -1272,19 +1367,34 @@ public class PhoneNumberUtil { * @param nationalNumber a string buffer to store the national significant number in, in the case * that a country code was extracted. The number is appended to any existing contents. If no * country code was extracted, this will be left unchanged. + * @param storeCountryCodeSource true if the country_code_source field of phoneNumber should be + * populated. + * @param phoneNumber the PhoneNumber.Builder object that needs to be populated with country code + * and country code source. Note the country code is always populated, whereas country code + * source is only populated when keepCountryCodeSource is true. * @return the country code extracted or 0 if none could be extracted */ @VisibleForTesting int maybeExtractCountryCode(String number, PhoneMetadata defaultRegionMetadata, - StringBuffer nationalNumber) + StringBuffer nationalNumber, boolean storeCountryCodeSource, + PhoneNumber.Builder phoneNumber) throws NumberParseException { + if (number.length() == 0) { + return 0; + } StringBuffer fullNumber = new StringBuffer(number); // Set the default prefix to be something that will never match. String possibleCountryIddPrefix = "NonMatch"; if (defaultRegionMetadata != null) { possibleCountryIddPrefix = defaultRegionMetadata.getInternationalPrefix(); } - if (maybeStripInternationalPrefixAndNormalize(fullNumber, possibleCountryIddPrefix)) { + + CountryCodeSource countryCodeSource = + maybeStripInternationalPrefixAndNormalize(fullNumber, possibleCountryIddPrefix); + if (storeCountryCodeSource) { + phoneNumber.setCountryCodeSource(countryCodeSource); + } + if (countryCodeSource != CountryCodeSource.FROM_DEFAULT_COUNTRY) { if (fullNumber.length() < MIN_LENGTH_FOR_NSN) { throw new NumberParseException(NumberParseException.ErrorType.TOO_SHORT_AFTER_IDD, "Phone number had an IDD, but after this was not " @@ -1292,6 +1402,7 @@ public class PhoneNumberUtil { } int potentialCountryCode = extractCountryCode(fullNumber, nationalNumber); if (potentialCountryCode != 0) { + phoneNumber.setCountryCode(potentialCountryCode); return potentialCountryCode; } @@ -1319,12 +1430,17 @@ public class PhoneNumberUtil { validNumberPattern); if (validNumberPattern.matcher(potentialNationalNumber).matches()) { nationalNumber.append(potentialNationalNumber); + if (storeCountryCodeSource) { + phoneNumber.setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITHOUT_PLUS_SIGN); + } + phoneNumber.setCountryCode(defaultCountryCode); return defaultCountryCode; } } } } // No country code present. + phoneNumber.setCountryCode(0); return 0; } @@ -1332,8 +1448,7 @@ public class PhoneNumberUtil { * Strips the IDD from the start of the number if present. Helper function used by * maybeStripInternationalPrefixAndNormalize. */ - private boolean parsePrefixAsIdd(Pattern iddPattern, - StringBuffer number) { + private boolean parsePrefixAsIdd(Pattern iddPattern, StringBuffer number) { Matcher m = iddPattern.matcher(number); if (m.lookingAt()) { int matchEnd = m.end(); @@ -1360,31 +1475,36 @@ public class PhoneNumberUtil { * dialing prefix from * @param possibleIddPrefix the international direct dialing prefix from the country we * think this number may be dialed in - * @return true if an international dialing prefix could be removed from the number, otherwise - * false if the number did not seem to be in international format + * @return the corresponding CountryCodeSource if an international dialing prefix could be + * removed from the number, otherwise CountryCodeSource.FROM_DEFAULT_COUNTRY if the number + * did not seem to be in international format. */ @VisibleForTesting - boolean maybeStripInternationalPrefixAndNormalize(StringBuffer number, String possibleIddPrefix) { + CountryCodeSource maybeStripInternationalPrefixAndNormalize( + StringBuffer number, + String possibleIddPrefix) { if (number.length() == 0) { - return false; + return CountryCodeSource.FROM_DEFAULT_COUNTRY; } if (number.charAt(0) == PLUS_SIGN) { number.deleteCharAt(0); // Can now normalize the rest of the number since we've consumed the "+" sign at the start. normalize(number); - return true; + return CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN; } // Attempt to parse the first digits as an international prefix. Pattern iddPattern = Pattern.compile(possibleIddPrefix); if (parsePrefixAsIdd(iddPattern, number)) { normalize(number); - return true; + return CountryCodeSource.FROM_NUMBER_WITH_IDD; } // If still not found, then try and normalize the number and then try again. This shouldn't be // done before, since non-numeric characters (+ and ~) may legally be in the international // prefix. normalize(number); - return parsePrefixAsIdd(iddPattern, number); + return parsePrefixAsIdd(iddPattern, number) + ? CountryCodeSource.FROM_NUMBER_WITH_IDD + : CountryCodeSource.FROM_DEFAULT_COUNTRY; } /** @@ -1489,7 +1609,8 @@ public class PhoneNumberUtil { /** * Parses a string and returns it in proto buffer format. This method differs from parse() in that - * it always populates the raw_input field of the protocol buffer with numberToParse. + * it always populates the raw_input field of the protocol buffer with numberToParse as well as + * the country_code_source field. * * @param numberToParse number that we are attempting to parse. This can contain formatting * such as +, ( and -, as well as a phone number extension. @@ -1571,10 +1692,12 @@ public class PhoneNumberUtil { // been created, and just remove the prefix, rather than taking in a string and then outputting // a string buffer. int countryCode = maybeExtractCountryCode(nationalNumber.toString(), countryMetadata, - normalizedNationalNumber); + normalizedNationalNumber, keepRawInput, phoneNumber); if (countryCode != 0) { String phoneNumberRegion = getRegionCodeForCountryCode(countryCode); - countryMetadata = getMetadataForRegion(phoneNumberRegion); + if (!phoneNumberRegion.equals(defaultCountry)) { + countryMetadata = getMetadataForRegion(phoneNumberRegion); + } } else { // If no extracted country code, use the region supplied instead. The national number is just // the normalized version of the number we were given to parse. @@ -1582,6 +1705,9 @@ public class PhoneNumberUtil { normalizedNationalNumber.append(nationalNumber); if (defaultCountry != null) { countryCode = countryMetadata.getCountryCode(); + phoneNumber.setCountryCode(countryCode); + } else if (keepRawInput) { + phoneNumber.clearCountryCodeSource(); } } if (normalizedNationalNumber.length() < MIN_LENGTH_FOR_NSN) { @@ -1607,7 +1733,6 @@ public class PhoneNumberUtil { "The string supplied is too long to be a " + "phone number."); } - phoneNumber.setCountryCode(countryCode); if (isLeadingZeroCountry(countryCode) && normalizedNationalNumber.charAt(0) == '0') { phoneNumber.setItalianLeadingZero(true); @@ -1642,10 +1767,12 @@ public class PhoneNumberUtil { firstNumber.mergeFrom(firstNumberIn); PhoneNumber.Builder secondNumber = PhoneNumber.newBuilder(); secondNumber.mergeFrom(secondNumberIn); - // First clear raw_input field and any empty-string extensions so that we can use the - // proto-buffer equality method. + // First clear raw_input and country_code_source field and any empty-string extensions so that + // we can use the proto-buffer equality method. firstNumber.clearRawInput(); + firstNumber.clearCountryCodeSource(); secondNumber.clearRawInput(); + secondNumber.clearCountryCodeSource(); if (firstNumber.hasExtension() && firstNumber.getExtension().equals("")) { firstNumber.clearExtension(); diff --git a/java/src/com/google/i18n/phonenumbers/Phonenumber.java b/java/src/com/google/i18n/phonenumbers/Phonenumber.java index 8ff783326..fd10de3a5 100644 --- a/java/src/com/google/i18n/phonenumbers/Phonenumber.java +++ b/java/src/com/google/i18n/phonenumbers/Phonenumber.java @@ -25,6 +25,49 @@ public final class Phonenumber { return defaultInstance; } + public enum CountryCodeSource + implements com.google.protobuf.Internal.EnumLite { + FROM_NUMBER_WITH_PLUS_SIGN(0, 1), + FROM_NUMBER_WITH_IDD(1, 5), + FROM_NUMBER_WITHOUT_PLUS_SIGN(2, 10), + FROM_DEFAULT_COUNTRY(3, 20), + ; + + + public final int getNumber() { return value; } + + public static CountryCodeSource valueOf(int value) { + switch (value) { + case 1: return FROM_NUMBER_WITH_PLUS_SIGN; + case 5: return FROM_NUMBER_WITH_IDD; + case 10: return FROM_NUMBER_WITHOUT_PLUS_SIGN; + case 20: return FROM_DEFAULT_COUNTRY; + default: return null; + } + } + + public static com.google.protobuf.Internal.EnumLiteMap + internalGetValueMap() { + return internalValueMap; + } + private static com.google.protobuf.Internal.EnumLiteMap + internalValueMap = + new com.google.protobuf.Internal.EnumLiteMap() { + public CountryCodeSource findValueByNumber(int number) { + return CountryCodeSource.valueOf(number) + ; } + }; + + private final int index; + private final int value; + private CountryCodeSource(int index, int value) { + this.index = index; + this.value = value; + } + + // @@protoc_insertion_point(enum_scope:i18n.phonenumbers.PhoneNumber.CountryCodeSource) + } + // required int32 country_code = 1; public static final int COUNTRY_CODE_FIELD_NUMBER = 1; private boolean hasCountryCode; @@ -59,8 +102,16 @@ public final class Phonenumber { private java.lang.String rawInput_ = ""; public boolean hasRawInput() { return hasRawInput; } public java.lang.String getRawInput() { return rawInput_; } - + + // optional .i18n.phonenumbers.PhoneNumber.CountryCodeSource country_code_source = 6; + public static final int COUNTRY_CODE_SOURCE_FIELD_NUMBER = 6; + private boolean hasCountryCodeSource; + private com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource countryCodeSource_; + public boolean hasCountryCodeSource() { return hasCountryCodeSource; } + public com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource getCountryCodeSource() { return countryCodeSource_; } + private void initFields() { + countryCodeSource_ = com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN; } public final boolean isInitialized() { if (!hasCountryCode) return false; @@ -86,6 +137,9 @@ public final class Phonenumber { if (hasRawInput()) { output.writeString(5, getRawInput()); } + if (hasCountryCodeSource()) { + output.writeEnum(6, getCountryCodeSource().getNumber()); + } } private int memoizedSerializedSize = -1; @@ -114,6 +168,10 @@ public final class Phonenumber { size += com.google.protobuf.CodedOutputStream .computeStringSize(5, getRawInput()); } + if (hasCountryCodeSource()) { + size += com.google.protobuf.CodedOutputStream + .computeEnumSize(6, getCountryCodeSource().getNumber()); + } memoizedSerializedSize = size; return size; } @@ -273,6 +331,9 @@ public final class Phonenumber { if (other.hasRawInput()) { setRawInput(other.getRawInput()); } + if (other.hasCountryCodeSource()) { + setCountryCodeSource(other.getCountryCodeSource()); + } return this; } @@ -311,6 +372,14 @@ public final class Phonenumber { setRawInput(input.readString()); break; } + case 48: { + int rawValue = input.readEnum(); + com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource value = com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource.valueOf(rawValue); + if (value != null) { + setCountryCodeSource(value); + } + break; + } } } } @@ -411,7 +480,28 @@ public final class Phonenumber { result.rawInput_ = getDefaultInstance().getRawInput(); return this; } - + + // optional .i18n.phonenumbers.PhoneNumber.CountryCodeSource country_code_source = 6; + public boolean hasCountryCodeSource() { + return result.hasCountryCodeSource(); + } + public com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource getCountryCodeSource() { + return result.getCountryCodeSource(); + } + public Builder setCountryCodeSource(com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource value) { + if (value == null) { + throw new NullPointerException(); + } + result.hasCountryCodeSource = true; + result.countryCodeSource_ = value; + return this; + } + public Builder clearCountryCodeSource() { + result.hasCountryCodeSource = false; + result.countryCodeSource_ = com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN; + return this; + } + // @@protoc_insertion_point(builder_scope:i18n.phonenumbers.PhoneNumber) } diff --git a/java/src/com/google/i18n/phonenumbers/phonenumber.proto b/java/src/com/google/i18n/phonenumbers/phonenumber.proto index 27b5996f8..1628cc371 100644 --- a/java/src/com/google/i18n/phonenumbers/phonenumber.proto +++ b/java/src/com/google/i18n/phonenumbers/phonenumber.proto @@ -55,6 +55,31 @@ message PhoneNumber { // canonicalized by the library. For example, it could be used to store alphanumerical numbers // such as "1-800-GOOG-411". optional string raw_input = 5; + +// The source from which the country_code is derived. This is not set in the general parsing method, +// but in the method that parses and keeps raw_input. New fields could be added upon request. + enum CountryCodeSource { + // The country_code is derived based on a phone number with a leading "+", e.g. the French + // number "+33 (0)1 42 68 53 00". + FROM_NUMBER_WITH_PLUS_SIGN = 1; + + // The country_code is derived based on a phone number with a leading IDD, e.g. the French + // number "011 33 (0)1 42 68 53 00", as it is dialled from US. + FROM_NUMBER_WITH_IDD = 5; + + // The country_code is derived based on a phone number without a leading "+", e.g. the French + // number "33 (0)1 42 68 53 00" when defaultCountry is supplied as France. + FROM_NUMBER_WITHOUT_PLUS_SIGN = 10; + + // The country_code is derived NOT based on the phone number itself, but from the defaultCountry + // parameter provided in the parsing function by the clients. This happens mostly for numbers + // written in the national format (without country code). For example, this would be set when + // parsing the French number "(0)1 42 68 53 00", when defaultCountry is supplied as France. + FROM_DEFAULT_COUNTRY = 20; + } + +// The source from which the country_code is derived. + optional CountryCodeSource country_code_source = 6; } // Examples diff --git a/java/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java b/java/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java index 43ded0fa3..653caee59 100644 --- a/java/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java +++ b/java/test/com/google/i18n/phonenumbers/PhoneNumberUtilTest.java @@ -19,6 +19,7 @@ package com.google.i18n.phonenumbers; import com.google.i18n.phonenumbers.Phonemetadata.NumberFormat; import com.google.i18n.phonenumbers.Phonemetadata.PhoneMetadata; import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber; +import com.google.i18n.phonenumbers.Phonenumber.PhoneNumber.CountryCodeSource; import com.google.protobuf.MessageLite; import junit.framework.TestCase; @@ -103,6 +104,8 @@ public class PhoneNumberUtilTest extends TestCase { assertEquals("900([135]\\d{6}|9\\d{7})", metadata.getPremiumRate().getNationalNumberPattern()); } + + public void testGetInstanceLoadARMetadata() { PhoneMetadata metadata = phoneUtil.getMetadataForRegion("AR"); assertEquals("AR", metadata.getId()); @@ -119,6 +122,64 @@ public class PhoneNumberUtilTest extends TestCase { assertEquals("$1 $2 $3 $4", metadata.getIntlNumberFormat(3).getFormat()); } + public void testGetLengthOfGeographicalAreaCode() { + // Google MTV, which has area code "650". + PhoneNumber usNumber1 = + PhoneNumber.newBuilder().setCountryCode(1).setNationalNumber(6502530000L).build(); + assertEquals(3, phoneUtil.getLengthOfGeographicalAreaCode(usNumber1)); + + // A North America toll-free number, which has no area code. + PhoneNumber usNumber2 = + PhoneNumber.newBuilder().setCountryCode(1).setNationalNumber(8002530000L).build(); + assertEquals(0, phoneUtil.getLengthOfGeographicalAreaCode(usNumber2)); + + // An invalid US number (1 digit shorter), which has no area code. + PhoneNumber usNumber3 = + PhoneNumber.newBuilder().setCountryCode(1).setNationalNumber(650253000L).build(); + assertEquals(0, phoneUtil.getLengthOfGeographicalAreaCode(usNumber3)); + + // Google London, which has area code "20". + PhoneNumber ukNumber1 = + PhoneNumber.newBuilder().setCountryCode(44).setNationalNumber(2070313000L).build(); + assertEquals(2, phoneUtil.getLengthOfGeographicalAreaCode(ukNumber1)); + + // A UK mobile phone, which has no area code. + PhoneNumber ukNumber2 = + PhoneNumber.newBuilder().setCountryCode(44).setNationalNumber(7123456789L).build(); + assertEquals(0, phoneUtil.getLengthOfGeographicalAreaCode(ukNumber2)); + + // Google Buenos Aires, which has area code "11". + PhoneNumber arNumber = + PhoneNumber.newBuilder().setCountryCode(54).setNationalNumber(1155303000L).build(); + assertEquals(2, phoneUtil.getLengthOfGeographicalAreaCode(arNumber)); + + // Google Sydney, which has area code "2". + PhoneNumber auNumber = + PhoneNumber.newBuilder().setCountryCode(61).setNationalNumber(293744000L).build(); + assertEquals(1, phoneUtil.getLengthOfGeographicalAreaCode(auNumber)); + + // Google Singapore. Singapore has no area code and no national prefix. + PhoneNumber sgNumber = + PhoneNumber.newBuilder().setCountryCode(65).setNationalNumber(65218000L).build(); + assertEquals(0, phoneUtil.getLengthOfGeographicalAreaCode(sgNumber)); + } + + public void testGetNationalSignificantNumber() { + PhoneNumber usNumber = + PhoneNumber.newBuilder().setCountryCode(1).setNationalNumber(6502530000L).build(); + assertEquals("6502530000", PhoneNumberUtil.getNationalSignificantNumber(usNumber)); + + // An Italian mobile number. + PhoneNumber itNumber1 = + PhoneNumber.newBuilder().setCountryCode(39).setNationalNumber(312345678L).build(); + assertEquals("312345678", PhoneNumberUtil.getNationalSignificantNumber(itNumber1)); + + // An Italian fixed line number. + PhoneNumber itNumber2 = + PhoneNumber.newBuilder().setCountryCode(39).setNationalNumber(236618300L).setItalianLeadingZero(true).build(); + assertEquals("0236618300", PhoneNumberUtil.getNationalSignificantNumber(itNumber2)); + } + public void testGetExampleNumber() throws IOException { PhoneNumber deNumber = PhoneNumber.newBuilder().setCountryCode(49).setNationalNumber(30123456).build(); @@ -902,56 +963,67 @@ public class PhoneNumberUtilTest extends TestCase { strippedNumber, numberToStrip.toString()); } - public void testMaybeStripInternationalPrefix() { + public void testMaybeStripInternationalPrefix() { String internationalPrefix = "00[39]"; StringBuffer numberToStrip = new StringBuffer("0034567700-3898003"); // Note the dash is removed as part of the normalization. StringBuffer strippedNumber = new StringBuffer("45677003898003"); - assertEquals(true, phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip, - internationalPrefix)); + assertEquals(CountryCodeSource.FROM_NUMBER_WITH_IDD, + phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip, + internationalPrefix)); assertEquals("The number supplied was not stripped of its international prefix.", strippedNumber.toString(), numberToStrip.toString()); - // Now the number no longer starts with an IDD prefix, so it should now report false. - assertEquals(false, phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip, - internationalPrefix)); + // Now the number no longer starts with an IDD prefix, so it should now report + // FROM_DEFAULT_COUNTRY. + assertEquals(CountryCodeSource.FROM_DEFAULT_COUNTRY, + phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip, + internationalPrefix)); numberToStrip = new StringBuffer("00945677003898003"); - assertEquals(true, phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip, - internationalPrefix)); + assertEquals(CountryCodeSource.FROM_NUMBER_WITH_IDD, + phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip, + internationalPrefix)); assertEquals("The number supplied was not stripped of its international prefix.", strippedNumber.toString(), numberToStrip.toString()); // Test it works when the international prefix is broken up by spaces. numberToStrip = new StringBuffer("00 9 45677003898003"); - assertEquals(true, phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip, - internationalPrefix)); + assertEquals(CountryCodeSource.FROM_NUMBER_WITH_IDD, + phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip, + internationalPrefix)); assertEquals("The number supplied was not stripped of its international prefix.", strippedNumber.toString(), numberToStrip.toString()); - // Now the number no longer starts with an IDD prefix, so it should now report false. - assertEquals(false, phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip, - internationalPrefix)); + // Now the number no longer starts with an IDD prefix, so it should now report + // FROM_DEFAULT_COUNTRY. + assertEquals(CountryCodeSource.FROM_DEFAULT_COUNTRY, + phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip, + internationalPrefix)); // Test the + symbol is also recognised and stripped. numberToStrip = new StringBuffer("+45677003898003"); strippedNumber = new StringBuffer("45677003898003"); - assertEquals(true, phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip, - internationalPrefix)); + assertEquals(CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN, + phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip, + internationalPrefix)); assertEquals("The number supplied was not stripped of the plus symbol.", strippedNumber.toString(), numberToStrip.toString()); // If the number afterwards is a zero, we should not strip this - no country code begins with 0. numberToStrip = new StringBuffer("0090112-3123"); strippedNumber = new StringBuffer("00901123123"); - assertEquals(false, phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip, - internationalPrefix)); + assertEquals(CountryCodeSource.FROM_DEFAULT_COUNTRY, + phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip, + internationalPrefix)); assertEquals("The number supplied had a 0 after the match so shouldn't be stripped.", strippedNumber.toString(), numberToStrip.toString()); // Here the 0 is separated by a space from the IDD. numberToStrip = new StringBuffer("009 0-112-3123"); - assertEquals(false, phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip, - internationalPrefix)); + assertEquals(CountryCodeSource.FROM_DEFAULT_COUNTRY, + phoneUtil.maybeStripInternationalPrefixAndNormalize(numberToStrip, + internationalPrefix)); } public void testMaybeExtractCountryCode() { + PhoneNumber.Builder number = PhoneNumber.newBuilder(); PhoneMetadata metadata = phoneUtil.getMetadataForRegion("US"); // Note that for the US, the IDD is 011. try { @@ -961,7 +1033,10 @@ public class PhoneNumberUtilTest extends TestCase { StringBuffer numberToFill = new StringBuffer(); assertEquals("Did not extract country code " + countryCode + " correctly.", countryCode, - phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill)); + phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill, true, + number)); + assertEquals("Did not figure out CountryCodeSource correctly", + CountryCodeSource.FROM_NUMBER_WITH_IDD, number.getCountryCodeSource()); // Should strip and normalize national significant number. assertEquals("Did not strip off the country code correctly.", strippedNumber, @@ -969,30 +1044,38 @@ public class PhoneNumberUtilTest extends TestCase { } catch (NumberParseException e) { fail("Should not have thrown an exception: " + e.toString()); } + number.clear(); try { String phoneNumber = "+6423456789"; int countryCode = 64; StringBuffer numberToFill = new StringBuffer(); assertEquals("Did not extract country code " + countryCode + " correctly.", countryCode, - phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill)); + phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill, true, + number)); + assertEquals("Did not figure out CountryCodeSource correctly", + CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN, number.getCountryCodeSource()); } catch (NumberParseException e) { fail("Should not have thrown an exception: " + e.toString()); } + number.clear(); try { String phoneNumber = "2345-6789"; StringBuffer numberToFill = new StringBuffer(); assertEquals("Should not have extracted a country code - no international prefix present.", 0, - phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill)); + phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill, true, + number)); + assertEquals("Did not figure out CountryCodeSource correctly", + CountryCodeSource.FROM_DEFAULT_COUNTRY, number.getCountryCodeSource()); } catch (NumberParseException e) { fail("Should not have thrown an exception: " + e.toString()); } + number.clear(); try { String phoneNumber = "0119991123456789"; StringBuffer numberToFill = new StringBuffer(); - phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, - numberToFill); + phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill, true, number); fail("Should have thrown an exception, no valid country code present."); } catch (NumberParseException e) { // Expected. @@ -1000,34 +1083,58 @@ public class PhoneNumberUtilTest extends TestCase { NumberParseException.ErrorType.INVALID_COUNTRY_CODE, e.getErrorType()); } + number.clear(); try { String phoneNumber = "(1 610) 619 4466"; int countryCode = 1; StringBuffer numberToFill = new StringBuffer(); assertEquals("Should have extracted the country code of the region passed in", countryCode, - phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill)); + phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill, true, + number)); + assertEquals("Did not figure out CountryCodeSource correctly", + CountryCodeSource.FROM_NUMBER_WITHOUT_PLUS_SIGN, + number.getCountryCodeSource()); } catch (NumberParseException e) { fail("Should not have thrown an exception: " + e.toString()); } + number.clear(); + try { + String phoneNumber = "(1 610) 619 4466"; + int countryCode = 1; + StringBuffer numberToFill = new StringBuffer(); + assertEquals("Should have extracted the country code of the region passed in", + countryCode, + phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill, false, + number)); + assertFalse("Should not contain CountryCodeSource.", number.hasCountryCodeSource()); + } catch (NumberParseException e) { + fail("Should not have thrown an exception: " + e.toString()); + } + number.clear(); try { String phoneNumber = "(1 610) 619 446"; StringBuffer numberToFill = new StringBuffer(); assertEquals("Should not have extracted a country code - invalid number after extraction " + "of uncertain country code.", 0, - phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill)); + phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill, false, + number)); + assertFalse("Should not contain CountryCodeSource.", number.hasCountryCodeSource()); } catch (NumberParseException e) { fail("Should not have thrown an exception: " + e.toString()); } + number.clear(); try { String phoneNumber = "(1 610) 619 43 446"; StringBuffer numberToFill = new StringBuffer(); assertEquals("Should not have extracted a country code - invalid number both before and " + "after extraction of uncertain country code.", 0, - phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, - numberToFill)); + phoneUtil.maybeExtractCountryCode(phoneNumber, metadata, numberToFill, true, + number)); + assertEquals("Did not figure out CountryCodeSource correctly", + CountryCodeSource.FROM_DEFAULT_COUNTRY, number.getCountryCodeSource()); } catch (NumberParseException e) { fail("Should not have thrown an exception: " + e.toString()); } @@ -1341,15 +1448,37 @@ public class PhoneNumberUtilTest extends TestCase { } public void testParseAndKeepRaw() throws Exception { - PhoneNumber alphaNumericNumber = + PhoneNumber alphaNumericNumber1 = PhoneNumber.newBuilder().setCountryCode(1).setNationalNumber(180074935247L) - .setRawInput("1800 six-flags").build(); - assertEquals(alphaNumericNumber, + .setRawInput("1800 six-flags") + .setCountryCodeSource(CountryCodeSource.FROM_DEFAULT_COUNTRY).build(); + assertEquals(alphaNumericNumber1, phoneUtil.parseAndKeepRawInput("1800 six-flags", "US")); + + PhoneNumber alphaNumericNumber2 = + PhoneNumber.newBuilder().setCountryCode(1).setNationalNumber(8007493524L) + .setRawInput("1800 six-flag") + .setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITHOUT_PLUS_SIGN).build(); + assertEquals(alphaNumericNumber2, + phoneUtil.parseAndKeepRawInput("1800 six-flag", "US")); + + PhoneNumber alphaNumericNumber3 = + PhoneNumber.newBuilder().setCountryCode(1).setNationalNumber(8007493524L) + .setRawInput("+1800 six-flag") + .setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITH_PLUS_SIGN).build(); + assertEquals(alphaNumericNumber3, + phoneUtil.parseAndKeepRawInput("+1800 six-flag", "CN")); + + PhoneNumber alphaNumericNumber4 = + PhoneNumber.newBuilder().setCountryCode(1).setNationalNumber(18007493524L) + .setRawInput("1800 six-flag") + .setCountryCodeSource(CountryCodeSource.FROM_NUMBER_WITH_IDD).build(); + assertEquals(alphaNumericNumber4, + phoneUtil.parseAndKeepRawInput("001800 six-flag", "NZ")); } public void testCountryWithNoNumberDesc() { - // Andorra is a country where we don't have PhoneNumberDesc info in the meta data. + // Andorra is a country where we don't have PhoneNumberDesc info in the metadata. PhoneNumber adNumber = PhoneNumber.newBuilder().setCountryCode(376).setNationalNumber(12345L).build(); assertEquals("+376 12345", phoneUtil.format(adNumber,