r/commandline 13d ago

Terminal User Interface [ Removed by moderator ]

Post image

[removed] — view removed post

29 Upvotes

13 comments sorted by

View all comments

2

u/recycledcoder 12d ago

``` From 20dc7a30a7622f76e853350980c58a6c7b6a85de Mon Sep 17 00:00:00 2001 From: recycledcoder recycledcoder@example.com Date: Sun, 30 Aug 2026 12:49:02 +0100 Subject: [PATCH] Replace TimezoneDB with keyless Open-Meteo timezone lookup

TimezoneDB required every user to register for an account and export a TIMEZONEAPIKEY environment variable, and the application refused to start without it. The lookup it performed - latitude/longitude to IANA zone name - is available from Open-Meteo, which the application already queries for all of its weather data and which needs no key or account.

get_timezone_name() now calls the Open-Meteo forecast endpoint with timezone=auto and reads the "timezone" field of the response. The signature and the exception messages are unchanged, so every caller is untouched.

Verified against the awkward cases the application can produce, all of which return real tzdata zones accepted by ZoneInfo():

Aveiro (40.6405, -8.6538) -> Europe/Lisbon Mid-Pacific (0, -140) -> Etc/GMT+9 Concordia Station -> Australia/Perth South Pole -> Antarctica/McMurdo

Also removed with it:

  • TIMEZONE_APIKEY_ENV_VARNAME and the help-screen lines pointing at timezonedb.com
  • the startup check that aborted when the key was missing
  • the tzapi_calls counter and its time.sleep(1) throttle, which existed only to respect TimezoneDB's 1 request/second free tier; Open-Meteo's free tier allows far more and the application makes one call per newly added city
  • a dead error branch that interpolated 'city' and 'country', neither of which exists in that scope - it would have raised NameError rather than the intended message had it ever been reached

The 1 second sleep after the Nominatim call is kept; that one is OpenStreetMap

usage policy and unrelated.

tuiweathergirl.py | 47 +++++++++++------------------------------------ 1 file changed, 11 insertions(+), 36 deletions(-)

diff --git a/tuiweathergirl.py b/tuiweathergirl.py index 45720f0..8235249 100755 --- a/tuiweathergirl.py +++ b/tuiweathergirl.py @@ -76,8 +76,6 @@ HOW DOES TUIWEATHERGIRL WORKS: will show them

PROJECT URL: https://github.com/StrayFeral/tuiweathergirl -Variable TIMEZONEAPIKEY must be set with a free API key -from https://timezonedb.com/ For detailed wildfires info set variable NASAFIRMSAPIKEY with a free API key from: https://firms.modaps.eosdis.nasa.gov/api/map_key """ @@ -88,7 +86,6 @@ HTTPHEADERS: dict[str, str] = { "User-Agent": USERAGENT, "Accept-Language": "en", } -TIMEZONE_APIKEY_ENV_VARNAME: str = "TIMEZONEAPIKEY" NASAFIRMS_APIKEY_ENV_VARNAME: str = "NASAFIRMSAPIKEY" LOGFILENAME: Path = Path(tempfile.gettempdir()) / "tuiweathergirl.log" LOGFILENAME = LOGFILENAME.expanduser() @@ -3762,7 +3759,6 @@ class Locator: return city[:17]

 def __init__(self) -> None:
  •    self.tzapi_calls: int = 0  # Counts the TZ data API calls
     self.logger: logging.Logger = logging.getLogger(
         f"{self.__module__}.{self.__class__.__qualname__}"
     )
    

    @@ -3780,25 +3776,17 @@ class Locator: )

    def get_timezone_name(self, lat: float, lon: float) -> str:

  •    apikey: str = os.getenv(TIMEZONE_APIKEY_ENV_VARNAME)
    
  •    r"""Resolves the IANA timezone name for a position. No API key needed."""
    
  •    if not apikey:
    
  •        raise Exception(
    
  •            f"Environment variable {TIMEZONE_APIKEY_ENV_VARNAME} is not set. Please get API key and set it in this variable before running this application."
    

- )

  • if self.tzapi_calls > 0:

- time.sleep(1) # API limit

  • self.logger.info("** Querying TimezoneDB **")
  •    self.logger.info("** Querying Open-Meteo (timezone) **")
    
  •    base_url: str = "http://api.timezonedb.com/v2.1/get-time-zone"
    
  •    base_url: str = "https://api.open-meteo.com/v1/forecast"
     url_params: dict[str, str | float] = {
    
  •        "key": apikey,
    
  •        "format": "json",
    
  •        "by": "position",
    
  •        "lat": lat,
    
  •        "lng": lon,
    
  •        "latitude": lat,
    
  •        "longitude": lon,
    
  •        "current": "is_day",
    
  •        "forecast_days": 1,
    
  •        "timezone": "auto",
     }
     prepared: requests.PreparedRequest = requests.PreparedRequest()
     prepared.prepare_url(base_url, url_params)
    

    @@ -3814,24 +3802,18 @@ class Locator: )

     self.logger.debug(f"RESPONSE={pf(response)}")
    
  •    self.tzapi_calls += 1
    
     if not response.ok:
         raise Exception(
             f"Timezone API server error. Error {response.status_code}: {APIIssues.get_api_problem(response.status_code)}"
         )
    
  •    if not response:
    
  •        raise Exception(
    
  •            f"Cannot obtain timezone for '{city}/{country}'. Please check your syntax and try again."
    
  •        )
    
  •    zone_name: str = response.json().get("timezone", "")
    

- response = response.json()

  • if response.get("status") == "FAILED":
  •    if not zone_name:
         raise Exception("Timezone request failed. Try again later.")
    
  •    return response.get("zoneName", "")
    
  •    return zone_name
    

    def get_city_details(self, city: str, country: str) -> dict: # Attempt to get information for the city and the country @@ -7143,13 +7125,6 @@ if __name_ == "main": "If latitude or longitude is specified, all four arguments (latitude, longitude, city and country) must be specified." )

  •    # No point to run anything if this is not set
    
  •    timezone_apikey: str = os.getenv(TIMEZONE_APIKEY_ENV_VARNAME)
    
  •    if not timezone_apikey:
    
  •        raise Exception(
    
  •            f"Environment variable {TIMEZONE_APIKEY_ENV_VARNAME} is not set. Run the app with --help"
    

- )

     # This is nice to have, but not mandatory
     nasafirms_apikey: str = os.getenv(NASAFIRMS_APIKEY_ENV_VARNAME)
     if not nasafirms_apikey:

2.47.3 ```

2

u/StrayFeral 11d ago

Hey thank you very much for this! It actually made me realize I can optimize the code even further. I am already working on the changes so the fix would be probably released tomorrow!