country-geo-data
v1.0.0
Published
Country names & ISO codes, calling codes, capitals with coordinates, and IANA timezones with coordinates — one small dependency-free dataset with lookup utilities.
Downloads
146
Maintainers
Readme
country-geo-data
Country names & ISO codes, calling codes, capitals with coordinates, and IANA timezones with coordinates — one small, dependency-free dataset with lookup utilities. Works with both import and require.
Install
npm install country-geo-dataQuick start
import {
getCountryByAlpha2,
alpha2ToAlpha3,
getCapital,
getCountriesByDialCode,
getTimezonesByCountry,
getTimezoneOffset,
convertTime,
} from 'country-geo-data';
getCountryByAlpha2('BD');
// { name: 'Bangladesh', alpha2: 'BD', alpha3: 'BGD', numeric: '050',
// dialCodes: ['+880'], capital: 'Dhaka', capitalLatLng: [23.7104, 90.40744],
// timezones: ['Asia/Dhaka'], official: true }
alpha2ToAlpha3('BD'); // 'BGD'
getCapital('FR'); // { name: 'Paris', latLng: [48.85341, 2.3488] }
getCountriesByDialCode('+1'); // [US, CA, ...every NANP member]
getTimezonesByCountry('US'); // full Timezone objects for all 29 US zones
getTimezoneOffset('Asia/Dhaka'); // '+06:00' (computed live, DST-aware)
// "A meeting at 15:00 in Dhaka — what time is that in New York?"
convertTime({ year: 2024, month: 7, day: 1, hour: 15, minute: 0 }, 'Asia/Dhaka', 'America/New_York');
// { year: 2024, month: 7, day: 1, hour: 5, minute: 0, second: 0, offset: '-04:00' }CommonJS works identically:
const { getCountryByAlpha2 } = require('country-geo-data');API
Lookups are O(1) — every exact-key function (alpha2/alpha3/numeric/dial code) reads from a Map built once when the module loads, not a scan over the 250 records. Only genuinely search-shaped functions (searchCountries, fuzzy name match, the two "nearest" geo functions) scan the dataset, and at this size (250 countries, 313 timezones) that's still sub-millisecond.
Official countries vs. territories
This package tracks 250 entries total: the 193 UN member states (official: true) plus ~57 other entries — dependencies, territories, and the 2 UN observer states, Vatican City and Palestine (official: false). General-purpose functions default to official countries only — pass { includeTerritories: true } to widen them. Specific, intentional lookups (by alpha2/alpha3/numeric/exact name) are unaffected by this and always search everything, since asking for "PR" is a deliberate ask regardless of UN status.
| Function | Default scope | With { includeTerritories: true } |
|---|---|---|
| getAllCountries() | 193 official | 250 (all tracked entries) |
| searchCountries(query) | 193 official | 250 |
| getCountryByName(name, { fuzzy: true }) | 193 official | 250 |
| getCountriesByDialCode(code) | 250 (opt out with { includeTerritories: false }) | — |
| getCountryByAlpha2/Alpha3/Numeric, exact getCountryByName | unaffected — always searches all 250 | — |
Countries
| Function | Description |
|---|---|
| getAllCountries({ includeTerritories }?) | Country records — 193 official by default, 250 with the option. |
| getOfficialCountries() | Shorthand for getAllCountries() — 193 UN member states. |
| getAllCountriesIncludingTerritories() | Shorthand for getAllCountries({ includeTerritories: true }) — all 250. |
| getCountryByAlpha2(code) | Lookup by ISO 3166-1 alpha-2 (case-insensitive). |
| getCountryByAlpha3(code) | Lookup by ISO 3166-1 alpha-3 (case-insensitive). |
| getCountryByNumeric(code) | Lookup by ISO 3166-1 numeric code. |
| getCountryByName(name, { fuzzy, includeTerritories }?) | Lookup by common name; fuzzy: true also matches substrings. |
| searchCountries(query, { includeTerritories }?) | All countries whose name contains query — for autocomplete. |
| getCountriesByDialCode(code, { includeTerritories }?) | All countries sharing a calling code (e.g. +1 → every NANP member, territories included by default). |
| getCapital(alpha2) | { name, latLng } for a country's capital. |
| getCountriesByTimezone(tzId) | All countries observing a given IANA timezone. |
| isValidAlpha2(code) / isValidAlpha3(code) | Guard helpers. |
| isOfficialCountry(alpha2) | True if the code is a UN member state, not a territory/dependency. |
| alpha2ToAlpha3(code) / alpha3ToAlpha2(code) | Direct code conversion, returns the bare code string. |
| getCountryByCoordinates(lat, lng) | Nearest-capital heuristic — see Accuracy notes. |
Timezones
| Function | Description |
|---|---|
| getAllTimezones() | All 313 IANA timezones observed by at least one country. |
| getTimezoneById(tzId) | Lookup by IANA id, e.g. "Asia/Dhaka" (case-sensitive, as IANA ids are). |
| getCountriesForTimezone(tzId) | Alpha-2 codes of countries observing a timezone. |
| getTimezonesByCountry(alpha2) | Full Timezone objects for a country (a country can span many). |
| getTimezoneOffset(tzId, date?) | Current UTC offset (e.g. "+06:00"), computed live so DST is always correct. |
| getWallTimeInTimezone(date, tzId) | Formats a real instant as its wall-clock reading in tzId. |
| convertTime(wallTime, fromTzId, toTzId) | Converts a wall-clock reading in one timezone to another — see below. |
| findNearestTimezone(lat, lng) | Nearest timezone by distance — see Accuracy notes. |
A note on convertTime: it takes a wall-clock reading, not a UTC instant — { year, month, day, hour, minute, second? } (month is 1-12), or anything new Date(...) accepts (only its calendar/clock digits are used, its own timezone is ignored). This matches how people actually think about it: "3pm in Dhaka" converts to "5am in New York" regardless of what timezone the server computing this happens to be in. fromTzId/toTzId must be an id from getAllTimezones() — generic aliases like "UTC" aren't included since the dataset (IANA's zone1970.tab) only lists zones tied to a country; use a real zero-offset zone like "Africa/Abidjan" if you need a UTC-like reference point.
Geo
| Function | Description |
|---|---|
| distanceBetween([lat1,lng1], [lat2,lng2]) | Great-circle distance in kilometers (haversine). |
Full type definitions ship in types/index.d.ts for both JS (via editor intellisense) and TypeScript consumers.
Data shape
data/countries.json — array, one entry per country:
{
name: string;
alpha2: string; // ISO 3166-1 alpha-2, e.g. "BD"
alpha3: string; // ISO 3166-1 alpha-3, e.g. "BGD"
numeric: string | null; // ISO 3166-1 numeric, e.g. "050"
dialCodes: string[]; // e.g. ["+880"]; ["+1"] shared by all NANP members
capital: string | null;
capitalLatLng: [number, number] | null;
timezones: string[]; // IANA ids, ref timezones.json
official: boolean; // true for the 193 UN member states
}data/timezones.json — object keyed by IANA timezone id:
{
"Asia/Dhaka": {
id: "Asia/Dhaka";
latLng: [23.716667, 90.416667];
countries: ["BD"];
}
}Both files are also importable directly if you don't need the utility functions:
import countries from 'country-geo-data/data/countries.json' with { type: 'json' };Data sources & licensing
This package deliberately does not hand-type ~250 countries × 6 fields, but every field is traceable to a primary/authoritative source, fetched by the scripts in scripts/fetch/ and merged by scripts/build.cjs:
| Field(s) | Source | License |
|---|---|---|
| Name, alpha2/alpha3/numeric codes, dial code, capital name | mledoze/countries (itself sourced from ISO 3166-1, ITU-T E.164, and UN data) | MIT |
| IANA timezone ids, per-timezone lat/lng, country↔timezone mapping | IANA Time Zone Database, zone1970.tab | Public domain |
| Capital city coordinates | GeoNames, cities1000.zip | CC BY 4.0 — per GeoNames' terms, this project attributes GeoNames as a data source |
Run npm run refresh-data to re-fetch everything from source and rebuild data/*.json. This is a maintainer-only step — it is not run on npm install for consumers.
Known data gaps
A handful of edge cases are intentionally left as null/empty rather than guessed, and are treated as expected in scripts/validate.cjs and the test suite:
- No capital coordinates:
AQ(Antarctica),BV(Bouvet Island),HM(Heard & McDonald Islands),UM(US Minor Outlying Islands) — no permanent population, no capital exists.EH(Western Sahara),IO(British Indian Ocean Territory) — the capital name is known but GeoNames records it under a name/spelling this build doesn't currently resolve to coordinates.TK(Tokelau) — has no single fixed capital in reality. - No timezone mapped:
BV,HM(uninhabited, as above), andXK(Kosovo) — Kosovo has no officially assigned ISO 3166 code, so it's absent from IANA's own country-code list; in practice it observesEurope/Belgrade. - South Africa and other countries with multiple constitutional capitals report only the administrative capital (Pretoria), matching what GeoNames flags as the primary seat of government.
officialcorrection:mledoze/countriesmarks Vatican City (VA) as a UN member (unMember: true), which is factually wrong — the Holy See is a UN Permanent Observer State, not a member.scripts/build.cjsoverrides this one entry so theofficialcount is exactly 193; seeUN_MEMBER_OVERRIDESin that file if a similar correction is ever needed for another entry.
Accuracy notes
getCountryByCoordinates and findNearestTimezone are proximity heuristics, not authoritative geocoding — this package ships point data (capitals, timezone representative points), not country border polygons. A coordinate near a shared border, or in a large country whose capital sits far from that point (e.g. Brazil, Australia, Russia), can resolve to the wrong neighbor. Use a dedicated point-in-polygon/boundary dataset if you need boundary-accurate results.
Development
npm run refresh-data # re-fetch from source + rebuild data/*.json + validate
npm run build # bundle src/ into dist/ (ESM + CJS) via tsup
npm test # run the test suiteProject layout:
scripts/fetch/ one script per upstream source
scripts/build.cjs merges raw sources into data/*.json (the only merge point)
scripts/validate.cjs data integrity checks, run in prepublishOnly
data/ shipped, built JSON — the actual product
src/ utility functions (ESM source)
types/ hand-written TypeScript definitions
test/ data integrity + unit tests (node:test)
dist/ built output (generated, not committed)License
MIT — see LICENSE. Data is re-derived from the sources listed above; see their licenses for terms governing the underlying facts (GeoNames requires attribution, which this README provides).
