feat(tracking): per-config quiet hours with app-level IANA timezone

Add quiet_hours_enabled/start/end to TrackingConfig (HH:MM strings
interpreted in the app-level timezone AppSetting). The dispatch path
loads the app timezone once per run and passes it through
event_allowed_by_config -> in_quiet_hours, so overnight windows like
22:00-07:00 work correctly in any IANA tz.

Frontend exposes a Timezone field under Settings and a Quiet Hours
section on the Immich tracking-config form with time-picker inputs.
This commit is contained in:
2026-04-22 02:31:48 +03:00
parent 56993d2ca3
commit 6c3dd67c1b
12 changed files with 113 additions and 13 deletions
@@ -21,7 +21,11 @@ from ..database.models import (
NotificationTrackerState,
ServiceProvider,
)
from .dispatch_helpers import event_allowed_by_config, load_link_data
from .dispatch_helpers import (
event_allowed_by_config,
get_app_timezone,
load_link_data,
)
_LOGGER = logging.getLogger(__name__)
@@ -85,7 +89,10 @@ async def check_tracker(tracker_id: int) -> dict[str, Any]:
}
# Load tracker-target links
link_data = await load_link_data(session, tracker_id, check_quiet_hours=True)
link_data = await load_link_data(session, tracker_id)
# Load app-level timezone for quiet-hours evaluation.
app_tz = await get_app_timezone(session)
# Snapshot the data we need
provider_type = provider.type
@@ -236,7 +243,7 @@ async def check_tracker(tracker_id: int) -> dict[str, Any]:
for ld in link_data:
# Apply per-link event filtering from tracking config
tc = ld["tracking_config"]
if tc and not event_allowed_by_config(event, tc):
if tc and not event_allowed_by_config(event, tc, app_tz):
_LOGGER.info(" Skipped by tracking config filter")
continue