diff --git a/.all-contributorsrc b/.all-contributorsrc
index 594c1996..a017d4b3 100644
--- a/.all-contributorsrc
+++ b/.all-contributorsrc
@@ -1580,6 +1580,17 @@
"maintenance"
]
},
+ {
+ "login": "SGXander",
+ "name": "SGXander",
+ "avatar_url": "https://avatars.githubusercontent.com/u/8619536?v=4",
+ "profile": "https://github.com/SGXander",
+ "contributions": [
+ "code",
+ "bug",
+ "test"
+ ]
+ },
{
"login": "FezVrasta",
"name": "Federico Zivolo",
diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml
index 8444ac4e..bf90a11f 100644
--- a/.github/workflows/docker-build.yml
+++ b/.github/workflows/docker-build.yml
@@ -23,7 +23,7 @@ jobs:
steps:
- uses: actions/checkout@v7.0.1
- uses: docker/setup-qemu-action@v4.3.0
- - uses: docker/setup-buildx-action@v4.3.0
+ - uses: docker/setup-buildx-action@v4.4.1
- uses: docker/login-action@v4.6.0
with:
registry: ${{ env.REGISTRY }}
@@ -39,7 +39,7 @@ jobs:
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=raw,value=latest,enable={{is_default_branch}}
- - uses: docker/build-push-action@v7.3.0
+ - uses: docker/build-push-action@v7.4.0
with:
context: .
platforms: ${{ matrix.platform }}
diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml
index 8eae2558..c5a38524 100644
--- a/.github/workflows/docs.yml
+++ b/.github/workflows/docs.yml
@@ -28,7 +28,7 @@ jobs:
python-version: '3.14.7'
- name: Install uv
- uses: astral-sh/setup-uv@v10.0.1
+ uses: astral-sh/setup-uv@v10.2.0
- name: Install dependencies
run: uv sync --group docs
diff --git a/.github/workflows/install_dependencies/action.yml b/.github/workflows/install_dependencies/action.yml
index 0afbe9ba..b5d8a8d1 100644
--- a/.github/workflows/install_dependencies/action.yml
+++ b/.github/workflows/install_dependencies/action.yml
@@ -32,7 +32,7 @@ runs:
with:
python-version: ${{ inputs.python-version }}
- name: Set up UV
- uses: astral-sh/setup-uv@v10.0.1
+ uses: astral-sh/setup-uv@v10.2.0
- name: Install dependencies
shell: bash
run: |
diff --git a/.github/workflows/markdown-code-runner.yml b/.github/workflows/markdown-code-runner.yml
index 9614ee7d..d272a69a 100644
--- a/.github/workflows/markdown-code-runner.yml
+++ b/.github/workflows/markdown-code-runner.yml
@@ -23,7 +23,7 @@ jobs:
python-version: "3.14.7"
- name: Install uv
- uses: astral-sh/setup-uv@v10.0.1
+ uses: astral-sh/setup-uv@v10.2.0
- name: Update generated content
run: ./scripts/update-generated-content
diff --git a/README.md b/README.md
index 92fad8f3..4f1bac64 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
[](https://github.com/hacs/integration)

-[](#contributors-)
+[](#contributors-)
# 🌞 Adaptive Lighting: Enhance Your Home's Atmosphere with Smart, Sun-Synchronized Lighting 🌙
@@ -1119,6 +1119,7 @@ Notice the values of `brightness_mode_time_light` and `brightness_mode_time_dark
 Tim Stallmann 💻 |
 lehneres 🤔 |
 Ahmad Tawakol 💻 🐛 🚧 |
+  SGXander 💻 🐛 ⚠️ |
 Federico Zivolo 🌍 |
diff --git a/custom_components/adaptive_lighting/color_and_brightness.py b/custom_components/adaptive_lighting/color_and_brightness.py
index e62beb0c..4645f994 100644
--- a/custom_components/adaptive_lighting/color_and_brightness.py
+++ b/custom_components/adaptive_lighting/color_and_brightness.py
@@ -193,10 +193,12 @@ class SunEvents:
middle = abs(sunset - sunrise) / 2
if sunset > sunrise:
noon = sunrise + middle
- midnight = noon + timedelta(hours=12) * (1 if noon.hour < 12 else -1)
+ next_sunrise = self.sunrise(dt + timedelta(days=1))
+ midnight = sunset + (next_sunrise - sunset) / 2
else:
midnight = sunset + middle
- noon = midnight + timedelta(hours=12) * (1 if midnight.hour < 12 else -1)
+ next_sunset = self.sunset(dt + timedelta(days=1))
+ noon = sunrise + (next_sunset - sunrise) / 2
return noon, midnight
def sun_events(self, dt: datetime.datetime) -> list[tuple[SunEvent, float]]:
diff --git a/custom_components/adaptive_lighting/translations/et.json b/custom_components/adaptive_lighting/translations/et.json
index 58c1c54d..8ccea2a9 100644
--- a/custom_components/adaptive_lighting/translations/et.json
+++ b/custom_components/adaptive_lighting/translations/et.json
@@ -18,7 +18,7 @@
"step": {
"init": {
"title": "Kohanduva valguse suvandid",
- "description": "Kohanduva valguse suvandid. Valikute nimetused ühtuvad YAML kirjes olevatega. Valikuid ei kuvata kui seadistus on tehtud YAML kirjes.",
+ "description": "Kohanduva valguse suvandid. Valikute nimetused ühtuvad YAML kirjes olevatega. Valikuid ei kuvata kui seadistus on tehtud YAML kirjes. Parameetrite mõju näitavate interaktiivsete graafikute vaatamiseks külasta [seda veebirakendust]({webapp_url}). Lisateabe saamiseks vaata [ametlikku dokumentatsiooni]({docs_url}).",
"data": {
"lights": "valgustid",
"interval": "Intervall, aeg muutuste vahel sekundites",
diff --git a/custom_components/adaptive_lighting/translations/it.json b/custom_components/adaptive_lighting/translations/it.json
index 576e7303..d6236f67 100644
--- a/custom_components/adaptive_lighting/translations/it.json
+++ b/custom_components/adaptive_lighting/translations/it.json
@@ -18,7 +18,7 @@
"step": {
"init": {
"title": "Opzioni Illuminazione Adattiva",
- "description": "Tutte le opzioni per il componente Illuminazione Adattiva. I nomi delle opzioni corrispondono con le impostazioni YAML. Non sono mostrate opzioni se hai la voce adaptive-lighting definita nella tua configurazione YAML.",
+ "description": "Tutte le opzioni per il componente Illuminazione Adattiva. I nomi delle opzioni corrispondono con le impostazioni YAML. Non sono mostrate opzioni se hai la voce adaptive-lighting definita nella tua configurazione YAML. Per grafici interattivi che mostrano l'effetto dei parametri, visita [questa web app]({webapp_url}). Per ulteriori dettagli, consulta la [documentazione ufficiale]({docs_url}).",
"data": {
"lights": "luci",
"interval": "interval: Tempo tra i cambiamenti dello switch. (secondi)",
diff --git a/custom_components/adaptive_lighting/translations/nb.json b/custom_components/adaptive_lighting/translations/nb.json
index 50464143..85c29f76 100644
--- a/custom_components/adaptive_lighting/translations/nb.json
+++ b/custom_components/adaptive_lighting/translations/nb.json
@@ -18,7 +18,7 @@
"step": {
"init": {
"title": "Adaptiv Belysning Innstillinger",
- "description": "Alle innstillinger for en adaptiv belysning konfigurasjon. Innstillingene er identiske med innstillingene for YAML konfigurasjon. Ingen innstillinger vises dersom du har definert adaptive_lighting i din YAML konfigurasjon.",
+ "description": "Alle innstillinger for en adaptiv belysning konfigurasjon. Innstillingene er identiske med innstillingene for YAML konfigurasjon. Ingen innstillinger vises dersom du har definert adaptive_lighting i din YAML konfigurasjon. For interaktive grafer som viser effekten av parametrene, besøk [denne nettappen]({webapp_url}). For flere detaljer, se [den offisielle dokumentasjonen]({docs_url}).",
"data": {
"lights": "Lys / Lyskilder",
"interval": "'interval': tiden mellom oppdateringer (i sekunder)",
diff --git a/custom_components/adaptive_lighting/translations/pt-BR.json b/custom_components/adaptive_lighting/translations/pt-BR.json
index 76042c8b..eae954dc 100644
--- a/custom_components/adaptive_lighting/translations/pt-BR.json
+++ b/custom_components/adaptive_lighting/translations/pt-BR.json
@@ -25,7 +25,7 @@
"step": {
"init": {
"title": "Opções da iluminação adaptiva",
- "description": "Todas as configurações de um componente de iluminação adaptativa. Os nomes das opções correspondem às configurações de YAML. Nenhuma opção será exibida se você tiver a entrada adaptive_lighting definida em sua configuração YAML.",
+ "description": "Todas as configurações de um componente de iluminação adaptativa. Os nomes das opções correspondem às configurações de YAML. Nenhuma opção será exibida se você tiver a entrada adaptive_lighting definida em sua configuração YAML. Para gráficos interativos que demonstram os efeitos dos parâmetros, acesse [este aplicativo web]({webapp_url}). Para mais detalhes, consulte a [documentação oficial]({docs_url}).",
"data": {
"lights": "luzes",
"interval": "interval: Tempo entre as atualizações do switch. (segundos)",
diff --git a/custom_components/adaptive_lighting/translations/ru.json b/custom_components/adaptive_lighting/translations/ru.json
index cdb4b768..a466c94c 100644
--- a/custom_components/adaptive_lighting/translations/ru.json
+++ b/custom_components/adaptive_lighting/translations/ru.json
@@ -18,7 +18,7 @@
"step": {
"init": {
"title": "Настройки Adaptive Lighting",
- "description": "Все настройки компонента Adaptive Lighting. Названия опций соответствуют настройкам в YAML. Параметры не отображаются, если в конфигурации YAML определена запись adaptive_lighting.",
+ "description": "Все настройки компонента Adaptive Lighting. Названия опций соответствуют настройкам в YAML. Параметры не отображаются, если в конфигурации YAML определена запись adaptive_lighting. Интерактивные графики, демонстрирующие влияние параметров, доступны в [этом веб-приложении]({webapp_url}). Подробнее см. в [официальной документации]({docs_url}).",
"data": {
"lights": "Осветительные приборы: список источников света, которыми нужно управлять (может быть пустым). 🌟",
"interval": "interval: Интервал между обновлениями переключателя. (секунды)",
diff --git a/custom_components/adaptive_lighting/translations/sv.json b/custom_components/adaptive_lighting/translations/sv.json
index e0b254e1..37d0d169 100644
--- a/custom_components/adaptive_lighting/translations/sv.json
+++ b/custom_components/adaptive_lighting/translations/sv.json
@@ -25,7 +25,7 @@
"step": {
"init": {
"title": "Adaptiv Ljussättning Inställningar",
- "description": "Alla inställningar för en Adaptiv Ljussättning komponent. Titeln på inställningarna är desamma som i YAML konfigurationen. Inga inställningar visas om enheten redan är konfigurerad i YAML.",
+ "description": "Alla inställningar för en Adaptiv Ljussättning komponent. Titeln på inställningarna är desamma som i YAML konfigurationen. Inga inställningar visas om enheten redan är konfigurerad i YAML. För interaktiva grafer som visar parametrarnas effekt, besök [den här webbappen]({webapp_url}). För mer information, se [den officiella dokumentationen]({docs_url}).",
"data": {
"lights": "lights, ljuskällor",
"interval": "interval, Tid mellan uppdateringar i sekunder",
diff --git a/custom_components/adaptive_lighting/translations/uk.json b/custom_components/adaptive_lighting/translations/uk.json
index f3d66617..999a434b 100644
--- a/custom_components/adaptive_lighting/translations/uk.json
+++ b/custom_components/adaptive_lighting/translations/uk.json
@@ -25,7 +25,7 @@
"step": {
"init": {
"title": "Опції адаптивного освітлення",
- "description": "Всі налаштування компонента адаптивного освітлення. Назви опцій відповідають налаштуванням у YAML. Опції не відображаються, якщо ви вже визначили їх у компоненті adaptive_lighting вашої YAML-конфігурації.",
+ "description": "Всі налаштування компонента адаптивного освітлення. Назви опцій відповідають налаштуванням у YAML. Опції не відображаються, якщо ви вже визначили їх у компоненті adaptive_lighting вашої YAML-конфігурації. Інтерактивні графіки, що демонструють вплив параметрів, доступні у [цьому вебдодатку]({webapp_url}). Докладніше див. в [офіційній документації]({docs_url}).",
"data": {
"lights": "прилади",
"interval": "interval: Час між оновленнями перемикача. (секунди)",
diff --git a/tests/test_color_and_brightness.py b/tests/test_color_and_brightness.py
index e7f0a21a..8598b121 100644
--- a/tests/test_color_and_brightness.py
+++ b/tests/test_color_and_brightness.py
@@ -153,6 +153,41 @@ def test_noon_and_midnight(tzinfo_and_location):
assert midnight == location.midnight(date)
+@pytest.mark.parametrize("day", [dt.date(2026, 4, 17), dt.date(2026, 8, 30)])
+def test_midnight_falls_in_the_night_it_belongs_to(day):
+ """Derived midnight must lie between sunset and the next sunrise (#1599)."""
+ location = Location(
+ LocationInfo(
+ name="name",
+ region="region",
+ timezone="UTC",
+ latitude=51.5,
+ longitude=0.0, # on the prime meridian
+ ),
+ )
+ sun_events = SunEvents(
+ name="test",
+ astral_observer=location.observer,
+ sunrise_time=None,
+ min_sunrise_time=None,
+ max_sunrise_time=dt.time(8, 0),
+ sunset_time=None,
+ min_sunset_time=dt.time(17, 0),
+ max_sunset_time=dt.time(20, 0),
+ timezone=zoneinfo.ZoneInfo("UTC"),
+ )
+ date = dt.datetime.combine(day, dt.time(12), tzinfo=dt.UTC)
+ sunset = sun_events.sunset(date)
+ next_sunrise = sun_events.sunrise(date + dt.timedelta(days=1))
+ _, midnight = sun_events.noon_and_midnight(date)
+ assert sunset < midnight < next_sunrise
+
+ just_after_sunset = sunset + dt.timedelta(minutes=40)
+ events = sun_events.prev_and_next_events(just_after_sunset)
+ assert [event for event, _ in events] == [SunEvent.SUNSET, SunEvent.MIDNIGHT]
+ assert sun_events.sun_position(just_after_sunset) > -0.5
+
+
def test_sun_events(tzinfo_and_location):
tzinfo, location = tzinfo_and_location
sun_events = SunEvents(