Замечали когда-нибудь, что все записывают телефонные номера по-разному? Некоторые пишут их через пробелы, другие через дефис, а кто-то использует скобки. Разные люди по-разному группируют цифры номеров в числа.
И это становится реальной проблемой, когда вам нужно сохранить телефонные номера в базе данных и затем извлекать из неё записи по ним.
▍ Содержание
- Что такое нормализованный телефонный номер?
- В форматах телефонных номеров неразбериха
- Почему это проблема
-
Как нормализовать телефонный номер
- Философия телефонных номеров
- Заключение
▍ Что такое нормализованный телефонный номер?
В сфере 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 ?
Комментарии (51)
titbit
28.06.2024 13:46+7Добавлю в копилку, что не все номера можно преобразовать в международный формат (и наоборот), поэтому нельзя просто так убирать 0 или 00 в начале - это может быть частью локального номера. По той же причине нельзя добавлять просто так код страны в начало. Хранить номер как число проблематично: 0770 и 770 - это могут быть разные абоненты.
В некоторых номерах есть DTMF коды, как часть номера или как дополнительный набор (добавочный номер).
p.s. есть страны где длина номера переменная и зависит от кучи вещей, например от оператора связи (и зависимость там не блочная, а бывает индивидуальная) и подвести все номера под один стандарт длины нельзя.
nv13
28.06.2024 13:46+1Где это расширенные дтмф могут гулять по телефонной сети? То что они из доп номера или ещё как появляются это кривизна намбер планов приватных сетей, в паблике их быть не должно.
Насчёт хранить как есть - ок, а как верифицировать корректность номера? Если у клиента пара миллионов лист контактов, допустим? Не верифицируешь, получишь кучу ошибок в репорте, по каждому клиент будет требовать объяснений.
Гугловская либа уже на этапе ввода номера не тольао может конвертировать в е.164, но и отфильтровать некорректно заданные номера. С мобильного же можно куда угодно дозвониться, а там этот стандарт и работает.
inkelyad
28.06.2024 13:46Где это расширенные дтмф могут гулять по телефонной сети? То что они из доп номера или ещё как появляются это кривизна намбер планов приватных сетей, в паблике их быть не должно.
В телефонной сети, может их не должно гулять.
Но вот чтобы дозвониться к абоненту, который за мини-атс сидит, дополнительный номер надо в какой-то момент все-таки пропиликать. Телефонные звонилки, например, умеют паузу в контакт добавлять и дальше дополнительный набор.А вот интерфейсы сайтов, которые телефон спрашивают - сильно вряд ли.
nv13
28.06.2024 13:46Дозвониться это как? Роботом? Так звонки на бизнес номера роботами запрещены, насколько я знаю. Ни один клиент за много лет не смог выставить подобное требование по поддержке доп набора в автомате.
inkelyad
28.06.2024 13:46Я не про дополнительный набор.
А про то, что когда спрашивают номер, чтобы потом дозвониться голосом - то поля для дополнительного номера нет в форме 'как с вами связаться?'. И как ему звонить потом? Не роботом, а голосом. Да, я в курсе, что оно почти вывелось и все вбивают прямой сотовый (в том числе и из за этого недостатка интерфейсов). Но, технически, такие номера вполне возможны и существуют.
Более, я бы себе на телефон такую 'АТС', понимающую дополнение к номеру поставил, если бы все это не зарезали напрочь. Чтобы было 'Вышего номера нет в контактах хозяина, пожалуйста, нажмите 1, если....<список из дюжины пунктов> или наберите шестизначный код доступа, чтобы телефон хозяина сразу зазвонил'.
nv13
28.06.2024 13:46Там проблема с моментом отсылки доп номера. На другом конце ivr, и в какой момент уже можно слать доп номер непонятно. Стандарта нет, каждый лепит что хочет, поэтому и не используется.
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.
DVegasa
28.06.2024 13:46+2Если честно, слишком растянутая статья для мысли "в международном формате без-цифровых символов". Ожидал услышать про особенности номеров -- дополнительные коды, прочие особенности (например, как вы сами и написали, арабские символы в Египетских номерах). Ну а если затронули ещё и UI, можно было упомянуть традицию США записывать телефон буквами (+1 444-LAWYER)
LeVoN_CCCP
28.06.2024 13:46+2Непонятна проблема, есть код страны и телефон. Пусть пользователи пишут как им удобно, ну отображение номера в зависимости от страны. Храним все равно как regexp_replace(number,'[^0-9]','','gi'). Вопрос только для чего телефон нужен. Если просто для информации как "данные о себе", когда каждый пишет кто во что горазд это одно. Другое если это контактная/ключевая информация и тогда её проверяют подтверждением/смс. При этом как ты решаешь у себя в базе писать так оно и будет. Проект в РФ и КЗ - можно даже код не вписывать и получаем 10 цифр.
RinNas
28.06.2024 13:46+2PostgreSQL функции для обработки номера телефона
phone_parse.sql -- разбирает номер телефона в международном формате E.164 или в локальном формате на составные части (country_code, area_code, local_number)
phone_normalize.sql -- нормализует номер телефона, возвращает номер телефона в международном формате E.164, например: +79651234567
phone_format.sql -- форматирует номер телефона по образцу
PostgreSQL домен (тип данных)
phone.sql -- phone number in the international format E.164
zerg903
Если убрать +, то можно хранить в виде числа int64, что уменьшит размер и значительно упростит поиск.
gazkom
Можно просто по граблям походить, зачем читать статью.
Akina
Причём тут хождение по граблям? Вам дали определённый стандартом формат, который содержит как значащие символы (в данном случае только цифры), так и шаблонные (как значащие - в данном случае префиксный плюс, так и незначащие - пробелы). Общеустоявшийся стандарт хранения настоятельно предлагает все эти символы при хранении значения удалять, а при извлечении соответственно вставлять. Так что комментарий по делу. Товарищ совершенно прав. Как в том, что плюс следует удалить так и в том, что это оптимизирует хранение и упрощает обработку.
navferty
Для знакового 64-битного целого максимальное число 9,223,372,036,854,775,807 - что даёт максимальную длину "телефонного номера" в 18 цифр (или 19 для беззнакового).
С одной стороны, кажется что "хватит всем" (с). С другой стороны, возьмём например 12-значный телефонный номер (быстрый гуглёж дал инфу про 10-значные номера в некоторых странах, например во Франции, но можно допустить что и 12-значный номер не невозможен).
А теперь представим, что мы храним корпоративный номер, и компания использует дополнительно две трёхзначные группы: три цифры для департамента/отдела, и ещё три - для конкретного сотрудника (это не значит, конечно, что у них миллион сотрудников, но мне такая схема не кажется невозможной).
И вот мы уже упёрлись в потолок - дальше только добавлять столбцы, или использовать какие-то ещё хаки, чтобы сохранить более длинный номер.
Не говоря уже о том, что номер может состоять не только из цифр:
Так что Ваш совет, на мой взгляд, очень вредный. Лучше хранить номера как строки (а в отдельных случаях, как упомянуто в статье, наряду с нормализованным номером в другом столбце хранить его также и в исходном виде).
Wesha
(Задумчиво): Очень оптимистичный молодой человек...
masterthemac
В статье же указано, что номер 7 и 007 это разные номера с точки зрения телефонной связи. В int64 это будет один номер.
zatim
Не вижу проблемы. В статье же указано что предлагается использовать форматирование согласно коду страны. Перед сохранением в базу нули отбросятся, при извлечении - добавятся. Телефонная связь не пострадает.
kspshnik
+7077.... и +7007.... - разные номера, точнее даже разные регионы, ABC-код отличается. А при приведении к bigint ведущие нули отбросятся.
zatim
И что? Посмотрите на свой собственный пример. Как отбросятся так и вернутся потом назад, ведь количество цифр в номере - фиксировано и заранее известно.
Dolios
Кто вам сказал такую глупость?
zatim
Вас на википедии забанили? Вы не умеете пользоваться интернетом? В большинстве стран количество цифр в номере фиксировано и заранее известно. А в тех странах, где это не так - не бывает нуля в местных префиксах. Вообще, если вы не в курсе, за исключением пары стран нигде не используют местные префиксы с нулями (надеюсь, причину этого вы сможете понять самостоятельно). Соответственно, проблема, описанная ТС, высосана из пальца и не имеет места быть в реальности.
Dolios
Пруфы в студию.
randomsimplenumber
Ну то есть из этого правила уже есть исключения. Ну и зачем оно нужно?
Firz
Можете привести пример, не до конца понятно как должно работать.
Есть два телефонных номера, 011 и 11, как они будут храниться в базе?
zatim
011 и 11 - это один и тот же номер. Еще раз повторяю, количество цифр в номере фиксированное и заранее известно.
Firz
беглое гугление подсказывает что нет, не фиксированное, куча стран где телефоны разной длины, к примеру Финляндия, 8–10
https://en.wikipedia.org/wiki/List_of_mobile_telephone_prefixes_by_country
zatim
Беглое гугление подсказывает что в этих странах код не начинается на 0. Вообще, страны где внутренний код может начинаться с 0 можно по пальцам пересчитать. Пальцам одной руки. И там фиксированная, заранее известная длина.
Wesha
О сколько Вам открытий чудных готовит суровая реальность, молодой человек...
zerg903
Любой современный язык программирования имеет функцию форматирования числа в строку по заданному шаблону. Хранить и работать с номером нужно в нормализованном виде, а отображать в том, в каком привык пользователь конкретного региона. Очень плохая практика, когда вариант хранения диктуется UI.
d-stream
Но придется играть с масками/логикой для работы с номерами 0123.
Пока спасает, что ведущий ноль/ноли имеют специальное значение
Ivan22
не надо в int. а то начнете велосипеды изобретать для добавочных кодов или номеров с буквами
izh-vii
Номерам с ведущими нолями int не понравится.
Достаточно много пришлось заниматься этим вопросом, по итогу храню либо в строке фиксированной длины, либо в decimal без дробной части. Страну при необходимости - в отдельном поле.
xSVPx
Ну количество ведущих нулей вы тоже можете сохранять. И скорее всего это всё равно будет компактнее и быстрее.
А просто 1 спереди добавить уже не влезет в инт ?
randomsimplenumber
А зачем? Сколько мегабайт удастся сэкономить на хранении всех номеров всех людей планеты?
xSVPx
А вы все значения всего в виде строк храните ?
Ну типа "чего экономить то, всёж бесконечное"...
Есть толстая разница между преждевременной оптимизацией и "и так сойдет".
randomsimplenumber
Есть ещё ложная дихотомия.
Ну вот и вопрос: чего вы планируете наэкономить? Если где то рядом в этой же таблице будут поля с ФИО, адресом и прочей информацией.
Положите квантор всеобщности на пол и медленно отойдите ;)
Dolios
Номер может быть записан буквами: https://en.wikipedia.org/wiki/Phoneword
zatim
Буквы - лишь мнемоники для лучшего запоминания. В итоге все равно передается цифра. Ни в устаревшем импульсном наборе, ни в dtmf не предусмотрено никаких способов передавать буквы.
Dolios
Вопрос не в том, как передаётся, а в том, как записывается и вводится пользователем.
zatim
Номер должен вводится пользователем в виде согласно международно утвержденному плану нумерации, действующем в конкретной стране. То есть исключительно цифрами. Буквы используются лишь как мнемоническое обозначение номера в рекламных целях и в целях облегчения запоминания.
Dolios
Номер должен вводиться так, как пользователю удобно. Хранение номера в числовом поле - это типичный пример "программизма", когда не очень квалифицированный инженер выдумывает проблемы на ровном месте, доставляя неудобства пользователю и создавая мину замедленного действия.
А откуда они появятся на сайте в рекрамных целях, амли программизд решил, что номер в базе будет лежать числом?
zatim
Хосспаде, ну что за бред вы несете. Сайт в рекламных целях вообще оформляет дизайнер, а не программист. Там номер, скорее всего, вообще будет вставлен bmp-шечкой нужным "красивым" шрифтом.
Dolios
Бред несёте тут только вы. Огромное количество людей записывает номера, в том числе, буквами и я среди них. Как я уже сказал, это от недостатка квалификации и узкого кругозора. Вы, почему-то, решили, что весь мир живёт так, как привыкли жить вы.
zatim
Пруфы в студию. Я уже 40 лет живу, ни разу не видел чтобы кто-то где-то записывал номер буквами. Вы там на выходных, похоже, слишком сильно на алкоголь налегаете.
Dolios
Я так и написал:
И опять вы проецируете свои проблемы на окружающих. Вы бы нарколога посетили, раз эта мысль первая к вам в голову пришла. Это может быть тревожным звонком. А судя по хамству, еще и психолог вас ждёт с нетерпением. Эта злость, скорее всего, тоже осознания своей низкой квалификации и бессилия от невозможности привести нормальные аргументы.
За сим откланяюсь, хамоватых чудаков и так вокруг слишком много, чтобы на них ещё и в интернете время тратить..
Wesha
Вам бы к окулисту записаться, уважаемый...
Хватит уже, или ещё насыпать? А то мне лень.
vlmonk
За хранения номера телефона как int64 нужно бить металической линейкой по пальцам. Сильно.
ImagineTables
Хранить всё надо в том виде, в котором собираешься использовать. Компрессировать при этом в int64, только потому, что большинство номеров туда влезает — та самая premature optimization, об опасности которой много написано.
А одно из базовых применений номера — внезапно, dialing. Dialing чаще всего происходит так: контроллер (компьютер) открывает текстовой канал с коммуникационным устройством (модемом), и отдаёт ему строку вида
ATD +123456789012
. И ждёт ответа. Получается, юзер вводит строку, мы её конвертируем в число чисто для компрессии, храним в int64, а для набора потом конвертируем обратно в строку. Не лучше ли включить компрессию в опциях СУБД? Если место так важно?Любой, кто разок нарвётся на расширения HAYES-команд, или необходимость хранить служебные номера со стандартными примочками
ATD
, сразу разучится баловаться с неподходящими типами.Wesha
ВНЕЗАПНО, в некоторых номерах приходится в стратегических местах добвлять паузы, вроде
+18001234567,,,,5,,,,1234.