Sign in

    dialnumber

    Phone number parsing, validation and formatting for MoonBit: 245 numbering plans behind E.164 and RFC 3966, a pattern engine that interprets each plan's published rules, valid versus possible verdicts, number types, the full NANP, grouping by each plan's own formatting rules, and a generated table with a reproducible data pipeline

    phone
    e164
    telephone
    msisdn
    dialing
    rfc3966
    itu-t
    formatting
    validation
    Download zip
    Author
    Version
    0.7.2
    License
    Apache-2.0
    Last updated
    6 hours ago
    Downloads
    19

    #dialnumber

    Phone number parsing, validation and formatting for MoonBit, driven by the national numbering plans behind ITU-T E.164.

    A phone number written by a person is a mess of spaces, dashes, parentheses, trunk prefixes and country codes, and the same number can be written a dozen ways. dialnumber turns any of them into one value, decides what the plan says about it, and renders it back in whichever of the standard shapes you need.

    The table covers 245 numbering plans, and each plan carries the real rules rather than only its lengths: a pattern engine interprets those rules, and a data pipeline regenerates the whole table from Google's libphonenumber metadata with a recorded checksum. Every public function is total — bad input comes back as None or false, never as an exception.

    #What it does

    • Parse an E.164 number (+81 90-1234-5678), a number dialled internationally (011 44 20 7946 0958 from the NANP), or a number dialled nationally with a region hint (090-1234-5678 in Japan) into a PhoneNumber.
    • Check possibility — the cheap test: a known calling code and a length the plan uses.
    • Check validity — the rule test: the national number must match one of the plan's published patterns. This is libphonenumber's "valid" verdict, and it is a real decision procedure rather than a length check.
    • Name the number type the rules report: fixed-line, mobile, the shared fixed-line-or-mobile, toll-free, premium-rate, shared-cost, voip, uan, personal, pager, voicemail — with libphonenumber's precedence between them.
    • Resolve the region of a number, including every calling code several territories share. All 25 territories of the +1 space resolve from their own area-code allocation, so +1 204… is Canada, +1 787… is Puerto Rico and +1 868… is Trinidad and Tobago.
    • Format to E.164, international, national, or an RFC 3966 tel: URI — and the two human renderings carry the plan's own grouping: (201) 555-0123, 020 7946 0958, 090-1234-5678, +55 11 98765-4321.
    • Look up regions by code or by calling code, with the plan's international and national prefixes, the lengths it uses and the raw rules behind a verdict.
    • Read and write RFC 3966 tel: URIs with the full grammar: global and local numbers, numeric or domain ;phone-context=, ;ext= and ;isub=. Parsing keeps each part separate, and the parser and serialiser are inverses.

    #Two levels of validation

    The distinction is the same one libphonenumber draws, and it is worth being explicit about because it is the difference between "could exist" and "is assigned".

    LevelQuestionAnswer comes from
    is_possibleknown calling code, length the plan uses?published lengths (and the NANP's shape rules for +1)
    is_validdoes the number match a published rule?the plan's per-type patterns, interpreted by the engine
    number_typewhich rule matched?the same walk, in libphonenumber's precedence order

    For +1 the structural rule runs first: ten digits, area code and exchange starting 2–9, no N11 area code. A number can therefore be possible and not valid — +1 555-555-0123 has the right shape, but 555 is not an assigned area code, so no territory's allocation claims it.

    ///|
    let pn = @dialnumber.parse("+1 204-254-5000", "")

    ///|
    match pn {
    Some(n) => {
    @dialnumber.region_of_number(n) // Some("CA")
    @dialnumber.is_possible(n) // true
    @dialnumber.is_valid(n) // true
    @dialnumber.number_type(n) // Some(FixedLineOrMobile)
    }
    None => ()
    }

    #The plans' own grouping

    A number is not written the same way in two countries, and the library does not invent a grouping: plan_data.mbt carries each plan's formatting rules — a pattern of digit groups, the text between them, the leading digits that select the rule, and the plan's national-prefix rule — and format_national / format_international render through the first rule that fits. When a plan publishes no rule for a shape, the number falls back to its ungrouped form.

    ///|
    let us = @dialnumber.PhoneNumber::new(1, "2015550123")
    @dialnumber.format_national(us, "US") // Some("(201) 555-0123")
    @dialnumber.format_international(us) // "+1 201-555-0123"

    ///|
    let gb = @dialnumber.PhoneNumber::new(44, "2079460958")
    @dialnumber.format_national(gb, "GB") // Some("020 7946 0958")
    @dialnumber.format_international(gb) // "+44 20 7946 0958"

    Two rules make the output reproducible rather than plausible. A national rendering must read back as the same number in the same region — the generated tests assert exactly that for every fixed-line and mobile example — and the grouping comes from data, so a plan that changes its style changes with a data refresh.

    National dialling is not always the inverse of formatting: Argentina writes a mobile as 011 15-… while its international form carries the 9, and a few plans rewrite the number while parsing. That rewriting (nationalPrefixForParsing with a transform rule) is not implemented, and the generated round-trip tests skip the plans that publish one instead of pretending otherwise.

    #The rule engine

    pattern.mbt is a small regular-expression engine built for exactly the grammar the numbering plans use: literals, \d, character classes with ranges, non-capturing groups, alternation, and the ?, *, +, {n}, {n,} and {n,m} quantifiers. A recursive-descent parser builds a syntax tree, the tree is compiled to a Thompson NFA, and matching simulates the NFA over a set of live states.

    Three properties follow from that construction, and all three are tested:

    • Linear in pattern size times input length. There is no backtracking, so no pattern can make a validation call explode — the failure mode that makes regex-based validators unsafe on hostile input simply does not exist here.
    • Total. A pattern that does not parse matches nothing, so a bad data row can never become a false positive. pattern_well_formed exposes the same check.
    • Data, not code. The rules are strings in the generated table; a plan that changes its ranges is a data refresh, not a source change.

    ///|
    @dialnumber.matches_pattern("[2-9]\\d{9}", "2042545000") // true
    @dialnumber.matches_pattern("[2-9]\\d{9}", "204254500") // false: full match only
    @dialnumber.pattern_well_formed("[2-9]\\d{9}") // true
    @dialnumber.plan_pattern("DK") // Some("[2-9]\\d{7}")

    #Where the data comes from, and how it is maintained

    The table is generated, not hand-written:

    PhoneNumberMetadata.xml upstream, Apache-2.0, pinned by SHA-256 | scripts/update-metadata.mjs fetch, extract, validate the grammar v data/numbering-plans.json one record per region: prefixes, types, | patterns, lengths, example numbers, | and the formatting rules | scripts/gen-metadata.mjs emit MoonBit v plan_data.mbt the table the library compiles plan_examples_test.mbt 1 132 published example numbers as golden tests plan_corpus_test.mbt the engine cross-checked against a regex engine

    data/numbering-plans.json records the source URL, the license, the retrieval date and the SHA-256 of the exact upstream file it was extracted from, so a refresh is auditable. Both scripts/gen-metadata.mjs --check and scripts/gen-corpus.mjs are run by CI: editing a generated file by hand, or forgetting to regenerate after a data refresh, fails the build instead of drifting silently.

    node scripts/update-metadata.mjs # fetch a fresh copy of the upstream XML node scripts/gen-metadata.mjs # regenerate the table and the example tests node scripts/gen-corpus.mjs # regenerate the differential corpus node scripts/check-docs.mjs # verify the docs still match real output

    #Coverage: 245 plans and the whole NANP

    The table holds every geographic territory the upstream metadata describes, with the number types each plan defines and the way it writes its numbers: 245 regions, 1 132 per-type rules, 740 formatting rules and 1 132 published example numbers.

    The North American Numbering Plan is the largest shared calling code: 25 territories on +1. Each publishes its own area-code allocation, so resolution walks those allocations instead of a hard-coded list — the United States, Canada, Puerto Rico, Jamaica, the Bahamas, Trinidad and Tobago, Guam and the rest all resolve to themselves, while a shared range such as a toll-free 800 number resolves to the plan's main country.

    ///|
    @dialnumber.nanp_region_codes().length() // 25
    @dialnumber.nanp_region_of("2042545000") // Some("CA")
    @dialnumber.nanp_region_of("7872345678") // Some("PR")
    @dialnumber.nanp_region_of("2011550123") // None: NXX starts with 1

    Non-geographic calling codes (+800 international freephone and its neighbours) describe networks rather than territories, so they are outside a region-keyed table by design; the README's boundary note applies to them.

    #RFC 3966 without losing the context

    A tel: URI can carry a local number whose meaning depends on a phone-context — either a numeric prefix (+1-919) or a domain (example.com). The two forms are handled differently:

    • numeric context resolves: the context prefix and the local digits form an E.164 number;
    • domain context parses, serialises and round-trips, but does not resolve, because answering it requires asking that domain.

    The parsed TelUri keeps global_number, local_number, phone_context, extension and isub in separate fields; nothing is merged into the digits. parse_tel_uri_full and to_tel_uri_full are inverses, and resolution is a separate step (resolve_tel_uri). Extensions and ISDN subaddresses are mutually exclusive, as the RFC requires; unknown, duplicate and empty parameters are rejected rather than dropped; spaces are not accepted as visual separators; and an isub value is checked as a run of URI characters, so a % must start a complete %HH escape.

    #Relationship to other MoonBit libraries

    The ecosystem has plenty of validators, and none of them do this. moon_zod, moonschema and jsonschema validate JSON against a schema, including email and uuid string formats; they do not know about telephone numbering. moovalid and maru are validator-combinator frameworks. mooncontract validates OpenAPI contracts, and moonmrz reads the ICAO 9303 machine-readable zone of a passport. dialnumber sits alongside them: it takes a phone number in and gives you its parts, its plan, its verdict and its canonical renderings, with no I/O and no dependencies.

    #Install

    moon add sssssurf/dialnumber

    #Quick start

    ///|
    let pn = @dialnumber.parse("011 44 20 7946 0958", "US")

    ///|
    match pn {
    Some(n) => {
    @dialnumber.format_e164(n) // "+442079460958"
    @dialnumber.format_national(n, "GB") // Some("020 7946 0958")
    @dialnumber.is_possible(n) // true
    @dialnumber.is_valid(n) // true
    @dialnumber.region_of_number(n) // Some("GB")
    }
    None => ()
    }

    #Command line

    The cmd/main package is a small front end. Every subcommand prints a plain report, and the text below is recorded from a real run.

    moon run cmd/main -- parse "+81 90-1234-5678" moon run cmd/main -- valid "+1 787-234-5678" moon run cmd/main -- possible "090-1234-5678" JP moon run cmd/main -- format "090-1234-5678" JP moon run cmd/main -- types "+86 131 2345 6789" moon run cmd/main -- region "+442079460958" moon run cmd/main -- info JP moon run cmd/main -- cc de moon run cmd/main -- tel "tel:555-0123;phone-context=+1-919;ext=42" moon run cmd/main -- match "\d{8}" 12345678 moon run cmd/main -- list

    A few of those, with the output they produce:

    $ moon run cmd/main -- parse "+81 90-1234-5678" country_code: 81 national_number: 9012345678 e164: +819012345678 region: JP $ moon run cmd/main -- valid "+1 787-234-5678" valid: yes type: fixed-line-or-mobile region: PR $ moon run cmd/main -- types "+86 131 2345 6789" length candidates: fixed-line,mobile,shared-cost pattern verdict: mobile $ moon run cmd/main -- info JP region: JP name: Japan calling_code: 81 idd: 010 national_prefix: 0 lengths: 8,9,10,11,12,13,14,15,16,17 $ moon run cmd/main -- tel "tel:555-0123;phone-context=+1-919;ext=42" global_number: (none) local_number: 5550123 phone_context: +1919 extension: 42 isub: (none) resolved: +19195550123 $ moon run cmd/main -- match "\d{8}" 12345678 well_formed: yes matches: yes

    #API

    Parsing and validation:

    FunctionPurpose
    parse(input, region)read any written form into a PhoneNumber?
    parse_e164(input)read only a +-prefixed number
    parse_national(input, region)read a number as dialled in a region
    is_possible(pn)known code and a length the plan uses, plus NANP shape for +1
    is_possible_in_region(pn, region)the same, against one named region
    is_possible_number(input, region)parse then check, in one call
    is_valid(pn)the national number matches a published rule
    is_valid_in_region(pn, region)the same, against one named region
    is_valid_number(input, region)parse then validate, in one call
    number_type(pn)the type the rules report, in libphonenumber's precedence
    number_type_in_region(pn, region)the same, within one named region
    possible_types(pn)number types whose length admits the number
    possible_types_in_region(pn, region)the same, within one region
    region_of_number(pn)the region the number resolves to

    The pattern engine and the rules behind a verdict:

    FunctionPurpose
    matches_pattern(pattern, text)full-match under the numbering-plan grammar
    pattern_well_formed(pattern)is the rule inside the supported grammar
    plan_pattern(code)the plan's overall rule, as published
    type_pattern(code, kind)the rule published for one number type

    Formatting and tel: URIs:

    FunctionPurpose
    format_e164(pn)+8613123456789
    format_international(pn)+86 131 2345 6789, grouped by the plan
    format_national(pn, region)trunk prefix plus grouped national number
    format_dialling(pn, from_region)011 442079460958, or the national form at home
    format_rfc3966(pn)tel:+8613123456789;ext=42
    parse_tel_uri(uri)parse and resolve a tel: URI
    parse_tel_uri_full(uri)parse into a TelUri? without resolving
    to_tel_uri_full(t)serialise a TelUri canonically
    resolve_tel_uri(t)resolve parsed parts, when the context allows
    to_tel_uri(pn)write a tel: URI for a number

    The NANP and the region table:

    FunctionPurpose
    nanp_possible(nsn)ten-digit NANP structure
    nanp_region_of(nsn)the territory a +1 number belongs to
    nanp_region_codes()every region in the NANP
    nanp_area_code(nsn)the first three digits of a NANP number
    region_codes()every region code in the table
    region_exists(code)is the code in the table
    canonical_region_code(code)the table's spelling of a code
    region_name(code)English name
    calling_code(code)E.164 calling code
    idd_prefix(code)international dial-out prefix
    national_prefix(code)trunk prefix, or None
    possible_lengths(code)national-number lengths
    local_only_lengths(code)lengths accepted only when dialled locally
    region_by_calling_code(cc)one region for a calling code
    regions_by_calling_code(cc)every region for a calling code

    The NumberType enum names the eleven categories. The PhoneNumber struct holds country_code, national_number (a string, so a significant leading zero survives), and extension. TelUri holds the five parts of a tel: URI.

    #Performance

    Three decisions keep a validation out of the millisecond range: a rule is compiled once and memoised in the table, the matcher walks only the states that are alive rather than the whole program, and a territory that publishes leading digits is filtered by them before its full rules run — the same first question libphonenumber asks. A plan rule is a large alternation, and after the first digit only a few of its branches can still match.

    Measured with moon bench benchmarks (wasm target, mean per call; the first call for a rule pays its compilation, later calls reuse it):

    OperationTime
    is_possible0.13 µs
    format_e1640.10 µs
    format_national3.9 µs
    is_valid (a single-plan number)4.5 µs
    parse (E.164)4.9 µs
    parse (tel: URI)9.1 µs
    region_of_number12.8 µs
    parse (national)15.8 µs
    is_valid (a NANP number, 25 territories to disambiguate)15.9 µs
    format_international18.7 µs

    The benchmarks live in benchmarks/ and are part of the repository so a regression shows up as a number, not as an opinion.

    #Tests

    342 tests cover the public API, the pattern engine, the formatting rules, the generated table, the tel: grammar, the invariants of random input, and robustness against malformed input. The bulk of them are generated from the same data the library ships, so a data refresh grows the suite with it:

    • one block per region, asserting that every published example number is accepted by its plan, resolves to the region the data says, reports a plausible type, and — for fixed-line and mobile examples — reads back as the same number when its national rendering is parsed again;
    • a differential corpus that runs every published rule against its example and a mutated near miss, with the expected answer computed by an independent regular-expression engine;
    • property tests: a deterministic generator walks random numbers through the invariants (an E.164 rendering parses back to itself, a valid number is always possible, a reported type implies validity), and random text through every entry point;
    • whitebox checks that the table matches the metadata counts, that every rule is inside the engine's grammar, and that the NANP is complete;
    • unit tests for the grammar, the operators, the refusals, matching time on long input, the grouping of a dozen plans, and the round-trip invariant;
    • a robustness suite that feeds junk, 500-digit numbers and lone separators through every public entry point.

    moon test

    #License

    Apache-2.0. See LICENSE. The numbering-plan data is derived from Google's libphonenumber (Apache-2.0), and the region display names come from CLDR through the generator's Intl.DisplayNames.

    NumberType

    pub(all) enum NumberType {
    FixedLine
    FixedLineOrMobile
    Mobile
    TollFree
    PremiumRate
    SharedCost
    Voip
    Uan
    PersonalNumber
    Pager
    Voicemail
    } derive(Eq,
    Debug
    )

    The categories a numbering plan uses to tell one kind of line from another. A number can fit several at once, so valid.mbt hands back the set that its length admits rather than a single value.
    impl Show for NumberType

    NumberType::equal

    fn NumberType::equal(NumberType, NumberType) -> Bool

    Eq promotes both methods; name them so the promotion is stated rather than implied.

    NumberType::not_equal

    fn NumberType::not_equal(x : NumberType, y : NumberType) -> Bool

    Eq promotes both methods; name them so the promotion is stated rather than implied.

    NumberType::output

    fn NumberType::output(self : NumberType, logger : &Logger) -> Unit

    Show promotes both output and to_string; name them so the promotion is stated rather than implied (the compiler flags the alternative, E0079).

    NumberType::to_repr

    Debug promotes to_repr the same way; name it too.

    NumberType::to_string

    fn NumberType::to_string(self : NumberType) -> String

    Show promotes both output and to_string; name them so the promotion is stated rather than implied (the compiler flags the alternative, E0079).

    PhoneNumber

    pub(all) struct PhoneNumber {
    country_code : Int
    national_number : String
    extension : String
    } derive(Eq,
    Debug
    )

    A parsed number, independent of how it was written.

    country_code is the E.164 calling code without the leading +. national_number is the significant part of the number as the plan defines it: a string, not an integer, because some plans (Italy for one) require a leading zero that an integer would drop. extension is empty when the input carried none.
    impl Show for PhoneNumber

    PhoneNumber::equal

    fn PhoneNumber::equal(PhoneNumber, PhoneNumber) -> Bool

    Eq promotes both methods; name them so the promotion is stated rather than implied (the compiler flags the alternative, E0079).

    PhoneNumber::new

    fn PhoneNumber::new(country_code : Int, national_number : String) -> PhoneNumber

    Build a number with no extension.

    PhoneNumber::not_equal

    fn PhoneNumber::not_equal(x : PhoneNumber, y : PhoneNumber) -> Bool

    Eq promotes both methods; name them so the promotion is stated rather than implied (the compiler flags the alternative, E0079).

    PhoneNumber::output

    fn PhoneNumber::output(self : PhoneNumber, logger : &Logger) -> Unit

    PhoneNumber::to_repr

    Debug promotes to_repr the same way; name it too. A parsed number is worth printing in a test failure, which is what this is for.

    PhoneNumber::to_string

    fn PhoneNumber::to_string(self : PhoneNumber) -> String

    PhoneNumber::with_extension

    fn PhoneNumber::with_extension(self : PhoneNumber, extension : String) -> PhoneNumber

    A copy with extension attached.

    TelUri

    pub(all) struct TelUri {
    global_number : String
    local_number : String
    phone_context : String
    extension : String
    isub : String
    parameters : Array[(String, String)]
    } derive(Eq,
    Debug
    )

    The parts of a tel: URI, kept apart.

    Exactly one of global_number and local_number is non-empty. A local number always has a non-empty phone_context; a global number never does. extension and isub are mutually exclusive, as RFC 3966 requires. global_number is canonical (+ and digits). local_number keeps digits, A–F, * and #, with the visual separators removed.
    impl Show for TelUri

    TelUri::equal

    fn TelUri::equal(TelUri, TelUri) -> Bool

    Eq promotes both methods; name them so the promotion is stated rather than implied.

    TelUri::not_equal

    fn TelUri::not_equal(x : TelUri, y : TelUri) -> Bool

    Eq promotes both methods; name them so the promotion is stated rather than implied.

    TelUri::output

    fn TelUri::output(self : TelUri, logger : &Logger) -> Unit

    Show promotes both output and to_string; name them so the promotion is stated rather than implied (the compiler flags the alternative, E0079).

    TelUri::to_repr

    Debug promotes to_repr the same way; name it too.

    TelUri::to_string

    fn TelUri::to_string(self : TelUri) -> String

    Show promotes both output and to_string; name them so the promotion is stated rather than implied (the compiler flags the alternative, E0079).

    calling_code

    fn calling_code(code : String) -> Int?

    The E.164 calling code of a region, without a leading +.

    canonical_region_code

    fn canonical_region_code(code : String) -> String?

    The table's own spelling of a region code, or None when it is not known. Useful for echoing a user-supplied code in a canonical form.

    format_dialling

    fn format_dialling(pn : PhoneNumber, from_region : String) -> String?

    The number as it is dialled from from_region: that region's international dial-out prefix followed by the calling code and the national number — 011 442079460958 from the NANP, 00 442079460958 from most of Europe.

    When the number is inside from_region, what is dialled there is the national form, so that is what comes back. None when the region is unknown; a plan whose dial-out prefix is published as an expression rather than as digits needs a choice this library cannot make for the caller, so the + form comes back instead.

    format_e164

    fn format_e164(pn : PhoneNumber) -> String

    The canonical E.164 rendering: +, the calling code, then the national number, with no separators. Any extension is dropped — use format_rfc3966 when the extension must survive.

    format_international

    fn format_international(pn : PhoneNumber) -> String

    The international rendering, grouped by the plan that owns the number: +44 20 7946 0958. Falls back to the ungrouped form when the plan publishes no rule for the number's shape.

    format_national

    fn format_national(pn : PhoneNumber, region : String) -> String?

    The number as dialled inside region: the plan's trunk prefix and the national number, grouped by the plan's own formatting rules — (201) 555-0123, 020 7946 0958, 090-1234-5678. None when the region is unknown or uses a different calling code than the number. A plan that publishes no rule for the shape falls back to trunk prefix plus ungrouped number.

    format_rfc3966

    fn format_rfc3966(pn : PhoneNumber) -> String

    The RFC 3966 tel: URI. The extension, when present, becomes ;ext=. The subscriber is the ungrouped E.164 number, as the grammar expects.

    idd_prefix

    fn idd_prefix(code : String) -> String?

    The international dial-out prefix used inside a region, e.g. "011" for the NANP or "00" for most of Europe.

    is_possible

    fn is_possible(pn : PhoneNumber) -> Bool

    True when the number could exist in some plan the table knows: a known calling code and a length the plan uses. For +1 this also applies the NANP's structural shape, which rejects an area code or exchange that cannot exist. Possible is not the same as assigned; use is_valid for that.

    is_possible_in_region

    fn is_possible_in_region(pn : PhoneNumber, region : String) -> Bool

    The same check as is_possible against one named region.

    For +1 this is stronger than a length test: the number must have NANP structure and its area code must belong to region. For other calling codes it is the length test against that region's published lengths.

    is_possible_number

    fn is_possible_number(input : String, region : String) -> Bool

    Parse then check possibility, in one call. None becomes false.

    is_valid

    fn is_valid(pn : PhoneNumber) -> Bool

    True when the national number matches a rule the plan publishes for the region it resolves to. This is libphonenumber's "valid": the number fits a type pattern of the plan, so it is in an assigned range.

    is_valid_in_region

    fn is_valid_in_region(pn : PhoneNumber, region : String) -> Bool

    True when the number matches a rule of this named region. Also false when the region is unknown or its calling code differs from the number's.

    is_valid_number

    fn is_valid_number(input : String, region : String) -> Bool

    Parse then check validity, in one call. None becomes false.

    local_only_lengths

    fn local_only_lengths(code : String) -> Array[Int]?

    The lengths a region accepts only for a number dialled locally (no trunk prefix or no area code), sorted and without repeats. Empty when the plan publishes none — which is most of them.

    matches_pattern

    fn matches_pattern(pattern : String, text : String) -> Bool

    True when text matches pattern in full, under the numbering-plan grammar. A pattern that does not parse matches nothing, so a bad data row can never turn into a false positive.

    nanp_area_code

    fn nanp_area_code(national_number : String) -> String?

    The area code of a ten-digit (not necessarily possible) NANP number.

    nanp_possible

    fn nanp_possible(national_number : String) -> Bool

    The plan's structural rule: ten digits, an area code and an exchange that both start 2-9, and no N11 area code.

    A 555 exchange is structurally valid; only 555-0100..0199 is reserved for fiction, which is an assignment fact and not part of the shape.

    nanp_region_codes

    fn nanp_region_codes() -> Array[String]

    Every region the table places inside the NANP, in table order.

    nanp_region_of

    fn nanp_region_of(national_number : String) -> String?

    The territory a +1 national number belongs to.

    The structural rule runs first, so a number without NANP shape has no territory at all. The territories' published patterns then decide: the region marked primary (the United States) wins a tie, which is what sends a toll-free or shared range to US while an ordinary Canadian number resolves to CA. None when the number is structurally valid but no territory's allocation claims it — possible, but not assigned.

    national_prefix

    fn national_prefix(code : String) -> String?

    The trunk prefix dialled before a national number, or None when the plan has none (Italy, Spain, Portugal and others).

    number_type

    fn number_type(pn : PhoneNumber) -> NumberType?

    The number type the plan's rules report for this number, using libphonenumber's precedence: a special-range rule (toll free, premium rate, VoIP, ...) wins over a geographic one, and a number a plan accepts as both fixed line and mobile is reported as FixedLineOrMobile. None when the number matches no rule — which is exactly when is_valid is false.

    number_type_in_region

    fn number_type_in_region(pn : PhoneNumber, region : String) -> NumberType?

    The number type inside one named region, or None when the region is unknown, uses a different calling code, or does not accept the number.

    parse

    fn parse(input : String, default_region : String) -> PhoneNumber?

    Parse a number written in any of the accepted shapes.

    default_region is the ISO 3166-1 alpha-2 code the number should be read in when it does not carry a + or an international prefix. Pass "" to accept only self-describing numbers. Returns None when the input cannot be read.

    parse_e164

    fn parse_e164(input : String) -> PhoneNumber?

    Parse a self-describing number: one that starts with +. The region hint is empty, so anything else returns None.

    parse_national

    fn parse_national(input : String, region : String) -> PhoneNumber?

    Parse a number that is written the way it is dialled inside region.

    parse_tel_uri

    fn parse_tel_uri(uri : String) -> PhoneNumber?

    Parse and resolve a tel: URI into a PhoneNumber.

    This is the convenience form of resolve_tel_uri(parse_tel_uri_full(uri)). Returns None for anything that is not a resolvable tel URI.

    parse_tel_uri_full

    fn parse_tel_uri_full(uri : String) -> TelUri?

    Parse a tel: URI into its parts, without resolving anything.

    The scheme and parameter names are case-insensitive. Unknown parameters, empty parameters, duplicate parameters, empty values and an ext/isub conflict are rejected rather than dropped: a URI this parser cannot fully represent is better refused than quietly changed. Spaces are not visual separators (RFC 3966 §5.1.1), so only the outside of the URI is trimmed.

    pattern_well_formed

    fn pattern_well_formed(pattern : String) -> Bool

    True when the pattern is a member of the supported grammar. Used by the data-pipeline tests to prove every published rule can be interpreted.

    plan_pattern

    fn plan_pattern(code : String) -> String?

    The plan's overall rule for its national numbers, e.g. "[2-9]\\d{9}" for the NANP. Exposed so a caller can see the rule a verdict was based on.

    possible_lengths

    fn possible_lengths(code : String) -> Array[Int]?

    The national-number lengths the region uses, sorted, without repeats.

    possible_types

    fn possible_types(pn : PhoneNumber) -> Array[NumberType]

    The number types whose length rule admits this number, within the region it resolves to. Empty when the length fits no category or no region matches. Several types can share a length, so this is a candidate set, not a verdict; number_type is the pattern-based answer.

    possible_types_in_region

    fn possible_types_in_region(pn : PhoneNumber, region : String) -> Array[NumberType]

    The number types in one named region whose length rule admits this number. For +1 the number must also have NANP structure and its area code must belong to the region.

    region_by_calling_code

    fn region_by_calling_code(cc : Int) -> String?

    One region that uses a calling code, or None when no region does. When several share the code (the whole NANP is +1) the region marked primary wins; otherwise the first in code order. Use regions_by_calling_code for all of them.

    region_codes

    fn region_codes() -> Array[String]

    Every region code in the table, sorted.

    region_exists

    fn region_exists(code : String) -> Bool

    True when the region code is one the table knows.

    region_name

    fn region_name(code : String) -> String?

    The English name of a region, e.g. "Japan".

    region_of_number

    fn region_of_number(pn : PhoneNumber) -> String?

    The region a parsed number belongs to.

    A calling code used by one region resolves to it. A shared code (the NANP is the big one: 25 territories on +1) is resolved by the plans' own rules: the first region whose patterns accept the national number wins, primary region first, and a number no rule accepts falls back to the region whose length rule admits it — it is then possible without being valid. None when the calling code is unknown or no region can claim the number at all.

    regions_by_calling_code

    fn regions_by_calling_code(cc : Int) -> Array[String]

    Every region that shares a calling code, in table order.

    resolve_tel_uri

    fn resolve_tel_uri(t : TelUri) -> PhoneNumber?

    Turn parsed parts into an E.164 number when that is possible without asking an external authority.

    A global number resolves directly. A local number resolves only when its context is a + prefix and its own digits are decimal; a domain context, or a local number containing *, # or hex digits, has no E.164 reading. isub is preserved in the TelUri but has no PhoneNumber field, so it does not survive resolution.

    to_tel_uri

    fn to_tel_uri(pn : PhoneNumber) -> String

    The RFC 3966 URI for a number. Same rendering as format_rfc3966, offered here so the two halves of URI handling sit together.

    to_tel_uri_full

    fn to_tel_uri_full(t : TelUri) -> String?

    Serialise the parts back to a canonical tel: URI. Parameters are written in RFC 3966's order: phone-context (local numbers only), then ext or isub. None for a combination the grammar cannot represent, such as a local number without a context or both numbers set.

    type_pattern

    fn type_pattern(code : String, kind : NumberType) -> String?

    The rule published for one number type in one region, if the plan has that type. Lets a caller audit is_valid / number_type against the data.