feat(prezentacja): strefa czasowa z lokalizacji — DST-świadomy offset (PRE-03)
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>
This commit was merged in pull request #43.
This commit is contained in:
2026-07-28 22:42:31 +02:00
parent 9a5d61b5eb
commit 4bdfb673cc
6 changed files with 263 additions and 0 deletions
@@ -86,3 +86,39 @@ def test_now_button_still_syncs_map():
"""Odświeżenie nazwy nie może zastąpić przesunięcia pineski i widoku mapy."""
body = _handler_body()
assert "marker.setLatLng" in body and "map.setView" in body
# ---- strefa czasowa z lokalizacji (PRE-03) ----
def test_setcoords_resolves_timezone():
"""Nowe współrzędne (mapa/wyszukiwarka) mają odświeżyć strefę i offset."""
src = _geo_source()
body = src[src.index("function setCoords"):src.index("\n }", src.index("function setCoords"))]
assert "resolveTz()" in body
def test_now_button_resolves_timezone():
"""„Tu i teraz" to nowe miejsce → nowa strefa."""
assert "resolveTz()" in _handler_body()
def test_date_change_recomputes_timezone():
"""Czas letni zależy od daty — zmiana daty musi przeliczyć offset."""
src = _geo_source()
assert "dateEl.addEventListener('change', resolveTz)" in src
def test_timezone_hint_does_not_clobber_manual_offset_on_load():
"""Na wejściu podpowiadamy strefę TYLKO gdy offset wygląda na nieustawiony —
inaczej nadpisalibyśmy wartość ręcznie wpisaną i wysłaną przez użytkownika."""
src = _geo_source()
assert "parseFloat(tzEl.value) === 0" in src
def test_timezone_fills_offset_field_and_shows_zone():
src = _geo_source()
start = src.index("function resolveTz")
body = src[start:src.index("\n }", start)]
assert "/timezone?lat=" in body
assert "setVal(tzEl, d.offset)" in body # wypełnia pole offsetu
assert "d.label" in body # pokazuje wykrytą strefę