mirror of
https://github.com/Casvt/MIND.git
synced 2026-02-19 11:54:46 -05:00
118 lines
3.6 KiB
Python
118 lines
3.6 KiB
Python
# -*- coding: utf-8 -*-
|
|
|
|
"""
|
|
Detector of timezone/DST changes and shifts reminder times to counter it.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import time
|
|
from datetime import datetime, timedelta
|
|
from typing import TYPE_CHECKING, Union
|
|
|
|
from backend.base.definitions import Constants
|
|
from backend.base.logging import LOGGER
|
|
from backend.features.reminder_handler import ReminderHandler
|
|
from backend.internals.db_models import UserlessRemindersDB
|
|
from backend.internals.server import Server
|
|
from backend.internals.settings import Settings
|
|
|
|
if TYPE_CHECKING:
|
|
from threading import Timer
|
|
|
|
|
|
def get_timezone_offset() -> int:
|
|
"""Get the offset between the local timezone and UTC, in minutes.
|
|
|
|
Returns:
|
|
int: The offset in minutes.
|
|
"""
|
|
time.tzset()
|
|
if time.localtime().tm_isdst == 0:
|
|
return -time.timezone
|
|
return -time.altzone
|
|
|
|
|
|
class TimezoneChangeHandler:
|
|
detector_timer: Union[Timer, None] = None
|
|
|
|
@staticmethod
|
|
def __get_next_trigger_time() -> int:
|
|
"""Get the epoch timestamp of the next time a timezone change check
|
|
should be done.
|
|
|
|
Returns:
|
|
int: The epoch timestamp.
|
|
"""
|
|
# Get current time, but set hour and minute to desired trigger time
|
|
current_time = datetime.now()
|
|
target_time = current_time.replace(
|
|
hour=Constants.TZ_CHANGE_CHECK_TIME[0],
|
|
minute=Constants.TZ_CHANGE_CHECK_TIME[1]
|
|
)
|
|
|
|
if target_time < current_time:
|
|
# Current target time is in the past (trigger time has already been
|
|
# today), so run tomorrow at trigger time.
|
|
target_time += timedelta(days=1)
|
|
|
|
return int(target_time.timestamp())
|
|
|
|
@classmethod
|
|
def detect_and_handle_timezone_change(cls) -> None:
|
|
"""
|
|
Check whether a change in timezone/DST has happened and if so shift
|
|
all reminders. Also update DB on last measured timezone.
|
|
"""
|
|
settings = Settings()
|
|
measured_timezone = settings.sv.measured_timezone
|
|
current_timezone = get_timezone_offset()
|
|
|
|
if measured_timezone == -1:
|
|
# First measurement, so nothing to compare against
|
|
settings.update({"measured_timezone": current_timezone})
|
|
|
|
elif measured_timezone != current_timezone:
|
|
# Timezone change
|
|
settings.update({"measured_timezone": current_timezone})
|
|
|
|
shift_delta = measured_timezone - current_timezone
|
|
UserlessRemindersDB().shift(
|
|
reminder_id=None,
|
|
offset=shift_delta
|
|
)
|
|
ReminderHandler.set_reminder_timer()
|
|
|
|
LOGGER.info(
|
|
"Detected timezone/DST change (%s to %s), shifted reminders",
|
|
measured_timezone, current_timezone
|
|
)
|
|
|
|
cls.detector_timer = None
|
|
cls.set_detector_timer()
|
|
return
|
|
|
|
@classmethod
|
|
def set_detector_timer(cls) -> None:
|
|
"""
|
|
Set the timer for checking on a change in timezone and handling it if
|
|
so. Doesn't do anything if timer is already set.
|
|
"""
|
|
if cls.detector_timer is not None:
|
|
return
|
|
|
|
cls.detector_timer = Server().get_db_timer_thread(
|
|
cls.__get_next_trigger_time() - time.time(),
|
|
cls.detect_and_handle_timezone_change,
|
|
"TimezoneChangeHandler"
|
|
)
|
|
cls.detector_timer.start()
|
|
return
|
|
|
|
@classmethod
|
|
def stop_detector_timer(cls) -> None:
|
|
"If the timezone change detection timer is running, stop it"
|
|
if cls.detector_timer is not None:
|
|
cls.detector_timer.cancel()
|
|
return
|