Skip to content

Utilidades de Números Telefónicos con libphonenumber en PHP: Detección de País, Idioma y Zona Horaria

Introducción

Al desarrollar aplicaciones globales, los números telefónicos son frecuentemente los datos de usuario más confiables disponibles. A diferencia de direcciones de correo electrónico o nombres de usuario, los números telefónicos contienen información geográfica inherente que puede ayudar a personalizar experiencias. Desde seleccionar automáticamente el idioma correcto para notificaciones hasta programar mensajes en horarios locales apropiados, extraer metadatos de números telefónicos es invaluable.

Esta guía demuestra cómo construir una clase PhoneUtil usando la biblioteca libphonenumber de Google (a través de la versión PHP) para extraer códigos de país, inferir idiomas preferidos y detectar zonas horarias de números telefónicos internacionales.

Prerrequisitos

  • PHP 8.1 o superior
  • Gestor de paquetes Composer
  • Conocimiento básico de clases PHP y manejo de excepciones

Instalando libphonenumber-for-php

Instala la biblioteca vía Composer:

bash
composer require giggsey/libphonenumber-for-php

Versión Lite Disponible

Si solo necesitas parsing y formateo básico sin geocodificación o información de operador, puedes usar giggsey/libphonenumber-for-php-lite para una instalación más ligera.

Construyendo la Clase PhoneUtil

Vamos a crear una clase utilitaria que proporciona tres métodos principales: detección de código de país, inferencia de idioma y resolución de zona horaria.

Estructura Básica

php
<?php

namespace App\Utils;

use libphonenumber\PhoneNumberToTimeZonesMapper;
use libphonenumber\PhoneNumberUtil;
use Throwable;

class PhoneUtil
{
    // Los métodos irán aquí
}

Obteniendo el Código de País

El primer método extrae el código de país ISO 3166-1 alfa-2 (ej: "US", "BR", "MX") de un número telefónico:

php
public static function getCountryCode(string $phone_number): ?string
{
    try {
        $phoneUtil = PhoneNumberUtil::getInstance();
        $phoneObj = $phoneUtil->parse("+{$phone_number}");

        return $phoneUtil->getRegionCodeForNumber($phoneObj);
    } catch (Throwable $e) {
        // Registra o reporta el error
        return null;
    }
}

Formato del Número Telefónico

Esta implementación espera números telefónicos sin el signo + inicial. El método lo agrega durante el parsing. Ajusta según cómo tu aplicación almacena los números.

Infiriendo el Idioma del Usuario

Basándose en el código de país, podemos hacer suposiciones educadas sobre el idioma preferido del usuario. Esto es particularmente útil para enviar notificaciones localizadas:

php
public static function getCountryLanguage(string $phone_number): ?string
{
    try {
        $country_code = self::getCountryCode($phone_number);

        if (! $country_code) {
            return null;
        }

        return match ($country_code) {
            // Países hispanohablantes
            'ES', 'MX', 'AR', 'CO', 'PE', 'VE', 'CL', 'EC',
            'GT', 'CU', 'BO', 'DO', 'HN', 'PY', 'SV', 'NI',
            'CR', 'PA', 'UY' => 'es',

            // Portugués
            'BR' => 'pt_BR',

            // Chino
            'CN', 'TW', 'HK', 'SG' => 'zh_CN',

            // Hindi
            'IN' => 'hi',

            // Por defecto inglés
            default => 'en',
        };
    } catch (Throwable $e) {
        return null;
    }
}

Expandiendo Soporte de Idiomas

El mapeo anterior cubre los idiomas principales. Expándelo según los locales soportados por tu aplicación. Considera países como Francia (FR => fr), Alemania (DE => de) o Japón (JP => ja) según sea necesario.

Detectando Zona Horaria

La detección de zona horaria es crucial para programar comunicaciones con usuarios en horarios apropiados. La biblioteca libphonenumber proporciona la clase PhoneNumberToTimeZonesMapper para este propósito:

php
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;
        }

        // Maneja fallbacks de zona horaria desconocida
        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;
    }
}

Manejando Zonas Horarias Obsoletas

Algunos números telefónicos pueden retornar identificadores de zona horaria obsoletos. Es buena práctica mapearlos a sus equivalentes modernos:

php
public static function getTimezone(string $phone_number): ?string
{
    try {
        // ... código anterior ...

        // Mapea zonas horarias obsoletas a equivalentes modernos
        $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;
    }
}

Implementación Completa

Aquí está la clase PhoneUtil completa combinando todos los métodos:

php
<?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) {
            // Considera registrar: 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;
        }
    }
}

Ejemplos de Uso

Uso Básico

php
use App\Utils\PhoneUtil;

// Número telefónico brasileño
$phone = '5511999998888';

$country = PhoneUtil::getCountryCode($phone);      // "BR"
$language = PhoneUtil::getCountryLanguage($phone); // "pt_BR"
$timezone = PhoneUtil::getTimezone($phone);        // "America/Sao_Paulo"

Aplicación Práctica: Notificaciones Localizadas

php
public function sendNotification(User $user, string $message): void
{
    $language = PhoneUtil::getCountryLanguage($user->phone);
    $timezone = PhoneUtil::getTimezone($user->phone);

    // Establece el locale para traducción
    app()->setLocale($language ?? 'en');

    // Programa en el horario local apropiado
    $sendAt = now($timezone)->setTime(10, 0);

    Notification::send($user, new UserNotification(
        message: __($message),
        scheduledAt: $sendAt
    ));
}

Depurando Números Telefónicos

El proyecto libphonenumber proporciona una demostración en línea para probar el parsing de números telefónicos:

https://libphonenumber.appspot.com

Usa esta herramienta para verificar el comportamiento esperado antes de reportar problemas.

Solución de Problemas

Retornos de Zona Horaria Desconocida

Para algunos números móviles, especialmente en países grandes como Brasil, México o India, la biblioteca puede retornar Etc/Unknown. Esto sucede cuando el número no proporciona suficiente especificidad geográfica. Maneja esto con fallbacks a nivel de país, como se muestra arriba.

Fallos de Parsing

Causas comunes de fallos de parsing:

  • Formato inválido de número telefónico
  • Prefijo de código de país ausente o incorrecto
  • Caracteres no numéricos en la entrada

Siempre sanitiza los números telefónicos antes de procesar:

php
$phone = preg_replace('/[^0-9]/', '', $phone);

Conclusión

Construir utilidades de números telefónicos con libphonenumber proporciona una base confiable para aplicaciones internacionalizadas. Al extraer códigos de país, inferir idiomas y detectar zonas horarias, puedes entregar experiencias personalizadas que respetan los contextos geográficos de los usuarios.

Los puntos clave son:

  • Usa PhoneNumberUtil para parsing y detección de país
  • Mapea códigos de país a tus locales soportados para inferencia de idioma
  • Maneja zonas horarias Etc/Unknown con defaults sensatos a nivel de país
  • Mapea identificadores de zona horaria obsoletos a sus equivalentes modernos

Para aplicaciones con requisitos estrictos, considera permitir que los usuarios sobrescriban los valores inferidos en sus preferencias, usando la detección basada en teléfono como valores predeterminados inteligentes.