Phone Number Utilities with libphonenumber in PHP: Country, Language, and Timezone Detection
Introduction
When building global applications, phone numbers are often the most reliable piece of user data available. Unlike email addresses or usernames, phone numbers carry inherent geographic information that can help personalize user experiences. From automatically selecting the right language for notifications to scheduling messages at appropriate local times, extracting metadata from phone numbers is invaluable.
This guide demonstrates how to build a PhoneUtil class using Google's libphonenumber library (via the PHP port) to extract country codes, infer preferred languages, and detect timezones from international phone numbers.
Prerequisites
- PHP 8.1 or higher
- Composer package manager
- Basic understanding of PHP classes and exception handling
Installing libphonenumber-for-php
Install the library via Composer:
composer require giggsey/libphonenumber-for-phpLite Version Available
If you only need basic parsing and formatting without geocoding or carrier information, you can use giggsey/libphonenumber-for-php-lite for a smaller footprint.
Building the PhoneUtil Class
Let's create a utility class that provides three key methods: country code detection, language inference, and timezone resolution.
Basic Structure
<?php
namespace App\Utils;
use libphonenumber\PhoneNumberToTimeZonesMapper;
use libphonenumber\PhoneNumberUtil;
use Throwable;
class PhoneUtil
{
// Methods will go here
}Getting the Country Code
The first method extracts the ISO 3166-1 alpha-2 country code (e.g., "US", "BR", "MX") from a phone number:
public static function getCountryCode(string $phone_number): ?string
{
try {
$phoneUtil = PhoneNumberUtil::getInstance();
$phoneObj = $phoneUtil->parse("+{$phone_number}");
return $phoneUtil->getRegionCodeForNumber($phoneObj);
} catch (Throwable $e) {
// Log or report the error
return null;
}
}Phone Number Format
This implementation expects phone numbers without the leading + sign. The method prepends it during parsing. Adjust based on how your application stores phone numbers.
Inferring User Language
Based on the country code, we can make educated guesses about a user's preferred language. This is particularly useful for sending localized notifications:
public static function getCountryLanguage(string $phone_number): ?string
{
try {
$country_code = self::getCountryCode($phone_number);
if (! $country_code) {
return null;
}
return match ($country_code) {
// Spanish-speaking countries
'ES', 'MX', 'AR', 'CO', 'PE', 'VE', 'CL', 'EC',
'GT', 'CU', 'BO', 'DO', 'HN', 'PY', 'SV', 'NI',
'CR', 'PA', 'UY' => 'es',
// Portuguese
'BR' => 'pt_BR',
// Chinese
'CN', 'TW', 'HK', 'SG' => 'zh_CN',
// Hindi
'IN' => 'hi',
// Default to English
default => 'en',
};
} catch (Throwable $e) {
return null;
}
}Expanding Language Support
The mapping above covers major languages. Extend it based on your application's supported locales. Consider countries like France (FR => fr), Germany (DE => de), or Japan (JP => ja) as needed.
Detecting Timezone
Timezone detection is crucial for scheduling user communications at appropriate times. The libphonenumber library provides the PhoneNumberToTimeZonesMapper class for this purpose:
public static function getTimezone(string $phone_number): ?string
{
try {
$country_code = self::getCountryCode($phone_number);
$phoneUtil = PhoneNumberUtil::getInstance();
$phoneObj = $phoneUtil->parse("+{$phone_number}", $country_code);
$timezoneMapper = PhoneNumberToTimeZonesMapper::getInstance();
$timezones = $timezoneMapper->getTimeZonesForNumber($phoneObj);
$timezone = $timezones[0] ?? null;
if (! $timezone) {
return null;
}
// Handle unknown timezone fallbacks
if ($timezone === 'Etc/Unknown') {
return match ($country_code) {
'MX' => 'America/Mexico_City',
'BR' => 'America/Sao_Paulo',
'IN' => 'Asia/Kolkata',
default => null,
};
}
return $timezone;
} catch (Throwable $e) {
return null;
}
}Handling Deprecated Timezones
Some phone numbers may return deprecated timezone identifiers. It's good practice to map these to their modern equivalents:
public static function getTimezone(string $phone_number): ?string
{
try {
// ... previous code ...
// Map deprecated timezones to modern equivalents
$deprecatedTimezones = [
'America/Buenos_Aires' => 'America/Argentina/Buenos_Aires',
'America/Catamarca' => 'America/Argentina/Catamarca',
'America/Cordoba' => 'America/Argentina/Cordoba',
'America/Jujuy' => 'America/Argentina/Jujuy',
'America/Mendoza' => 'America/Argentina/Mendoza',
];
if (isset($deprecatedTimezones[$timezone])) {
return $deprecatedTimezones[$timezone];
}
return $timezone;
} catch (Throwable $e) {
return null;
}
}Complete Implementation
Here's the full PhoneUtil class combining all the methods:
<?php
namespace App\Utils;
use libphonenumber\PhoneNumberToTimeZonesMapper;
use libphonenumber\PhoneNumberUtil;
use Throwable;
class PhoneUtil
{
public static function getCountryCode(string $phone_number): ?string
{
try {
$phoneUtil = PhoneNumberUtil::getInstance();
$phoneObj = $phoneUtil->parse("+{$phone_number}");
return $phoneUtil->getRegionCodeForNumber($phoneObj);
} catch (Throwable $e) {
// Consider logging: Errors::reportThrowable($e);
return null;
}
}
public static function getCountryLanguage(string $phone_number): ?string
{
try {
$country_code = self::getCountryCode($phone_number);
if (! $country_code) {
return null;
}
return match ($country_code) {
'ES', 'MX', 'AR', 'CO', 'PE', 'VE', 'CL', 'EC',
'GT', 'CU', 'BO', 'DO', 'HN', 'PY', 'SV', 'NI',
'CR', 'PA', 'UY' => 'es',
'BR' => 'pt_BR',
'CN', 'TW', 'HK', 'SG' => 'zh_CN',
'IN' => 'hi',
default => 'en',
};
} catch (Throwable $e) {
return null;
}
}
public static function getTimezone(string $phone_number): ?string
{
try {
$country_code = self::getCountryCode($phone_number);
$phoneUtil = PhoneNumberUtil::getInstance();
$phoneObj = $phoneUtil->parse("+{$phone_number}", $country_code);
$timezoneMapper = PhoneNumberToTimeZonesMapper::getInstance();
$timezones = $timezoneMapper->getTimeZonesForNumber($phoneObj);
$timezone = $timezones[0] ?? null;
if (! $timezone) {
return null;
}
if ($timezone === 'Etc/Unknown') {
return match ($country_code) {
'MX' => 'America/Mexico_City',
'BR' => 'America/Sao_Paulo',
'IN' => 'Asia/Kolkata',
default => null,
};
}
$deprecatedTimezones = [
'America/Buenos_Aires' => 'America/Argentina/Buenos_Aires',
'America/Catamarca' => 'America/Argentina/Catamarca',
'America/Cordoba' => 'America/Argentina/Cordoba',
'America/Jujuy' => 'America/Argentina/Jujuy',
'America/Mendoza' => 'America/Argentina/Mendoza',
];
return $deprecatedTimezones[$timezone] ?? $timezone;
} catch (Throwable $e) {
return null;
}
}
}Usage Examples
Basic Usage
use App\Utils\PhoneUtil;
// Brazilian phone number
$phone = '5511999998888';
$country = PhoneUtil::getCountryCode($phone); // "BR"
$language = PhoneUtil::getCountryLanguage($phone); // "pt_BR"
$timezone = PhoneUtil::getTimezone($phone); // "America/Sao_Paulo"Practical Application: Localized Notifications
public function sendNotification(User $user, string $message): void
{
$language = PhoneUtil::getCountryLanguage($user->phone);
$timezone = PhoneUtil::getTimezone($user->phone);
// Set locale for translation
app()->setLocale($language ?? 'en');
// Schedule at appropriate local time
$sendAt = now($timezone)->setTime(10, 0);
Notification::send($user, new UserNotification(
message: __($message),
scheduledAt: $sendAt
));
}Debugging Phone Numbers
The libphonenumber project provides an online demo for testing phone number parsing:
https://libphonenumber.appspot.com
Use this tool to verify expected behavior before reporting issues.
Troubleshooting
Unknown Timezone Returns
For some mobile numbers, especially in large countries like Brazil, Mexico, or India, the library may return Etc/Unknown. This happens when the number doesn't provide enough geographic specificity. Handle this with country-level fallbacks as shown above.
Parsing Failures
Common causes of parsing failures:
- Invalid phone number format
- Missing or incorrect country code prefix
- Non-numeric characters in the input
Always sanitize phone numbers before processing:
$phone = preg_replace('/[^0-9]/', '', $phone);Conclusion
Building phone number utilities with libphonenumber provides a reliable foundation for internationalized applications. By extracting country codes, inferring languages, and detecting timezones, you can deliver personalized experiences that respect users' geographic contexts.
The key takeaways are:
- Use
PhoneNumberUtilfor parsing and country detection - Map country codes to your supported locales for language inference
- Handle
Etc/Unknowntimezones with sensible country-level defaults - Map deprecated timezone identifiers to their modern equivalents
For applications with strict requirements, consider allowing users to override inferred values in their preferences while using phone-based detection as intelligent defaults.

