4bdfb673cc
Testy / Testy warstwy logicznej (silnik) (pull_request) Successful in 11m2s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m56s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 34s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 23s
build / build (push) Successful in 1m40s
Testy / Testy warstwy logicznej (silnik) (push) Successful in 11m12s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m52s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 32s
Testy / Kontrola składni wszystkich warstw (push) Successful in 19s
„Logika dwóch lokalizacji": dotąd offset GMT był ręcznym polem (PRE-19) — trzeba
było go znać i samemu pamiętać o czasie letnim. Teraz liczymy go z lokalizacji.
Sedno wymagania: strefę ustalamy RAZ i trzymamy jako stałą liczbę, żeby drobna
zmiana współrzędnych nie przerzuciła DST i nie „przeskoczyła" Ascendenta na
sąsiedni znak. „Większa miejscowość z bazy" okazuje się zbędna — strefa IANA jest
i tak regionalna, więc wioska daje tę samą strefę co pobliskie miasto.
Jak:
- `timezone.py`: współrzędne → strefa IANA (tzfpy, OFFLINE — bez sieci), a z niej
offset DLA DATY URODZENIA. `zoneinfo`/`tzdata` znają reguły historyczne i DST:
Kraków 1984 to +1h zimą, +2h latem; Katmandu +5:45. Degraduje się do None (brak
biblioteki / punkt bez strefy / zła data) — wtedy zostaje ręczny offset.
- Endpoint `GET /timezone?lat&lon&date&time` → {tz, offset, dst, label}. 404, gdy
nie da się ustalić.
- `geo.js`: po wyborze miejsca (mapa / wyszukiwarka / „Tu i teraz") oraz przy
zmianie DATY (bo DST zależy od pory roku) pobiera offset i wypełnia pole
tz_offset, pokazując wykrytą strefę („Wykryto: Europe/Warsaw · +2:00 (czas
letni)"). Pole zostaje edytowalne. Na wejściu podpowiada tylko gdy offset
wygląda na nieustawiony — nie nadpisuje wartości ręcznie wpisanej i wysłanej.
Zależności (lekkie, offline): tzfpy (wheel Rust) + tzdata (dla zoneinfo w slim-obrazie).
Weryfikacja: żywy serwer — Kraków 1984-06 → +2:00 (czas letni), 1984-01 → +1:00,
Katmandu → +5:45. Testy: +12 strefa (moduł + endpoint), +5 JS. Prezentacja 187.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
94 lines
3.0 KiB
Python
94 lines
3.0 KiB
Python
"""Strefa czasowa z lokalizacji (PRE-03).
|
|
|
|
Najważniejsze: offset ma być DST-świadomy i liczony DLA DATY URODZENIA (reguły
|
|
historyczne), a nie „na sztywno" — inaczej wróciłby dokładnie ten błąd, który
|
|
PRE-03 ma usunąć (pomylona godzina / czas letni → przeskok Ascendenta).
|
|
"""
|
|
import pytest
|
|
|
|
from app import timezone as tz
|
|
|
|
|
|
requires_tzfpy = pytest.mark.skipif(tz.get_tz is None, reason="brak tzfpy w środowisku")
|
|
|
|
|
|
@requires_tzfpy
|
|
def test_krakow_summer_is_plus_two_with_dst():
|
|
r = tz.resolve(50.0647, 19.945, "1984-06-01", "11:20")
|
|
assert r["tz"] == "Europe/Warsaw"
|
|
assert r["offset"] == 2.0
|
|
assert r["dst"] is True
|
|
assert "czas letni" in r["label"]
|
|
|
|
|
|
@requires_tzfpy
|
|
def test_krakow_winter_is_plus_one_without_dst():
|
|
r = tz.resolve(50.0647, 19.945, "1984-01-15", "10:00")
|
|
assert r["offset"] == 1.0
|
|
assert r["dst"] is False
|
|
assert "czas letni" not in r["label"]
|
|
|
|
|
|
@requires_tzfpy
|
|
def test_kathmandu_is_five_forty_five():
|
|
"""Katmandu +5:45 — sztandarowy przypadek offsetu w 15-minutowym kroku."""
|
|
r = tz.resolve(27.7172, 85.3240, "2000-05-01", "08:00")
|
|
assert r["tz"] == "Asia/Kathmandu"
|
|
assert r["offset"] == 5.75
|
|
assert "+5:45" in r["label"]
|
|
|
|
|
|
@requires_tzfpy
|
|
def test_new_york_dst_differs_by_season():
|
|
summer = tz.resolve(40.7128, -74.0060, "2020-07-01", "12:00")
|
|
winter = tz.resolve(40.7128, -74.0060, "2020-01-01", "12:00")
|
|
assert summer["offset"] == -4.0 and summer["dst"] is True # EDT
|
|
assert winter["offset"] == -5.0 and winter["dst"] is False # EST
|
|
|
|
|
|
@requires_tzfpy
|
|
def test_missing_time_defaults_to_noon_not_crash():
|
|
r = tz.resolve(50.0647, 19.945, "1984-06-01", "")
|
|
assert r and r["offset"] == 2.0
|
|
|
|
|
|
@requires_tzfpy
|
|
def test_bad_date_returns_none_not_exception():
|
|
assert tz.resolve(50.0, 19.9, "nonsense", "12:00") is None
|
|
|
|
|
|
def test_no_library_degrades_to_none(monkeypatch):
|
|
"""Brak tzfpy nie może wywalić — po prostu bez podpowiedzi (ręczny offset zostaje)."""
|
|
monkeypatch.setattr(tz, "get_tz", None)
|
|
assert tz.resolve(50.0, 19.9, "1984-06-01", "11:20") is None
|
|
|
|
|
|
# ---- endpoint /timezone ----
|
|
|
|
def _client():
|
|
import os
|
|
|
|
os.environ.pop("APP_PASSWORD", None) # bez bramki logowania w teście
|
|
from starlette.testclient import TestClient
|
|
|
|
from app.main import app
|
|
|
|
return TestClient(app)
|
|
|
|
|
|
@requires_tzfpy
|
|
def test_endpoint_returns_offset_for_a_point():
|
|
r = _client().get("/timezone", params={"lat": 50.0647, "lon": 19.945,
|
|
"date": "1984-06-01", "time": "11:20"})
|
|
assert r.status_code == 200
|
|
body = r.json()
|
|
assert body["offset"] == 2.0 and body["tz"] == "Europe/Warsaw"
|
|
|
|
|
|
def test_endpoint_404_when_zone_cannot_be_resolved(monkeypatch):
|
|
"""Gdy strefy nie da się ustalić → 404, front zostawia ręczny offset."""
|
|
from app import timezone as tzmod
|
|
monkeypatch.setattr(tzmod, "get_tz", None)
|
|
r = _client().get("/timezone", params={"lat": 0.0, "lon": 0.0, "date": "2000-01-01"})
|
|
assert r.status_code == 404
|