Best Practices for tzfpy

tzfpy’s feature is very simple, only convert GPS coordinates to timezone name. In fact, those name will be used with other datatime related functions and other use cases.

Datatime convert

Get local time via datetime

1pip install tzfpy
 1# https://github.com/ringsaturn/tzfpy/blob/main/examples/tzfpy_with_datetime.py
 2from datetime import datetime, timezone
 3from zoneinfo import ZoneInfo
 4
 5from tzfpy import get_tz
 6
 7tz = get_tz(139.7744, 35.6812)  # Tokyo
 8
 9now = datetime.now(timezone.utc)
10now = now.replace(tzinfo=ZoneInfo(tz))
11print(now)

Output:

12025-04-29 01:33:56.325194+09:00

Get local time via arrow

1pip install arrow tzfpy
 1# https://github.com/ringsaturn/tzfpy/blob/main/examples/tzfpy_with_arrow.py
 2from zoneinfo import ZoneInfo
 3
 4import arrow
 5from tzfpy import get_tz
 6
 7tz = get_tz(139.7744, 35.6812)  # Tokyo
 8
 9arrow_now = arrow.now(ZoneInfo(tz))
10print(arrow_now)

Output:

12025-04-29T10:33:45.551282+09:00

Get local time via whenever

1pip install tzfpy whenever
 1# https://github.com/ringsaturn/tzfpy/blob/main/examples/tzfpy_with_whenever.py
 2from tzfpy import get_tz
 3from whenever import Instant
 4
 5now = Instant.now()
 6
 7tz = get_tz(139.7744, 35.6812)  # Tokyo
 8
 9now = now.to_tz(tz)
10
11print(now)

Output:

12025-04-29T10:33:28.427784+09:00[Asia/Tokyo]

Batch convert with dataframes

Consider a table that contains many cities and their GPS coordinates. The fastest way to get their timezone name is to use dataframes’ own map/apply functions.

Pandas

Pandas does provide built-in apply feature, however, it’s not very efficient. With NumPy’s help, we can get a much faster solution.

1pip install pandas numpy tzfpy
 1# https://github.com/ringsaturn/tzfpy/blob/main/examples/tzfpy_with_dataframe_pandas.py
 2import time
 3
 4import citiespy
 5import numpy as np
 6import pandas as pd
 7import tzfpy
 8
 9# lazy init
10_ = tzfpy.get_tz(0, 0)
11
12
13cities_as_dict = []
14for city in citiespy.all_cities():
15    cities_as_dict.append({"name": city.name, "lng": city.lng, "lat": city.lat})
16
17df = pd.DataFrame(cities_as_dict)
18
19
20start = time.perf_counter()
21df["tz_from_tzfpy"] = df.apply(lambda x: tzfpy.get_tz(x.lng, x.lat), axis=1)
22end = time.perf_counter()
23print(f"[tzfpy_with_dataframe] Pandas apply Time taken: {end - start} seconds")
24
25vec_tzfpy_get_tz = np.vectorize(tzfpy.get_tz)
26start = time.perf_counter()
27df["tz_from_tzfpy_vec"] = vec_tzfpy_get_tz(df.lng, df.lat)
28end = time.perf_counter()
29print(
30    f"[tzfpy_with_dataframe] Pandas apply with NumPy vectorize Time taken: {end - start} seconds"
31)

Output:

1[tzfpy_with_dataframe] Pandas apply Time taken: 0.8276746249757707 seconds
2[tzfpy_with_dataframe] Pandas apply with NumPy vectorize Time taken: 0.348435917054303 seconds

Polars

1pip install polars tzfpy
 1import time
 2
 3import citiespy
 4import polars as pl
 5import tzfpy
 6
 7# lazy init
 8_ = tzfpy.get_tz(0, 0)
 9
10
11cities_as_dict = []
12for city in citiespy.all_cities():
13    cities_as_dict.append({"name": city.name, "lng": city.lng, "lat": city.lat})
14
15df = pl.from_dicts(cities_as_dict)
16
17start = time.perf_counter()
18df = df.with_columns(
19    pl.struct(["lng", "lat"])
20    .map_elements(
21        lambda cols: tzfpy.get_tz(cols["lng"], cols["lat"]), return_dtype=pl.Utf8
22    )
23    .alias("tz_from_tzfpy")
24)
25end = time.perf_counter()
26print(f"[tzfpy_with_dataframe] Polars Time taken: {end - start} seconds")

Output:

1[tzfpy_with_dataframe] Polars Time taken: 0.34632241702638566 seconds

Pure NumPy

1pip install numpy tzfpy
 1import time
 2
 3import citiespy
 4import numpy as np
 5import pandas as pd
 6import tzfpy
 7
 8# lazy init
 9_ = tzfpy.get_tz(0, 0)
10
11
12cities_as_dict = []
13for city in citiespy.all_cities():
14    cities_as_dict.append({"name": city.name, "lng": city.lng, "lat": city.lat})
15
16df = pd.DataFrame(cities_as_dict)
17
18lngs = df.lng.values
19lats = df.lat.values
20
21vec_tzfpy_get_tz = np.vectorize(tzfpy.get_tz)
22
23start = time.perf_counter()
24_ = vec_tzfpy_get_tz(lngs, lats)
25end = time.perf_counter()
26print(f"[tzfpy_with_dataframe] Numpy Time taken: {end - start} seconds")

Output:

1[tzfpy_with_dataframe] Numpy Time taken: 0.33512612502090633 seconds

Web API

FastAPI

1pip install fastapi uvicorn tzfpy
  1from fastapi import FastAPI, Query
  2from pydantic import BaseModel, Field
  3from tzfpy import data_version, get_tz, get_tzs, timezonenames
  4
  5# lazy init
  6_ = get_tz(0, 0)
  7
  8
  9class TimezoneResponse(BaseModel):
 10    timezone: str = Field(..., description="Timezone", examples=["Asia/Tokyo"])
 11
 12
 13class TimezonesResponse(BaseModel):
 14    timezones: list[str] = Field(
 15        ..., description="Timezones", examples=[["Asia/Shanghai", "Asia/Urumqi"]]
 16    )
 17
 18
 19class TimezonenamesResponse(BaseModel):
 20    timezonenames: list[str] = Field(
 21        ..., description="Timezonenames", examples=[["Etc/GMT+1", "Etc/GMT+2"]]
 22    )
 23
 24
 25class DataVersionResponse(BaseModel):
 26    data_version: str = Field(..., description="Data version", examples=["2025b"])
 27
 28
 29app = FastAPI(
 30    title="tzfpy with FastAPI",
 31    description="tzfpy with FastAPI",
 32    contact={
 33        "name": "tzfpy",
 34        "url": "https://github.com/ringsaturn/tzfpy",
 35    },
 36)
 37
 38
 39@app.get("/")
 40def read_root():
 41    return {"message": "Hello, World!"}
 42
 43
 44@app.get("/timezone", response_model=TimezoneResponse)
 45def get_timezone(
 46    longitude: float = Query(
 47        ...,
 48        description="Longitude",
 49        ge=-180,
 50        le=180,
 51        examples=[139.767125],
 52        openapi_examples={"example-Tokyo": {"value": 139.767125}},
 53    ),
 54    latitude: float = Query(
 55        ...,
 56        description="Latitude",
 57        ge=-90,
 58        le=90,
 59        examples=[35.681236],
 60        openapi_examples={"example-Tokyo": {"value": 35.681236}},
 61    ),
 62):
 63    return TimezoneResponse(timezone=get_tz(longitude, latitude))
 64
 65
 66@app.get("/timezones", response_model=TimezonesResponse)
 67def get_timezones(
 68    longitude: float = Query(
 69        ...,
 70        description="Longitude",
 71        ge=-180,
 72        le=180,
 73        examples=[87.617733],
 74        openapi_examples={"example-Urumqi": {"value": 87.617733}},
 75    ),
 76    latitude: float = Query(
 77        ...,
 78        description="Latitude",
 79        ge=-90,
 80        le=90,
 81        examples=[43.792818],
 82        openapi_examples={"example-Urumqi": {"value": 43.792818}},
 83    ),
 84):
 85    return TimezonesResponse(timezones=get_tzs(longitude, latitude))
 86
 87
 88@app.get("/timezonenames", response_model=TimezonenamesResponse)
 89def get_all_timezones():
 90    return TimezonenamesResponse(timezonenames=timezonenames())
 91
 92
 93@app.get("/data_version", response_model=DataVersionResponse)
 94def get_data_version():
 95    return DataVersionResponse(data_version=data_version())
 96
 97
 98if __name__ == "__main__":
 99    import uvicorn
100
101    uvicorn.run(app, host="0.0.0.0", port=8010)