E.164, the format to store phone numbers in
E.164 is the ITU's recommendation for international telephone numbers: a country code of one to three digits, then the national significant number, fifteen digits at most. Written for machines it is a plus and the digits, nothing else: +447400123456. Store that form, and every other one follows from it.
From what people type to E.164
| Plan | Typed, national form | E.164 |
|---|---|---|
| United Kingdom | 07400 123456 | +447400123456 |
| United States | (201) 555-0123 | +12015550123 |
| France | 06 12 34 56 78 | +33612345678 |
| Italy | 02 1234 5678 | +390212345678 |
The national form carries a trunk prefix that E.164 drops: the 0 of 07400 123456, the 0 of the French number. Strip the punctuation and put +44 in front, and 07400 123456 becomes +4407400123456, which is not the number's E.164 form. Dropping every leading 0 instead breaks Italy: its plan has no trunk prefix, and the 0 of 02 1234 5678 stays in +390212345678. 101 of the 245 plans have no trunk prefix. Only the plan says which digits go, which is what the guides' parse reads from it.
How long a number can be
- The 245 numbering plans share 206 country codes. 27 plans have a one-digit code (+1 is shared by 25 plans, +7 by 2), 51 a two-digit code and 167 a three-digit code.
- With the country code, the longest mobile numbers have 15 digits (Austria) and the shortest 7 (Niue, South Africa and Tokelau).
- E.164 allows fifteen digits. The plans also allow longer numbers in Germany (fixed line, toll-free, shared cost, pager and UAN), Indonesia (toll-free), Japan (toll-free), Nigeria (toll-free and UAN), South Korea (toll-free) and Uruguay (toll-free). A check written to the recommendation refuses them; decide whether you take them.
Why store E.164
- One string per number: 07400 123456, +44 7400 123456 and 0044 7400 123456 are the same number, and only the E.164 form makes them equal strings, for comparing, deduplicating and joining.
- Every other form follows from it: the national and international forms the country pages show, and the
tel:link (tel:+447400123456, RFC 3966). - Keep it as text, sixteen characters at most: a number is an identifier, not a quantity, and the plus is part of it. An extension is not part of E.164; keep it in its own field.
- The printed form is a separate question: Recommendation E.123 writes the international form with spaces, +44 7400 123456, and each plan has its national form. Format for people when you display, not when you store.
The shape check, and what it does not check
A plus, then two to fifteen digits, the first not 0: ^\+[1-9][0-9]{1,14}$. Use it where a value must already be E.164, in a column constraint or at an API that accepts nothing else. It says nothing about the number: a string can have the shape and be no number in any plan. Input from people goes through parse, which knows the plans; the table shows the two side by side, with the United Kingdom, the United States, France and Italy loaded.
| Value | Shape check | parse returns |
|---|---|---|
| +447400123456 | passes | +447400123456 |
| +4407400123456 | passes | +447400123456 |
| 447400123456 | fails | nothing: no plan accepts it |
| +0447400123456 | fails | nothing: no plan accepts it |
| +44 7400 123456 | fails | +447400123456 |
| +1234567890123456 | fails | nothing: no plan accepts it |
+4407400123456 passes the shape though it is not that number's E.164 form: the trunk prefix is still in it. parse reads it the way people write +44 (0)7400 123456 and returns the E.164 form.
Add it at the end of phone.mjs, the file from the JavaScript guide:
// E.164 as written for machines: a plus, then 2 to 15 digits, the first not 0.
const E164 = /^\+[1-9][0-9]{1,14}$/
export const isE164 = (s) => E164.test(s)Add it at the end of phone.py, the file from the Python guide:
_E164 = re.compile(r"\+[1-9][0-9]{1,14}")
def is_e164(s: str) -> bool:
"""E.164 as written for machines: a plus, then 2 to 15 digits, the first not 0."""
return _E164.fullmatch(s) is not NoneAdd it inside the class in Phone.java, the file from the Java guide:
private static final Pattern E164 = Pattern.compile("\\+[1-9][0-9]{1,14}");
/** E.164 as written for machines: a plus, then 2 to 15 digits, the first not 0. */
public static boolean isE164(String s) {
return E164.matcher(s).matches();
}Add it at the end of phone.go, the file from the Go guide:
var e164 = regexp.MustCompile(`^\+[1-9][0-9]{1,14}$`)
// IsE164 reports whether s is E.164 as written for machines: a plus, then 2 to 15 digits, the first not 0.
func IsE164(s string) bool { return e164.MatchString(s) }Add it inside the class in Phone.php, the file from the PHP guide:
/** E.164 as written for machines: a plus, then 2 to 15 digits, the first not 0. */
public static function isE164(string $s): bool
{
return preg_match('/\A\+[1-9][0-9]{1,14}\z/', $s) === 1;
}Add it inside the module in phone.rb, the file from the Ruby guide:
E164 = /\A\+[1-9][0-9]{1,14}\z/
# E.164 as written for machines: a plus, then 2 to 15 digits, the first not 0.
def self.e164?(text)
E164.match?(text)
endAdd it inside the class in Phone.cs, the file from the C# guide:
static readonly Regex E164 = new(@"\A\+[1-9][0-9]{1,14}\z");
/// <summary>E.164 as written for machines: a plus, then 2 to 15 digits, the first not 0.</summary>
public static bool IsE164(string s) => E164.IsMatch(s);Add it in src/phone.rs, before the tests, the file from the Rust guide:
static E164: LazyLock<Regex> = LazyLock::new(|| Regex::new(r"^\+[1-9][0-9]{1,14}$").expect("a valid pattern"));
/// E.164 as written for machines: a plus, then 2 to 15 digits, the first not 0.
pub fn is_e164(s: &str) -> bool {
E164.is_match(s)
}Sources: Recommendation E.164: The international public telecommunication numbering plan (ITU-T); Recommendation E.123: Notation for national and international telephone numbers (ITU-T); National numbering plans, ITU-T E.164 (ITU).