Skip to content

Commit

Permalink
fix: Convert Daily Schedule Time to UTC (#3914)
Browse files Browse the repository at this point in the history
  • Loading branch information
michael-genson authored Jul 20, 2024
1 parent e33b62b commit 5b1e827
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,20 @@

### General

| Variables | Default | Description |
| ----------------------------- | :-------------------: | ----------------------------------------------------------------------------------- |
| PUID | 911 | UserID permissions between host OS and container |
| PGID | 911 | GroupID permissions between host OS and container |
| DEFAULT_GROUP | Home | The default group for users |
| BASE_URL | http://localhost:8080 | Used for Notifications |
| TOKEN_TIME | 48 | The time in hours that a login/auth token is valid |
| API_PORT | 9000 | The port exposed by backend API. **Do not change this if you're running in Docker** |
| API_DOCS | True | Turns on/off access to the API documentation locally. |
| TZ | UTC | Must be set to get correct date/time on the server |
| ALLOW_SIGNUP<super>\*</super> | false | Allow user sign-up without token |
| LOG_CONFIG_OVERRIDE | | Override the config for logging with a custom path |
| LOG_LEVEL | info | Logging level (e.g. critical, error, warning, info, debug, trace) |
| DAILY_SCHEDULE_TIME | 23:45 | The time of day to run the daily tasks. |
| Variables | Default | Description |
| ----------------------------- | :-------------------: | --------------------------------------------------------------------------------------------------------- |
| PUID | 911 | UserID permissions between host OS and container |
| PGID | 911 | GroupID permissions between host OS and container |
| DEFAULT_GROUP | Home | The default group for users |
| BASE_URL | http://localhost:8080 | Used for Notifications |
| TOKEN_TIME | 48 | The time in hours that a login/auth token is valid |
| API_PORT | 9000 | The port exposed by backend API. **Do not change this if you're running in Docker** |
| API_DOCS | True | Turns on/off access to the API documentation locally |
| TZ | UTC | Must be set to get correct date/time on the server |
| ALLOW_SIGNUP<super>\*</super> | false | Allow user sign-up without token |
| LOG_CONFIG_OVERRIDE | | Override the config for logging with a custom path |
| LOG_LEVEL | info | Logging level (e.g. critical, error, warning, info, debug, trace) |
| DAILY_SCHEDULE_TIME | 23:45 | The time of day to run daily server tasks, in HH:MM format. Use the server's local time, *not* UTC |

<super>\*</super> Starting in v1.4.0 this was changed to default to `false` as part of a security review of the application.

Expand Down
47 changes: 47 additions & 0 deletions mealie/core/settings/settings.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import logging
import secrets
from datetime import datetime, timezone
from pathlib import Path
from typing import NamedTuple

from dateutil.tz import tzlocal
from pydantic import field_validator
from pydantic_settings import BaseSettings, SettingsConfigDict

Expand All @@ -9,6 +13,11 @@
from .db_providers import AbstractDBProvider, db_provider_factory


class ScheduleTime(NamedTuple):
hour: int
minute: int


def determine_secrets(data_dir: Path, production: bool) -> str:
if not production:
return "shh-secret-test-key"
Expand Down Expand Up @@ -58,6 +67,44 @@ class AppSettings(BaseSettings):
ALLOW_SIGNUP: bool = False

DAILY_SCHEDULE_TIME: str = "23:45"
"""Local server time, in HH:MM format. See `DAILY_SCHEDULE_TIME_UTC` for the parsed UTC equivalent"""

_logger: logging.Logger | None = None

@property
def logger(self) -> logging.Logger:
if self._logger is None:
# lazy load the logger, since get_logger depends on the settings being loaded
from mealie.core.root_logger import get_logger

self._logger = get_logger()

return self._logger

@property
def DAILY_SCHEDULE_TIME_UTC(self) -> ScheduleTime:
"""The DAILY_SCHEDULE_TIME in UTC, parsed into hours and minutes"""

# parse DAILY_SCHEDULE_TIME into hours and minutes
try:
hour_str, minute_str = self.DAILY_SCHEDULE_TIME.split(":")
local_hour = int(hour_str)
local_minute = int(minute_str)
except ValueError:
local_hour = 23
local_minute = 45
self.logger.exception(
f"Unable to parse {self.DAILY_SCHEDULE_TIME=} as HH:MM; defaulting to {local_hour}:{local_minute}"
)

# DAILY_SCHEDULE_TIME is in local time, so we convert it to UTC
local_tz = tzlocal()
now = datetime.now(local_tz)
local_time = now.replace(hour=local_hour, minute=local_minute)
utc_time = local_time.astimezone(timezone.utc)

self.logger.debug(f"Local time: {local_hour}:{local_minute} | UTC time: {utc_time.hour}:{utc_time.minute}")
return ScheduleTime(utc_time.hour, utc_time.minute)

# ===============================================
# Security Configuration
Expand Down
24 changes: 5 additions & 19 deletions mealie/services/scheduler/scheduler_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,12 @@ async def start():

async def schedule_daily():
now = datetime.now(timezone.utc)
daily_schedule_time = get_app_settings().DAILY_SCHEDULE_TIME
logger.debug(
"Current time is %s and DAILY_SCHEDULE_TIME is %s",
str(now),
daily_schedule_time,
)
try:
hour_target, minute_target = _parse_daily_schedule_time(daily_schedule_time)
except Exception:
logger.exception(f"Unable to parse {daily_schedule_time=}")
hour_target = 23
minute_target = 45
daily_schedule_time = get_app_settings().DAILY_SCHEDULE_TIME_UTC
logger.debug(f"Current time is {now} and DAILY_SCHEDULE_TIME (in UTC) is {daily_schedule_time}")

next_schedule = now.replace(hour=hour_target, minute=minute_target, second=0, microsecond=0)
next_schedule = now.replace(
hour=daily_schedule_time.hour, minute=daily_schedule_time.minute, second=0, microsecond=0
)
delta = next_schedule - now
if delta < timedelta(0):
next_schedule = next_schedule + timedelta(days=1)
Expand All @@ -61,12 +53,6 @@ async def schedule_daily():
await run_daily()


def _parse_daily_schedule_time(time):
hour_target = int(time.split(":")[0])
minute_target = int(time.split(":")[1])
return hour_target, minute_target


def _scheduled_task_wrapper(callable):
try:
callable()
Expand Down

0 comments on commit 5b1e827

Please sign in to comment.