Замечали когда-нибудь, что все записывают телефонные номера по-разному? Некоторые пишут их через пробелы, другие через дефис, а кто-то использует скобки. Разные люди по-разному группируют цифры номеров в числа.

И это становится реальной проблемой, когда вам нужно сохранить телефонные номера в базе данных и затем извлекать из неё записи по ним.

▍ Содержание



▍ Что такое нормализованный телефонный номер?


В сфере IT и компьютерных науках нормализация означает приведение чего-либо к согласованности или стандарту. В отношении телефонных номеров это подразумевает, что все их записи делаются в согласованном формате.

Существует ли международный стандарт для номеров телефонов? Международный союз электросвязи (International Telecommunication Union, ITU) опубликовал стандарт E.123, который определяет стандартные записи телефонных номеров, email и веб-адресов.

Записанный в формате E.123, международный телефонный номер выглядит так:

+12 345 678 901

Символ + указывает на международный формат номера. Код страны здесь представлен числом 12, а остальные цифры — это, собственно, сам телефонный номер.

Но когда людей просят ввести номер в поле формы, большинство записывают его так, как принято в их регионе, или как им больше нравится. В результате варианты написания могут сильно отличаться как от страны к стране, так и от человека к человеку.

▍ В форматах телефонных номеров полная неразбериха


Например, в США тот же телефонный номер будет записан так:

(345) 678-901

В Великобритании местные номера записываются так:

01234 56 7890

А в Индии так:

0123-456-7890

При этом некоторые люди порой вместо символа + используют международный код доступа 00.

0012 345 678 901

▍ Почему это проблема?


Когда вы сохраняете телефонные номера в базе данных, вам нужна возможность находить их независимо от того, как они были введены.

Представьте, что у вас в базе есть следующие номера:

  • 123-456-7890
  • (234) 567-8901
  • 0345 / 678 9012
  • +456 789 0123
  • 567-890-1234

Если же вам поступит вызов с +12 345 678 9012, как вы найдёте его в базе данных, чтобы получить информацию о соответствующем клиенте?

Как нормализовать телефонный номер


Наименее неоднозначным вариантом для хранения телефонного номера является определённый E.123 международный формат, в котором отсутствуют пробелы: +123456789012.

Но как к нему прийти?

▍ Через пользовательский интерфейс


Лучше всего будет попросить пользователей изначально вводить свои номера в этом формате.
Для этого можно предоставить им выпадающий список стран и автоматически форматировать телефонный номер на основе выбранного кода.

Например, библиотека International Telephone Input предлагает такой удобный UI:


Источник: github.com/jackocnr/intl-tel-input

▍ Обработка существующих данных


Если вы не можете обеспечить, чтобы пользователи вводили номера телефонов в подходящем формате, потому что имеете дело с легаси-данными или импортируете их из другого источника, то задача усложнится, но не станет невозможной.

Для начала рассмотрите вариант использования библиотеки. У Google есть опенсорсное решение под названием libphonenumber, которое окажется полезным во многих случаях.

Если вы работаете с Python, попробуйте портированную на этот язык версию той же libphonenumber — phonenumbers.

Также есть её вариант для JavaScript, а именно google-libphonenumber.

Но иногда вам потребуется написать собственную функцию нормализации или обрабатывать особые случаи, которые не может обработать библиотека из-за недостатка контекста.

Я же сосредоточусь на том подавляющем числе сценариев, в которых вашим основным врагом выступает несогласованное написание телефонных номеров. Кстати, в разделе «Философия телефонных номеров» приведены наиболее странные причины такого написания.

Вот список шагов, которые вам потребуется проделать для стандартизации:

▍ Исключите нечисловые символы


При удалении нечисловых символов на первом шаге нужно быть очень внимательным, так как некоторые из них могут служить для определения кода области.

Способ написания телефонного номера может дать некоторые подсказки относительно кода страны и области. Например, в США код области зачастую включается в скобки:

  • (234) 567-8901

А иногда отделяется пробелом или слэшем:

  • 0345 / 678 9012
  • 01234 56 7890

Учитывайте эти варианты написания, чтобы лишний раз не гадать о коде страны или области.

▍ Определите код страны


Если вы имеете дело с телефонными номерами одной страны, то можете предположить, что либо в эти номера уже включён соответствующий код в виде +1, +12 или +123, либо это местные номера.

Люди могут также вводить код страны без символа +, например, указывая только 1 или 12.

Если местный номер начинается с одного 0, уберите этот 0 и добавьте код страны. Если номер начинается с 00, уберите 00, но предположите, что код страны уже присутствует.

  • 01234 56 7890 станет +441234 56 7890
  • 0044 1234 56 7890 станет +441234 56 7890

Если вы работаете с номерами из разных стран, вам потребуется понять, какую страну предполагал пользователь, когда вводил свой номер.

Если вам повезёт, и нужно будет обработать всего несколько стран, коды области могут дать подсказку, поскольку среди них есть такие, которые используются в одной стране, но не в других.

▍ Сохраните только цифры и символ +


В качестве заключительного шага удалите все нечисловые символы, кроме + в начале.

Итоговый нормализованный номер будет выглядеть так:

+123456789012

Теперь его можно сохранить в виде строки.

Когда вам нужно найти телефонный номер, предварительно нормализуйте поисковое выражение аналогичным образом.

На всякий случай желательно также сохранить исходный номер. Он может потребоваться позднее для верификации или показа пользователю.

▍ Философия телефонных номеров


Должен признаться, что преподнёс ситуацию гораздо проще, чем она обстоит на самом деле. Предыдущая часть статьи в основном охватывает те случаи, с которыми вы никогда не столкнётесь, и в реальности всё гораздо запутанней.

Более подробно эта тема раскрыта в статье «Falsehoods Programmers Believe About Phone Numbers», которую можно найти в репозитории библиотеки libphonenumber.

Вот ряд наиболее коварных аспектов, в отношении которых нужно быть внимательными:

  • Телефонные номера не всегда уникальны: люди могут иметь по несколько номеров, или один номер может использоваться разными людьми.
  • Телефонные номера иногда меняются: люди порой меняют свои номера, которые впоследствии могут закрепляться за другими людьми.
  • Не все номера можно набрать на телефоне или в виде текста.
  • Могут использоваться символы не из кодировки ASCII: например, в Египте для написания номеров зачастую используется арабский шрифт.
  • Телефонные планы нумерации изменяются: в странах иногда меняются планы нумерации. При этом может присутствовать переходный период, когда действительными являются оба формата.
  • Телефонные номера — это не числа: в математическом смысле телефонный номер не является числом, то есть номер 7 не равнозначен номеру 007.

▍ Заключение


Сохранение телефонных номеров в том виде, в котором они вводятся, с большой вероятностью создаст проблемы при их дальнейшем поиске. Поэтому желательно их изначально нормализовать.

Вот краткий перечень основных пунктов статьи:

  • Старайтесь изначально получать от пользователей номера в нормализованном виде.
  • Показывайте эту нормализованную форму пользователям и просите её подтвердить.
  • Некоторые нечисловые символы могут дать подсказку относительно кодов страны/области.
  • При попытке определить код страны учитывайте местные особенности написания номеров.
  • Не нужно просто удалять нечисловые символы и сохранять оставшиеся цифры как номер.
  • Вносите телефонные номера в базу данных в виде нормализованных строк, а также сохраняйте их изначальную форму.

Telegram-канал со скидками, розыгрышами призов и новостями IT ?

Комментарии (47)


  1. zerg903
    28.06.2024 13:46
    +3

    Итоговый нормализованный номер будет выглядеть так: +123456789012
    Теперь его можно сохранить в виде строки.

    Если убрать +, то можно хранить в виде числа int64, что уменьшит размер и значительно упростит поиск.


    1. gazkom
      28.06.2024 13:46
      +9

      Можно просто по граблям походить, зачем читать статью.


      1. Akina
        28.06.2024 13:46
        +2

        Причём тут хождение по граблям? Вам дали определённый стандартом формат, который содержит как значащие символы (в данном случае только цифры), так и шаблонные (как значащие - в данном случае префиксный плюс, так и незначащие - пробелы). Общеустоявшийся стандарт хранения настоятельно предлагает все эти символы при хранении значения удалять, а при извлечении соответственно вставлять. Так что комментарий по делу. Товарищ совершенно прав. Как в том, что плюс следует удалить так и в том, что это оптимизирует хранение и упрощает обработку.


        1. navferty
          28.06.2024 13:46
          +4

          Для знакового 64-битного целого максимальное число 9,223,372,036,854,775,807 - что даёт максимальную длину "телефонного номера" в 18 цифр (или 19 для беззнакового).

          С одной стороны, кажется что "хватит всем" (с). С другой стороны, возьмём например 12-значный телефонный номер (быстрый гуглёж дал инфу про 10-значные номера в некоторых странах, например во Франции, но можно допустить что и 12-значный номер не невозможен).

          А теперь представим, что мы храним корпоративный номер, и компания использует дополнительно две трёхзначные группы: три цифры для департамента/отдела, и ещё три - для конкретного сотрудника (это не значит, конечно, что у них миллион сотрудников, но мне такая схема не кажется невозможной).

          И вот мы уже упёрлись в потолок - дальше только добавлять столбцы, или использовать какие-то ещё хаки, чтобы сохранить более длинный номер.

          Не говоря уже о том, что номер может состоять не только из цифр:

          • Могут использоваться символы не из кодировки ASCII: например, в Египте для написания номеров зачастую используется арабский шрифт.

          Так что Ваш совет, на мой взгляд, очень вредный. Лучше хранить номера как строки (а в отдельных случаях, как упомянуто в статье, наряду с нормализованным номером в другом столбце хранить его также и в исходном виде).


        1. Wesha
          28.06.2024 13:46

          определённый стандартом формат

          (Задумчиво): Очень оптимистичный молодой человек...


        1. masterthemac
          28.06.2024 13:46
          +3

          В статье же указано, что номер 7 и 007 это разные номера с точки зрения телефонной связи. В int64 это будет один номер.


          1. zatim
            28.06.2024 13:46

            Не вижу проблемы. В статье же указано что предлагается использовать форматирование согласно коду страны. Перед сохранением в базу нули отбросятся, при извлечении - добавятся. Телефонная связь не пострадает.


            1. kspshnik
              28.06.2024 13:46
              +1

              +7077.... и +7007.... - разные номера, точнее даже разные регионы, ABC-код отличается. А при приведении к bigint ведущие нули отбросятся.


              1. zatim
                28.06.2024 13:46

                И что? Посмотрите на свой собственный пример. Как отбросятся так и вернутся потом назад, ведь количество цифр в номере - фиксировано и заранее известно.


                1. Dolios
                  28.06.2024 13:46

                  ведь количество цифр в номере - фиксировано и заранее известно

                  Кто вам сказал такую глупость?


                  1. zatim
                    28.06.2024 13:46

                    Вас на википедии забанили? Вы не умеете пользоваться интернетом? В большинстве стран количество цифр в номере фиксировано и заранее известно. А в тех странах, где это не так - не бывает нуля в местных префиксах. Вообще, если вы не в курсе, за исключением пары стран нигде не используют местные префиксы с нулями (надеюсь, причину этого вы сможете понять самостоятельно). Соответственно, проблема, описанная ТС, высосана из пальца и не имеет места быть в реальности.


                    1. Dolios
                      28.06.2024 13:46

                      Пруфы в студию.


                    1. randomsimplenumber
                      28.06.2024 13:46

                      за исключением пары стран

                      В большинстве стран

                      Ну то есть из этого правила уже есть исключения. Ну и зачем оно нужно?


            1. Firz
              28.06.2024 13:46

              Можете привести пример, не до конца понятно как должно работать.

              Есть два телефонных номера, 011 и 11, как они будут храниться в базе?


              1. zatim
                28.06.2024 13:46

                011 и 11 - это один и тот же номер. Еще раз повторяю, количество цифр в номере фиксированное и заранее известно.


                1. Firz
                  28.06.2024 13:46

                  Еще раз повторяю, количество цифр в номере фиксированное и заранее известно.

                  беглое гугление подсказывает что нет, не фиксированное, куча стран где телефоны разной длины, к примеру Финляндия, 8–10

                  https://en.wikipedia.org/wiki/List_of_mobile_telephone_prefixes_by_country


                  1. zatim
                    28.06.2024 13:46

                    Беглое гугление подсказывает что в этих странах код не начинается на 0. Вообще, страны где внутренний код может начинаться с 0 можно по пальцам пересчитать. Пальцам одной руки. И там фиксированная, заранее известная длина.


                1. Wesha
                  28.06.2024 13:46

                  011 и 11 - это один и тот же номер

                  О сколько Вам открытий чудных готовит суровая реальность, молодой человек...


      1. zerg903
        28.06.2024 13:46
        +3

        Любой современный язык программирования имеет функцию форматирования числа в строку по заданному шаблону. Хранить и работать с номером нужно в нормализованном виде, а отображать в том, в каком привык пользователь конкретного региона. Очень плохая практика, когда вариант хранения диктуется UI.


    1. d-stream
      28.06.2024 13:46
      +2

      Но придется играть с масками/логикой для работы с номерами 0123.

      Пока спасает, что ведущий ноль/ноли имеют специальное значение


    1. Ivan22
      28.06.2024 13:46
      +4

      не надо в int. а то начнете велосипеды изобретать для добавочных кодов или номеров с буквами


    1. izh-vii
      28.06.2024 13:46
      +3

      Номерам с ведущими нолями int не понравится.

      Достаточно много пришлось заниматься этим вопросом, по итогу храню либо в строке фиксированной длины, либо в decimal без дробной части. Страну при необходимости - в отдельном поле.


      1. xSVPx
        28.06.2024 13:46

        Ну количество ведущих нулей вы тоже можете сохранять. И скорее всего это всё равно будет компактнее и быстрее.

        А просто 1 спереди добавить уже не влезет в инт ?


        1. randomsimplenumber
          28.06.2024 13:46

          А зачем? Сколько мегабайт удастся сэкономить на хранении всех номеров всех людей планеты?


          1. xSVPx
            28.06.2024 13:46
            +1

            А вы все значения всего в виде строк храните ?

            Ну типа "чего экономить то, всёж бесконечное"...

            Есть толстая разница между преждевременной оптимизацией и "и так сойдет".


            1. randomsimplenumber
              28.06.2024 13:46

              Есть толстая разница

              Есть ещё ложная дихотомия.

              чего экономить то, всёж бесконечное

              Ну вот и вопрос: чего вы планируете наэкономить? Если где то рядом в этой же таблице будут поля с ФИО, адресом и прочей информацией.

              А вы все значения всего в виде строк храните ?

              Положите квантор всеобщности на пол и медленно отойдите ;)


    1. Dolios
      28.06.2024 13:46
      +2

      Номер может быть записан буквами: https://en.wikipedia.org/wiki/Phoneword


      1. zatim
        28.06.2024 13:46

        Буквы - лишь мнемоники для лучшего запоминания. В итоге все равно передается цифра. Ни в устаревшем импульсном наборе, ни в dtmf не предусмотрено никаких способов передавать буквы.


        1. Dolios
          28.06.2024 13:46

          Вопрос не в том, как передаётся, а в том, как записывается и вводится пользователем.


          1. zatim
            28.06.2024 13:46

            Номер должен вводится пользователем в виде согласно международно утвержденному плану нумерации, действующем в конкретной стране. То есть исключительно цифрами. Буквы используются лишь как мнемоническое обозначение номера в рекламных целях и в целях облегчения запоминания.


            1. Dolios
              28.06.2024 13:46

              Номер должен вводиться так, как пользователю удобно. Хранение номера в числовом поле - это типичный пример "программизма", когда не очень квалифицированный инженер выдумывает проблемы на ровном месте, доставляя неудобства пользователю и создавая мину замедленного действия.

              Буквы используются лишь как мнемоническое обозначение номера в рекламных целях

              А откуда они появятся на сайте в рекрамных целях, амли программизд решил, что номер в базе будет лежать числом?


              1. zatim
                28.06.2024 13:46

                Хосспаде, ну что за бред вы несете. Сайт в рекламных целях вообще оформляет дизайнер, а не программист. Там номер, скорее всего, вообще будет вставлен bmp-шечкой нужным "красивым" шрифтом.


                1. Dolios
                  28.06.2024 13:46

                  Бред несёте тут только вы. Огромное количество людей записывает номера, в том числе, буквами и я среди них. Как я уже сказал, это от недостатка квалификации и узкого кругозора. Вы, почему-то, решили, что весь мир живёт так, как привыкли жить вы.


                  1. zatim
                    28.06.2024 13:46

                    Огромное количество людей записывает номера буквами

                    Пруфы в студию. Я уже 40 лет живу, ни разу не видел чтобы кто-то где-то записывал номер буквами. Вы там на выходных, похоже, слишком сильно на алкоголь налегаете.


                    1. Dolios
                      28.06.2024 13:46

                      Я так и написал:

                      это от недостатка квалификации и узкого кругозора. Вы, почему-то, решили, что весь мир живёт так, как привыкли жить вы.

                      Вы там на выходных, похоже, слишком сильно на алкоголь налегаете.

                      И опять вы проецируете свои проблемы на окружающих. Вы бы нарколога посетили, раз эта мысль первая к вам в голову пришла. Это может быть тревожным звонком. А судя по хамству, еще и психолог вас ждёт с нетерпением. Эта злость, скорее всего, тоже осознания своей низкой квалификации и бессилия от невозможности привести нормальные аргументы.

                      За сим откланяюсь, хамоватых чудаков и так вокруг слишком много, чтобы на них ещё и в интернете время тратить..


                    1. Wesha
                      28.06.2024 13:46

                      > Огромное количество людей записывает номера буквами

                      Пруфы в студию

                      Я уже 40 лет живу, ни разу не видел чтобы кто-то где-то записывал номер буквами.

                      Вам бы к окулисту записаться, уважаемый...

                      Хватит уже, или ещё насыпать? А то мне лень.


    1. vlmonk
      28.06.2024 13:46
      +6

      За хранения номера телефона как int64 нужно бить металической линейкой по пальцам. Сильно.


    1. ImagineTables
      28.06.2024 13:46

      Хранить всё надо в том виде, в котором собираешься использовать. Компрессировать при этом в int64, только потому, что большинство номеров туда влезает — та самая premature optimization, об опасности которой много написано.

      А одно из базовых применений номера — внезапно, dialing. Dialing чаще всего происходит так: контроллер (компьютер) открывает текстовой канал с коммуникационным устройством (модемом), и отдаёт ему строку вида ATD +123456789012. И ждёт ответа. Получается, юзер вводит строку, мы её конвертируем в число чисто для компрессии, храним в int64, а для набора потом конвертируем обратно в строку. Не лучше ли включить компрессию в опциях СУБД? Если место так важно?

      Любой, кто разок нарвётся на расширения HAYES-команд, или необходимость хранить служебные номера со стандартными примочками ATD, сразу разучится баловаться с неподходящими типами.


      1. Wesha
        28.06.2024 13:46

        ВНЕЗАПНО, в некоторых номерах приходится в стратегических местах добвлять паузы, вроде +18001234567,,,,5,,,,1234.


  1. titbit
    28.06.2024 13:46
    +6

    Добавлю в копилку, что не все номера можно преобразовать в международный формат (и наоборот), поэтому нельзя просто так убирать 0 или 00 в начале - это может быть частью локального номера. По той же причине нельзя добавлять просто так код страны в начало. Хранить номер как число проблематично: 0770 и 770 - это могут быть разные абоненты.

    В некоторых номерах есть DTMF коды, как часть номера или как дополнительный набор (добавочный номер).

    p.s. есть страны где длина номера переменная и зависит от кучи вещей, например от оператора связи (и зависимость там не блочная, а бывает индивидуальная) и подвести все номера под один стандарт длины нельзя.


    1. nv13
      28.06.2024 13:46
      +1

      Где это расширенные дтмф могут гулять по телефонной сети? То что они из доп номера или ещё как появляются это кривизна намбер планов приватных сетей, в паблике их быть не должно.

      Насчёт хранить как есть - ок, а как верифицировать корректность номера? Если у клиента пара миллионов лист контактов, допустим? Не верифицируешь, получишь кучу ошибок в репорте, по каждому клиент будет требовать объяснений.

      Гугловская либа уже на этапе ввода номера не тольао может конвертировать в е.164, но и отфильтровать некорректно заданные номера. С мобильного же можно куда угодно дозвониться, а там этот стандарт и работает.


  1. Hint
    28.06.2024 13:46
    +2

    При нормализации помогает знание хотя бы длины номера в каждой стране. Преобразовали, извлекли код страны, проверили длину. Если длина не совпадает, значит или номер введён неверно, или нормализация была неправильной. Проблема в том, что в некоторых странах номера могут быть разной длины (в том числе в зависимости от префикса). Есть библиотеки, содержащие информацию о длине, о префиксах, операторах и пр., но это много информации, которую нужно обновлять. Знать хотя бы правильную длину – это дешево и сердито.

    А вообще бывает много странных штук в планах нумерации. Например, в Аргентине для звонков используется один номер, а для SMS другой.

    https://en.wikipedia.org/wiki/Telephone_numbers_in_Argentina

    To send an SMS text message to an Argentine cell phone from another country, the 9 used internationally when dialing the number for a voice call (and the 15 used for calls within Argentina) is omitted. For example, if the mobile number in Argentina is (11) 15 1234–5678, a voice call from abroad would be dialled as +54 9 11 1234 5678, but a text message would require +54 11 1234 5678, with +54 9 11 1234 5678 usually being invalid.


  1. DVegasa
    28.06.2024 13:46
    +2

    Если честно, слишком растянутая статья для мысли "в международном формате без-цифровых символов". Ожидал услышать про особенности номеров -- дополнительные коды, прочие особенности (например, как вы сами и написали, арабские символы в Египетских номерах). Ну а если затронули ещё и UI, можно было упомянуть традицию США записывать телефон буквами (+1 444-LAWYER)


  1. LeVoN_CCCP
    28.06.2024 13:46
    +2

    Непонятна проблема, есть код страны и телефон. Пусть пользователи пишут как им удобно, ну отображение номера в зависимости от страны. Храним все равно как regexp_replace(number,'[^0-9]','','gi'). Вопрос только для чего телефон нужен. Если просто для информации как "данные о себе", когда каждый пишет кто во что горазд это одно. Другое если это контактная/ключевая информация и тогда её проверяют подтверждением/смс. При этом как ты решаешь у себя в базе писать так оно и будет. Проект в РФ и КЗ - можно даже код не вписывать и получаем 10 цифр.


  1. RinNas
    28.06.2024 13:46
    +1

    PostgreSQL функции для обработки номера телефона

    1. phone_parse.sql -- разбирает номер телефона в международном формате E.164 или в локальном формате на составные части (country_code, area_code, local_number)

    2. phone_normalize.sql -- нормализует номер телефона, возвращает номер телефона в международном формате E.164, например: +79651234567

    3. phone_format.sql -- форматирует номер телефона по образцу

    PostgreSQL домен (тип данных)

    • phone.sql -- phone number in the international format E.164


  1. zyxd
    28.06.2024 13:46

    Как в таком случае обрабатывать добавочные номера типа +123456789123#123?


  1. zyxd
    28.06.2024 13:46

    Как в таком случае обрабатывать добавочные номера типа +123456789123#123?