zip2info 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- zip2info-1.0.0/.github/workflows/publish.yml +45 -0
- zip2info-1.0.0/.gitignore +18 -0
- zip2info-1.0.0/.python-version +1 -0
- zip2info-1.0.0/LICENSE +21 -0
- zip2info-1.0.0/PKG-INFO +118 -0
- zip2info-1.0.0/README.md +109 -0
- zip2info-1.0.0/pyproject.toml +25 -0
- zip2info-1.0.0/src/zip2info/__init__.py +95 -0
- zip2info-1.0.0/src/zip2info/_data.py +38710 -0
- zip2info-1.0.0/src/zip2tz/py.typed +0 -0
- zip2info-1.0.0/tests/smoke_test.py +27 -0
- zip2info-1.0.0/tests/test_coordinates.py +67 -0
- zip2info-1.0.0/tests/test_timezone.py +166 -0
- zip2info-1.0.0/uv.lock +151 -0
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
build:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
- name: Install uv
|
|
15
|
+
uses: astral-sh/setup-uv@v4
|
|
16
|
+
|
|
17
|
+
- name: Build package
|
|
18
|
+
run: uv build
|
|
19
|
+
|
|
20
|
+
- name: Upload artifacts
|
|
21
|
+
uses: actions/upload-artifact@v4
|
|
22
|
+
with:
|
|
23
|
+
name: dist
|
|
24
|
+
path: dist/
|
|
25
|
+
|
|
26
|
+
publish:
|
|
27
|
+
needs: build
|
|
28
|
+
runs-on: ubuntu-latest
|
|
29
|
+
environment:
|
|
30
|
+
name: pypi
|
|
31
|
+
url: https://pypi.org/p/zip2info
|
|
32
|
+
permissions:
|
|
33
|
+
id-token: write
|
|
34
|
+
steps:
|
|
35
|
+
- name: Download artifacts
|
|
36
|
+
uses: actions/download-artifact@v4
|
|
37
|
+
with:
|
|
38
|
+
name: dist
|
|
39
|
+
path: dist/
|
|
40
|
+
|
|
41
|
+
- name: Install uv
|
|
42
|
+
uses: astral-sh/setup-uv@v4
|
|
43
|
+
|
|
44
|
+
- name: Publish to PyPI
|
|
45
|
+
run: uv publish
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
|
|
12
|
+
# Source data (not shipped in the package)
|
|
13
|
+
/data/*
|
|
14
|
+
!/data/coordinate_overrides.json
|
|
15
|
+
|
|
16
|
+
# Internal scripts for data processing
|
|
17
|
+
/scripts/*
|
|
18
|
+
!/scripts/compile_data.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.13
|
zip2info-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
zip2info-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: zip2info
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Fast US ZIP code lookups for timezone and geocoordinates
|
|
5
|
+
Author-email: kylehogate <kyle.holgate@gmail.com>
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
|
|
10
|
+
# zip2info
|
|
11
|
+
|
|
12
|
+
Fast, zero-dependency US ZIP code lookups for Python.
|
|
13
|
+
|
|
14
|
+
- **38,000+ zip codes** mapped to IANA timezones
|
|
15
|
+
- **Geocoordinates** for ZIP centroids (latitude/longitude)
|
|
16
|
+
- **Zero dependencies** — pure Python, works everywhere
|
|
17
|
+
- **O(1) lookup** — instant hash table lookups, no database or file I/O
|
|
18
|
+
- **Type-annotated** — full type hints included
|
|
19
|
+
|
|
20
|
+
## Installation
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install zip2info
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
uv add zip2info
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Usage
|
|
33
|
+
|
|
34
|
+
```python
|
|
35
|
+
import zip2info
|
|
36
|
+
|
|
37
|
+
# Timezone lookup
|
|
38
|
+
tz = zip2info.timezone("90210")
|
|
39
|
+
print(tz) # America/Los_Angeles
|
|
40
|
+
|
|
41
|
+
# Coordinate lookup (latitude, longitude)
|
|
42
|
+
coords = zip2info.coordinates("90210")
|
|
43
|
+
print(coords) # (34.0901, -118.4065)
|
|
44
|
+
|
|
45
|
+
# Rich metadata
|
|
46
|
+
record = zip2info.info("10001")
|
|
47
|
+
print(record.timezone) # America/New_York
|
|
48
|
+
print(record.latitude) # 40.7484
|
|
49
|
+
print(record.longitude) # -73.9967
|
|
50
|
+
|
|
51
|
+
# Works with integers too
|
|
52
|
+
tz = zip2info.timezone(60601)
|
|
53
|
+
print(tz) # America/Chicago
|
|
54
|
+
|
|
55
|
+
# Returns None if zip code not found
|
|
56
|
+
tz = zip2info.timezone("00000")
|
|
57
|
+
print(tz) # None
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
## API
|
|
61
|
+
|
|
62
|
+
### `timezone(zipcode: str | int) -> str | None`
|
|
63
|
+
|
|
64
|
+
Returns the IANA timezone string for a US ZIP code.
|
|
65
|
+
|
|
66
|
+
### `coordinates(zipcode: str | int) -> tuple[float, float] | None`
|
|
67
|
+
|
|
68
|
+
Returns `(latitude, longitude)` for a US ZIP code, or `None` if unavailable.
|
|
69
|
+
|
|
70
|
+
### `info(zipcode: str | int) -> ZipInfo | None`
|
|
71
|
+
|
|
72
|
+
Returns a `ZipInfo` dataclass with `zipcode`, `timezone`, `latitude`, and `longitude`.
|
|
73
|
+
Returns `None` when timezone or coordinate data is unavailable.
|
|
74
|
+
|
|
75
|
+
## Data sources
|
|
76
|
+
|
|
77
|
+
- **Timezones**: bundled US ZIP-to-timezone dataset (same coverage as the original `zip2tz` project)
|
|
78
|
+
- **Coordinates** (merged in priority order):
|
|
79
|
+
1. [GeoNames](https://www.geonames.org/) US postal codes ([CC BY 4.0](https://creativecommons.org/licenses/by/4.0/))
|
|
80
|
+
2. U.S. Census ZCTA gazetteer centroids (public domain)
|
|
81
|
+
3. Manual overrides in `data/coordinate_overrides.json`
|
|
82
|
+
4. 3-digit ZIP prefix centroid fallback for remaining USPS-only ZIPs (PO boxes, unique entity codes, etc.)
|
|
83
|
+
|
|
84
|
+
Regenerate packaged data with:
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
python scripts/compile_data.py
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Migration from zip2tz
|
|
91
|
+
|
|
92
|
+
`zip2info` is a new PyPI package and import path. Existing `zip2tz` installs are unchanged.
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
# before
|
|
96
|
+
import zip2tz
|
|
97
|
+
zip2tz.timezone("90210")
|
|
98
|
+
|
|
99
|
+
# after
|
|
100
|
+
import zip2info
|
|
101
|
+
zip2info.timezone("90210")
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Coverage
|
|
105
|
+
|
|
106
|
+
Covers all 50 US states plus DC, including Alaska, Hawaii, Indiana county-level boundaries, and other edge cases.
|
|
107
|
+
|
|
108
|
+
## Data accuracy
|
|
109
|
+
|
|
110
|
+
Timezone and coordinate mappings are provided on a **best-effort basis**. ZIP codes can span multiple timezones or areas, and coordinate points are centroids/estimates rather than exact address locations.
|
|
111
|
+
|
|
112
|
+
If you find incorrect data, please open an issue with the ZIP code, expected value, and a source if available.
|
|
113
|
+
|
|
114
|
+
## License
|
|
115
|
+
|
|
116
|
+
MIT License — see [LICENSE](LICENSE) for details.
|
|
117
|
+
|
|
118
|
+
Coordinate data includes GeoNames material licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
|
zip2info-1.0.0/README.md
ADDED
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# zip2info
|
|
2
|
+
|
|
3
|
+
Fast, zero-dependency US ZIP code lookups for Python.
|
|
4
|
+
|
|
5
|
+
- **38,000+ zip codes** mapped to IANA timezones
|
|
6
|
+
- **Geocoordinates** for ZIP centroids (latitude/longitude)
|
|
7
|
+
- **Zero dependencies** — pure Python, works everywhere
|
|
8
|
+
- **O(1) lookup** — instant hash table lookups, no database or file I/O
|
|
9
|
+
- **Type-annotated** — full type hints included
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install zip2info
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv add zip2info
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
import zip2info
|
|
27
|
+
|
|
28
|
+
# Timezone lookup
|
|
29
|
+
tz = zip2info.timezone("90210")
|
|
30
|
+
print(tz) # America/Los_Angeles
|
|
31
|
+
|
|
32
|
+
# Coordinate lookup (latitude, longitude)
|
|
33
|
+
coords = zip2info.coordinates("90210")
|
|
34
|
+
print(coords) # (34.0901, -118.4065)
|
|
35
|
+
|
|
36
|
+
# Rich metadata
|
|
37
|
+
record = zip2info.info("10001")
|
|
38
|
+
print(record.timezone) # America/New_York
|
|
39
|
+
print(record.latitude) # 40.7484
|
|
40
|
+
print(record.longitude) # -73.9967
|
|
41
|
+
|
|
42
|
+
# Works with integers too
|
|
43
|
+
tz = zip2info.timezone(60601)
|
|
44
|
+
print(tz) # America/Chicago
|
|
45
|
+
|
|
46
|
+
# Returns None if zip code not found
|
|
47
|
+
tz = zip2info.timezone("00000")
|
|
48
|
+
print(tz) # None
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## API
|
|
52
|
+
|
|
53
|
+
### `timezone(zipcode: str | int) -> str | None`
|
|
54
|
+
|
|
55
|
+
Returns the IANA timezone string for a US ZIP code.
|
|
56
|
+
|
|
57
|
+
### `coordinates(zipcode: str | int) -> tuple[float, float] | None`
|
|
58
|
+
|
|
59
|
+
Returns `(latitude, longitude)` for a US ZIP code, or `None` if unavailable.
|
|
60
|
+
|
|
61
|
+
### `info(zipcode: str | int) -> ZipInfo | None`
|
|
62
|
+
|
|
63
|
+
Returns a `ZipInfo` dataclass with `zipcode`, `timezone`, `latitude`, and `longitude`.
|
|
64
|
+
Returns `None` when timezone or coordinate data is unavailable.
|
|
65
|
+
|
|
66
|
+
## Data sources
|
|
67
|
+
|
|
68
|
+
- **Timezones**: bundled US ZIP-to-timezone dataset (same coverage as the original `zip2tz` project)
|
|
69
|
+
- **Coordinates** (merged in priority order):
|
|
70
|
+
1. [GeoNames](https://www.geonames.org/) US postal codes ([CC BY 4.0](https://creativecommons.org/licenses/by/4.0/))
|
|
71
|
+
2. U.S. Census ZCTA gazetteer centroids (public domain)
|
|
72
|
+
3. Manual overrides in `data/coordinate_overrides.json`
|
|
73
|
+
4. 3-digit ZIP prefix centroid fallback for remaining USPS-only ZIPs (PO boxes, unique entity codes, etc.)
|
|
74
|
+
|
|
75
|
+
Regenerate packaged data with:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
python scripts/compile_data.py
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Migration from zip2tz
|
|
82
|
+
|
|
83
|
+
`zip2info` is a new PyPI package and import path. Existing `zip2tz` installs are unchanged.
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
# before
|
|
87
|
+
import zip2tz
|
|
88
|
+
zip2tz.timezone("90210")
|
|
89
|
+
|
|
90
|
+
# after
|
|
91
|
+
import zip2info
|
|
92
|
+
zip2info.timezone("90210")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Coverage
|
|
96
|
+
|
|
97
|
+
Covers all 50 US states plus DC, including Alaska, Hawaii, Indiana county-level boundaries, and other edge cases.
|
|
98
|
+
|
|
99
|
+
## Data accuracy
|
|
100
|
+
|
|
101
|
+
Timezone and coordinate mappings are provided on a **best-effort basis**. ZIP codes can span multiple timezones or areas, and coordinate points are centroids/estimates rather than exact address locations.
|
|
102
|
+
|
|
103
|
+
If you find incorrect data, please open an issue with the ZIP code, expected value, and a source if available.
|
|
104
|
+
|
|
105
|
+
## License
|
|
106
|
+
|
|
107
|
+
MIT License — see [LICENSE](LICENSE) for details.
|
|
108
|
+
|
|
109
|
+
Coordinate data includes GeoNames material licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/).
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "zip2info"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "Fast US ZIP code lookups for timezone and geocoordinates"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "kylehogate", email = "kyle.holgate@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.10"
|
|
10
|
+
dependencies = []
|
|
11
|
+
|
|
12
|
+
[build-system]
|
|
13
|
+
requires = ["hatchling"]
|
|
14
|
+
build-backend = "hatchling.build"
|
|
15
|
+
|
|
16
|
+
[tool.hatch.build.targets.sdist]
|
|
17
|
+
exclude = [
|
|
18
|
+
"scripts/**",
|
|
19
|
+
"data/**",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
[dependency-groups]
|
|
23
|
+
dev = [
|
|
24
|
+
"pytest>=8.4.2",
|
|
25
|
+
]
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Optional
|
|
3
|
+
|
|
4
|
+
from zip2info._data import TIMEZONES, ZIP_INFO
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass(frozen=True, slots=True)
|
|
8
|
+
class ZipInfo:
|
|
9
|
+
"""Metadata for a US ZIP code."""
|
|
10
|
+
|
|
11
|
+
zipcode: int
|
|
12
|
+
timezone: str
|
|
13
|
+
latitude: float
|
|
14
|
+
longitude: float
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _normalize_zipcode(zipcode: str | int) -> Optional[int]:
|
|
18
|
+
try:
|
|
19
|
+
return int(zipcode)
|
|
20
|
+
except (ValueError, TypeError):
|
|
21
|
+
return None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def timezone(zipcode: str | int) -> Optional[str]:
|
|
25
|
+
"""
|
|
26
|
+
Return the IANA timezone for a US ZIP code.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
zipcode: ZIP code as a string or integer.
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
Timezone string (for example, ``America/New_York``) or ``None``.
|
|
33
|
+
"""
|
|
34
|
+
zip_int = _normalize_zipcode(zipcode)
|
|
35
|
+
if zip_int is None:
|
|
36
|
+
return None
|
|
37
|
+
|
|
38
|
+
record = ZIP_INFO.get(zip_int)
|
|
39
|
+
if record is None:
|
|
40
|
+
return None
|
|
41
|
+
tz_idx, _, _ = record
|
|
42
|
+
return TIMEZONES[tz_idx]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def coordinates(zipcode: str | int) -> Optional[tuple[float, float]]:
|
|
46
|
+
"""
|
|
47
|
+
Return centroid latitude and longitude for a US ZIP code.
|
|
48
|
+
|
|
49
|
+
Coordinates come from the generated ZIP metadata dataset.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
zipcode: ZIP code as a string or integer.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
``(latitude, longitude)`` or ``None``.
|
|
56
|
+
"""
|
|
57
|
+
zip_int = _normalize_zipcode(zipcode)
|
|
58
|
+
if zip_int is None:
|
|
59
|
+
return None
|
|
60
|
+
|
|
61
|
+
record = ZIP_INFO.get(zip_int)
|
|
62
|
+
if record is None:
|
|
63
|
+
return None
|
|
64
|
+
_, latitude, longitude = record
|
|
65
|
+
return latitude, longitude
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def info(zipcode: str | int) -> Optional[ZipInfo]:
|
|
69
|
+
"""
|
|
70
|
+
Return timezone and coordinate metadata for a US ZIP code.
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
zipcode: ZIP code as a string or integer.
|
|
74
|
+
|
|
75
|
+
Returns:
|
|
76
|
+
``ZipInfo`` when timezone data exists, otherwise ``None``.
|
|
77
|
+
"""
|
|
78
|
+
zip_int = _normalize_zipcode(zipcode)
|
|
79
|
+
if zip_int is None:
|
|
80
|
+
return None
|
|
81
|
+
|
|
82
|
+
record = ZIP_INFO.get(zip_int)
|
|
83
|
+
if record is None:
|
|
84
|
+
return None
|
|
85
|
+
|
|
86
|
+
tz_idx, latitude, longitude = record
|
|
87
|
+
return ZipInfo(
|
|
88
|
+
zipcode=zip_int,
|
|
89
|
+
timezone=TIMEZONES[tz_idx],
|
|
90
|
+
latitude=latitude,
|
|
91
|
+
longitude=longitude,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
__all__ = ["ZipInfo", "coordinates", "info", "timezone"]
|