API documentation
Global Functions
- timezonefinder.timezone_at(*, lng: float, lat: float) str | None[source]
Look up the timezone for a geographic coordinate using the global singleton.
- Parameters:
lng – Longitude of the point in degrees (-180.0 to 180.0)
lat – Latitude of the point in degrees (-90.0 to 90.0)
- Returns:
The timezone name of a matching polygon, or None if no match found
- Thread Safety:
This function is thread-safe for concurrent calls. The underlying global TimezoneFinder instance uses a thread-safe singleton pattern. However, for performance-critical parallel workloads, create separate TimezoneFinder instances per thread to avoid singleton overhead.
- Example:
>>> timezone_at(lng=13.4, lat=52.5) 'Europe/Berlin'
- timezonefinder.timezone_ids_at(*, lngs: ArrayLike, lats: ArrayLike, on_invalid: Literal['raise', 'skip'] = 'raise') ndarray[source]
Look up many coordinates at once using the global singleton, answering with ids.
Equivalent to
TimezoneFinder.timezone_ids_at(), which documents the arguments, theon_invalidpolicies and every error raised.- Returns:
one
int16timezone id per input coordinate, orNO_ZONE_ID(-1) where the scalar lookup would answerNone
- Example:
>>> ids = timezone_ids_at(lngs=[13.358, 2.3522], lats=[52.5061, 48.8566]) >>> ids.dtype dtype('int16')
- timezonefinder.timezone_names_at(*, lngs: ArrayLike, lats: ArrayLike, on_invalid: Literal['raise', 'skip'] = 'raise') list[str | None][source]
Look up many coordinates at once using the global singleton, answering with names.
Equivalent to
TimezoneFinder.timezone_names_at(). Prefertimezone_ids_at()whenever the names are not the end product.- Returns:
one timezone name per input coordinate, or
Nonewhere no zone covers the point or the coordinate was skipped
- Example:
>>> timezone_names_at(lngs=[13.358, 2.3522], lats=[52.5061, 48.8566]) ['Europe/Berlin', 'Europe/Paris']
- timezonefinder.timezone_at_land(*, lng: float, lat: float) str | None[source]
Look up the land timezone for a geographic coordinate using the global singleton.
Returns None for ocean coordinates (which have fixed-offset timezones like Etc/GMT±XX).
- Parameters:
lng – Longitude of the point in degrees (-180.0 to 180.0)
lat – Latitude of the point in degrees (-90.0 to 90.0)
- Returns:
The timezone name for land locations, or None for ocean areas
- Thread Safety:
This function is thread-safe for concurrent calls. The underlying global TimezoneFinder instance uses a thread-safe singleton pattern. However, for performance-critical parallel workloads, create separate TimezoneFinder instances per thread to avoid singleton overhead.
- timezonefinder.timezone_ids_at_land(*, lngs: ArrayLike, lats: ArrayLike, on_invalid: Literal['raise', 'skip'] = 'raise') ndarray[source]
Look up many coordinates at once using the global singleton, answering with land ids.
Equivalent to
TimezoneFinder.timezone_ids_at_land(), which documents the arguments, theon_invalidpolicies and every error raised.- Returns:
one
int16timezone id per input coordinate, orNO_ZONE_ID(-1) wheretimezone_at_land()would answerNone
- Example:
>>> ids = timezone_ids_at_land(lngs=[13.358, -30.0], lats=[52.5061, 0.0]) >>> ids[1] # mid-Atlantic: an ocean zone, so no land answer np.int16(-1)
- timezonefinder.timezone_names_at_land(*, lngs: ArrayLike, lats: ArrayLike, on_invalid: Literal['raise', 'skip'] = 'raise') list[str | None][source]
Look up many coordinates at once using the global singleton, answering with land names.
Equivalent to
TimezoneFinder.timezone_names_at_land(). Prefertimezone_ids_at_land()whenever the names are not the end product.- Returns:
one timezone name per input coordinate, or
Nonewhere an ocean zone matched, no zone covers the point, or the coordinate was skipped
- Example:
>>> timezone_names_at_land(lngs=[13.358, -30.0], lats=[52.5061, 0.0]) ['Europe/Berlin', None]
- timezonefinder.unique_timezone_at(*, lng: float, lat: float) str | None[source]
Get the timezone for a coordinate if the shortcut zone is unambiguous.
Returns None if the H3 shortcut cell contains multiple timezones or no zones.
- Parameters:
lng – Longitude of the point in degrees (-180.0 to 180.0)
lat – Latitude of the point in degrees (-90.0 to 90.0)
- Returns:
The timezone name if the shortcut contains exactly one zone, None otherwise
- Thread Safety:
This function is thread-safe for concurrent calls. The underlying global TimezoneFinder instance uses a thread-safe singleton pattern. However, for performance-critical parallel workloads, create separate TimezoneFinder instances per thread to avoid singleton overhead.
- Note:
This is faster than timezone_at() but may return None even for valid coordinates if the H3 cell spans multiple timezones.
- timezonefinder.certain_timezone_at(*, lng: float, lat: float) str | None[source]
Get the timezone for a coordinate with certainty (tests all polygons).
This function checks if a point is contained in ANY timezone polygon. It is slower than timezone_at() but useful when you have custom timezone data with areas of no coverage.
- Parameters:
lng – Longitude of the point in degrees (-180.0 to 180.0)
lat – Latitude of the point in degrees (-90.0 to 90.0)
- Returns:
The timezone name if definitely matched, None if not in any polygon
- Thread Safety:
This function is thread-safe for concurrent calls. The underlying global TimezoneFinder instance uses a thread-safe singleton pattern. However, for performance-critical parallel workloads, create separate TimezoneFinder instances per thread to avoid singleton overhead.
- Note:
For the standard global dataset, this is equivalent to timezone_at() since all earth locations are covered by polygons (including ocean zones). This is primarily useful with custom timezone data.
- timezonefinder.get_geometry(tz_name: str | None = '', tz_id: int | None = 0, use_id: bool = False, coords_as_pairs: bool = False) list[list[list[tuple[float, float]] | list[list[float]]]][source]
Retrieves the geometry of a timezone polygon. Uses the global TimezoneFinder instance.
Note: This function is not thread-safe. For multi-threaded environments, create separate TimezoneFinder instances.
- Parameters:
tz_name – one of the names in
timezone_names.txtorself.timezone_namestz_id – the id of the timezone (=index in
self.timezone_names)use_id – if
Trueusestz_idinstead oftz_namecoords_as_pairs – determines the structure of the polygon representation
- Returns:
a data structure representing the multipolygon of this timezone output format:
[ [polygon1, hole1, hole2...], [polygon2, ...], ...]and each polygon and hole is itself formatted like:([longitudes], [latitudes])or[(lng1,lat1), (lng2,lat2),...]ifcoords_as_pairs=True.
Timezone conversion helpers
The three steps most callers take after the lookup, as global functions and as methods
on both finder classes. They resolve the returned IANA name through the standard
library’s zoneinfo, which applies the inverted Etc/GMT±X sign convention
correctly - deriving an offset by reading the name instead produces the wrong sign
without failing, and the packaged data returns an Etc/GMT zone for every coordinate
at sea.
Note
Windows ships no system timezone database, so pip install tzdata is required
there before any of these resolve a name; without it they raise
zoneinfo.ZoneInfoNotFoundError. This package returns IANA names and does not
carry the database itself.
- timezonefinder.zoneinfo_at(*, lng: float, lat: float) ZoneInfo | None[source]
Look up the timezone for a coordinate as a
zoneinfo.ZoneInfo, using the global singleton.Equivalent to
TimezoneFinder.zoneinfo_at(), which documents the arguments and every error raised - including thetzdataa Windows machine needs installed before any IANA name resolves.- Returns:
the zone covering the point, or None where
timezone_at()answers None
- Example:
>>> zoneinfo_at(lng=13.358, lat=52.5061) zoneinfo.ZoneInfo(key='Europe/Berlin')
- timezonefinder.utc_offset_at(*, lng: float, lat: float, when: datetime | None = None) timedelta | None[source]
Get the UTC offset in force at a coordinate, using the global singleton.
Equivalent to
TimezoneFinder.utc_offset_at(), which documents the arguments, how a naive and an awarewhendiffer, and every error raised - including thetzdataa Windows machine needs installed.- Returns:
the offset as a
timedelta, or None wheretimezone_at()answers None
- Example:
>>> from datetime import datetime >>> utc_offset_at(lng=13.358, lat=52.5061, when=datetime(2026, 1, 1)) datetime.timedelta(seconds=3600)
- timezonefinder.localize(dt: datetime, *, lng: float, lat: float) datetime | None[source]
Attach the timezone covering a coordinate to a naive datetime, using the global singleton.
Equivalent to
TimezoneFinder.localize(), which documents the arguments and every error raised - including thetzdataa Windows machine needs installed.- Returns:
the same wall-clock time made aware, or None where
timezone_at()answers None
- Example:
>>> from datetime import datetime >>> localize(datetime(2026, 1, 1, 12), lng=13.358, lat=52.5061) datetime.datetime(2026, 1, 1, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Berlin'))
TimezoneFinderL
- class timezonefinder.TimezoneFinderL(bin_file_location: str | Path | None = None)[source]
Bases:
AbstractTimezoneFinderA lightweight version of TimezoneFinder for quick timezone suggestions.
Instead of using timezone polygon data like
TimezoneFinder, this class only uses a precomputed ‘shortcut’ to suggest a probable result: the most common zone in a rectangle of a half degree of latitude and one degree of longitude.- Thread Safety:
Each thread that performs timezone lookups must create its own independent TimezoneFinderL instance. Do not share a single instance across threads.
- timezone_at(*, lng: float, lat: float) str | None[source]
instantly returns the name of the most common zone within the corresponding shortcut
- Note: ‘most common’ in this context means that the boundary polygons with the most coordinates in sum
occurring in the corresponding shortcut belong to this zone.
- Parameters:
lng – longitude of the point in degree (-180.0 to 180.0)
lat – latitude in degree (90.0 to -90.0)
- Returns:
the timezone name of the most common zone or None if there are no timezone polygons in this shortcut
- data_location: Path
- shortcuts: ShortcutIndex
which timezones can possibly cover a point. This class asks it what a cell resolves to and never how that is stored - see
timezonefinder/shortcut_index.py.
- zone_names: ZoneNames
the dataset’s names, and every way a zone id becomes one. This class produces ids and asks it to name them - see
timezonefinder/zone_names.py.
- zone_ids: ndarray
- holes_dir
- boundaries_dir
- boundaries
- holes
- __init__(bin_file_location: str | Path | None = None)
Initialize the AbstractTimezoneFinder.
Loads the zone names, the per-polygon zone ids and the shortcut index, all of which are always held in memory. Selecting how the polygon coordinate data is accessed belongs to the subclass that loads it:
TimezoneFindertakesin_memoryfor that, and this class has nothing to apply it to.- Parameters:
bin_file_location – Path to the directory containing binary timezone data. If None, uses the bundled package data directory.
- Raises:
FileNotFoundError – If timezone data files cannot be found at the specified location
ValueError – If timezone data files are corrupted or in an invalid format
- cleanup() None
Clean up resources. Override in subclasses as needed.
- property data_version: str
The timezone-boundary-builder release this finder answers from.
Reads the stamp
scripts/file_converter.pywrote into the data directory at build time (data_version.txt), so an installedtimezonefindercan state which dataset it is answering from without reverse-engineering it from the package version - which also changes for unrelated code fixes and, under the automated data-update pipeline, changes together with the data in a way callers cannot distinguish.For the packaged data this is the release
update_data.shdownloaded. A data directory compiled from your own GeoJSON reads"unknown"unlessscripts/file_converter.py --data-versionnamed the release it came from, since nothing about the input states it.- Raises:
FileNotFoundError – if the data directory carries no stamp, which a directory compiled before this file existed does not.
- localize(dt: datetime, *, lng: float, lat: float) datetime | None
Attach the timezone covering a point to a naive datetime.
The datetime is read as local wall-clock time there: the instant it denotes is decided by the zone, which is what makes this different from
dt.astimezone(...)on an already-aware value.- Parameters:
dt – a naive datetime, i.e. local time at the point
lng – longitude of the point in degree (-180.0 to 180.0)
lat – latitude in degree (90.0 to -90.0)
- Returns:
the same wall-clock time made aware, or
Nonewheretimezone_at()answersNone- Raises:
ValueError – if the coordinates are out of bounds, or if
dtalready carries a timezone - converting one isdt.astimezone()’s job, and silently re-labelling it would move the instant it denoteszoneinfo.ZoneInfoNotFoundError – on a platform without a timezone database - see
zoneinfo_at(), which names the Windows case
- Example:
>>> tf = TimezoneFinder() >>> tf.localize(datetime(2026, 1, 1, 12), lng=13.358, lat=52.5061) datetime.datetime(2026, 1, 1, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Berlin'))
- property nr_of_zones: int
Get the number of timezones.
- Return type:
int
- timezone_at_land(*, lng: float, lat: float) str | None
computes in which land timezone a point is included in
Especially for large polygons it is expensive to check if a point is really included. To speed things up there are “shortcuts” being used (stored in a binary file), which have been precomputed and store which timezone polygons have to be checked.
- Parameters:
lng – longitude of the point in degree (-180.0 to 180.0)
lat – latitude in degree (90.0 to -90.0)
- Returns:
the timezone name of a matching polygon or
Nonewhen an ocean timezone (“Etc/GMT+-XX”) has been matched.
- timezone_ids_at(*, lngs: ArrayLike, lats: ArrayLike, on_invalid: Literal['raise', 'skip'] = 'raise') ndarray
Look up many coordinates at once, answering with timezone ids.
The batch counterpart of
timezone_at(), and the primary one: a caller doing millions of lookups should not pay for millions of string lookups it maps straight back to something else.timezone_names_at()is the convenience on top.What a batch amortises is the per-call overhead, not the geometry. Validation, the integer scaling and the shortcut lookup run once over the whole batch as numpy operations; ambiguous points still fall through to the point-in-polygon loop one at a time, and
h3’s cell lookup has no vectorised form, so it stays a loop too. Expect the win to be largest on points whose cell a single zone covers.- Parameters:
lngs – longitudes in degrees, as any 1-D array-like. A C-contiguous
float64numpy array is used without copying.lats – latitudes in degrees, the same length as
lngs.on_invalid – what to do with a coordinate outside the valid range (which includes
NaNand infinity)."raise"(the default) matches the scalar methods;"skip"answers those points withNO_ZONE_IDand the rest normally.
- Returns:
one
int16per input coordinate - a timezone id, orNO_ZONE_ID(-1) where the scalar method would answerNone: no zone covers the point, or it was skipped.- Raises:
TypeError – if either axis holds values that are not numbers.
ValueError – if the two axes differ in length, either is not one-dimensional,
on_invalidis not a known policy, or - underon_invalid="raise"- a coordinate is out of range.
Note
coordinates are passed one axis per argument on purpose. A single
(N, 2)array would have to be read positionally, and a swapped pair is still a valid coordinate for most of the populated world - so the mistake would return a real but wrong timezone instead of raising.- Example:
>>> tf = TimezoneFinder() >>> ids = tf.timezone_ids_at(lngs=[13.358, 2.3522], lats=[52.5061, 48.8566]) >>> [tf.zone_name_from_id(int(i)) for i in ids] ['Europe/Berlin', 'Europe/Paris']
- timezone_ids_at_land(*, lngs: ArrayLike, lats: ArrayLike, on_invalid: Literal['raise', 'skip'] = 'raise') ndarray
Look up many coordinates at once, answering with land timezone ids.
The batch counterpart of
timezone_at_land(), and - as withtimezone_ids_at(), whose arguments and errors this shares - the primary one, withtimezone_names_at_land()the convenience on top.The ocean check costs nothing per point here. Ocean-ness is a fixed property of a zone id for a given dataset, so the whole answer array is masked in one indexing operation rather than testing each answer’s name - which makes this cheaper per point than calling
timezone_at_land()in a loop, not merely equal to it.- Parameters:
lngs – longitudes in degrees, as any 1-D array-like.
lats – latitudes in degrees, the same length as
lngs.on_invalid – what to do with a coordinate outside the valid range - see
timezone_ids_at(), which documents the policies.
- Returns:
one
int16per input coordinate - a land timezone id, orNO_ZONE_ID(-1) wheretimezone_at_land()would answerNone: an ocean zone matched, no zone covers the point, or it was skipped. The three are deliberately one sentinel, exactly as intimezone_ids_at().- Raises:
TypeError – if either axis holds values that are not numbers.
ValueError – if the two axes differ in length, either is not one-dimensional,
on_invalidis not a known policy, or - underon_invalid="raise"- a coordinate is out of range.
Note
coordinates are passed one axis per argument, for the reason
timezone_ids_at()gives: a single(N, 2)array would be read positionally, and a swapped pair is still a valid coordinate for most of the populated world - so the mistake would return a real but wrong answer instead of raising. Such an array is rejected as not one-dimensional.- Example:
>>> tf = TimezoneFinder() >>> ids = tf.timezone_ids_at_land(lngs=[13.358, -30.0], lats=[52.5061, 0.0]) >>> ids[1] == NO_ZONE_ID # mid-Atlantic: an ocean zone, so no land answer True
- property timezone_names: list[str]
All timezone names of the loaded dataset, in zone id order.
A read-only view onto
zone_names, which owns the list and everything that turns an id back into one.- Return type:
list[str]
- timezone_names_at(*, lngs: ArrayLike, lats: ArrayLike, on_invalid: Literal['raise', 'skip'] = 'raise') list[str | None]
Look up many coordinates at once, answering with timezone names.
The convenience on top of
timezone_ids_at(), which documents the arguments, theon_invalidpolicies and every error raised. Each answer is whattimezone_at()would return for that point,Noneincluded.Prefer the id form whenever the names are not the end product: this method adds one list index and one Python object per coordinate, which is most of what a batch lookup was meant to avoid.
- Returns:
one timezone name per input coordinate, or
Nonewhere no zone covers the point or the coordinate was skipped.
- Example:
>>> tf = TimezoneFinder() >>> tf.timezone_names_at(lngs=[13.358, 2.3522], lats=[52.5061, 48.8566]) ['Europe/Berlin', 'Europe/Paris']
- timezone_names_at_land(*, lngs: ArrayLike, lats: ArrayLike, on_invalid: Literal['raise', 'skip'] = 'raise') list[str | None]
Look up many coordinates at once, answering with land timezone names.
The convenience on top of
timezone_ids_at_land(), which documents the arguments and every error raised. Each answer is whattimezone_at_land()would return for that point,Noneincluded.Prefer the id form whenever the names are not the end product, for the reason
timezone_names_at()gives.- Returns:
one timezone name per input coordinate, or
Nonewhere an ocean zone matched, no zone covers the point, or the coordinate was skipped.
- Example:
>>> tf = TimezoneFinder() >>> tf.timezone_names_at_land(lngs=[13.358, -30.0], lats=[52.5061, 0.0]) ['Europe/Berlin', None]
- unique_timezone_at(*, lng: float, lat: float) str | None
returns the name of a unique zone within the corresponding shortcut
- Parameters:
lng – longitude of the point in degree (-180.0 to 180.0)
lat – latitude in degree (90.0 to -90.0)
- Returns:
the timezone name of the unique zone or
Noneif there are no or multiple zones in this shortcut
- static using_clang_pip() bool
- Returns:
True if the compiled C implementation of the point in polygon algorithm is being used
- static using_numba() bool
Check if Numba is being used.
- Return type:
bool
- Returns:
True if Numba is being used to JIT compile helper functions
- utc_offset_at(*, lng: float, lat: float, when: datetime | None = None) timedelta | None
The UTC offset in force at a point, at a given moment.
The offset is a property of a zone and a date, since it changes with daylight saving time - so it is read off an aware datetime rather than off the zone.
- Parameters:
lng – longitude of the point in degree (-180.0 to 180.0)
lat – latitude in degree (90.0 to -90.0)
when – the moment to read the offset at, defaulting to now. A naive datetime is read as local wall-clock time in the zone found; an aware one is read as the instant it denotes.
- Returns:
the offset as a
timedelta, orNonewheretimezone_at()answersNone- Raises:
ValueError – if the coordinates are out of bounds
zoneinfo.ZoneInfoNotFoundError – on a platform without a timezone database - see
zoneinfo_at(), which names the Windows case
- Example:
>>> tf = TimezoneFinder() >>> tf.utc_offset_at(lng=13.358, lat=52.5061, when=datetime(2026, 1, 1)) datetime.timedelta(seconds=3600)
- zone_id_of(boundary_id: int | integer) int
Get the timezone ID for a specific boundary polygon.
- Parameters:
boundary_id – The numeric identifier of the boundary polygon
- Returns:
The timezone ID (index into timezone_names)
- Raises:
ValueError – If
boundary_iddoes not select exactly one zone id - negative, out of range, not usable as an index, or selecting several. The underlyingIndexErrorandTypeErrorare both re-raised asValueError, so that is the only type callers have to handle.
- zone_ids_of(boundary_ids: ndarray) ndarray
Get the zone IDs of multiple boundary polygons.
- Parameters:
boundary_ids – An array of boundary polygon IDs.
- Returns:
array of corresponding timezone IDs.
- Raises:
ValueError – If any id is negative. Out-of-range ids raise
IndexErrorfrom NumPy, as they do for any array indexing.
- zone_name_from_boundary_id(boundary_id: int | integer) str
Get the zone name from a boundary polygon ID.
- Parameters:
boundary_id – The ID of the boundary polygon.
- Returns:
The name of the zone.
- Raises:
ValueError – If
boundary_idis negative or does not select exactly one zone id, as forzone_id_of().
- zone_name_from_id(zone_id: int) str
Get the timezone name corresponding to a zone ID.
- Parameters:
zone_id – The numeric ID of the timezone (0-based index)
- Returns:
The IANA timezone name (e.g., ‘Europe/Berlin’)
- Raises:
ValueError – If
zone_idis negative or out of range for the loaded dataset. The underlyingIndexErroris re-raised asValueError.TypeError – If
zone_idis not an integer.
- Example:
>>> tf = TimezoneFinder() >>> tf.zone_name_from_id(0) 'Africa/Abidjan'
- zone_names_from_ids(zone_ids: ArrayLike) list[str | None]
Convert many zone ids to timezone names in one call.
The batch counterpart of
zone_name_from_id(), and whattimezone_ids_at()is meant to be paired with: keep the ids while they are being joined, filtered or grouped, and name them once at the end. Above a threshold the conversion is a numpy gather rather than a Python loop, which is several times faster per id on a large batch.- Parameters:
zone_ids – the ids to name, as any 1-D array-like of integers.
- Returns:
one name per id, with
Nonewherever the id isNO_ZONE_ID(-1) - so an answer fromtimezone_ids_at()round-trips to exactly whattimezone_names_at()would have returned.- Raises:
TypeError – if
zone_idsdoes not hold integers.ValueError – if
zone_idsis not one-dimensional, or holds an id that is neither a valid zone id nor the sentinel. A negative other than-1is rejected rather than counted from the end of the dataset, as forzone_name_from_id().
- Example:
>>> tf = TimezoneFinder() >>> ids = tf.timezone_ids_at(lngs=[13.358, 2.3522], lats=[52.5061, 48.8566]) >>> tf.zone_names_from_ids(ids) ['Europe/Berlin', 'Europe/Paris']
- zoneinfo_at(*, lng: float, lat: float) ZoneInfo | None
The timezone covering a point, as a
zoneinfo.ZoneInfo.- Parameters:
lng – longitude of the point in degree (-180.0 to 180.0)
lat – latitude in degree (90.0 to -90.0)
- Returns:
the zone
timezone_at()names, orNonewhere that answersNone- Raises:
ValueError – if the coordinates are out of bounds
zoneinfo.ZoneInfoNotFoundError – if the platform has no timezone database holding that name. Windows ships none, so
pip install tzdatais required there - this package returns IANA names and does not carry the database itself.
- Example:
>>> tf = TimezoneFinder() >>> tf.zoneinfo_at(lng=13.358, lat=52.5061) zoneinfo.ZoneInfo(key='Europe/Berlin')
TimezoneFinder
- class timezonefinder.TimezoneFinder(bin_file_location: str | Path | None = None, in_memory: bool = False)[source]
Bases:
AbstractTimezoneFinderClass for quickly finding the timezone of a point on earth offline.
Because of indexing (“shortcuts”), not all timezone polygons have to be tested during a query.
Opens the required timezone polygon data in binary files to enable fast access. For a detailed documentation of data management please refer to the code documentation of file_converter.py
- Thread Safety:
Each thread that performs timezone lookups must create its own independent TimezoneFinder instance. Do not share a single instance across threads, as this can lead to race conditions and incorrect results. Example:
import threading from timezonefinder import TimezoneFinder
- def lookup_in_thread(lng, lat):
# Each thread creates its own instance tf = TimezoneFinder(in_memory=True) return tf.timezone_at(lng=lng, lat=lat)
- Parameters:
- param bin_file_location:
path to the binary data files to use, None if native package data should be used
- param in_memory:
Whether to completely read and keep the coordinate data in memory as numpy arrays.
- __init__(bin_file_location: str | Path | None = None, in_memory: bool = False)[source]
Initialize the AbstractTimezoneFinder.
Loads the zone names, the per-polygon zone ids and the shortcut index, all of which are always held in memory. Selecting how the polygon coordinate data is accessed belongs to the subclass that loads it:
TimezoneFindertakesin_memoryfor that, and this class has nothing to apply it to.- Parameters:
bin_file_location – Path to the directory containing binary timezone data. If None, uses the bundled package data directory.
- Raises:
FileNotFoundError – If timezone data files cannot be found at the specified location
ValueError – If timezone data files are corrupted or in an invalid format
- holes_dir
- boundaries_dir
- boundaries
- holes
- hole_registry
- property nr_of_polygons: int
- property nr_of_holes: int
- coords_of(boundary_id: int | integer = 0) ndarray[source]
Get the coordinates of a boundary polygon from the FlatBuffers collection.
- Parameters:
boundary_id – The index of the polygon.
- Returns:
Array of coordinates.
- get_polygon(boundary_id: int | integer, coords_as_pairs: bool = False) list[list[tuple[float, float]] | list[list[float]]][source]
Get the polygon coordinates of a given boundary polygon including its holes.
- Parameters:
boundary_id – ID of the boundary polygon
coords_as_pairs – If True, returns coordinates as pairs (lng, lat). If False, returns coordinates as separate lists of longitudes and latitudes.
- Returns:
List of polygon coordinates
- get_geometry(tz_name: str | None = '', tz_id: int | None = 0, use_id: bool = False, coords_as_pairs: bool = False) list[list[list[tuple[float, float]] | list[list[float]]]][source]
retrieves the geometry of a timezone: multiple boundary polygons with holes
- Parameters:
tz_name – one of the names in
timezone_names.txtorself.timezone_namestz_id – the id of the timezone (=index in
self.timezone_names)use_id – if
Trueusestz_idinstead oftz_namecoords_as_pairs – determines the structure of the polygon representation
- Returns:
a data structure representing the multipolygon of this timezone output format:
[ [polygon1, hole1, hole2...], [polygon2, ...], ...]and each polygon and hole is itself formatted like:([longitudes], [latitudes])or[(lng1,lat1), (lng2,lat2),...]ifcoords_as_pairs=True.
- inside_of_polygon(boundary_id: int | integer, x: int, y: int) bool[source]
Check if a point is inside a boundary polygon.
- Parameters:
boundary_id – boundary polygon ID
x – X-coordinate of the point
y – Y-coordinate of the point
- Returns:
True if the point lies inside the boundary polygon, False if outside or in a hole.
- timezone_at(*, lng: float, lat: float) str | None[source]
Find the timezone for a given point using hybrid shortcuts, considering both land and ocean timezones.
Uses precomputed hybrid shortcuts to reduce the number of polygons checked. Returns the timezone name of the matched polygon, which may be an ocean timezone (“Etc/GMT+-XX”) if applicable.
Since ocean timezones span the whole globe, some timezone will always be matched! None can only be returned when using custom timezone data without such ocean timezones.
Note
for speed the last remaining zone is returned without a point in polygon test: once no other zone can be matched, its polygons cannot change the outcome. With the packaged data this is always correct, since the ocean zones cover the globe and every point therefore lies within one of the candidate polygons. With custom data that leaves areas uncovered it is not: a point inside none of the candidates is still attributed to that last zone. Use
certain_timezone_at()there, which tests every candidate.- Parameters:
lng – longitude of the point in degrees (-180.0 to 180.0)
lat – latitude of the point in degrees (90.0 to -90.0)
- Returns:
the timezone name of the matched polygon, or None if no match is found.
- cleanup() None
Clean up resources. Override in subclasses as needed.
- data_location: Path
- property data_version: str
The timezone-boundary-builder release this finder answers from.
Reads the stamp
scripts/file_converter.pywrote into the data directory at build time (data_version.txt), so an installedtimezonefindercan state which dataset it is answering from without reverse-engineering it from the package version - which also changes for unrelated code fixes and, under the automated data-update pipeline, changes together with the data in a way callers cannot distinguish.For the packaged data this is the release
update_data.shdownloaded. A data directory compiled from your own GeoJSON reads"unknown"unlessscripts/file_converter.py --data-versionnamed the release it came from, since nothing about the input states it.- Raises:
FileNotFoundError – if the data directory carries no stamp, which a directory compiled before this file existed does not.
- localize(dt: datetime, *, lng: float, lat: float) datetime | None
Attach the timezone covering a point to a naive datetime.
The datetime is read as local wall-clock time there: the instant it denotes is decided by the zone, which is what makes this different from
dt.astimezone(...)on an already-aware value.- Parameters:
dt – a naive datetime, i.e. local time at the point
lng – longitude of the point in degree (-180.0 to 180.0)
lat – latitude in degree (90.0 to -90.0)
- Returns:
the same wall-clock time made aware, or
Nonewheretimezone_at()answersNone- Raises:
ValueError – if the coordinates are out of bounds, or if
dtalready carries a timezone - converting one isdt.astimezone()’s job, and silently re-labelling it would move the instant it denoteszoneinfo.ZoneInfoNotFoundError – on a platform without a timezone database - see
zoneinfo_at(), which names the Windows case
- Example:
>>> tf = TimezoneFinder() >>> tf.localize(datetime(2026, 1, 1, 12), lng=13.358, lat=52.5061) datetime.datetime(2026, 1, 1, 12, 0, tzinfo=zoneinfo.ZoneInfo(key='Europe/Berlin'))
- property nr_of_zones: int
Get the number of timezones.
- Return type:
int
- shortcuts: ShortcutIndex
which timezones can possibly cover a point. This class asks it what a cell resolves to and never how that is stored - see
timezonefinder/shortcut_index.py.
- timezone_at_land(*, lng: float, lat: float) str | None
computes in which land timezone a point is included in
Especially for large polygons it is expensive to check if a point is really included. To speed things up there are “shortcuts” being used (stored in a binary file), which have been precomputed and store which timezone polygons have to be checked.
- Parameters:
lng – longitude of the point in degree (-180.0 to 180.0)
lat – latitude in degree (90.0 to -90.0)
- Returns:
the timezone name of a matching polygon or
Nonewhen an ocean timezone (“Etc/GMT+-XX”) has been matched.
- timezone_ids_at(*, lngs: ArrayLike, lats: ArrayLike, on_invalid: Literal['raise', 'skip'] = 'raise') ndarray
Look up many coordinates at once, answering with timezone ids.
The batch counterpart of
timezone_at(), and the primary one: a caller doing millions of lookups should not pay for millions of string lookups it maps straight back to something else.timezone_names_at()is the convenience on top.What a batch amortises is the per-call overhead, not the geometry. Validation, the integer scaling and the shortcut lookup run once over the whole batch as numpy operations; ambiguous points still fall through to the point-in-polygon loop one at a time, and
h3’s cell lookup has no vectorised form, so it stays a loop too. Expect the win to be largest on points whose cell a single zone covers.- Parameters:
lngs – longitudes in degrees, as any 1-D array-like. A C-contiguous
float64numpy array is used without copying.lats – latitudes in degrees, the same length as
lngs.on_invalid – what to do with a coordinate outside the valid range (which includes
NaNand infinity)."raise"(the default) matches the scalar methods;"skip"answers those points withNO_ZONE_IDand the rest normally.
- Returns:
one
int16per input coordinate - a timezone id, orNO_ZONE_ID(-1) where the scalar method would answerNone: no zone covers the point, or it was skipped.- Raises:
TypeError – if either axis holds values that are not numbers.
ValueError – if the two axes differ in length, either is not one-dimensional,
on_invalidis not a known policy, or - underon_invalid="raise"- a coordinate is out of range.
Note
coordinates are passed one axis per argument on purpose. A single
(N, 2)array would have to be read positionally, and a swapped pair is still a valid coordinate for most of the populated world - so the mistake would return a real but wrong timezone instead of raising.- Example:
>>> tf = TimezoneFinder() >>> ids = tf.timezone_ids_at(lngs=[13.358, 2.3522], lats=[52.5061, 48.8566]) >>> [tf.zone_name_from_id(int(i)) for i in ids] ['Europe/Berlin', 'Europe/Paris']
- timezone_ids_at_land(*, lngs: ArrayLike, lats: ArrayLike, on_invalid: Literal['raise', 'skip'] = 'raise') ndarray
Look up many coordinates at once, answering with land timezone ids.
The batch counterpart of
timezone_at_land(), and - as withtimezone_ids_at(), whose arguments and errors this shares - the primary one, withtimezone_names_at_land()the convenience on top.The ocean check costs nothing per point here. Ocean-ness is a fixed property of a zone id for a given dataset, so the whole answer array is masked in one indexing operation rather than testing each answer’s name - which makes this cheaper per point than calling
timezone_at_land()in a loop, not merely equal to it.- Parameters:
lngs – longitudes in degrees, as any 1-D array-like.
lats – latitudes in degrees, the same length as
lngs.on_invalid – what to do with a coordinate outside the valid range - see
timezone_ids_at(), which documents the policies.
- Returns:
one
int16per input coordinate - a land timezone id, orNO_ZONE_ID(-1) wheretimezone_at_land()would answerNone: an ocean zone matched, no zone covers the point, or it was skipped. The three are deliberately one sentinel, exactly as intimezone_ids_at().- Raises:
TypeError – if either axis holds values that are not numbers.
ValueError – if the two axes differ in length, either is not one-dimensional,
on_invalidis not a known policy, or - underon_invalid="raise"- a coordinate is out of range.
Note
coordinates are passed one axis per argument, for the reason
timezone_ids_at()gives: a single(N, 2)array would be read positionally, and a swapped pair is still a valid coordinate for most of the populated world - so the mistake would return a real but wrong answer instead of raising. Such an array is rejected as not one-dimensional.- Example:
>>> tf = TimezoneFinder() >>> ids = tf.timezone_ids_at_land(lngs=[13.358, -30.0], lats=[52.5061, 0.0]) >>> ids[1] == NO_ZONE_ID # mid-Atlantic: an ocean zone, so no land answer True
- property timezone_names: list[str]
All timezone names of the loaded dataset, in zone id order.
A read-only view onto
zone_names, which owns the list and everything that turns an id back into one.- Return type:
list[str]
- timezone_names_at(*, lngs: ArrayLike, lats: ArrayLike, on_invalid: Literal['raise', 'skip'] = 'raise') list[str | None]
Look up many coordinates at once, answering with timezone names.
The convenience on top of
timezone_ids_at(), which documents the arguments, theon_invalidpolicies and every error raised. Each answer is whattimezone_at()would return for that point,Noneincluded.Prefer the id form whenever the names are not the end product: this method adds one list index and one Python object per coordinate, which is most of what a batch lookup was meant to avoid.
- Returns:
one timezone name per input coordinate, or
Nonewhere no zone covers the point or the coordinate was skipped.
- Example:
>>> tf = TimezoneFinder() >>> tf.timezone_names_at(lngs=[13.358, 2.3522], lats=[52.5061, 48.8566]) ['Europe/Berlin', 'Europe/Paris']
- timezone_names_at_land(*, lngs: ArrayLike, lats: ArrayLike, on_invalid: Literal['raise', 'skip'] = 'raise') list[str | None]
Look up many coordinates at once, answering with land timezone names.
The convenience on top of
timezone_ids_at_land(), which documents the arguments and every error raised. Each answer is whattimezone_at_land()would return for that point,Noneincluded.Prefer the id form whenever the names are not the end product, for the reason
timezone_names_at()gives.- Returns:
one timezone name per input coordinate, or
Nonewhere an ocean zone matched, no zone covers the point, or the coordinate was skipped.
- Example:
>>> tf = TimezoneFinder() >>> tf.timezone_names_at_land(lngs=[13.358, -30.0], lats=[52.5061, 0.0]) ['Europe/Berlin', None]
- unique_timezone_at(*, lng: float, lat: float) str | None
returns the name of a unique zone within the corresponding shortcut
- Parameters:
lng – longitude of the point in degree (-180.0 to 180.0)
lat – latitude in degree (90.0 to -90.0)
- Returns:
the timezone name of the unique zone or
Noneif there are no or multiple zones in this shortcut
- static using_clang_pip() bool
- Returns:
True if the compiled C implementation of the point in polygon algorithm is being used
- static using_numba() bool
Check if Numba is being used.
- Return type:
bool
- Returns:
True if Numba is being used to JIT compile helper functions
- utc_offset_at(*, lng: float, lat: float, when: datetime | None = None) timedelta | None
The UTC offset in force at a point, at a given moment.
The offset is a property of a zone and a date, since it changes with daylight saving time - so it is read off an aware datetime rather than off the zone.
- Parameters:
lng – longitude of the point in degree (-180.0 to 180.0)
lat – latitude in degree (90.0 to -90.0)
when – the moment to read the offset at, defaulting to now. A naive datetime is read as local wall-clock time in the zone found; an aware one is read as the instant it denotes.
- Returns:
the offset as a
timedelta, orNonewheretimezone_at()answersNone- Raises:
ValueError – if the coordinates are out of bounds
zoneinfo.ZoneInfoNotFoundError – on a platform without a timezone database - see
zoneinfo_at(), which names the Windows case
- Example:
>>> tf = TimezoneFinder() >>> tf.utc_offset_at(lng=13.358, lat=52.5061, when=datetime(2026, 1, 1)) datetime.timedelta(seconds=3600)
- zone_id_of(boundary_id: int | integer) int
Get the timezone ID for a specific boundary polygon.
- Parameters:
boundary_id – The numeric identifier of the boundary polygon
- Returns:
The timezone ID (index into timezone_names)
- Raises:
ValueError – If
boundary_iddoes not select exactly one zone id - negative, out of range, not usable as an index, or selecting several. The underlyingIndexErrorandTypeErrorare both re-raised asValueError, so that is the only type callers have to handle.
- zone_ids: ndarray
- zone_ids_of(boundary_ids: ndarray) ndarray
Get the zone IDs of multiple boundary polygons.
- Parameters:
boundary_ids – An array of boundary polygon IDs.
- Returns:
array of corresponding timezone IDs.
- Raises:
ValueError – If any id is negative. Out-of-range ids raise
IndexErrorfrom NumPy, as they do for any array indexing.
- zone_name_from_boundary_id(boundary_id: int | integer) str
Get the zone name from a boundary polygon ID.
- Parameters:
boundary_id – The ID of the boundary polygon.
- Returns:
The name of the zone.
- Raises:
ValueError – If
boundary_idis negative or does not select exactly one zone id, as forzone_id_of().
- zone_name_from_id(zone_id: int) str
Get the timezone name corresponding to a zone ID.
- Parameters:
zone_id – The numeric ID of the timezone (0-based index)
- Returns:
The IANA timezone name (e.g., ‘Europe/Berlin’)
- Raises:
ValueError – If
zone_idis negative or out of range for the loaded dataset. The underlyingIndexErroris re-raised asValueError.TypeError – If
zone_idis not an integer.
- Example:
>>> tf = TimezoneFinder() >>> tf.zone_name_from_id(0) 'Africa/Abidjan'
- zone_names: ZoneNames
the dataset’s names, and every way a zone id becomes one. This class produces ids and asks it to name them - see
timezonefinder/zone_names.py.
- zone_names_from_ids(zone_ids: ArrayLike) list[str | None]
Convert many zone ids to timezone names in one call.
The batch counterpart of
zone_name_from_id(), and whattimezone_ids_at()is meant to be paired with: keep the ids while they are being joined, filtered or grouped, and name them once at the end. Above a threshold the conversion is a numpy gather rather than a Python loop, which is several times faster per id on a large batch.- Parameters:
zone_ids – the ids to name, as any 1-D array-like of integers.
- Returns:
one name per id, with
Nonewherever the id isNO_ZONE_ID(-1) - so an answer fromtimezone_ids_at()round-trips to exactly whattimezone_names_at()would have returned.- Raises:
TypeError – if
zone_idsdoes not hold integers.ValueError – if
zone_idsis not one-dimensional, or holds an id that is neither a valid zone id nor the sentinel. A negative other than-1is rejected rather than counted from the end of the dataset, as forzone_name_from_id().
- Example:
>>> tf = TimezoneFinder() >>> ids = tf.timezone_ids_at(lngs=[13.358, 2.3522], lats=[52.5061, 48.8566]) >>> tf.zone_names_from_ids(ids) ['Europe/Berlin', 'Europe/Paris']
- zoneinfo_at(*, lng: float, lat: float) ZoneInfo | None
The timezone covering a point, as a
zoneinfo.ZoneInfo.- Parameters:
lng – longitude of the point in degree (-180.0 to 180.0)
lat – latitude in degree (90.0 to -90.0)
- Returns:
the zone
timezone_at()names, orNonewhere that answersNone- Raises:
ValueError – if the coordinates are out of bounds
zoneinfo.ZoneInfoNotFoundError – if the platform has no timezone database holding that name. Windows ships none, so
pip install tzdatais required there - this package returns IANA names and does not carry the database itself.
- Example:
>>> tf = TimezoneFinder() >>> tf.zoneinfo_at(lng=13.358, lat=52.5061) zoneinfo.ZoneInfo(key='Europe/Berlin')
- certain_timezone_at(*, lng: float, lat: float) str | None[source]
checks in which timezone polygon the point is certainly included in using hybrid shortcuts
Note
this is only meaningful when you have compiled your own timezone data where there are areas without timezone polygon coverage. Otherwise, some timezone will always be matched and the functionality is equal to using .timezone_at() -> useless to actually test all polygons.
Note
using this function is less performant than .timezone_at()
- Parameters:
lng – longitude of the point in degree
lat – latitude of the point in degree
- Returns:
the timezone name of the polygon the point is included in or None