|
|
|
|
@ -213,16 +213,75 @@ public extension String { |
|
|
|
|
// MARK: - FFT Source Importing |
|
|
|
|
public extension String { |
|
|
|
|
enum RegexStatic { |
|
|
|
|
static let mobileNumber = /^(?:\+33|0033|0)[6-7](?:[ .-]?[0-9]{2}){4}$/ |
|
|
|
|
static let phoneNumber = /^(?:\+33|0033|0)[1-9](?:[ .-]?[0-9]{2}){4}$/ |
|
|
|
|
// Patterns for France only |
|
|
|
|
static let phoneNumber = /^(\+33|0033|33|0)[1-9][0-9]{8}$/ |
|
|
|
|
static let phoneNumberWithExtra0 = /^33[0][1-9][0-9]{8}$/ |
|
|
|
|
static let mobileNumber = /^(\+33|0033|33|0)[6-7][0-9]{8}$/ |
|
|
|
|
static let mobileNumberWithExtra0 = /^33[0][6-7][0-9]{8}$/ |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
private func cleanedNumberForValidation() -> String { |
|
|
|
|
// Keep leading '+' if present, remove all other non-digit characters |
|
|
|
|
var cleaned = self.trimmingCharacters(in: .whitespacesAndNewlines) |
|
|
|
|
if cleaned.hasPrefix("+") { |
|
|
|
|
// Preserve '+' at start, remove all other non-digit characters |
|
|
|
|
let digitsOnly = cleaned.dropFirst().components(separatedBy: CharacterSet.decimalDigits.inverted).joined() |
|
|
|
|
cleaned = "+" + digitsOnly |
|
|
|
|
} else { |
|
|
|
|
// Remove all non-digit characters |
|
|
|
|
cleaned = cleaned.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() |
|
|
|
|
} |
|
|
|
|
return cleaned |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
// MARK: - Phone Number Validation |
|
|
|
|
|
|
|
|
|
func isMobileNumber() -> Bool { |
|
|
|
|
firstMatch(of: RegexStatic.mobileNumber) != nil |
|
|
|
|
/// Validate if the string is a mobile number for the specified locale. |
|
|
|
|
/// - Parameter locale: The locale to validate against. Defaults to `.current`. |
|
|
|
|
/// - Returns: True if the string matches the mobile number pattern for the locale. |
|
|
|
|
func isMobileNumber(locale: Locale = .current) -> Bool { |
|
|
|
|
// TODO: Support additional regions/locales in the future. |
|
|
|
|
switch locale.region?.identifier { |
|
|
|
|
case "FR", "fr", nil: |
|
|
|
|
// French logic for now |
|
|
|
|
let cleaned = cleanedNumberForValidation() |
|
|
|
|
if cleaned.firstMatch(of: RegexStatic.mobileNumber) != nil { |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
if cleaned.firstMatch(of: RegexStatic.mobileNumberWithExtra0) != nil { |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
return false |
|
|
|
|
default: |
|
|
|
|
// For unsupported locales, fallback to checking if the string contains at least 8 digits |
|
|
|
|
// This is a generic minimum length for most countries' phone numbers |
|
|
|
|
let digitsOnly = self.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() |
|
|
|
|
return digitsOnly.count >= 8 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func isPhoneNumber() -> Bool { |
|
|
|
|
firstMatch(of: RegexStatic.phoneNumber) != nil |
|
|
|
|
/// Validate if the string is a phone number for the specified locale. |
|
|
|
|
/// - Parameter locale: The locale to validate against. Defaults to `.current`. |
|
|
|
|
/// - Returns: True if the string matches the phone number pattern for the locale. |
|
|
|
|
func isPhoneNumber(locale: Locale = .current) -> Bool { |
|
|
|
|
// TODO: Support additional regions/locales in the future. |
|
|
|
|
switch locale.region?.identifier { |
|
|
|
|
case "FR", "fr", nil: |
|
|
|
|
// French logic for now |
|
|
|
|
let cleaned = cleanedNumberForValidation() |
|
|
|
|
if cleaned.firstMatch(of: RegexStatic.phoneNumber) != nil { |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
if cleaned.firstMatch(of: RegexStatic.phoneNumberWithExtra0) != nil { |
|
|
|
|
return true |
|
|
|
|
} |
|
|
|
|
return false |
|
|
|
|
default: |
|
|
|
|
// For unsupported locales, fallback to checking if the string contains at least 8 digits |
|
|
|
|
// This is a generic minimum length for most countries' phone numbers |
|
|
|
|
let digitsOnly = self.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() |
|
|
|
|
return digitsOnly.count >= 8 |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
func normalize(_ phone: String) -> String { |
|
|
|
|
@ -231,7 +290,11 @@ public extension String { |
|
|
|
|
normalized = normalized.components(separatedBy: CharacterSet.decimalDigits.inverted).joined() |
|
|
|
|
// Remove leading country code for France (33) if present |
|
|
|
|
if normalized.hasPrefix("33") { |
|
|
|
|
if normalized.dropFirst(2).hasPrefix("0") { |
|
|
|
|
// Keep as is, don't strip the zero after 33 |
|
|
|
|
} else { |
|
|
|
|
normalized = "0" + normalized.dropFirst(2) |
|
|
|
|
} |
|
|
|
|
} else if normalized.hasPrefix("0033") { |
|
|
|
|
normalized = "0" + normalized.dropFirst(4) |
|
|
|
|
} |
|
|
|
|
|