diff --git a/app/archiver/manager.py b/app/archiver/manager.py index f4d43e0..031cb43 100644 --- a/app/archiver/manager.py +++ b/app/archiver/manager.py @@ -22,6 +22,37 @@ _scheduler: Optional[BackgroundScheduler] = None CLEANUP_JOB_ID = 'daily_cleanup' +def get_local_timezone_name() -> str: + """ + Get the local timezone name for display purposes. + Uses TZ environment variable if set, otherwise detects from system. + + Returns: + Timezone name (e.g., 'Europe/Warsaw', 'UTC', 'CET') + """ + import os + from datetime import datetime + + # First check TZ environment variable + tz_env = os.environ.get('TZ') + if tz_env: + return tz_env + + # Fall back to system timezone detection + try: + # Try to get timezone name from datetime + local_tz = datetime.now().astimezone().tzinfo + if local_tz: + tz_name = str(local_tz) + # Clean up timezone name if needed + if tz_name and tz_name != 'None': + return tz_name + except Exception: + pass + + return 'local' + + def get_archive_path(archive_date: str) -> Path: """ Get the path to an archive file for a specific date. @@ -341,7 +372,7 @@ def schedule_cleanup(enabled: bool, hour: int = 1) -> bool: Args: enabled: True to enable cleanup job, False to disable - hour: Hour (0-23 UTC) at which to run cleanup job + hour: Hour (0-23, local time) at which to run cleanup job Returns: True if successful, False otherwise @@ -358,7 +389,7 @@ def schedule_cleanup(enabled: bool, hour: int = 1) -> bool: if not isinstance(hour, int) or hour < 0 or hour > 23: hour = 1 - # Add cleanup job at specified hour UTC + # Add cleanup job at specified hour (local time) trigger = CronTrigger(hour=hour, minute=0) _scheduler.add_job( @@ -369,7 +400,8 @@ def schedule_cleanup(enabled: bool, hour: int = 1) -> bool: replace_existing=True ) - logger.info(f"Cleanup job scheduled - will run daily at {hour:02d}:00 UTC") + tz_name = get_local_timezone_name() + logger.info(f"Cleanup job scheduled - will run daily at {hour:02d}:00 ({tz_name})") else: # Remove cleanup job if it exists try: @@ -400,7 +432,8 @@ def init_cleanup_schedule(): if settings.get('enabled'): hour = settings.get('hour', 1) schedule_cleanup(enabled=True, hour=hour) - logger.info(f"Auto-cleanup enabled from saved settings (hour={hour:02d}:00 UTC)") + tz_name = get_local_timezone_name() + logger.info(f"Auto-cleanup enabled from saved settings (hour={hour:02d}:00 {tz_name})") else: logger.info("Auto-cleanup is disabled in saved settings") @@ -424,12 +457,15 @@ def schedule_daily_archiving(): return try: + # Use local timezone (from TZ env variable or system default) + tz_name = get_local_timezone_name() + _scheduler = BackgroundScheduler( - daemon=True, - timezone='UTC' # Use UTC for consistency + daemon=True + # No timezone specified = uses system local timezone ) - # Schedule job for midnight every day + # Schedule job for midnight every day (local time) trigger = CronTrigger(hour=0, minute=0) _scheduler.add_job( @@ -442,7 +478,7 @@ def schedule_daily_archiving(): _scheduler.start() - logger.info("Archive scheduler started - will run daily at 00:00 UTC") + logger.info(f"Archive scheduler started - will run daily at 00:00 ({tz_name})") # Initialize cleanup schedule from saved settings init_cleanup_schedule() diff --git a/app/routes/api.py b/app/routes/api.py index 11244f4..6566121 100644 --- a/app/routes/api.py +++ b/app/routes/api.py @@ -1989,15 +1989,22 @@ def get_cleanup_settings_api(): "types": [1, 2, 3, 4], "date_field": "last_advert", "days": 30, - "name_filter": "" - } + "name_filter": "", + "hour": 1 + }, + "timezone": "Europe/Warsaw" } """ try: + from app.archiver.manager import get_local_timezone_name + settings = get_cleanup_settings() + timezone = get_local_timezone_name() + return jsonify({ 'success': True, - 'settings': settings + 'settings': settings, + 'timezone': timezone }), 200 except Exception as e: logger.error(f"Error getting cleanup settings: {e}") @@ -2009,8 +2016,10 @@ def get_cleanup_settings_api(): 'types': [1, 2, 3, 4], 'date_field': 'last_advert', 'days': 30, - 'name_filter': '' - } + 'name_filter': '', + 'hour': 1 + }, + 'timezone': 'local' }), 500 @@ -2088,13 +2097,14 @@ def update_cleanup_settings_api(): }), 500 # Update scheduler based on enabled state and hour - from app.archiver.manager import schedule_cleanup + from app.archiver.manager import schedule_cleanup, get_local_timezone_name schedule_cleanup(enabled=updated.get('enabled', False), hour=updated.get('hour', 1)) return jsonify({ 'success': True, 'message': 'Cleanup settings updated', - 'settings': updated + 'settings': updated, + 'timezone': get_local_timezone_name() }), 200 except Exception as e: diff --git a/app/static/js/contacts.js b/app/static/js/contacts.js index b909331..e9072df 100644 --- a/app/static/js/contacts.js +++ b/app/static/js/contacts.js @@ -54,6 +54,7 @@ let sortOrder = 'desc'; // 'asc' or 'desc' // Auto-cleanup state let autoCleanupSettings = null; let cleanupSaveDebounceTimer = null; +let cleanupTimezone = 'local'; // Timezone from server (e.g., 'Europe/Warsaw') // Map state (Leaflet) let leafletMap = null; @@ -300,8 +301,9 @@ async function loadCleanupSettings() { if (data.success) { autoCleanupSettings = data.settings; + cleanupTimezone = data.timezone || 'local'; applyCleanupSettingsToUI(autoCleanupSettings); - console.log('Loaded cleanup settings:', autoCleanupSettings); + console.log('Loaded cleanup settings:', autoCleanupSettings, 'timezone:', cleanupTimezone); } else { console.error('Failed to load cleanup settings:', data.error); if (statusText) statusText.textContent = 'Error loading settings'; @@ -346,6 +348,7 @@ function applyCleanupSettingsToUI(settings) { const autoCleanupSwitch = document.getElementById('autoCleanupSwitch'); const statusText = document.getElementById('autoCleanupStatusText'); const hourSelect = document.getElementById('cleanupHour'); + const timezoneLabel = document.getElementById('cleanupTimezoneLabel'); if (autoCleanupSwitch) { autoCleanupSwitch.checked = settings.enabled || false; @@ -358,10 +361,15 @@ function applyCleanupSettingsToUI(settings) { hourSelect.disabled = !settings.enabled; } + // Display timezone next to hour selector + if (timezoneLabel) { + timezoneLabel.textContent = `(${cleanupTimezone})`; + } + if (statusText) { if (settings.enabled) { const hourStr = hour.toString().padStart(2, '0'); - statusText.textContent = `Enabled (runs daily at ${hourStr}:00 UTC)`; + statusText.textContent = `Enabled (runs daily at ${hourStr}:00 ${cleanupTimezone})`; statusText.classList.remove('text-muted'); statusText.classList.add('text-success'); } else { @@ -476,13 +484,17 @@ async function saveCleanupSettings(enabled) { if (data.success) { autoCleanupSettings = data.settings; + // Update timezone if provided in response + if (data.timezone) { + cleanupTimezone = data.timezone; + } // Update status text if (statusText) { if (data.settings.enabled) { const savedHour = data.settings.hour !== undefined ? data.settings.hour : 1; const hourStr = savedHour.toString().padStart(2, '0'); - statusText.textContent = `Enabled (runs daily at ${hourStr}:00 UTC)`; + statusText.textContent = `Enabled (runs daily at ${hourStr}:00 ${cleanupTimezone})`; statusText.classList.remove('text-muted'); statusText.classList.add('text-success'); } else { @@ -503,7 +515,7 @@ async function saveCleanupSettings(enabled) { if (autoCleanupSettings.enabled) { const prevHour = autoCleanupSettings.hour !== undefined ? autoCleanupSettings.hour : 1; const hourStr = prevHour.toString().padStart(2, '0'); - statusText.textContent = `Enabled (runs daily at ${hourStr}:00 UTC)`; + statusText.textContent = `Enabled (runs daily at ${hourStr}:00 ${cleanupTimezone})`; } else { statusText.textContent = 'Disabled'; } diff --git a/app/templates/contacts-manage.html b/app/templates/contacts-manage.html index 85e77a9..ab44070 100644 --- a/app/templates/contacts-manage.html +++ b/app/templates/contacts-manage.html @@ -135,7 +135,7 @@ + title="When enabled, contacts matching the above criteria will be automatically deleted daily at the specified hour"> Status: Loading... @@ -143,31 +143,32 @@