Phone numbers as data: validate, inspect, search, generate.
A pragmatic, data-oriented Clojure wrapper around Google's Libphonenumber for
validating, inspecting, formatting, searching text, and generating phone numbers. It
treats phone numbers as data: most functions accept strings, numbers, PhoneNumber
instances, or keyword-keyed maps, and return values that are pleasant to pipe
through.
Data-first, polymorphic API: phone numbers can be expressed as numbers, strings,
PhoneNumber objects, or maps.
Rich, keyword-keyed info maps with optional namespace inference
(e.g. a key can be :phone-number/type or just :type;
a type can be :phone-number.type/mobile or just :mobile).
Optional enrichment with carrier, location, and time zone data (via libphonenumber add-on modules), exposed through the same data-first API.
Stable options-map API for text searching (phone-number.core/find-numbers-opts),
backed by lazy maps (LazyMap) so expensive info can be computed on demand.
Built-in support for short numbers (e.g. emergency numbers): validation, reachability, and expected cost class.
Sample generation that returns both the PhoneNumber and reproducibility/debug data
(including :phone-number.sample/random-seed and sampler stats).
Specs with generators for generative testing, plus structured errors via ex-info
with :phone-number/error in ex-data.
If your program processes a lot of phone numbers and your strategy is to keep them in
a native format (a result of calling phone-number.core/number), then be aware that,
by default, all created PhoneNumber objects will have their raw input stored
internally. This affects comparison in a way that the object representing
a number +442920183133 will not be equal to the object representing the same
number but with spaces (+44 2920 183 133). This is because the equality test is
based, among other things, on raw input values, which are also used to generate each
object's hash code.
To work around this, you have two options:
Use phone-number.core/number-noraw on input data to parse numbers without
preserving raw inputs.
Use phone-number.core/number-noraw on existing objects to create raw-input-free
copies.
Optionally, phone-number.core/number-optraw can also be used (especially in
processing pipelines) to preserve raw input only when the created object is
initialized from an existing one (an instance of PhoneNumber). For other argument
types, the protocol method behaves like number-noraw.
Some functions validate their arguments and may throw clojure.lang.ExceptionInfo on
invalid inputs. Such exceptions include :phone-number/error in ex-data, which can
be used for error classification.
(require '[phone-number.core :as phone])
;; region taken from a phone number
;; using system's default locale
(phone/info "+44 29 2018 3133")
{:phone-number/country-code 44,
:phone-number/dialing-region :phone-number.region/gb,
:phone-number.dialing-region/defaulted? false,
:phone-number.dialing-region/derived? true,
:phone-number.dialing-region/valid-for? true,
:phone-number/geographical? true,
:phone-number/location "Cardiff",
:phone-number/possible? true,
:phone-number/region :phone-number.region/gb,
:phone-number/type :phone-number.type/fixed-line,
:phone-number/valid? true,
:phone-number.format/e164 "+442920183133",
:phone-number.format/international "+44 29 2018 3133",
:phone-number.format/national "029 2018 3133",
:phone-number.format/raw-input "+44 29 2018 3133",
:phone-number.format/rfc3966 "tel:+44-29-2018-3133",
:phone-number.tz-format/full-standalone '("Greenwich Mean Time"),
:phone-number.tz-format/id '("Europe/London"),
:phone-number.tz-format/short-standalone '("GMT"),
:phone.number.short/possible? false,
:phone.number.short/valid? false}
;; region passed as an argument
;; locale setting passed as an argument
(phone/info "601 100 601" :pl :pl)
{:phone-number/country-code 48,
:phone-number/carrier "Plus",
:phone-number/dialing-region :phone-number.region/pl,
:phone-number.dialing-region/defaulted? false,
:phone-number.dialing-region/derived? true,
:phone-number.dialing-region/valid-for? true,
:phone-number/geographical? false,
:phone-number/location "Polska",
:phone-number/possible? true,
:phone-number/region :phone-number.region/pl,
:phone-number/type :phone-number.type/mobile,
:phone-number/valid? true,
:phone-number.format/e164 "+48601100601",
:phone-number.format/international "+48 601 100 601",
:phone-number.format/national "601 100 601",
:phone-number.format/raw-input "601 100 601",
:phone-number.format/rfc3966 "tel:+48-601-100-601",
:phone-number.tz-format/full-standalone '("Czas środkowoeuropejski"),
:phone-number.tz-format/id '("Europe/Warsaw"),
:phone-number.tz-format/short-standalone '("CET"),
:phone.number.short/possible? false,
:phone.number.short/valid? false}
(phone/info "8081 570001" :gb :en)
{:phone-number/country-code 44,
:phone-number/dialing-region :phone-number.region/gb,
:phone-number.dialing-region/defaulted? false,
:phone-number.dialing-region/derived? true,
:phone-number.dialing-region/valid-for? true,
:phone-number/geographical? false,
:phone-number/possible? true,
:phone-number/region :phone-number.region/gb,
:phone-number/type :phone-number.type/toll-free,
:phone-number/valid? true,
:phone-number.format/e164 "+448081570001",
:phone-number.format/international "+44 808 157 0001",
:phone-number.format/national "0808 157 0001",
:phone-number.format/raw-input "8081 570001",
:phone-number.format/rfc3966 "tel:+44-808-157-0001",
:phone-number.tz-format/full-standalone '("Greenwich Mean Time" "British Time"),
:phone-number.tz-format/id '("Europe/Guernsey"
"Europe/Isle_of_Man"
"Europe/Jersey"
"Europe/London"),
:phone-number.tz-format/short-standalone '("GMT" "BT"),
:phone.number.short/possible? false,
:phone.number.short/valid? false}
(require '[phone-number.core :as phone])
(phone/valid? 8081570001 :gb) ; => true
(phone/valid? "+448081570001") ; => true
(phone/valid? 8081570001 :pl) ; => false
(phone/valid? "8081570001") ; => false
(phone/possible? "8081570001") ; => false
(phone/possible? "8081570001" :gb) ; => true
(phone/possible? "8081570001" :pl) ; => true
(require '[phone-number.core :as phone])
(->> (phone/find-numbers-opts
"Call me at +44 808 157 0001!" {:region-code :gb
:leniency :valid
:max-tries 1})
first
(into {}) ;; materialize lazy-map (also forces :phone-number/info)
(dissoc :phone-number/number))
;; If you don't want the info map to be generated at all:
(->> (phone/find-numbers-opts
"Call me at +44 808 157 0001!" {:region-code :gb
:max-tries 1
:locale-specification false})
first
(into {})
(dissoc :phone-number/number))
(require '[phone-number.core :as phone])
phone/formats
#{:phone-number.format/e164
:phone-number.format/international
:phone-number.format/national
:phone-number.format/raw-input
:phone-number.format/rfc3966}
phone/types
#{:phone-number.type/fixed-line
:phone-number.type/fixed-line-or-mobile
:phone-number.type/mobile
:phone-number.type/pager
:phone-number.type/personal
:phone-number.type/premium-rate
:phone-number.type/shared-cost
:phone-number.type/toll-free
:phone-number.type/uan
:phone-number.type/unknown
:phone-number.type/voicemail
:phone-number.type/voip}
(phone/generate)
{:phone-number/info
{:phone-number/country-code 213,
:phone-number/geographical? false,
:phone-number/possible? true,
:phone-number/region :phone-number.region/dz,
:phone-number/type :phone-number.type/unknown,
:phone-number/valid? false,
:phone-number/dialing-region :phone-number.region/dz,
:phone-number.dialing-region/defaulted? false,
:phone-number.dialing-region/derived? true,
:phone-number.dialing-region/valid-for? false,
:phone-number.format/e164 "+213181525997",
:phone-number.format/international "+213 181525997",
:phone-number.format/national "181525997",
:phone-number.format/rfc3966 "tel:+213-181525997",
:phone.number.short/possible? false,
:phone.number.short/valid? false},
:phone-number/number #<com.google.i18n.phonenumbers.Phonenumber$PhoneNumber@3edea9e6>,
:phone-number.sample/digits ["+213" nil "181525997"],
:phone-number.sample/hits 10,
:phone-number.sample/max-samples 1000,
:phone-number.sample/random-seed 7521527664400716800,
:phone-number.sample/samples 11}
(require [phone-number.spec :as spec]
[clojure.spec.alpha :as s]
[clojure.spec.gen.alpha :as gen])
(gen/generate (s/gen :phone-number/valid))
{:phone-number/info #delay[{:status :pending, :val nil} 0x3810d15d],
:phone-number/number #<com.google.i18n.phonenumbers.Phonenumber$PhoneNumber@79cc08fb>,
:phone-number.sample/digits ["+7" nil "937627908"],
:phone-number.sample/hits 11,
:phone-number.sample/max-samples 150,
:phone-number.sample/random-seed 7581363778716192180,
:phone-number.sample/samples 27}
(gen/generate (s/gen (s/and :phone-number/possible :phone-number/invalid)))
{:phone-number/info #delay[{:status :pending, :val nil} 0x41c0e225],
:phone-number/number #<com.google.i18n.phonenumbers.Phonenumber$PhoneNumber@36a74c18>,
:phone-number.sample/digits ["+84" nil "0270454"],
:phone-number.sample/hits 8,
:phone-number.sample/max-samples 200,
:phone-number.sample/random-seed -9105741821593959780,
:phone-number.sample/samples 12}
And more…
To use phone-number in your project, add the following to the dependencies section of
project.clj or build.boot:
[io.randomseed/phone-number "3.23-0"]
For deps.edn, add the following as an element of a map under the :deps or
:extra-deps key:
io.randomseed/phone-number {:mvn/version "3.23-0"}
Additionally, if you want to use the specs and generators provided by phone-number, add the following to your development profile:
org.clojure/spec.alpha {:mvn/version "0.6.249"}
org.clojure/test.check {:mvn/version "1.1.3"}
You can also download the JAR from Clojars.
Full documentation including usage examples is available at:
Copyright © 2020–2026 Paweł Wilk
Phone-number is copyrighted software owned by Paweł Wilk (pw@gnu.org). You may redistribute and/or modify this software as long as you comply with the terms of the GNU Lesser General Public License (version 3).
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
make docs
make jar
make pom
make sig
make deploy
bin/repl
Starts a REPL and an nREPL server (the port is stored in .nrepl-port).
Can you improve this documentation?Edit on GitHub
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |