fix: correct wttr.in endpoint usage and improve timezone resolution logic for diverse location inputs
Browse files- core/tools.py +59 -6
core/tools.py
CHANGED
|
@@ -292,9 +292,11 @@ def get_weather(location: str) -> str:
|
|
| 292 |
Returns a concise summary string.
|
| 293 |
"""
|
| 294 |
try:
|
|
|
|
|
|
|
| 295 |
resp = httpx.get(
|
| 296 |
-
"https://wttr.in/",
|
| 297 |
-
params={"format": "j1"
|
| 298 |
timeout=10,
|
| 299 |
headers={"User-Agent": "Mozilla/5.0"},
|
| 300 |
)
|
|
@@ -328,6 +330,7 @@ def get_weather(location: str) -> str:
|
|
| 328 |
def get_timezone(location: str) -> str:
|
| 329 |
from datetime import datetime
|
| 330 |
from zoneinfo import ZoneInfo
|
|
|
|
| 331 |
|
| 332 |
_CITY_MAP = {
|
| 333 |
"tokyo": "Asia/Tokyo", "osaka": "Asia/Tokyo",
|
|
@@ -343,14 +346,64 @@ def get_timezone(location: str) -> str:
|
|
| 343 |
"vancouver": "America/Vancouver",
|
| 344 |
}
|
| 345 |
|
| 346 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 347 |
if not tz_name:
|
| 348 |
try:
|
| 349 |
import zoneinfo
|
| 350 |
all_zones = zoneinfo.available_timezones()
|
| 351 |
-
|
| 352 |
-
|
| 353 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 354 |
except Exception:
|
| 355 |
pass
|
| 356 |
|
|
|
|
| 292 |
Returns a concise summary string.
|
| 293 |
"""
|
| 294 |
try:
|
| 295 |
+
# wttr.in requires the location as a path parameter (e.g., https://wttr.in/Paris)
|
| 296 |
+
# using q=location query parameters returns 500 server errors
|
| 297 |
resp = httpx.get(
|
| 298 |
+
f"https://wttr.in/{location}",
|
| 299 |
+
params={"format": "j1"},
|
| 300 |
timeout=10,
|
| 301 |
headers={"User-Agent": "Mozilla/5.0"},
|
| 302 |
)
|
|
|
|
| 330 |
def get_timezone(location: str) -> str:
|
| 331 |
from datetime import datetime
|
| 332 |
from zoneinfo import ZoneInfo
|
| 333 |
+
import re
|
| 334 |
|
| 335 |
_CITY_MAP = {
|
| 336 |
"tokyo": "Asia/Tokyo", "osaka": "Asia/Tokyo",
|
|
|
|
| 346 |
"vancouver": "America/Vancouver",
|
| 347 |
}
|
| 348 |
|
| 349 |
+
loc_clean = location.lower().strip()
|
| 350 |
+
tz_name = None
|
| 351 |
+
|
| 352 |
+
# 1. Exact map check
|
| 353 |
+
if loc_clean in _CITY_MAP:
|
| 354 |
+
tz_name = _CITY_MAP[loc_clean]
|
| 355 |
+
|
| 356 |
if not tz_name:
|
| 357 |
try:
|
| 358 |
import zoneinfo
|
| 359 |
all_zones = zoneinfo.available_timezones()
|
| 360 |
+
|
| 361 |
+
# 2. Match full string with underscores in timezone name
|
| 362 |
+
loc_underscore = loc_clean.replace(" ", "_")
|
| 363 |
+
matches = [z for z in all_zones if loc_underscore in z.lower()]
|
| 364 |
+
if matches:
|
| 365 |
+
tz_name = matches[0]
|
| 366 |
+
|
| 367 |
+
# 3. Handle region/country suffixes (e.g. "Tokyo, Japan" -> "Tokyo")
|
| 368 |
+
if not tz_name and "," in loc_clean:
|
| 369 |
+
city_part = loc_clean.split(",")[0].strip()
|
| 370 |
+
if city_part in _CITY_MAP:
|
| 371 |
+
tz_name = _CITY_MAP[city_part]
|
| 372 |
+
else:
|
| 373 |
+
city_underscore = city_part.replace(" ", "_")
|
| 374 |
+
matches = [z for z in all_zones if city_underscore in z.lower()]
|
| 375 |
+
if matches:
|
| 376 |
+
tz_name = matches[0]
|
| 377 |
+
|
| 378 |
+
# 4. Fallback: match sub-phrases of words
|
| 379 |
+
if not tz_name:
|
| 380 |
+
words = [w for w in re.split(r"[\s,;_]+", loc_clean) if w]
|
| 381 |
+
# Prioritize exact match with the city component of the zone
|
| 382 |
+
for length in range(len(words), 0, -1):
|
| 383 |
+
for i in range(len(words) - length + 1):
|
| 384 |
+
phrase = "_".join(words[i : i + length])
|
| 385 |
+
if len(phrase) > 2:
|
| 386 |
+
matches = [z for z in all_zones if z.lower().split("/")[-1] == phrase]
|
| 387 |
+
if matches:
|
| 388 |
+
tz_name = matches[0]
|
| 389 |
+
break
|
| 390 |
+
if tz_name:
|
| 391 |
+
break
|
| 392 |
+
|
| 393 |
+
# Fallback to substring match on city component (excluding common words)
|
| 394 |
+
if not tz_name:
|
| 395 |
+
for length in range(len(words), 0, -1):
|
| 396 |
+
for i in range(len(words) - length + 1):
|
| 397 |
+
phrase = "_".join(words[i : i + length])
|
| 398 |
+
if len(phrase) > 2:
|
| 399 |
+
if phrase in {"the", "what", "time", "date", "local", "zone", "city"}:
|
| 400 |
+
continue
|
| 401 |
+
matches = [z for z in all_zones if phrase in z.lower().split("/")[-1]]
|
| 402 |
+
if matches:
|
| 403 |
+
tz_name = matches[0]
|
| 404 |
+
break
|
| 405 |
+
if tz_name:
|
| 406 |
+
break
|
| 407 |
except Exception:
|
| 408 |
pass
|
| 409 |
|