電話番号の正規化を Google 製電話番号ライブラリ libphonenumber に置き換えたはなし

https://qiita.com/Daniel_Nakano/items/75b653fc398d78570e41 からの転記

これはなに?

トレタのAPIでは、電話番号の正規化をするのにcountries gemを使用しています。countries gemを利用することで各国の情報(国、通貨、電話番号など)が取得できるようになります。

しかし、一部電話番号周りで正しくデータが入ってない箇所があったので、libphonenumberベースのgemであるphonelibに置き換えるために調査をしたメモです。

libphonenumberとは?

countries gemを使ってるのになぜlibphonenumberが必要だったのか?

countries gemを利用すれば基本的に問題がないのですが、一部の国で正しく情報が取得できないために問題が発生したので、今回電話番号周りのみをlibphonenumberに置き換えることにしました。

問題となっていたのは、台湾の国内プレフィクスです。

ruby2.4.2、countries gemは2.1.2バージョンで実際の取得しようとすると以下のようになります。

[1] pry(main)> require 'countries'
=> true
[2] pry(main)> tw = ISO3166::Country.new('TW')
=> #<ISO3166::Country:0x00007fe3c3bc4770
 @country_data_or_code="TW",
 @data=
  {"continent"=>"Asia",
   "address_format"=>"{{recipient}}\n{{street}}\n{{city}} {{region_short}} {{postalcode}}\n{{country}}",
   "alpha2"=>"TW",
   "alpha3"=>"TWN",
   "country_code"=>"886",
   "international_prefix"=>"002",
   "ioc"=>"TPE",
   "gec"=>"TW",
   "name"=>"Taiwan, Province of China",
   "national_destination_code_lengths"=>[2],
   "national_number_lengths"=>[7, 8],
   "national_prefix"=>"None",
   "number"=>"158",
   "region"=>"Asia",
   "subregion"=>"Eastern Asia",
   "world_region"=>"APAC",
   "un_locode"=>"TW",
   "nationality"=>"Taiwanese",
   "postal_code"=>true,
   "unofficial_names"=>["Taiwan", "Taiwán", "台湾"],
   "languages_official"=>["zh"],
   "languages_spoken"=>["zh"],
   "geo"=>
    {"latitude"=>23.69781,
     "latitude_dec"=>"23.685789108276367",
     "longitude"=>120.960515,
     "longitude_dec"=>"120.89749145507812",
     "max_latitude"=>26.4545,
     "max_longitude"=>123.5021012,
     "min_latitude"=>20.5170001,
     "min_longitude"=>116.6665,
     "bounds"=>{"northeast"=>{"lat"=>26.4545, "lng"=>123.5021012}, "southwest"=>{"lat"=>20.5170001, "lng"=>116.6665}}},
   "currency_code"=>"TWD",
   "start_of_week"=>"monday",
   "translations"=>{"en"=>"Taiwan"},
   "translated_names"=>["Taiwan"]}>
[3] pry(main)> tw.national_prefix
=> "None"

本来、台湾の国内プレフィックは0なのですが、"None"が返ってきてしまい正しく電話番号の正規化が出来ませんでした。

暫定的に国が台湾の場合に0を差し込むことも考えたのですが、今後別の国でも同じような問題が発生する可能性もありました。そこで、電話番号の部分を別のライブラリに置き換えることが出来ないかと調査したところ、googleで作成と管理をしている電話番号のライブラリであるlibphonenumberがあり使おうということに決めました。

じゃあphonelibとは?

libphonenumberベースのgemはいくつか存在しているのですが、その中で最もアクティブに開発がされているphonelibといgemを採用しました。

ちなみに、他にもglobal_phonetelephone_numberなどがありました。

phonelibの使い方

今回紹介するのは、実際にトレタで使用してる部分のみです。 他にもいろんなことが出来るので、興味が有る方はphonelibのGithubのリポジトリを御覧ください。

インストール

bundlerを利用する場合はGemfileに以下を追加します。

gem 'phonelib'

直接インストールする場合は、

$ gem install phonelib

電話番号のフォーマット

まず、Phonelibクラスのインスタンスを作ります。

[1] pry(main)> phone = Phonelib.parse('09011112222', :jp)
=> #<Phonelib::Phone:0x0055b475198ed8
 @data=
  {"JP"=>
    {:id=>"JP",
     :country_code=>"81",
     :international_prefix=>"010",
     :national_prefix=>"0",
     :national_prefix_formatting_rule=>"$NP$FG",
     :mobile_number_portable_region=>"true",
     :national=>"9011112222",
     :format=>{:pattern=>"(\\d{2})(\\d{4})(\\d{4})", :leading_digits=>"[2579]0|80[1-9]", :format=>"$1-$2-$3"},
     :valid=>[:mobile],
     :possible=>[:toll_free, :voip, :pager, :mobile]}},
 @extension="",
 @national_number="9011112222",
 @original="09011112222",
 @original_s="09011112222",
 @sanitized="09011112222">

電話番号が指定の国に対して有効かどうかの判断

[2] pry(main)> phone.valid?
=> true

国内表記

  • ハイフンとかが入った表記
[3] pry(main)> phone.national
=> "090-1111-2222"
  • 電話番号のみの表記
[4] pry(main)> phone.national(false)
=> "09011112222"

国際表記

  • ハイフンとかスペースが入った表記
[5] pry(main)> phone.international
=> "+81 90-1111-2222"
  • 電話番号のみの表記
[6] pry(main)> phone.international(false)
=> "819011112222"

国を指定しない場合

国を指定しない場合でも、電話番号が国際表記であり、かつ正しければ国を判別することが可能です。

[7] pry(main)> phone = Phonelib.parse('81911112222')
=> #<Phonelib::Phone:0x0055b474e961e0
 @data=
  {"JP"=>
    {:id=>"JP",
     :country_code=>"81",
     :international_prefix=>"010",
     :national_prefix=>"0",
     :national_prefix_formatting_rule=>"0$1",
     :mobile_number_portable_region=>"true",
     :national=>"911112222",
     :format=>{:pattern=>"(\\d{2})(\\d{3})(\\d{4})", :leading_digits=>"[2479][1-9]", :format=>"$1-$2-$3"},
     :valid=>[],
     :possible=>[:premium_rate, :toll_free, :personal_number, :uan, :fixed_line]}},
 @extension="",
 @national_number="911112222",
 @original="810911112222",
 @original_s="810911112222",
 @sanitized="810911112222">

指定の国の情報を取得する

Phonelib.phone_data[国コード](国コードはISO 3166-1のalpha-2形式)で指定の国の情報を取得することがでいます。

[10] pry(main)> Phonelib.phone_data['JP']
=> {:id=>"JP",
 :country_code=>"81",
 :international_prefix=>"010",
 :national_prefix=>"0",
 :national_prefix_formatting_rule=>"0$1",
 :mobile_number_portable_region=>"true",
...(省略)
}

国内プレフィクスが必要なときは、

[11] pry(main)> Phonelib.phone_data['JP'][:national_prefix]
=> "0"

指定の国の電話番号のフォーマットをしりたいときは、

[3] pry(main)> Phonelib.phone_data['JP'][:formats]
=> [{:pattern=>"(\\d{3})(\\d{3})(\\d{3})", :leading_digits=>"(?:12|57|99)0", :format=>"$1-$2-$3"},
 {:pattern=>"(\\d{3})(\\d{3})(\\d{4})", :leading_digits=>"800", :format=>"$1-$2-$3"},
 {:pattern=>"(\\d{4})(\\d{4})",
  :national_prefix_formatting_rule=>"$FG",
  :leading_digits=>"0077",
  :format=>"$1-$2",
  :intl_format=>"NA"},
 {:pattern=>"(\\d{4})(\\d{2})(\\d{3,4})",
  :national_prefix_formatting_rule=>"$FG",
  :leading_digits=>"0077",
  :format=>"$1-$2-$3",
  :intl_format=>"NA"},
...(省略)
}

ちなみに、目的である台湾の国内プレフィクスが取得できるかについては、

[4] pry(main)> Phonelib.phone_data['TW'][:national_prefix]
=> "0"

のように正しく取得できます。

所感

基本的な情報を取得する部分でライブラリを使用しているときに、新たな国や地域でサービスを展開する場合は必ず事前調査が必ず必要であることを学びました。 また、libphonenumberのライブラリを知れたのも非常に有益でした。

参考