Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 41 additions & 24 deletions PhoneNumberKit/ParseManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,9 @@ final class ParseManager {
return result
}

// If all attempts fail, the number is invalid
throw PhoneNumberError.invalidNumber
// Final fallback for countryCode == 0: try all territories with the same country code as the region
// This handles cases where a number from one NANP territory (e.g., US) is parsed with another NANP region (e.g., AS, PR)
return try tryTerritoriesWithCode(regionMetadata.countryCode, nationalNumber: nationalNumber, excludingRegion: regionMetadata.codeID, ignoreType: ignoreType, numberString: numberString, numberExtension: numberExtension)
}

// STEP 6: Update metadata if extracted country code differs from region's default
Expand All @@ -112,27 +113,7 @@ final class ParseManager {
// STEP 7: Final fallback - try all territories with the same country code
// Some country codes are shared by multiple territories (e.g., +1 for US, CA, etc.)
// Try each territory's metadata to see if the number is valid in any of them
var possibleResults: Set<PhoneNumber> = []
if let metadataList = metadataManager.filterTerritories(byCode: countryCode) {
for metadata in metadataList where regionMetadata.codeID != metadata.codeID {
if let result = try validPhoneNumber(from: nationalNumber, using: metadata, countryCode: countryCode, ignoreType: ignoreType, numberString: numberString, numberExtension: numberExtension) {
possibleResults.insert(result)
}
}
}

// Return results based on how many valid interpretations were found
switch possibleResults.count {
case 0:
// No valid interpretation found
throw PhoneNumberError.invalidNumber
case 1:
// Exactly one valid interpretation - return it
return possibleResults.first!
default:
// Multiple valid interpretations - ambiguous number
throw PhoneNumberError.ambiguousNumber(phoneNumbers: possibleResults)
}
return try tryTerritoriesWithCode(countryCode, nationalNumber: nationalNumber, excludingRegion: regionMetadata.codeID, ignoreType: ignoreType, numberString: numberString, numberExtension: numberExtension)
}

// Parse task
Expand Down Expand Up @@ -192,7 +173,43 @@ final class ParseManager {
return nil
}

// MARK: Internal method
// MARK: Internal methods

/// Try all territories with the same country code to find a valid interpretation of the number
/// - Parameters:
/// - countryCode: The country code to search territories for
/// - nationalNumber: The national number to validate
/// - excludingRegion: The region ID to exclude from the search
/// - ignoreType: Whether to skip type checking
/// - numberString: The original number string
/// - numberExtension: Optional extension
/// - Returns: A valid PhoneNumber if exactly one interpretation is found
/// - Throws: invalidNumber if no valid interpretation found, ambiguousNumber if multiple interpretations found
private func tryTerritoriesWithCode(_ countryCode: UInt64, nationalNumber: String, excludingRegion: String, ignoreType: Bool, numberString: String, numberExtension: String?) throws -> PhoneNumber {
guard let metadataManager = metadataManager else { throw PhoneNumberError.generalError }

var possibleResults: Set<PhoneNumber> = []
if let metadataList = metadataManager.filterTerritories(byCode: countryCode) {
for metadata in metadataList where excludingRegion != metadata.codeID {
if let result = try validPhoneNumber(from: nationalNumber, using: metadata, countryCode: countryCode, ignoreType: ignoreType, numberString: numberString, numberExtension: numberExtension) {
possibleResults.insert(result)
}
}
}

// Return results based on how many valid interpretations were found
switch possibleResults.count {
case 0:
// No valid interpretation found
throw PhoneNumberError.invalidNumber
case 1:
// Exactly one valid interpretation - return it
return possibleResults.first!
default:
// Multiple valid interpretations - ambiguous number
throw PhoneNumberError.ambiguousNumber(phoneNumbers: possibleResults)
}
}

/// Creates a valid phone number given a specifc region metadata, used internally by the parse function
private func validPhoneNumber(from nationalNumber: String, using regionMetadata: MetadataTerritory, countryCode: UInt64, ignoreType: Bool, numberString: String, numberExtension: String?) throws -> PhoneNumber? {
Expand Down
9 changes: 9 additions & 0 deletions PhoneNumberKitTests/PhoneNumberUtilityParsingTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -442,4 +442,13 @@ final class PhoneNumberUtilityParsingTests: XCTestCase {
XCTAssertEqual(error as? PhoneNumberError, .invalidNumber)
}
}

func testValidUSNumberInOtherNANPRegion() {
// A valid US number when parsed in another NANP region (e.g. AS or PR or CA) should still be valid.
let number = "(212)222-8688"
XCTAssertNoThrow(try sut.parse(number, withRegion: "AS"))
XCTAssertNoThrow(try sut.parse(number, withRegion: "PR"))
XCTAssertNoThrow(try sut.parse(number, withRegion: "CA"))
XCTAssertNoThrow(try sut.parse(number, withRegion: "US"))
}
}