- 技術 💻
電話番号の正規表現とチェック|携帯・固定・060・フリーダイヤル
日本の電話番号のチェックは、1本の正規表現で書こうとせず、「①入力の揺れをそろえて数字だけにする → ②番号の種類ごとの桁数で判定する」の2段に分けると、短く・正確に書けます。
090-1234-5678 / 090-1234-5678 / +81 90-1234-5678
↓ ① 全角→半角、ハイフン・空白・括弧を除く、+81 → 0
09012345678
↓ ② 種類ごとの正規表現で判定
携帯電話(11桁)
この記事では、番号の種類と桁数の早見表と、Python・JavaScript・Java・PHP・SQL の完成コードを載せました。どのコードも、同じ17件のテストデータで同じ結果になることを確かめています。
確認環境:Python 3.13、Node.js 22、OpenJDK 21、PHP 8.4、PostgreSQL 16、MySQL 8.0、phonenumbers 9.0.41(Python)、libphonenumber-js 1.13.15(確認日 2026年10月10日)。番号の割り当ては総務省のページで確認しました。
番号の種類と桁数の早見表
| 種類 | 先頭 | 桁数 | ハイフンの区切り |
|---|---|---|---|
| 携帯電話 | 070・080・090、060(追加予定) | 11桁 | 3-4-4(090-1234-5678) |
| IP電話 | 050 | 11桁 | 3-4-4(050-1234-5678) |
| 固定電話 | 0+市外局番(1〜4桁) | 10桁 | 市外局番によって変わる(03-1234-5678、0466-12-3456) |
| フリーダイヤル | 0120 | 10桁 | 4-3-3(0120-123-456) |
| フリーダイヤル | 0800 | 11桁 | 4-3-4(0800-123-4567) |
| ナビダイヤル | 0570 | 10桁 | 4-3-3(0570-123-456) |
桁数は、電話番号ライブラリ phonenumbers の判定(is_valid_number)でも確かめました。表の桁数から1桁増やした・減らした番号(0120 の11桁、0800 の10桁、050 の10桁、03 の9桁・11桁など)は、すべて正しくない番号と判定されました。
060 の携帯電話番号に注意します。 総務省は、060-1〜9 で始まる11桁を携帯電話番号として追加しました。当初は2026年7月以降の予定でしたが、2026年7月8日に事業者が提供開始の延期を公表し、総務省は2026年度末までの開始を促しています。0[789]0 で携帯電話を判定しているシステムは、060 を受け付けられません。
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「電話番号の正規表現とチェック|携帯・固定・060・フリーダイヤル」
よくある正規表現の落とし穴
| 書き方 | 問題 |
|---|---|
^0[789]0-?\d{4}-?\d{4}$ |
060 を受け付けない。全角・長音「ー」・空白にも対応していない |
^0\d{9,10}$ |
0800 で始まる番号も 090 で始まる番号も同じ扱いで、種類が分からない。0001234567 のような存在しない番号も通る |
^0[6789]0\d{8}$ で携帯を判定 |
0800(フリーダイヤル)を携帯電話と判定してしまう(08001234567 が一致する) |
^\d{2,4}-\d{2,4}-\d{4}$ |
ハイフン無しの入力を受け付けない。12-34-5678 のような番号も通る |
| 1本の正規表現で全部を判定 | 長くなって読めず、060 の追加のような変更のたびに壊れやすい |
3つ目は、この記事のコードを書く途中で実際に起きました。フリーダイヤルを先に判定し、携帯電話は4桁目を [1-9] にすることで防いでいます。
完成コード:Python
import re
import unicodedata
# ハイフン・長音・ダッシュとして入力されやすい文字(NFKC のあとに残るもの)
_SEP = re.compile(r"[\s\-‐‑‒–—―−ーー().]")
_RULES = [ # 判定の順番に意味がある(0800・0120 を先に判定する)
("フリーダイヤル", re.compile(r"0120\d{6}|0800\d{7}")),
("携帯電話", re.compile(r"0[6789]0[1-9]\d{7}")),
("IP電話", re.compile(r"050[1-9]\d{7}")),
("ナビダイヤル", re.compile(r"0570\d{6}")),
("固定電話", re.compile(r"0[1-9][1-9]\d{7}")),
]
def normalize(text: str) -> str:
"""全角・ハイフン・括弧・空白・+81 をそろえて、数字だけにする"""
s = unicodedata.normalize("NFKC", text).strip()
if s.startswith("+81"):
s = "0" + re.sub(r"^\(?0\)?", "", s[3:].lstrip(" -"))
return _SEP.sub("", s)
def classify(text: str):
"""(種類, 数字だけの番号) を返す。電話番号でなければ (None, 数字だけの番号)"""
digits = normalize(text)
if digits.isascii() and digits.isdigit():
for kind, rule in _RULES:
if rule.fullmatch(digits):
return kind, digits
return None, digits
def format_hyphen(digits: str) -> str:
"""市外局番の長さが決まらない固定電話以外を、ハイフン区切りにする"""
if re.fullmatch(r"0800\d{7}", digits): # 0800-123-4567
return f"{digits[:4]}-{digits[4:7]}-{digits[7:]}"
if re.fullmatch(r"0(?:120|570)\d{6}", digits): # 0120-123-456
return f"{digits[:4]}-{digits[4:7]}-{digits[7:]}"
if re.fullmatch(r"0[5-9]0\d{8}", digits): # 090-1234-5678
return f"{digits[:3]}-{digits[3:7]}-{digits[7:]}"
return digits # 固定電話はそのまま
テスト結果(17件)
| 入力 | 数字だけにした結果 | 判定 | ハイフン区切り |
|---|---|---|---|
090-1234-5678 |
09012345678 | 携帯電話 | 090-1234-5678 |
090-1234-5678(全角) |
09012345678 | 携帯電話 | 090-1234-5678 |
090ー1234ー5678(長音「ー」) |
09012345678 | 携帯電話 | 090-1234-5678 |
(03)1234-5678 |
0312345678 | 固定電話 | (そのまま) |
03 1234 5678 |
0312345678 | 固定電話 | (そのまま) |
+81 90-1234-5678 |
09012345678 | 携帯電話 | 090-1234-5678 |
+81(0)3-1234-5678 |
0312345678 | 固定電話 | (そのまま) |
0120-123-456 |
0120123456 | フリーダイヤル | 0120-123-456 |
0800-123-4567 |
08001234567 | フリーダイヤル | 0800-123-4567 |
0570-123-456 |
0570123456 | ナビダイヤル | 0570-123-456 |
050-1234-5678 |
05012345678 | IP電話 | 050-1234-5678 |
060-1234-5678 |
06012345678 | 携帯電話 | 060-1234-5678 |
060-0234-5678 |
06002345678 | × | |
090-1234-567(1桁足りない) |
0901234567 | × | |
03-1234-56789(1桁多い) |
03123456789 | × | |
1234-5678(市外局番なし) |
12345678 | × | |
090-1234-5678 内線12 |
09012345678内線12 | × |
全角数字は NFKC で半角にそろえます。 日本語入力で「ー」(長音)や「-」(全角ハイフン)が入ることが多いため、区切りとして取り除く文字に入れています。内線番号は別の入力欄に分けるのが確実です。
完成コード:JavaScript(ブラウザ・Node.js)
// ハイフン・長音・ダッシュとして入力されやすい文字(NFKC のあとに残るもの)
const SEP = /[\s\-‐‑‒–—―−ーー().]/g;
const RULES = [ // 判定の順番に意味がある(0800・0120 を先に判定する)
['フリーダイヤル', /^(?:0120\d{6}|0800\d{7})$/],
['携帯電話', /^0[6789]0[1-9]\d{7}$/],
['IP電話', /^050[1-9]\d{7}$/],
['ナビダイヤル', /^0570\d{6}$/],
['固定電話', /^0[1-9][1-9]\d{7}$/],
];
export function normalizePhone(text) {
let s = text.normalize('NFKC').trim();
if (s.startsWith('+81')) s = '0' + s.slice(3).replace(/^[ -]+/, '').replace(/^\(?0\)?/, '');
return s.replace(SEP, '');
}
export function classifyPhone(text) {
const digits = normalizePhone(text);
if (/^\d+$/.test(digits)) {
for (const [kind, rule] of RULES) if (rule.test(digits)) return { kind, digits };
}
return { kind: null, digits };
}
export function formatHyphen(d) {
if (/^0800\d{7}$/.test(d)) return `${d.slice(0, 4)}-${d.slice(4, 7)}-${d.slice(7)}`;
if (/^0(?:120|570)\d{6}$/.test(d)) return `${d.slice(0, 4)}-${d.slice(4, 7)}-${d.slice(7)}`;
if (/^0[5-9]0\d{8}$/.test(d)) return `${d.slice(0, 3)}-${d.slice(3, 7)}-${d.slice(7)}`;
return d; // 固定電話はそのまま
}
入力欄は <input type="tel" inputmode="tel" autocomplete="tel"> にすると、スマートフォンで数字のキーボードが出て、ブラウザの自動入力も使えます。HTML の pattern 属性だけでは全角やハイフンの揺れに対応できないため、blur(入力欄から離れたとき)で classifyPhone() を呼び、数字だけにした値を入れ直します。
完成コード:Java
import java.text.Normalizer;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.regex.Pattern;
public final class Phone {
// ハイフン・長音・ダッシュとして入力されやすい文字(NFKC のあとに残るもの)
private static final Pattern SEP = Pattern.compile("[\\s\\-‐‑‒–—―−ーー().]");
private static final Map<String, Pattern> RULES = new LinkedHashMap<>(); // 判定の順番に意味がある
static {
RULES.put("フリーダイヤル", Pattern.compile("0120\\d{6}|0800\\d{7}"));
RULES.put("携帯電話", Pattern.compile("0[6789]0[1-9]\\d{7}"));
RULES.put("IP電話", Pattern.compile("050[1-9]\\d{7}"));
RULES.put("ナビダイヤル", Pattern.compile("0570\\d{6}"));
RULES.put("固定電話", Pattern.compile("0[1-9][1-9]\\d{7}"));
}
public record Result(String kind, String digits) {}
public static String normalize(String text) {
String s = Normalizer.normalize(text, Normalizer.Form.NFKC).strip();
if (s.startsWith("+81")) {
s = "0" + s.substring(3).replaceFirst("^[ -]+", "").replaceFirst("^\\(?0\\)?", "");
}
return SEP.matcher(s).replaceAll("");
}
public static Result classify(String text) {
String digits = normalize(text);
if (digits.matches("[0-9]+")) {
for (var e : RULES.entrySet()) {
if (e.getValue().matcher(digits).matches()) return new Result(e.getKey(), digits);
}
}
return new Result(null, digits);
}
public static String formatHyphen(String d) {
if (d.matches("0800\\d{7}") || d.matches("0(?:120|570)\\d{6}")) {
return d.substring(0, 4) + "-" + d.substring(4, 7) + "-" + d.substring(7);
}
if (d.matches("0[5-9]0\\d{8}")) {
return d.substring(0, 3) + "-" + d.substring(3, 7) + "-" + d.substring(7);
}
return d; // 固定電話はそのまま
}
}
Java の \d は既定で半角の 0〜9 だけに一致します。NFKC で全角を半角にしてから判定しているので、全角数字の入力も受け付けられます。
完成コード:PHP
<?php
const PHONE_RULES = [ // 判定の順番に意味がある(0800・0120 を先に判定する)
'フリーダイヤル' => '/\A(?:0120\d{6}|0800\d{7})\z/',
'携帯電話' => '/\A0[6789]0[1-9]\d{7}\z/',
'IP電話' => '/\A050[1-9]\d{7}\z/',
'ナビダイヤル' => '/\A0570\d{6}\z/',
'固定電話' => '/\A0[1-9][1-9]\d{7}\z/',
];
function normalize_phone(string $text): string
{
$s = trim(Normalizer::normalize($text, Normalizer::FORM_KC));
if (str_starts_with($s, '+81')) {
$s = '0' . preg_replace('/\A\(?0\)?/u', '', ltrim(substr($s, 3), ' -'));
}
return preg_replace('/[\s\-‐‑‒–—―−ーー().]/u', '', $s);
}
/** @return array{0: ?string, 1: string} [種類, 数字だけの番号] */
function classify_phone(string $text): array
{
$digits = normalize_phone($text);
if (preg_match('/\A[0-9]+\z/', $digits)) {
foreach (PHONE_RULES as $kind => $rule) {
if (preg_match($rule, $digits)) return [$kind, $digits];
}
}
return [null, $digits];
}
function format_hyphen(string $d): string
{
if (preg_match('/\A(?:0800\d{7}|0(?:120|570)\d{6})\z/', $d)) {
return substr($d, 0, 4) . '-' . substr($d, 4, 3) . '-' . substr($d, 7);
}
if (preg_match('/\A0[5-9]0\d{8}\z/', $d)) {
return substr($d, 0, 3) . '-' . substr($d, 3, 4) . '-' . substr($d, 7);
}
return $d; // 固定電話はそのまま
}
Normalizer は PHP の intl 拡張に入っています。PHP の正規表現の $ は末尾の改行の前にも一致するため、\A〜\z で全体を囲んでいます。
完成コード:SQL(登録済みのデータを点検する)
すでにDBに入っている電話番号を点検するときの SQL です。
PostgreSQL(13以降)
SELECT id, tel, d AS digits,
CASE
WHEN d ~ '^(0120\d{6}|0800\d{7})$' THEN 'フリーダイヤル'
WHEN d ~ '^0[6789]0[1-9]\d{7}$' THEN '携帯電話'
WHEN d ~ '^050[1-9]\d{7}$' THEN 'IP電話'
WHEN d ~ '^0570\d{6}$' THEN 'ナビダイヤル'
WHEN d ~ '^0[1-9][1-9]\d{7}$' THEN '固定電話'
ELSE '×'
END AS kind
FROM (
SELECT id, tel,
regexp_replace(
regexp_replace(normalize(tel, NFKC), '^\+81[ -]*\(?0?\)?', '0'),
'[\s\-‐‑‒–—―−ーー().]', '', 'g') AS d
FROM customer
) t
ORDER BY id;
PostgreSQL は normalize(tel, NFKC) で全角を半角にできるため、17件ともPythonと同じ結果になりました。
MySQL(8.0以降)
SELECT id, tel, d AS digits,
CASE
WHEN d REGEXP '^(0120[0-9]{6}|0800[0-9]{7})$' THEN 'フリーダイヤル'
WHEN d REGEXP '^0[6789]0[1-9][0-9]{7}$' THEN '携帯電話'
WHEN d REGEXP '^050[1-9][0-9]{7}$' THEN 'IP電話'
WHEN d REGEXP '^0570[0-9]{6}$' THEN 'ナビダイヤル'
WHEN d REGEXP '^0[1-9][1-9][0-9]{7}$' THEN '固定電話'
ELSE '×'
END AS kind
FROM (
SELECT id, tel,
REGEXP_REPLACE(
REGEXP_REPLACE(tel, '^\\+81[ -]*\\(?0?\\)?', '0'),
'[[:space:]().\\-‐‑‒–—―−ー-ー]', '') AS d
FROM customer
) t
ORDER BY id;
MySQL には NFKC の関数が無いため、全角数字の行(090-1234-5678)は 09012345678 のまま残り、× になりました。それ以外の16件は PostgreSQL と同じ結果です。全角の行はアプリ側で正規化して更新します。
固定電話のハイフンは、市外局番の表が要る
固定電話は10桁ですが、市外局番が2〜5桁(0を含む)と地域ごとに違うため、数字だけからハイフンの位置を決められません。
| 番号 | 正しい区切り | 地域 |
|---|---|---|
| 0312345678 | 03-1234-5678 | 東京23区など |
| 0466123456 | 0466-12-3456 | 神奈川県藤沢市など |
| 0991234567 | 09912-3-4567 | 鹿児島県の一部(市外局番が5桁) |
3つ目は、Google の電話番号ライブラリ libphonenumber の Python 版(phonenumbers)で区切った結果です。099-123-4567 と入力しても、市外局番の表に従って 09912-3-4567 に直されました。
import phonenumbers
n = phonenumbers.parse("0466123456", "JP")
print(phonenumbers.is_valid_number(n), phonenumbers.number_type(n)) # True 0(0 は FIXED_LINE)
print(phonenumbers.format_number(n, phonenumbers.PhoneNumberFormat.NATIONAL)) # 0466-12-3456
print(phonenumbers.format_number(n, phonenumbers.PhoneNumberFormat.E164)) # +81466123456
確認環境の phonenumbers 9.0.41・libphonenumber-js 1.13.15 は、060 の番号も携帯電話(MOBILE)と判定しました。固定電話のハイフン区切りや、市外局番まで含めた厳密なチェックが必要なら、ライブラリを使うのが確実です。
| 言語 | ライブラリ |
|---|---|
| Java | libphonenumber(com.googlecode.libphonenumber:libphonenumber) |
| JavaScript | libphonenumber-js |
| Python | phonenumbers |
| PHP | giggsey/libphonenumber-for-php |
保存のしかた
| 用途 | 保存・送信する形 | 例 |
|---|---|---|
| DBへの保存 | 数字だけ(ハイフンなし) | 09012345678 |
| 画面・帳票の表示 | 表示のときにハイフン区切りにする | 090-1234-5678 |
| SMS送信・海外のAPI | E.164(+81 から始め、先頭の0を除く) | +819012345678 |
数字だけで保存しておくと、検索・重複チェック・表示形式の変更がすべて簡単になります。列の型は、先頭の0が消えないように文字列(VARCHAR)にします。
よくある質問
Q. 060 の番号は、いつから使われますか。 A. 総務省のページでは、関連する事業者の対応が終わった後に順次使えるようになるとされています。当初の予定(2026年7月以降)から延期され、総務省は2026年度末までに開始されるよう事業者に促しています。今のうちに入力チェックを 060 に対応させておくと、開始時に慌てずに済みます。
Q. 入力時にハイフンを必須にしたほうがよいですか。 A. 必須にしないほうが入力しやすくなります。ハイフンあり・なし・全角のどれでも受け付けて、保存のときに数字だけにそろえ、表示のときにハイフンを付けるのが利用者にとって楽です。
Q. 海外の電話番号も受け付けたいです。 A. 国ごとに桁数や形式が違うため、正規表現で書くのは現実的ではありません。国番号の選択欄を付けて、libphonenumber で判定します。
Q. 番号が存在するかまでチェックできますか。 A. 正規表現やライブラリで分かるのは「形式として正しいか」までです。実際に使われている番号かどうかは、SMS認証などで確かめます。
まとめ
- 電話番号のチェックは「数字だけにそろえる → 種類ごとに判定」の2段に分ける
- 全角は NFKC、ハイフンは長音「ー」・全角「-」も取り除く。+81 は 0 に置き換える
- 携帯電話は 060 も11桁で追加される。
0[789]0だけの判定は直しておく - 0800 を携帯と判定しないよう、フリーダイヤルを先に判定し、携帯は4桁目を
[1-9]にする - 固定電話のハイフン位置は市外局番で変わる。区切りが必要なら libphonenumber を使う
- 保存は数字だけの文字列、表示でハイフン、SMS・海外APIは E.164
参考・出典
確認日はいずれも 2026年10月10日です。
- 総務省「電気通信番号指定状況」:https://www.soumu.go.jp/main_sosiki/joho_tsusin/top/tel_number/number_shitei.html
- 総務省「060携帯電話番号について」:https://www.soumu.go.jp/main_sosiki/joho_tsusin/top/tel_number/060keitai.html
本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。
(記載例)出典:株式会社RJC「電話番号の正規表現とチェック|携帯・固定・060・フリーダイヤル」
株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。
WE ARE HIRING
RJCで一緒に開発しながら、
成長を楽しみませんか?
RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!
- 127日年間休日
- 12時間平均残業時間
- 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!
ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。