phonex

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 11, 2026 License: MIT Imports: 17 Imported by: 0

README

📞 phonex

Phonex Logo

Go Reference CI Go Report Card MIT licence

A phone number parsing, validation and formatting library for Go.

Documentation · API reference

The metadata is generated directly from Google's libphonenumber PhoneNumberMetadata.xml, so number ranges, formatting rules, trunk prefixes and example numbers are the same ones the reference implementation uses. Parsing an international number into an existing Phone performs no allocations.

p, err := phonex.Parse("+998 90 123 45 67")

p.E164()          // +998901234567
p.International() // +998 90 123 45 67
p.National()      // 90 123 45 67
p.Country()       // UZ
p.Type()          // MOBILE
p.IsValid()       // true

Install

go get github.com/bakhod1r/phonex

Requires Go 1.26 or newer.


1. Parsing

phonex.Parse("+998901234567")                                  // international
phonex.Parse("998901234567")                                   // no '+', calling code present
phonex.Parse("90 123 45 67", phonex.WithDefaultCountry("UZ"))  // national
phonex.Parse("011 44 20 7031 3000", phonex.WithDefaultCountry("US")) // dialled with an IDD
phonex.Parse("tel:+1-202-555-0123;ext=42")                     // RFC 3966
phonex.Parse("1-800-FLOWERS", phonex.WithDefaultCountry("US"), phonex.WithAlphaCharacters())

Punctuation, spacing and grouping characters are ignored. The trunk prefix is stripped using the region's own rules, so 020 7031 3000 in GB and +44 20 7031 3000 produce the same number.

Options
Option Effect
WithDefaultCountry(region) Region assumed when the input carries no calling code.
WithAlphaCharacters() Maps vanity letters to keypad digits (FLOWERS3569377).
WithoutRawInput() Drops the original string from the parsed number.

Parse fails when no region can be determined, when the input holds characters a number cannot, or when the digit count falls outside the bounds E.164 sets for any number (2 to 17 digits).

It deliberately does not judge the number against its own country's rules. A number of the wrong length for its region, or in an unassigned range, still parses — so that Possibility can say why and IsValid can say whether it is real. This mirrors libphonenumber. Check IsValid before trusting a parsed number.

Errors

Every failure is a *ValidationError and is comparable with errors.Is:

_, err := phonex.Parse("+9989012")
errors.Is(err, phonex.ErrTooShort) // true

ErrTooShort, ErrTooLong, ErrInvalidLength, ErrInvalidCountry, ErrInvalidCountryCode, ErrMissingCountry, ErrInvalidCharacters, ErrInvalidExtension.


2. Validation

p.IsPossible() // the digit count is one the numbering plan uses
p.IsValid()    // the number falls inside an assigned range

The distinction matters: +1 000 000 0000 has the right shape for the US but is not an assigned number, so it is possible but not valid. IsValid is the check to gate on.

Possibility reports why a length check failed, without compiling any pattern:

p.Possibility()
// IS_POSSIBLE | IS_POSSIBLE_LOCAL_ONLY | TOO_SHORT | INVALID_LENGTH | TOO_LONG

IsPossible is true for IS_POSSIBLE and for IS_POSSIBLE_LOCAL_ONLY, since both are lengths the plan really uses; call Possibility to tell them apart.

Lengths are judged against the region that owns the calling code, while validity is judged against the region the number resolves to. That asymmetry is libphonenumber's, and it is why a Curaçao-length number written with a Bonaire prefix is possible but not valid.

Other checks:

p.IsValidForRegion("BS")          // valid *and* belonging to that region
p.CanBeInternationallyDialled()   // reachable from abroad

3. Number types

p.Type() // MOBILE, FIXED_LINE, FIXED_LINE_OR_MOBILE, TOLL_FREE,
         // PREMIUM_RATE, SHARED_COST, VOIP, PERSONAL_NUMBER,
         // PAGER, UAN, VOICEMAIL, UNKNOWN

with predicates for each: IsMobile, IsLandline, IsFixedLineOrMobile, IsTollFree, IsPremiumRate, IsSharedCost, IsVoIP, IsPager, IsUAN, IsVoicemail, IsPersonalNumber.

Many regions do not separate mobile from fixed-line ranges; those numbers report FIXED_LINE_OR_MOBILE rather than guessing.


4. Formatting

p, _ := phonex.Parse("+442070313000")

p.E164()             // +442070313000
p.International()    // +44 20 7031 3000
p.National()         // 020 7031 3000
p.RFC3966()          // tel:+44-20-7031-3000
p.OutOfCountry("US") // 011 44 20 7031 3000
p.OutOfCountry("GB") // 020 7031 3000

Format(FormatE164 | FormatInternational | FormatNational | FormatRFC3966) selects one at runtime, and AppendE164(dst []byte) []byte writes into a caller-supplied buffer without allocating.

E.164 is a stable identity for valid numbers: parse one, store E164(), parse it again, and you get the same string back. That does not extend to invalid numbers — nothing says where their digits end and a trunk prefix begins, so +358 0000000 legitimately comes back as +358 000000. Validate before you store.

Grouping comes from the region's own rules, including regions that share a calling code: a +1 242 number is written the way the Bahamas writes it.


5. As-you-type formatting

f := phonex.NewFormatter("US")
f.InputDigit('2') // 2
f.InputDigit('0') // 20
f.InputDigit('2') // 202
...
f.Input("5550123") // (202) 555-0123

f.RemoveLastDigit() // (202) 555-012
f.Clear()

Typed digits are never lost, only regrouped, which is the property an input field needs.


6. Extensions

Extensions are recognised in free text and in RFC 3966 form:

p, _ := phonex.Parse("+1 202 555 0123 ext. 4321")
p.Extension() // 4321
p.E164()      // +12025550123  (E.164 has no extension)
p.RFC3966()   // tel:+1-202-555-0123;ext=4321

Markers understood: ext, extn, extension, x, #, ~, anexo, interno, ramal, int, and ;ext=.


7. Comparing numbers

phonex.MatchNumbers("901234567", "+998901234567", phonex.WithDefaultCountry("UZ"))
// EXACT_MATCH

MatchType grades the correspondence: EXACT_MATCH, NSN_MATCH (national numbers agree but a calling code is unconfirmed), SHORT_NSN_MATCH (one is a suffix of the other), NO_MATCH.

Equal is the boolean form, and EqualExact additionally requires the two to have been written identically.


8. Country metadata

m, _ := phonex.Country("UZ")
m.Name, m.ISO3, m.DialCode, m.MinLength, m.MaxLength

phonex.CountryByDialCode("+1")     // US, the main region for +1
phonex.RegionsForDialCode("1")     // every region sharing +1
phonex.CountryByPhone("+442070313000")
phonex.SearchCountries("united")
phonex.SupportedRegions()          // 245 ISO-3166 alpha-2 codes
phonex.NonGeoEntities()            // +800, +808, +870, ...

Example numbers come from libphonenumber and are valid by construction:

p, _ := phonex.ExampleNumberForType("GB", phonex.Mobile)
Generating numbers

For test fixtures and demos, Generate builds a random number that IsValid accepts, by keeping an example's area and operator digits and randomising the subscriber part:

phonex.Generate("GB")                        // random, prefers mobile
phonex.GenerateForType("GB", phonex.Mobile)  // a particular range

Both draw from the global math/rand. Where the output has to be reproducible from a seed, supply the randomness instead — intn returns a value in [0,n), so any generator fits, and phonex is not tied to one:

r := rand.New(rand.NewSource(1))
phonex.GenerateWith("GB", phonex.Mobile, r.Intn)  // same seed, same number
phonex.GenerateWith("GB", phonex.AnyType, r.Intn) // any range the region has

To generate around a code you already know — an area or operator prefix — give it to GenerateForPrefix:

phonex.GenerateForPrefix("GB", "20", r.Intn)   // +44 20 xxxx xxxx, London
phonex.GenerateForPrefix("UZ", "93", r.Intn)   // +998 93 xxx xx xx, Ucell

The prefix may be written either way round. National number lengths vary within a country — London's 20 takes eight further digits where most UK codes take seven — and some plans count the trunk digit as part of the national number, so Rome is 06 to phonex and 6 in an atlas. Both readings are tried, and the shape that fits is cached. It reports false when no shape the plan defines accepts the prefix.

Generated numbers are valid, which means they may well belong to a real subscriber. Never dial or message them.


9. Storage and encoding

Phone implements json.Marshaler, json.Unmarshaler, encoding.TextMarshaler, encoding.TextUnmarshaler, sql.Scanner and driver.Valuer. Everything round-trips through E.164, and anything that does not parse is rejected, so a stored Phone is never invalid.

type User struct {
    Phone *phonex.Phone `json:"phone"`
}
// {"phone":"+998901234567"}

10. Privacy helpers

p.Mask()                  // +998*******67
p.Mask(phonex.MaskLast4)  // *********4567
phonex.Redact(p)          // +998*******67

p.Hash()                            // SHA-256 of the E.164 form
phonex.Fingerprint(p, phonex.WithSecret(key)) // HMAC-SHA256

Hashes are computed over the canonical E.164 form, so the same number written differently hashes alike.


11. Short numbers

Short numbers — 112, 911, 10086 — have no international form and no calling code, and the same digits mean different things in different countries. Parse rejects them as too short. They live in their own package, which carries its own metadata so a program that never asks about them does not link half a megabyte of tables:

import "github.com/bakhod1r/phonex/shortnumber"

shortnumber.IsEmergency("112", "GB")           // true
shortnumber.IsEmergency("112", "UZ")           // false — Uzbekistan dials 02
shortnumber.ConnectsToEmergency("911123", "US") // true
shortnumber.IsValid("100", "GB")               // true, the BT operator
shortnumber.ExpectedCost("10086", "CN")        // STANDARD_RATE
shortnumber.IsCarrierSpecific("454 00", "UZ")  // true

Every call takes the region, because without it the digits mean nothing, and the digits are read exactly as dialled — no trunk prefix rules are applied.

IsEmergency requires an exact match. ConnectsToEmergency also accepts digits typed after the emergency number, because that is what the network acts on; in Brazil, Chile and Nicaragua, where it does not, it reports false. Use ConnectsToEmergency when deciding whether a number is safe to dial.


12. Where a number is, and whose it is

Three optional packages answer questions the core metadata cannot, each from its own data set:

import (
    "github.com/bakhod1r/phonex/geocoding"
    "github.com/bakhod1r/phonex/carrier"
    "github.com/bakhod1r/phonex/timezone"
)

p, _ := phonex.Parse("+44 20 7031 3000")
geocoding.Area(p)     // "London"
geocoding.Describe(p) // "London", or the country name when there is no area
timezone.For(p)       // ["Europe/London"]

q, _ := phonex.Parse("+44 7400 123456")
carrier.Name(q)            // "Three" — the network the range was issued to
carrier.SafeDisplayName(q) // "" — Britain has number portability
timezone.For(q)            // ["Europe/Guernsey" "Europe/Isle_of_Man" "Europe/London"]

Read the answers for what they are. All three key off the number's prefix, so they describe where and how the number was issued, not where its owner is or which network it is on today. A mobile number keeps its area and time zone when its owner emigrates, and in a country with number portability the carrier name can be years out of date — which is why SafeDisplayName returns nothing there rather than something misleading.

A note on carrier lookup

Getting the network from a number is the most frequently asked of the three, and the one most often misread, so it is worth being precise about what it gives you.

p, _ := phonex.Parse("+998 93 123 45 67")
carrier.Name(p)                                  // "Ucell"
carrier.NameForDigits("998931234567")            // "Ucell", without parsing
carrier.NameForNumber("93 123 45 67", "UZ")      // "Ucell", in one step
carrier.SafeDisplayName(p)                       // "Ucell" — see below
carrier.Count()                                  // 28962 prefixes

The answer comes from a prefix table, which has three consequences worth knowing before you show it to anyone:

It is the network the range was issued to, not the one serving the number today. Where subscribers can keep their number when they switch operator, the table cannot know they did. Name still returns the original network; SafeDisplayName returns "" in those regions instead, and is the one to use for anything a user will read. Uzbekistan is not among them, so both agree there.

Coverage is uneven, and the gaps are deliberate. The data set covers 206 calling codes, but only the ranges upstream is confident about. There are no entries for United States or Russian mobile numbers at all — portability there makes a prefix table close to meaningless — so Name returns "" for them. An empty result means "not in the data", never "no such carrier".

Only mobile ranges are covered. A fixed line returns "", because a landline belongs to whoever operates the exchange rather than to a network in this sense.

For Uzbekistan the whole mobile table is short enough to print:

Prefix Carrier
33 HUMANS
50, 93, 94 Ucell
77, 95, 99 Uzbektelecom
88, 97 MobiUZ
90, 91 Beeline
98 Perfectum

Anything else — +998 59 …, say — is not an assigned range, and IsValid reports that before carrier lookup becomes a question.

A lookup takes about 150 ns and allocates nothing.

Why these are separate packages

They are separate packages because the data is large and most programs need none of it. A hello-world binary, built with Go 1.26 on darwin/arm64:

Imports Binary
phonex alone 3.8 MB
+ timezone 3.9 MB
+ carrier 4.1 MB
+ geocoding 7.2 MB
all of them, plus shortnumber 7.9 MB

Geocoding and carrier data is English only.


13. Batch helpers

phonex.ParseMany(numbers, opts...)  // one Result per input, order preserved
phonex.Unique(numbers)              // distinct E.164 numbers, first-seen order
phonex.SortNumbers(numbers)         // sorted E.164 numbers

Performance

Apple M4 Pro, Go 1.26, -benchtime defaults, median of three runs:

BenchmarkParseInternational-12       18116932     55.7 ns/op      0 B/op    0 allocs/op
BenchmarkParseSharedCallingCode-12    2408018    493.6 ns/op      0 B/op    0 allocs/op
BenchmarkParseNational-12             3283627    365.3 ns/op     16 B/op    1 allocs/op
BenchmarkParseAllocating-12          14347630     82.5 ns/op    112 B/op    1 allocs/op
BenchmarkIsValid-12                   3400000    343.0 ns/op    112 B/op    1 allocs/op
BenchmarkE164-12                     54000000     21.8 ns/op     16 B/op    1 allocs/op
BenchmarkAppendE164-12              257336388      4.4 ns/op      0 B/op    0 allocs/op
BenchmarkFormatNational-12            1400000    834.4 ns/op    178 B/op    9 allocs/op
BenchmarkGeocodingArea-12             7881004    150.4 ns/op      0 B/op    0 allocs/op

The geocoding lookup is a binary search over 269379 prefixes, probed from the longest down, and it allocates nothing.

Reading those: a number in international form costs one array lookup and a length check. A shared calling code such as +1 costs more because the region has to be identified by matching the national number against candidate ranges, which is inherent — libphonenumber does the same work. The one remaining allocation on the national path is the submatch index slice the regexp engine returns while stripping a trunk prefix, and it only affects the 44 regions whose prefix rule is a pattern rather than a literal.

Three things make that possible:

  • Constant-time calling-code lookup. Calling codes are indexed by their numeric value in fixed-size arrays, not scanned across a map of regions.
  • Inline storage. A Phone keeps its digits and its parsing scratch buffer inside the struct, so parsing into an existing value never touches the heap.
  • Lazily compiled patterns. The ~2500 metadata patterns are compiled on first use, so start-up stays cheap and a program that only ever sees Uzbek numbers never compiles Brazil's.

For hot loops, reuse a Phone and pass options as a struct so the variadic option slice is not built per call:

var p phonex.Phone
opts := phonex.DefaultParseOptions()
opts.DefaultCountry = "UZ"
opts.KeepRawInput = false

for _, s := range numbers {
    if err := p.ParseWith(s, opts); err == nil {
        use(p.E164())
    }
}

A Phone is not safe for concurrent modification. Metadata is immutable and safe to read from any goroutine.


Metadata

Every table under countries/, shortnumber/, timezone/, carrier/ and geocoding/ is produced by cmd/phonexgen from the data vendored under internal/metadata/ — libphonenumber v9.0.32, verbatim — plus internal/metadata/metadata.json, which supplies the ISO-3166 alpha-3 codes, English country names and time zones that libphonenumber does not carry.

The data is pinned to a tagged release rather than to master, so an update is a deliberate, reviewable step. Each generated package exports a SourceHash recording the SHA-256 of the exact data it was built from, and a test in each fails if the two drift apart.

make generate         # regenerate from the vendored XML
make update-metadata  # fetch the pinned release, regenerate, run everything

Bump METADATA_VERSION in the Makefile to move to a newer release.

Review the diff before committing an upstream refresh: it changes number ranges, not only formatting.

The test suite parses every example number in the metadata — around 1200 across 245 regions — and asserts that each one is valid, resolves to the range it was listed under, and survives a round trip through the national, international and RFC 3966 formats.


Not included

  • Languages other than English for geocoding and carrier names. Upstream ships 34 and 10 respectively; vendoring them all would multiply the data. The generator takes the directory as a flag, so adding one is a matter of vendoring it and generating a second package.
  • Metadata.Timezones, the country-level field on the region metadata, is empty. Use the timezone package, which is prefix-level and far more precise.

Differential testing

difftest/ is a separate module that compares phonex against Google's libphonenumber, through the nyaruka/phonenumbers port, over 12785 ordinary numbers across all 245 regions and 8044 short numbers across 241 — every example number in the metadata, in international and national form, plus pseudo-random numbers in each region's shape. It is a separate module so that phonex itself keeps no dependencies.

make diff

Current agreement:

Check Result
Accept / reject no disagreement
E.164 output no disagreement
Resolved region no disagreement
Number type no disagreement
IsPossible no disagreement
OutOfCountry no disagreement
Short: IsEmergency, ConnectsToEmergency no disagreement
Short: IsPossible, IsValid no disagreement
geocoding.Area, timezone.For no disagreement
carrier.Name 12 of 12784
IsValid 1 of 12784
International / National / RFC3966 6 of 4948 each

The residual cases are data skew, not logic: phonex is generated from libphonenumber v9.0.32, while nyaruka bundles data regenerated from snapshots taken at other moments, so a handful of ranges, formats and carrier names genuinely differ between the two sets. Ghana's AirtelTigo has rebranded to "AT" in ours, for instance. The test prints every disagreement and fails if the count grows, so a real regression appears as new cases.

Building it caught four real bugs that the unit tests did not:

  • Trunk prefixes were not stripped from numbers written in international form, so +44 (0)20 7031 3000 kept its 0.
  • The region was resolved by preferring the caller's default country, where libphonenumber always lets the number itself decide.
  • RFC3966 only replaced spaces, leaving New Caledonia's 20.12.34 intact.
  • The national prefix rule was spliced onto a literal $1, but Argentina's mobile format is $2 15-$3-$4 and has no $1, so the trunk prefix was lost.

It also caught three smaller ones: IsPossible rejected local-only lengths that libphonenumber accepts, length was judged against the resolved region rather than the region that owns the calling code, and a carrier selection code was dropped in regions whose trunk prefix rule also rewrites the number.


Maturity

Verified on every commit by CI:

  • Every example number in the metadata (~1200 across 245 regions) parses, validates, and resolves to the range it was listed under, and every short number example is recognised in its own region.
  • Every generated table matches the data vendored beside it.
  • Every region's example survives a round trip through the national, international and RFC 3966 formats.
  • Differential agreement with libphonenumber over 12785 inputs (see above).
  • go test -race is clean, including a test that hammers one finished Phone from sixteen goroutines.
  • Fuzzing, with no panic and no round-trip failure (11M executions locally, two minutes per CI run).
  • go vet and gofmt clean, and a check that the generated metadata tables match the vendored XML.
  • Statement coverage: 93.1% for phonex, 91.9% for shortnumber, and 100% for geocoding, carrier, timezone and the two internal packages.

What it has not been through: a tagged release or production traffic. The API is still v0 and may change.


Contributing

Bug reports and pull requests are welcome. CONTRIBUTING.md covers the two things particular to this repository: which files are generated and must not be edited by hand, and the differential test a change has to survive. Vulnerabilities go through SECURITY.md, not the issue tracker.

Licence

phonex is released under the MIT licence; see LICENSE.

The phone number metadata, and the geocoding, carrier and time zone data, are taken verbatim from Google's libphonenumber and remain under the Apache License 2.0. Redistributing phonex therefore means keeping NOTICE, which records what is vendored and where it came from.

Documentation

Overview

Package phonex parses, validates and formats international phone numbers.

The metadata behind it is generated directly from Google's libphonenumber PhoneNumberMetadata.xml, so the number ranges, formatting rules and trunk prefixes are the same ones the reference implementation uses.

Parsing

Parse accepts numbers in international form, in national form when a default region is given, and in RFC 3966 form:

p, err := phonex.Parse("+998 90 123 45 67")
p, err := phonex.Parse("90 123 45 67", phonex.WithDefaultCountry("UZ"))
p, err := phonex.Parse("tel:+1-202-555-0123;ext=42")

Parse enforces only the bounds E.164 sets for any number. It does not judge the number against its own country's rules, so a wrong-length or unassigned number still parses and the checks below are what report on it. Gate on IsValid before trusting a parsed number.

p.IsPossible()  // the length is one the numbering plan uses
p.IsValid()     // the number falls in an assigned range
p.Possibility() // why a length check failed

Formatting

p.E164()             // +12025550123
p.International()    // +1 202-555-0123
p.National()         // (202) 555-0123
p.RFC3966()          // tel:+1-202-555-0123
p.OutOfCountry("GB") // 00 1 202-555-0123

Formatter formats a number while it is being typed, for use in an input field:

f := phonex.NewFormatter("US")
f.Input("2025550123") // (202) 555-0123

Allocation behaviour

A Phone stores its digits inline, so parsing into an existing value does no allocation at all for numbers in international form. In a hot loop, reuse a Phone and pass options as a struct:

var p phonex.Phone
opts := phonex.DefaultParseOptions()
opts.DefaultCountry = "UZ"
for _, s := range numbers {
    if err := p.ParseWith(s, opts); err == nil {
        use(p.E164())
    }
}

Short numbers

Short numbers such as 112 and 911 are a different problem: they have no international form and the same digits mean different things in different countries. Parse rejects them as too short. They live in the shortnumber subpackage, which keeps its own metadata so that programs which never need it do not link it in:

shortnumber.IsEmergency("112", "GB")        // true
shortnumber.ConnectsToEmergency("911123", "US") // true

Where a number is, and whose it is

The geocoding, carrier and timezone subpackages answer questions the core metadata cannot. They key off the number's prefix, so they describe where and how the number was issued, not where its owner is now:

geocoding.Area(p)          // "London"
timezone.For(p)            // ["Europe/London"]
carrier.SafeDisplayName(p) // "" where number portability makes it unreliable

Each carries its own data and is a separate package so that a program which does not need it does not link it in: geocoding alone is several megabytes.

Regenerating the metadata

countries/generated.go is produced by cmd/phonexgen from the vendored upstream XML, pinned to a tagged libphonenumber release. Refresh it with:

go generate ./...

The difftest module compares this package against libphonenumber itself over the whole metadata; see difftest/ and "make diff".

Index

Examples

Constants

View Source
const (
	FixedLine         = countries.FixedLine
	Mobile            = countries.Mobile
	TollFree          = countries.TollFree
	PremiumRate       = countries.PremiumRate
	SharedCost        = countries.SharedCost
	VoIP              = countries.VoIP
	PersonalNumber    = countries.PersonalNumber
	Pager             = countries.Pager
	UAN               = countries.UAN
	Voicemail         = countries.Voicemail
	FixedLineOrMobile = countries.FixedLineOrMobile
	Unknown           = countries.Unknown
)

Number ranges, mirroring libphonenumber's PhoneNumberType.

View Source
const AnyType = Unknown

AnyType asks for a number of whatever range the region defines, preferring mobile. It is the type Generate uses.

It shares a value with Unknown, which Type reports when no range matches: both mean "no particular range", read from opposite ends. Passing a Type() result straight back into GenerateWith therefore asks for any type rather than failing, which is the harmless reading of the two.

Variables

View Source
var (
	ErrInvalidCharacters  = &ValidationError{Code: CodeInvalidCharacters, Message: "invalid characters"}
	ErrTooShort           = &ValidationError{Code: CodeTooShort, Message: "too short"}
	ErrTooLong            = &ValidationError{Code: CodeTooLong, Message: "too long"}
	ErrInvalidCountry     = &ValidationError{Code: CodeInvalidCountry, Message: "invalid country"}
	ErrInvalidCountryCode = &ValidationError{Code: CodeInvalidCountryCode, Message: "invalid country code"}
	ErrInvalidPrefix      = &ValidationError{Code: CodeInvalidPrefix, Message: "invalid prefix"}
	ErrInvalidFormat      = &ValidationError{Code: CodeInvalidFormat, Message: "invalid format"}
	ErrInvalidType        = &ValidationError{Code: CodeInvalidType, Message: "invalid type"}
	ErrMissingCountry     = &ValidationError{Code: CodeMissingCountry, Message: "missing country"}
	ErrInvalidExtension   = &ValidationError{Code: CodeInvalidExtension, Message: "invalid extension"}
	// ErrInvalidLength reports a digit count that falls between two lengths
	// the region allows, so the number is neither too short nor too long.
	ErrInvalidLength = &ValidationError{Code: CodeInvalidLength, Message: "invalid length for region"}
)
View Source
var (
	MaskLast4  = MaskOptions{Prefix: 0, Suffix: 4, Mask: "*"} // masks everything except last 4
	MaskFirst3 = MaskOptions{Prefix: 3, Suffix: 0, Mask: "*"} // masks everything except first 3
	MaskMiddle = MaskOptions{Prefix: 4, Suffix: 2, Mask: "*"} // e.g. +99890****67
	MaskFull   = MaskOptions{Prefix: 0, Suffix: 0, Mask: "*"}
)

Functions

func Equal

func Equal(a, b string, opts ...ParseOption) bool

Equal parses both inputs and reports whether they are the same number.

func Fingerprint

func Fingerprint(p *Phone, opts ...HashOption) string

Fingerprint returns a stable hash of the phone number. If WithSecret is provided, it uses HMAC-SHA256, otherwise it uses standard SHA256.

func IsPossible

func IsPossible(input string, opts ...ParseOption) bool

IsPossible parses input and reports whether its length is plausible.

func IsValid

func IsValid(input string, opts ...ParseOption) bool

IsValid parses input and reports whether it is a valid number.

func Normalize

func Normalize(input string) string

Normalize strips punctuation and spacing from input, keeping the digits and a leading '+'. It does not parse or validate: use E164 for a canonical number, and this for cheap cleanup of an input field.

func NormalizeBytes

func NormalizeBytes(input []byte) []byte

NormalizeBytes is Normalize for a byte slice. The result is a fresh slice.

func Redact

func Redact(p *Phone) string

Redact is a convenience function for masking a phone number for logs/output.

func SortNumbers

func SortNumbers(numbers []string, opts ...ParseOption) []string

SortNumbers returns the E.164 form of each input in ascending order. Sorting the canonical form groups numbers by calling code, which is what makes the result useful for display. Inputs that fail to parse are skipped.

func SupportedRegions

func SupportedRegions() []string

SupportedRegions returns every ISO-3166 alpha-2 code with metadata, sorted.

func ToE164

func ToE164(input string, opts ...ParseOption) (string, error)

ToE164 parses input and returns its canonical E.164 form, or an error if it cannot be parsed. It is the one-call form of Parse followed by E164.

func Unique

func Unique(numbers []string, opts ...ParseOption) []string

Unique returns the E.164 form of each distinct number, in the order it was first seen. Inputs that fail to parse are skipped.

Types

type CountryCodeSource

type CountryCodeSource uint8

CountryCodeSource records how the calling code was determined while parsing.

const (
	// FromDefaultCountry means the number carried no calling code and the
	// default region supplied it.
	FromDefaultCountry CountryCodeSource = iota
	// FromNumberWithPlusSign means the number started with '+'.
	FromNumberWithPlusSign
	// FromNumberWithIDD means the number started with the default region's
	// international dialling prefix, e.g. "00" or "011".
	FromNumberWithIDD
	// FromNumberWithoutPlusSign means the number started with the calling
	// code but no '+' and no IDD.
	FromNumberWithoutPlusSign
)

func (CountryCodeSource) String

func (s CountryCodeSource) String() string

type ErrorCode

type ErrorCode int
const (
	CodeInvalidCharacters ErrorCode = iota + 1
	CodeTooShort
	CodeTooLong
	CodeInvalidCountry
	CodeInvalidCountryCode
	CodeInvalidPrefix
	CodeInvalidFormat
	CodeInvalidType
	CodeMissingCountry
	CodeInvalidExtension
	CodeInvalidLength
)

type FormatType

type FormatType int

FormatType selects an output format.

const (
	// FormatE164 is the canonical machine format, e.g. "+998901234567".
	FormatE164 FormatType = iota
	// FormatInternational is the human-readable international format,
	// e.g. "+998 90 123 45 67".
	FormatInternational
	// FormatNational is the format used inside the country, including the
	// trunk prefix, e.g. "(202) 555-0123" or "090 123 45 67".
	FormatNational
	// FormatRFC3966 is the "tel:" URI form, e.g. "tel:+998-90-123-45-67".
	FormatRFC3966
)

type Formatter

type Formatter struct {
	// contains filtered or unexported fields
}

Formatter formats a number while it is being typed, one digit at a time.

It is the counterpart of libphonenumber's AsYouTypeFormatter and is meant to drive an input field:

f := phonex.NewFormatter("US")
for _, r := range "2025550123" {
    out = f.InputDigit(r)
}
// out == "(202) 555-0123"

A Formatter is not safe for concurrent use.

func NewFormatter

func NewFormatter(region string) *Formatter

NewFormatter returns a Formatter for numbers typed in region. An unknown region still yields a usable Formatter: it formats numbers typed in international form and echoes anything else.

Example
package main

import (
	"fmt"

	"github.com/bakhod1r/phonex"
)

func main() {
	f := phonex.NewFormatter("US")
	for _, r := range "2025550123" {
		fmt.Println(f.InputDigit(r))
	}
}
Output:
2
20
202
202-5
202-55
202-555
202-5550
(202) 555-01
(202) 555-012
(202) 555-0123

func (*Formatter) Clear

func (f *Formatter) Clear()

Clear resets the Formatter so it can format another number.

func (*Formatter) Digits

func (f *Formatter) Digits() string

Digits returns the digits typed so far, without punctuation.

func (*Formatter) Input

func (f *Formatter) Input(s string) string

Input appends every character of s and returns the result.

func (*Formatter) InputDigit

func (f *Formatter) InputDigit(r rune) string

InputDigit appends one typed character and returns the number formatted so far. Characters other than digits and a leading '+' are ignored.

func (*Formatter) RemoveLastDigit

func (f *Formatter) RemoveLastDigit() string

RemoveLastDigit removes the most recently typed character and returns the number formatted so far.

func (*Formatter) String

func (f *Formatter) String() string

String returns the current formatted number.

type HashAlgorithm

type HashAlgorithm int
const (
	SHA256 HashAlgorithm = iota
	SHA512
	SHA1
	MD5
)

type HashOption

type HashOption func(*HashOptions)

func WithSecret

func WithSecret(s string) HashOption

type HashOptions

type HashOptions struct {
	Secret string
}

type MaskOptions

type MaskOptions struct {
	Prefix int
	Suffix int
	Mask   string
}

type MatchType

type MatchType uint8

MatchType grades how closely two numbers correspond.

const (
	// NoMatch means the numbers cannot be the same.
	NoMatch MatchType = iota
	// ShortNSNMatch means one national number is a suffix of the other, but
	// neither carries enough context to be sure.
	ShortNSNMatch
	// NSNMatch means the national numbers are equal but the calling codes
	// could not both be confirmed.
	NSNMatch
	// ExactMatch means calling code, national number and extension all agree.
	ExactMatch
)

func MatchNumbers

func MatchNumbers(a, b string, opts ...ParseOption) MatchType

MatchNumbers parses both inputs and grades how closely they correspond. Inputs that fail to parse yield NoMatch.

Example
package main

import (
	"fmt"

	"github.com/bakhod1r/phonex"
)

func main() {
	// The same number written nationally and internationally.
	fmt.Println(phonex.MatchNumbers("901234567", "+998901234567", phonex.WithDefaultCountry("UZ")))
	fmt.Println(phonex.MatchNumbers("+998901234567", "+998901234568"))
}
Output:
EXACT_MATCH
NO_MATCH

func (MatchType) String

func (m MatchType) String() string

type Metadata

type Metadata = countries.Metadata

Metadata describes one calling region. It is always handled through a pointer: it carries lazily compiled patterns and must not be copied.

func Countries

func Countries() []*Metadata

Countries returns the metadata of every region, ordered by ISO-3166 alpha-2 code. Non-geographical ranges are not included; see NonGeoEntities.

func Country

func Country(iso2 string) (*Metadata, bool)

Country returns the metadata for an ISO-3166 alpha-2 region code.

func CountryByDialCode

func CountryByDialCode(code string) (*Metadata, bool)

CountryByDialCode returns the main region for a calling code. The code may be given with or without a leading '+'. Regions sharing a code (such as the many behind "+1") resolve to the main one; use RegionsForDialCode for all of them.

func CountryByPhone

func CountryByPhone(number string, opts ...ParseOption) (*Metadata, bool)

CountryByPhone parses a number and returns the metadata of its region.

func NonGeoEntities

func NonGeoEntities() []*Metadata

NonGeoEntities returns the metadata of the non-geographical ranges such as +800 (universal freephone), ordered by calling code.

func RegionsForDialCode

func RegionsForDialCode(code string) []*Metadata

RegionsForDialCode returns every region sharing a calling code, main region first. The returned slice must not be modified.

func SearchCountries

func SearchCountries(query string) []*Metadata

SearchCountries returns the regions whose name or ISO code matches query, ordered by ISO-3166 alpha-2 code. Matching is case-insensitive: an exact alpha-2 or alpha-3 code matches that region alone, otherwise the query is matched as a substring of the country name.

type ParseOption

type ParseOption func(ParseOptions) ParseOptions

ParseOption customises parsing. Options take and return the option struct by value so that applying them never forces it onto the heap, which is what keeps Parse allocation-free.

func WithAlphaCharacters

func WithAlphaCharacters() ParseOption

WithAlphaCharacters enables vanity-number parsing, mapping letters to the digits they share a telephone key with.

func WithDefaultCountry

func WithDefaultCountry(region string) ParseOption

WithDefaultCountry sets the region assumed for numbers written without a calling code, e.g. "90 123 45 67" with "UZ".

Example
package main

import (
	"fmt"

	"github.com/bakhod1r/phonex"
)

func main() {
	p, err := phonex.Parse("(202) 555-0123", phonex.WithDefaultCountry("US"))
	if err != nil {
		panic(err)
	}
	fmt.Println(p.E164())
}
Output:
+12025550123

func WithoutRawInput

func WithoutRawInput() ParseOption

WithoutRawInput drops the original input from the parsed number.

type ParseOptions

type ParseOptions struct {
	// DefaultCountry is the ISO-3166 alpha-2 region assumed when the input
	// carries no calling code.
	DefaultCountry string
	// AllowAlpha maps vanity letters onto their keypad digits, so that
	// "1-800-FLOWERS" parses as "+18003569377".
	AllowAlpha bool
	// KeepRawInput retains the original string on the parsed number. It is
	// on by default; turning it off lets a Phone outlive a large input
	// buffer without pinning it.
	KeepRawInput bool
}

ParseOptions configures parsing. Build one with the With* options.

func DefaultParseOptions

func DefaultParseOptions() ParseOptions

DefaultParseOptions is the configuration Parse uses when given no options.

type Phone

type Phone struct {
	// contains filtered or unexported fields
}

Phone is a parsed phone number.

A Phone stores its digits inline, so parsing into an existing value does not allocate. The zero value is not a usable number; obtain one from Parse or (*Phone).Parse.

A Phone is not safe for concurrent modification, but a Phone that is no longer being parsed into may be read from several goroutines.

func ExampleNumber

func ExampleNumber(region string) (*Phone, bool)

ExampleNumber returns a valid example number for a region, or nil if the metadata carries none. Examples come from libphonenumber and are safe to use in documentation and tests.

func ExampleNumberForType

func ExampleNumberForType(region string, t PhoneType) (*Phone, bool)

ExampleNumberForType returns a valid example number of a given range, or nil when the region does not define that range.

func Generate

func Generate(region string) (*Phone, bool)

Generate returns a random valid number for a region, preferring a mobile number. It reports false when the region is unknown or its metadata carries no example to build on.

Generated numbers are for tests and demos. They are valid in the sense that IsValid accepts them, which means they may well belong to a real subscriber — never dial or message them.

func GenerateForPrefix added in v0.2.0

func GenerateForPrefix(region, prefix string, intn func(n int) int) (*Phone, bool)

GenerateForPrefix returns a valid number for the region whose national number starts with the given digits — an area or operator code, such as "20" for London or "416" for Toronto.

The caller knows the code but not the shape around it: the national number's length varies within a country (London's 20 takes eight more digits where most UK codes take seven), and some plans count the trunk digit as part of the national number, so Rome is "06" here and 6 in an atlas. Both are resolved here, so the prefix may be written either way. It reports false when no shape the plan defines accepts the prefix.

intn supplies the randomness, as in GenerateWith; a nil intn uses the global source.

See Generate for the warning that applies to the result.

func GenerateForType

func GenerateForType(region string, t PhoneType) (*Phone, bool)

GenerateForType returns a random valid number of a given range. It reports false when the region does not define that range.

Where a country does not separate its fixed-line and mobile ranges, asking for either returns a number the metadata calls FixedLineOrMobile, since that is as precise as the plan gets.

See Generate for the warning that applies to the result.

func GenerateWith added in v0.2.0

func GenerateWith(region string, t PhoneType, intn func(n int) int) (*Phone, bool)

GenerateWith is Generate with the randomness supplied by the caller: intn must return a value in [0,n). A program that has to reproduce its output from a seed cannot use the global math/rand that Generate draws from.

r := rand.New(rand.NewSource(1))
p, ok := phonex.GenerateWith("GB", phonex.Mobile, r.Intn)

Pass AnyType for t to accept any range the region defines, which is what Generate does. A nil intn falls back to the global source.

See Generate for the warning that applies to the result.

func Parse

func Parse(input string, opts ...ParseOption) (*Phone, error)

Parse parses a phone number.

The input may be in international form ("+998901234567", "00998901234567"), in national form when a default country is given, or in RFC 3966 form ("tel:+1-202-555-0123;ext=42"). Punctuation and spacing are ignored.

Parse fails when no region can be determined, when the input holds characters a number cannot, or when the digit count is outside the bounds E.164 sets for any number. It deliberately does not judge the number against its own country's rules: a wrong-length or unassigned number still parses, and Possibility and IsValid are what report on it.

Callers that only want numbers they can dial should check IsValid.

Example
package main

import (
	"fmt"

	"github.com/bakhod1r/phonex"
)

func main() {
	p, err := phonex.Parse("+998 90 123 45 67")
	if err != nil {
		panic(err)
	}
	fmt.Println(p.E164(), p.Country(), p.Type(), p.IsValid())
}
Output:
+998901234567 UZ MOBILE true

func ParseBytes

func ParseBytes(input []byte, opts ...ParseOption) (*Phone, error)

ParseBytes parses a phone number held in a byte slice. The slice is not retained unless raw input is kept.

func ParseWith

func ParseWith(input string, options ParseOptions) (*Phone, error)

ParseWith parses a phone number using an explicit option struct.

func (*Phone) AppendE164

func (p *Phone) AppendE164(dst []byte) []byte

AppendE164 appends the E.164 form to dst without allocating.

func (*Phone) CanBeInternationallyDialled

func (p *Phone) CanBeInternationallyDialled() bool

CanBeInternationallyDialled reports whether the number is reachable from outside its own country. Some ranges, such as short service numbers, are domestic only.

func (*Phone) CarrierCode

func (p *Phone) CarrierCode() string

CarrierCode returns the domestic carrier selection code stripped while parsing, or "" if there was none.

func (*Phone) Clone

func (p *Phone) Clone() *Phone

Clone returns an independent copy.

func (*Phone) Country

func (p *Phone) Country() string

Country returns the ISO-3166 alpha-2 region code, or "001" for numbers in a non-geographical range such as +800.

func (*Phone) CountryCode

func (p *Phone) CountryCode() string

CountryCode returns the calling code with a leading '+'.

func (*Phone) CountryName

func (p *Phone) CountryName() string

CountryName returns the English region name.

func (*Phone) DialCode

func (p *Phone) DialCode() string

DialCode returns the calling code without a leading '+'.

func (*Phone) Digits

func (p *Phone) Digits() string

Digits returns every significant digit: the calling code followed by the national significant number, with no '+'.

func (*Phone) E164

func (p *Phone) E164() string

E164 returns the number in E.164 format. Extensions are not part of E.164 and are omitted.

func (*Phone) Equal

func (p *Phone) Equal(other *Phone) bool

Equal reports whether two numbers are the same number, extension included.

func (*Phone) EqualExact

func (p *Phone) EqualExact(other *Phone) bool

EqualExact reports whether two numbers are equal and were written identically. Use it only when the original spelling is itself significant.

func (*Phone) Extension

func (p *Phone) Extension() string

Extension returns the extension digits, or "" if the number has none.

func (*Phone) Format

func (p *Phone) Format(f FormatType) string

Format renders the number in the requested format.

Example
package main

import (
	"fmt"

	"github.com/bakhod1r/phonex"
)

func main() {
	p, _ := phonex.Parse("+442070313000")
	fmt.Println(p.E164())
	fmt.Println(p.International())
	fmt.Println(p.National())
	fmt.Println(p.RFC3966())
	fmt.Println(p.OutOfCountry("US"))
}
Output:
+442070313000
+44 20 7031 3000
020 7031 3000
tel:+44-20-7031-3000
011 44 20 7031 3000

func (*Phone) HasExtension

func (p *Phone) HasExtension() bool

HasExtension reports whether an extension was parsed.

func (*Phone) Hash

func (p *Phone) Hash(algo ...HashAlgorithm) string

Hash returns a hash of the phone number.

func (*Phone) ISO2

func (p *Phone) ISO2() string

ISO2 is an alias for Country.

func (*Phone) ISO3

func (p *Phone) ISO3() string

ISO3 returns the ISO-3166 alpha-3 region code.

func (*Phone) International

func (p *Phone) International() string

International returns the number grouped for international display, e.g. "+44 20 7031 3000".

func (*Phone) IsFixedLineOrMobile

func (p *Phone) IsFixedLineOrMobile() bool

IsFixedLineOrMobile reports whether the number falls in a range the region uses for both fixed-line and mobile numbers.

func (*Phone) IsLandline

func (p *Phone) IsLandline() bool

IsLandline reports whether the number is a fixed-line number.

func (*Phone) IsMobile

func (p *Phone) IsMobile() bool

IsMobile reports whether the number is a mobile number. Numbers in regions that do not separate mobile from fixed-line ranges report false; use Type when that distinction matters.

func (*Phone) IsNonGeographical

func (p *Phone) IsNonGeographical() bool

IsNonGeographical reports whether the number belongs to a global range such as +800 (universal freephone) rather than to a country.

func (*Phone) IsPager

func (p *Phone) IsPager() bool

IsPager reports whether the number is a pager.

func (*Phone) IsPersonalNumber

func (p *Phone) IsPersonalNumber() bool

IsPersonalNumber reports whether the number is a personal ("follow me") number.

func (*Phone) IsPossible

func (p *Phone) IsPossible() bool

IsPossible reports whether the number's length is one the region uses, including lengths that only work when dialled locally. A number can be possible without being valid: "+1 555 555 5555" has the right shape for the US but is not an assigned range.

Use Possibility to tell a full number from a local-only one.

func (*Phone) IsPremiumRate

func (p *Phone) IsPremiumRate() bool

IsPremiumRate reports whether calls to the number are charged at a premium.

func (*Phone) IsSharedCost

func (p *Phone) IsSharedCost() bool

IsSharedCost reports whether the cost of calls is shared with the callee.

func (*Phone) IsTollFree

func (p *Phone) IsTollFree() bool

IsTollFree reports whether calls to the number are free to the caller.

func (*Phone) IsUAN

func (p *Phone) IsUAN() bool

IsUAN reports whether the number is a universal access number.

func (*Phone) IsValid

func (p *Phone) IsValid() bool

IsValid reports whether the number matches one of its region's number ranges. This is the check to use before storing or dialling a number.

Example
package main

import (
	"fmt"

	"github.com/bakhod1r/phonex"
)

func main() {
	// A number can have a plausible shape without being an assigned number.
	possible, _ := phonex.Parse("+1 000 000 0000")
	fmt.Println(possible.IsPossible(), possible.IsValid())
}
Output:
true false

func (*Phone) IsValidForRegion

func (p *Phone) IsValidForRegion(region string) bool

IsValidForRegion reports whether the number is valid and belongs to the given region. Use it when a number must come from one specific country rather than from anywhere sharing its calling code.

func (*Phone) IsVoIP

func (p *Phone) IsVoIP() bool

IsVoIP reports whether the number is a VoIP number.

func (*Phone) IsVoicemail

func (p *Phone) IsVoicemail() bool

IsVoicemail reports whether the number reaches a voicemail service.

func (*Phone) MarshalJSON

func (p *Phone) MarshalJSON() ([]byte, error)

MarshalJSON encodes the number as its E.164 string.

func (*Phone) MarshalText

func (p *Phone) MarshalText() ([]byte, error)

MarshalText encodes the number as its E.164 string, which makes Phone usable as a map key in encoding/json and as a value in any text-based encoder.

func (*Phone) Mask

func (p *Phone) Mask(opts ...MaskOptions) string

func (*Phone) Match

func (p *Phone) Match(other *Phone) MatchType

Match grades how closely two numbers correspond, in the spirit of libphonenumber's isNumberMatch. It is the comparison to use when one side is written in national form and the other in international form.

func (*Phone) Metadata

func (p *Phone) Metadata() *Metadata

Metadata returns the region metadata backing this number. The caller must not modify it.

func (*Phone) MobileNumberPortable

func (p *Phone) MobileNumberPortable() bool

MobileNumberPortable reports whether the region supports number portability, meaning the range a number falls into does not reliably identify its carrier.

func (*Phone) NSN

func (p *Phone) NSN() string

NSN returns the national significant number: the digits after the calling code, with the national (trunk) prefix removed.

func (*Phone) National

func (p *Phone) National() string

National returns the number as it is written inside its own country, e.g. "020 7031 3000".

func (*Phone) NationalDigits

func (p *Phone) NationalDigits() string

NationalDigits is an alias for NSN.

func (*Phone) NationalWithCarrier

func (p *Phone) NationalWithCarrier(carrierCode string) string

NationalWithCarrier returns the national format with a carrier selection code applied, falling back to the plain national format in regions that do not use one.

func (*Phone) OutOfCountry

func (p *Phone) OutOfCountry(fromRegion string) string

OutOfCountry returns the number as it must be dialled from fromRegion, including that region's international dialling prefix. Dialling from the same country yields the national format.

func (*Phone) Parse

func (p *Phone) Parse(input string, opts ...ParseOption) error

Parse parses into the receiver, reusing its storage. Parsing into an existing Phone performs no allocation for numbers in international or plain national form.

func (*Phone) ParseBytes

func (p *Phone) ParseBytes(input []byte, opts ...ParseOption) error

ParseBytes parses into the receiver, reusing its storage.

func (*Phone) ParseWith

func (p *Phone) ParseWith(input string, options ParseOptions) error

ParseWith parses into the receiver using an explicit option struct. It is the form to reach for in hot loops, where building the variadic option slice would itself cost an allocation.

Example
package main

import (
	"fmt"

	"github.com/bakhod1r/phonex"
)

func main() {
	// Reusing a Phone and an option struct keeps a hot loop allocation-free.
	var p phonex.Phone
	opts := phonex.DefaultParseOptions()
	opts.DefaultCountry = "GB"
	opts.KeepRawInput = false

	for _, s := range []string{"020 7031 3000", "07400 123456"} {
		if err := p.ParseWith(s, opts); err != nil {
			continue
		}
		fmt.Println(p.E164(), p.Type())
	}
}
Output:
+442070313000 FIXED_LINE
+447400123456 MOBILE

func (*Phone) Possibility

func (p *Phone) Possibility() Possibility

Possibility reports whether the number has a plausible length, without checking it against the detailed range patterns. It is the cheap check: it never compiles a pattern.

Lengths are judged against the region that owns the calling code rather than the specific region the number resolves to, because a length is a property of the numbering plan as a whole. A Curaçao-length number written with a Bonaire prefix is therefore possible but not valid.

func (*Phone) PossibilityForType

func (p *Phone) PossibilityForType(t PhoneType) Possibility

PossibilityForType is Possibility restricted to one range.

func (*Phone) RFC3966

func (p *Phone) RFC3966() string

RFC3966 returns the number as a "tel:" URI, including any extension.

func (*Phone) RawInput

func (p *Phone) RawInput() string

RawInput returns the string this number was parsed from.

func (*Phone) Scan

func (p *Phone) Scan(value any) error

Scan reads a number from a database column holding text. A NULL column leaves the Phone untouched.

func (*Phone) Source

func (p *Phone) Source() CountryCodeSource

Source reports how the calling code was determined.

func (*Phone) String

func (p *Phone) String() string

String returns the number in E.164 format, with any extension appended in RFC 3966 style. It is meant for logs and tests, not for display to users; use Format for that.

func (*Phone) Timezones

func (p *Phone) Timezones() []string

Timezones returns the IANA time zones of the region. The result must not be modified.

The bundled metadata carries no time zones, so this currently returns nil for every region: libphonenumber does not publish them, and the prefix level data set that would is not vendored here. The accessor exists so that filling internal/metadata/metadata.json is all it takes to enable it.

func (*Phone) Type

func (p *Phone) Type() PhoneType

Type returns the range this number falls into. The result is computed on first use and cached.

func (*Phone) UnmarshalJSON

func (p *Phone) UnmarshalJSON(data []byte) error

UnmarshalJSON parses a JSON string into the number. Anything that does not parse is rejected, so a Phone field can never hold an invalid value.

func (*Phone) UnmarshalText

func (p *Phone) UnmarshalText(text []byte) error

UnmarshalText parses the number from its text form.

func (Phone) Value

func (p Phone) Value() (driver.Value, error)

Value writes the number to a database column as its E.164 string.

type PhoneType

type PhoneType = countries.PhoneType

PhoneType identifies the range a number falls into.

type Possibility

type Possibility uint8

Possibility is the outcome of a length-only check. It distinguishes the reasons a number cannot be valid, which callers use to write precise error messages without re-deriving them.

const (
	// IsPossibleNumber means the length is valid for the region.
	IsPossibleNumber Possibility = iota
	// IsPossibleLocalOnly means the length is only valid when dialled from
	// inside the same local area.
	IsPossibleLocalOnly
	// InvalidCountryCode means no region uses the calling code.
	InvalidCountryCode
	// TooShort means the number has fewer digits than any valid length.
	TooShort
	// InvalidLength means the digit count falls between two valid lengths.
	InvalidLength
	// TooLong means the number has more digits than any valid length.
	TooLong
)

func (Possibility) String

func (p Possibility) String() string

type Result

type Result struct {
	Phone *Phone
	Error error
}

Result pairs a parsed number with the error that parsing it produced. Exactly one of the two fields is set.

func ParseMany

func ParseMany(numbers []string, opts ...ParseOption) []Result

ParseMany parses each input, returning one Result per input in the same order. Inputs that fail to parse do not stop the others.

type ValidationError

type ValidationError struct {
	Code    ErrorCode
	Message string
}

func (*ValidationError) Error

func (e *ValidationError) Error() string

func (*ValidationError) Is

func (e *ValidationError) Is(target error) bool

Directories

Path Synopsis
Package carrier reports the network a phone number was issued on.
Package carrier reports the network a phone number was issued on.
cmd
phonexgen command
Command phonexgen generates countries/generated.go from Google's libphonenumber PhoneNumberMetadata.xml.
Command phonexgen generates countries/generated.go from Google's libphonenumber PhoneNumberMetadata.xml.
Package countries holds the phone-number metadata generated from Google's libphonenumber PhoneNumberMetadata.xml.
Package countries holds the phone-number metadata generated from Google's libphonenumber PhoneNumberMetadata.xml.
Package geocoding reports the area a phone number was issued in.
Package geocoding reports the area a phone number was issued in.
internal
lazyre
Package lazyre provides a regular expression that is compiled on first use.
Package lazyre provides a regular expression that is compiled on first use.
prefixmap
Package prefixmap looks up the longest number prefix that a data set knows about.
Package prefixmap looks up the longest number prefix that a data set knows about.
Package shortnumber answers questions about short numbers: the three- to six-digit codes such as 112, 911 or 10086 that only work inside one country and carry no calling code.
Package shortnumber answers questions about short numbers: the three- to six-digit codes such as 112, 911 or 10086 that only work inside one country and carry no calling code.
Package timezone reports the IANA time zones a phone number reaches.
Package timezone reports the IANA time zones a phone number reaches.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL