From bb67f678b6adf35266ce26a5645da494235418ee Mon Sep 17 00:00:00 2001 From: Elias Benbourenane Date: Sat, 28 Feb 2026 07:58:38 -0500 Subject: [PATCH 01/17] feat: Global datetime timezone setter/resolver --- plexapi/__init__.py | 3 ++- plexapi/utils.py | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 1 deletion(-) diff --git a/plexapi/__init__.py b/plexapi/__init__.py index af856aa88..e0b3dc8eb 100644 --- a/plexapi/__init__.py +++ b/plexapi/__init__.py @@ -6,7 +6,7 @@ from plexapi.config import PlexConfig, reset_base_headers import plexapi.const as const -from plexapi.utils import SecretsFilter +from plexapi.utils import SecretsFilter, setDatetimeTimezone # Load User Defined Config DEFAULT_CONFIG_PATH = os.path.expanduser('~/.config/plexapi/config.ini') @@ -17,6 +17,7 @@ PROJECT = 'PlexAPI' VERSION = __version__ = const.__version__ TIMEOUT = CONFIG.get('plexapi.timeout', 30, int) +DATETIME_TIMEZONE = setDatetimeTimezone(CONFIG.get('plexapi.timezone', False)) X_PLEX_CONTAINER_SIZE = CONFIG.get('plexapi.container_size', 100, int) X_PLEX_ENABLE_FAST_CONNECT = CONFIG.get('plexapi.enable_fast_connect', False, bool) diff --git a/plexapi/utils.py b/plexapi/utils.py index 4c14a0f9e..3dc59d9eb 100644 --- a/plexapi/utils.py +++ b/plexapi/utils.py @@ -18,6 +18,7 @@ from threading import Event, Thread from urllib.parse import quote from xml.etree import ElementTree +from zoneinfo import ZoneInfo, ZoneInfoNotFoundError import requests from requests.status_codes import _codes as codes @@ -103,6 +104,9 @@ # Plex Objects - Populated at runtime PLEXOBJECTS = {} +# Global timezone for toDatetime() conversions, set by setDatetimeTimezone() +DATETIME_TIMEZONE = None + class SecretsFilter(logging.Filter): """ Logging filter to hide secrets. """ @@ -326,6 +330,39 @@ def threaded(callback, listargs): return [r for r in results if r is not None] +def setDatetimeTimezone(value): + """ Sets the timezone to use when converting values with :func:`toDatetime`. + + Parameters: + value (bool, str): + - ``False`` or ``None`` to disable timezone (default). + - ``True`` or ``"local"`` to use the local timezone. + - A valid IANA timezone (e.g. ``UTC`` or ``America/New_York``). + + Returns: + datetime.tzinfo: Resolved timezone object or ``None`` if disabled or invalid. + """ + global DATETIME_TIMEZONE + + # Disable timezone if value is False or None + if value is None or value is False: + tzinfo = None + # Use local timezone if value is True or "local" + elif value is True or (isinstance(value, str) and value.strip().lower() == 'local'): + tzinfo = datetime.now().astimezone().tzinfo + # Attempt to resolve value as an IANA timezone string + else: + setting = str(value).strip() + try: + tzinfo = ZoneInfo(setting) + except ZoneInfoNotFoundError: + tzinfo = None + log.warning('Failed to set timezone to "%s", defaulting to None', value) + + DATETIME_TIMEZONE = tzinfo + return DATETIME_TIMEZONE + + def toDatetime(value, format=None): """ Returns a datetime object from the specified value. From 0c4b51e0d35646e9e5a6a83a8c40b1d5250a83b2 Mon Sep 17 00:00:00 2001 From: Elias Benbourenane Date: Sat, 28 Feb 2026 07:59:47 -0500 Subject: [PATCH 02/17] feat: Normalize to configured timezone in utils.toDatetime --- plexapi/utils.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/plexapi/utils.py b/plexapi/utils.py index 3dc59d9eb..ccc8f3cee 100644 --- a/plexapi/utils.py +++ b/plexapi/utils.py @@ -371,9 +371,11 @@ def toDatetime(value, format=None): format (str): Format to pass strftime (optional; if value is a str). """ if value is not None: + tzinfo = DATETIME_TIMEZONE if format: try: - return datetime.strptime(value, format) + dt = datetime.strptime(value, format) + return dt.replace(tzinfo=tzinfo) if tzinfo else dt except ValueError: log.info('Failed to parse "%s" to datetime as format "%s", defaulting to None', value, format) return None @@ -384,9 +386,13 @@ def toDatetime(value, format=None): log.info('Failed to parse "%s" to datetime as timestamp, defaulting to None', value) return None try: + if tzinfo: + return datetime.fromtimestamp(value, tz=tzinfo) return datetime.fromtimestamp(value) except (OSError, OverflowError, ValueError): try: + if tzinfo: + return datetime.fromtimestamp(0, tz=tzinfo) + timedelta(seconds=value) return datetime.fromtimestamp(0) + timedelta(seconds=value) except OverflowError: log.info('Failed to parse "%s" to datetime as timestamp (out-of-bounds), defaulting to None', value) From fb588e7cccde6f8cc16272aa1e86a2c9a6cd7fc0 Mon Sep 17 00:00:00 2001 From: Elias Benbourenane Date: Sat, 28 Feb 2026 08:01:40 -0500 Subject: [PATCH 03/17] chore: setDatetimeTimezone tests --- tests/test_utils.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/tests/test_utils.py b/tests/test_utils.py index bdb7fc091..e9c06dfce 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -12,6 +12,35 @@ def test_utils_toDatetime(): # assert str(utils.toDatetime('0'))[:-9] in ['1970-01-01', '1969-12-31'] +def test_utils_setDatetimeTimezone_disabled_and_utc(): + original_tz = utils.DATETIME_TIMEZONE + try: + assert utils.setDatetimeTimezone(False) is None + assert utils.toDatetime("0").tzinfo is None + + tzinfo = utils.setDatetimeTimezone("UTC") + assert tzinfo is not None + assert utils.toDatetime("0").tzinfo == tzinfo + assert utils.toDatetime("2026-01-01", format="%Y-%m-%d").tzinfo == tzinfo + finally: # Restore for other tests + utils.DATETIME_TIMEZONE = original_tz + + +def test_utils_setDatetimeTimezone_local_and_invalid(): + original_tz = utils.DATETIME_TIMEZONE + try: + assert utils.setDatetimeTimezone(True) is not None + assert utils.toDatetime("0").tzinfo is not None + + assert utils.setDatetimeTimezone("local") is not None + assert utils.toDatetime("0").tzinfo is not None + + assert utils.setDatetimeTimezone("Not/A_Real_Timezone") is None + assert utils.toDatetime("0").tzinfo is None + finally: # Restore for other tests + utils.DATETIME_TIMEZONE = original_tz + + def test_utils_threaded(): def _squared(num, results, i, job_is_done_event=None): time.sleep(0.5) From af28a5ea425c751b5b598dbe1b994da6eae57de1 Mon Sep 17 00:00:00 2001 From: Elias Benbourenane Date: Sat, 28 Feb 2026 08:19:35 -0500 Subject: [PATCH 04/17] style: Solve flake8 warnings --- tests/test_utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_utils.py b/tests/test_utils.py index e9c06dfce..6180cc032 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -22,7 +22,7 @@ def test_utils_setDatetimeTimezone_disabled_and_utc(): assert tzinfo is not None assert utils.toDatetime("0").tzinfo == tzinfo assert utils.toDatetime("2026-01-01", format="%Y-%m-%d").tzinfo == tzinfo - finally: # Restore for other tests + finally: # Restore for other tests utils.DATETIME_TIMEZONE = original_tz @@ -37,7 +37,7 @@ def test_utils_setDatetimeTimezone_local_and_invalid(): assert utils.setDatetimeTimezone("Not/A_Real_Timezone") is None assert utils.toDatetime("0").tzinfo is None - finally: # Restore for other tests + finally: # Restore for other tests utils.DATETIME_TIMEZONE = original_tz From 4351935c61f58abce31c145cda74e8f99f12ec8b Mon Sep 17 00:00:00 2001 From: Elias Benbourenane Date: Sat, 28 Feb 2026 08:29:15 -0500 Subject: [PATCH 05/17] docs: Note possible breaking behavioral change when plexapi.timezone is toggled --- docs/configuration.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/docs/configuration.rst b/docs/configuration.rst index 9c283e3b3..735fa3141 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -12,6 +12,7 @@ are optional. An example config.ini file may look like the following with all po [plexapi] container_size = 50 timeout = 30 + timezone = false [auth] myplex_username = johndoe @@ -69,6 +70,15 @@ Section [plexapi] Options When the options is set to `true` the connection procedure will be aborted with first successfully established connection (default: false). +**timezone** + Controls whether :func:`~plexapi.utils.toDatetime` returns timezone-aware datetime objects. + + * `false` (default): keep naive datetime objects (backward compatible). + * `true` or `local`: use the local machine timezone. + * IANA timezone string (for example `UTC` or `America/New_York`): use that timezone. + + Toggling this option may break comparisons between aware and naive datetimes. + Section [auth] Options ---------------------- From aa8432e48062a3826e14817acdb18c7cd2d121ac Mon Sep 17 00:00:00 2001 From: Elias Benbourenane Date: Sat, 28 Feb 2026 08:36:11 -0500 Subject: [PATCH 06/17] refactor: Solve C901 flake8 error --- plexapi/utils.py | 39 ++++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 17 deletions(-) diff --git a/plexapi/utils.py b/plexapi/utils.py index ccc8f3cee..1d2ad9415 100644 --- a/plexapi/utils.py +++ b/plexapi/utils.py @@ -363,6 +363,27 @@ def setDatetimeTimezone(value): return DATETIME_TIMEZONE +def _parseTimestamp(value, tzinfo): + """ Helper function to parse a timestamp value into a datetime object. """ + try: + value = int(value) + except ValueError: + log.info('Failed to parse "%s" to datetime as timestamp, defaulting to None', value) + return None + try: + if tzinfo: + return datetime.fromtimestamp(value, tz=tzinfo) + return datetime.fromtimestamp(value) + except (OSError, OverflowError, ValueError): + try: + if tzinfo: + return datetime.fromtimestamp(0, tz=tzinfo) + timedelta(seconds=value) + return datetime.fromtimestamp(0) + timedelta(seconds=value) + except OverflowError: + log.info('Failed to parse "%s" to datetime as timestamp (out-of-bounds), defaulting to None', value) + return None + + def toDatetime(value, format=None): """ Returns a datetime object from the specified value. @@ -380,23 +401,7 @@ def toDatetime(value, format=None): log.info('Failed to parse "%s" to datetime as format "%s", defaulting to None', value, format) return None else: - try: - value = int(value) - except ValueError: - log.info('Failed to parse "%s" to datetime as timestamp, defaulting to None', value) - return None - try: - if tzinfo: - return datetime.fromtimestamp(value, tz=tzinfo) - return datetime.fromtimestamp(value) - except (OSError, OverflowError, ValueError): - try: - if tzinfo: - return datetime.fromtimestamp(0, tz=tzinfo) + timedelta(seconds=value) - return datetime.fromtimestamp(0) + timedelta(seconds=value) - except OverflowError: - log.info('Failed to parse "%s" to datetime as timestamp (out-of-bounds), defaulting to None', value) - return None + return _parseTimestamp(value, tzinfo) return value From 424ec9d1a85cc7c63f9631d4afab29c04ae2dcd6 Mon Sep 17 00:00:00 2001 From: Elias Benbourenane Date: Sat, 28 Feb 2026 08:47:39 -0500 Subject: [PATCH 07/17] chore: Integration test for timezone awareness --- tests/test_server.py | 30 ++++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/tests/test_server.py b/tests/test_server.py index 6d726190c..77d7eb4b4 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -3,11 +3,11 @@ from urllib.parse import quote_plus import pytest -from datetime import datetime +from datetime import datetime, timedelta from PIL import Image from plexapi.exceptions import BadRequest, NotFound from plexapi.server import PlexServer -from plexapi.utils import download +from plexapi.utils import download, setDatetimeTimezone, DATETIME_TIMEZONE from requests import Session from . import conftest as utils @@ -32,6 +32,32 @@ def test_server_attr(plex, account): assert len(plex.version) >= 5 +def test_server_updatedAt_timezone(plex): + original = DATETIME_TIMEZONE + try: + # no timezone configured, should be naive + setDatetimeTimezone(False) + dt_naive = plex.updatedAt + assert dt_naive.tzinfo is None + + # local timezone configured, should be aware + setDatetimeTimezone(True) + dt_local = plex.updatedAt + assert dt_local.tzinfo is not None + + # explicit IANA zones. Check that the offset is correct too + setDatetimeTimezone("UTC") + dt: datetime = plex.updatedAt + assert dt.tzinfo is not None + assert dt.tzinfo.utcoffset(dt) == timedelta(0) + setDatetimeTimezone("Asia/Dubai") + dt: datetime = plex.updatedAt + assert dt.tzinfo is not None + assert dt.tzinfo.utcoffset(dt) == timedelta(hours=4) + finally: # Restore for other tests + setDatetimeTimezone(original) + + def test_server_alert_listener(plex, movies): try: messages = [] From fcff93bf110489631bf105004554ba31277a27fc Mon Sep 17 00:00:00 2001 From: Elias Benbourenane Date: Sat, 28 Feb 2026 08:52:13 -0500 Subject: [PATCH 08/17] chore: Force a realod to get re-run datetime parsing with configure tz --- tests/test_server.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/test_server.py b/tests/test_server.py index 77d7eb4b4..fd449c68a 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -37,20 +37,24 @@ def test_server_updatedAt_timezone(plex): try: # no timezone configured, should be naive setDatetimeTimezone(False) + plex.reload() dt_naive = plex.updatedAt assert dt_naive.tzinfo is None # local timezone configured, should be aware setDatetimeTimezone(True) + plex.reload() dt_local = plex.updatedAt assert dt_local.tzinfo is not None # explicit IANA zones. Check that the offset is correct too setDatetimeTimezone("UTC") + plex.reload() dt: datetime = plex.updatedAt assert dt.tzinfo is not None assert dt.tzinfo.utcoffset(dt) == timedelta(0) setDatetimeTimezone("Asia/Dubai") + plex.reload() dt: datetime = plex.updatedAt assert dt.tzinfo is not None assert dt.tzinfo.utcoffset(dt) == timedelta(hours=4) From fa5cbcdde56bbf119ca88fc9ee12873be724efde Mon Sep 17 00:00:00 2001 From: Elias Benbourenane Date: Sat, 28 Feb 2026 09:00:14 -0500 Subject: [PATCH 09/17] chore: Migrate tz tests from the server module to a less important video module as to not conflict with other tests --- tests/test_server.py | 34 ++-------------------------------- tests/test_video.py | 35 ++++++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 33 deletions(-) diff --git a/tests/test_server.py b/tests/test_server.py index fd449c68a..6d726190c 100644 --- a/tests/test_server.py +++ b/tests/test_server.py @@ -3,11 +3,11 @@ from urllib.parse import quote_plus import pytest -from datetime import datetime, timedelta +from datetime import datetime from PIL import Image from plexapi.exceptions import BadRequest, NotFound from plexapi.server import PlexServer -from plexapi.utils import download, setDatetimeTimezone, DATETIME_TIMEZONE +from plexapi.utils import download from requests import Session from . import conftest as utils @@ -32,36 +32,6 @@ def test_server_attr(plex, account): assert len(plex.version) >= 5 -def test_server_updatedAt_timezone(plex): - original = DATETIME_TIMEZONE - try: - # no timezone configured, should be naive - setDatetimeTimezone(False) - plex.reload() - dt_naive = plex.updatedAt - assert dt_naive.tzinfo is None - - # local timezone configured, should be aware - setDatetimeTimezone(True) - plex.reload() - dt_local = plex.updatedAt - assert dt_local.tzinfo is not None - - # explicit IANA zones. Check that the offset is correct too - setDatetimeTimezone("UTC") - plex.reload() - dt: datetime = plex.updatedAt - assert dt.tzinfo is not None - assert dt.tzinfo.utcoffset(dt) == timedelta(0) - setDatetimeTimezone("Asia/Dubai") - plex.reload() - dt: datetime = plex.updatedAt - assert dt.tzinfo is not None - assert dt.tzinfo.utcoffset(dt) == timedelta(hours=4) - finally: # Restore for other tests - setDatetimeTimezone(original) - - def test_server_alert_listener(plex, movies): try: messages = [] diff --git a/tests/test_video.py b/tests/test_video.py index 24fdac8de..fd391da2e 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -1,10 +1,12 @@ import os -from datetime import datetime +from datetime import datetime, timedelta from time import sleep from urllib.parse import quote_plus import pytest +import plexapi.utils as plexutils from plexapi.exceptions import BadRequest, NotFound +from plexapi.utils import setDatetimeTimezone from plexapi.sync import VIDEO_QUALITY_3_MBPS_720p from . import conftest as utils @@ -21,6 +23,37 @@ def test_video_Movie_attributeerror(movie): movie.asshat +def test_video_Movie_datetime_timezone(movie): + original = plexutils.DATETIME_TIMEZONE + try: + # no timezone configured, should be naive + setDatetimeTimezone(False) + movie.reload() + dt_naive = movie.updatedAt + assert dt_naive.tzinfo is None + + # local timezone configured, should be aware + setDatetimeTimezone(True) + movie.reload() + dt_local = movie.updatedAt + assert dt_local.tzinfo is not None + + # explicit IANA zones. Check that the offset is correct too + setDatetimeTimezone("UTC") + movie.reload() + dt = movie.updatedAt + assert dt.tzinfo is not None + assert dt.tzinfo.utcoffset(dt) == timedelta(0) + + setDatetimeTimezone("Asia/Dubai") + movie.reload() + dt = movie.updatedAt + assert dt.tzinfo is not None + assert dt.tzinfo.utcoffset(dt) == timedelta(hours=4) + finally: # Restore for other tests + setDatetimeTimezone(original) + + def test_video_ne(movies): assert ( len( From 40ed3f95e09036beb925a28471cacc819f10055d Mon Sep 17 00:00:00 2001 From: Elias Benbourenane Date: Sat, 28 Feb 2026 09:34:29 -0500 Subject: [PATCH 10/17] Document tzdata requirement on systems that don't have it by default (e.g. alpine, windows) Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- docs/configuration.rst | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/configuration.rst b/docs/configuration.rst index 735fa3141..d3019934f 100644 --- a/docs/configuration.rst +++ b/docs/configuration.rst @@ -77,6 +77,10 @@ Section [plexapi] Options * `true` or `local`: use the local machine timezone. * IANA timezone string (for example `UTC` or `America/New_York`): use that timezone. + This feature relies on Python's :class:`zoneinfo.ZoneInfo` and the availability of IANA tzdata + on the system. On platforms without system tzdata (notably Windows), you may need to install + the :mod:`tzdata` Python package for IANA timezone strings (such as ``America/New_York``) to + work as expected. Toggling this option may break comparisons between aware and naive datetimes. From f025972a9444d527f4a2d0490ddce3dde7c80a25 Mon Sep 17 00:00:00 2001 From: Elias Benbourenane Date: Sat, 28 Feb 2026 09:35:20 -0500 Subject: [PATCH 11/17] fix: Handle case where toDatetime is called with format when the dt already has timezone info --- plexapi/utils.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/plexapi/utils.py b/plexapi/utils.py index 1d2ad9415..8bd28c3c9 100644 --- a/plexapi/utils.py +++ b/plexapi/utils.py @@ -396,7 +396,11 @@ def toDatetime(value, format=None): if format: try: dt = datetime.strptime(value, format) - return dt.replace(tzinfo=tzinfo) if tzinfo else dt + # If parsed datetime already contains timezone + if dt.tzinfo is not None: + return dt.astimezone(tzinfo) if tzinfo else dt + else: + return dt.replace(tzinfo=tzinfo) if tzinfo else dt except ValueError: log.info('Failed to parse "%s" to datetime as format "%s", defaulting to None', value, format) return None From 32c82bca97a38870ab0b6a8ce0090ccf58816b7d Mon Sep 17 00:00:00 2001 From: Elias Benbourenane Date: Sat, 28 Feb 2026 09:35:31 -0500 Subject: [PATCH 12/17] refactor: Keep `plexapi.DATETIME_TIMEZONE` and `utils.DATETIME_TIMEZONE` in sync (aliased) --- plexapi/__init__.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/plexapi/__init__.py b/plexapi/__init__.py index e0b3dc8eb..998bbc0a7 100644 --- a/plexapi/__init__.py +++ b/plexapi/__init__.py @@ -6,7 +6,7 @@ from plexapi.config import PlexConfig, reset_base_headers import plexapi.const as const -from plexapi.utils import SecretsFilter, setDatetimeTimezone +from plexapi.utils import SecretsFilter, setDatetimeTimezone, DATETIME_TIMEZONE as GLOBAL_DATETIME_TIMEZONE # Load User Defined Config DEFAULT_CONFIG_PATH = os.path.expanduser('~/.config/plexapi/config.ini') @@ -17,7 +17,9 @@ PROJECT = 'PlexAPI' VERSION = __version__ = const.__version__ TIMEOUT = CONFIG.get('plexapi.timeout', 30, int) -DATETIME_TIMEZONE = setDatetimeTimezone(CONFIG.get('plexapi.timezone', False)) +DATETIME_TIMEZONE = GLOBAL_DATETIME_TIMEZONE +setDatetimeTimezone(CONFIG.get('plexapi.timezone', False)) # Sets the DATETIME_TIMEZONE global variable + X_PLEX_CONTAINER_SIZE = CONFIG.get('plexapi.container_size', 100, int) X_PLEX_ENABLE_FAST_CONNECT = CONFIG.get('plexapi.enable_fast_connect', False, bool) From c71faba8d06b8e08849c4cbfb4a2954535248fb7 Mon Sep 17 00:00:00 2001 From: Elias Benbourenane Date: Sat, 28 Feb 2026 09:40:23 -0500 Subject: [PATCH 13/17] chore: Fix incorrect type assignment on timezone test --- tests/test_video.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_video.py b/tests/test_video.py index fd391da2e..4b16058ac 100644 --- a/tests/test_video.py +++ b/tests/test_video.py @@ -24,7 +24,7 @@ def test_video_Movie_attributeerror(movie): def test_video_Movie_datetime_timezone(movie): - original = plexutils.DATETIME_TIMEZONE + original_tz = plexutils.DATETIME_TIMEZONE try: # no timezone configured, should be naive setDatetimeTimezone(False) @@ -51,7 +51,7 @@ def test_video_Movie_datetime_timezone(movie): assert dt.tzinfo is not None assert dt.tzinfo.utcoffset(dt) == timedelta(hours=4) finally: # Restore for other tests - setDatetimeTimezone(original) + plexutils.DATETIME_TIMEZONE = original_tz def test_video_ne(movies): From 4ee211647a487eca7dc4c10eb85db7dae17409ae Mon Sep 17 00:00:00 2001 From: Elias Benbourenane Date: Sat, 28 Feb 2026 09:48:09 -0500 Subject: [PATCH 14/17] fix: bool-like parsing in setDatetimeTimezone resolution Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- plexapi/utils.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/plexapi/utils.py b/plexapi/utils.py index 8bd28c3c9..5e5c567f0 100644 --- a/plexapi/utils.py +++ b/plexapi/utils.py @@ -350,14 +350,21 @@ def setDatetimeTimezone(value): # Use local timezone if value is True or "local" elif value is True or (isinstance(value, str) and value.strip().lower() == 'local'): tzinfo = datetime.now().astimezone().tzinfo - # Attempt to resolve value as an IANA timezone string + # Attempt to resolve value as an IANA timezone string or boolean-like string else: setting = str(value).strip() - try: - tzinfo = ZoneInfo(setting) - except ZoneInfoNotFoundError: + lower = setting.lower() + # Handle common boolean-like strings from config/environment + if lower in ('true', '1'): + tzinfo = datetime.now().astimezone().tzinfo + elif lower in ('false', '0'): tzinfo = None - log.warning('Failed to set timezone to "%s", defaulting to None', value) + else: + try: + tzinfo = ZoneInfo(setting) + except ZoneInfoNotFoundError: + tzinfo = None + log.warning('Failed to set timezone to "%s", defaulting to None', value) DATETIME_TIMEZONE = tzinfo return DATETIME_TIMEZONE From 080094f0d026a925a3913b07fc461f088f7de650 Mon Sep 17 00:00:00 2001 From: Elias Benbourenane Date: Sat, 28 Feb 2026 09:49:08 -0500 Subject: [PATCH 15/17] fix: Stale DATETIME_TIMZONE ref Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- plexapi/__init__.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/plexapi/__init__.py b/plexapi/__init__.py index 998bbc0a7..ca4b6119b 100644 --- a/plexapi/__init__.py +++ b/plexapi/__init__.py @@ -17,8 +17,7 @@ PROJECT = 'PlexAPI' VERSION = __version__ = const.__version__ TIMEOUT = CONFIG.get('plexapi.timeout', 30, int) -DATETIME_TIMEZONE = GLOBAL_DATETIME_TIMEZONE -setDatetimeTimezone(CONFIG.get('plexapi.timezone', False)) # Sets the DATETIME_TIMEZONE global variable +DATETIME_TIMEZONE = setDatetimeTimezone(CONFIG.get('plexapi.timezone', False)) # Sets and returns the DATETIME_TIMEZONE global variable X_PLEX_CONTAINER_SIZE = CONFIG.get('plexapi.container_size', 100, int) X_PLEX_ENABLE_FAST_CONNECT = CONFIG.get('plexapi.enable_fast_connect', False, bool) From 3be4e76853c453903932a891fb6ca972e62e4fad Mon Sep 17 00:00:00 2001 From: Elias Benbourenane Date: Sat, 28 Feb 2026 09:49:58 -0500 Subject: [PATCH 16/17] style: Unused import --- plexapi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexapi/__init__.py b/plexapi/__init__.py index ca4b6119b..0057a6c6d 100644 --- a/plexapi/__init__.py +++ b/plexapi/__init__.py @@ -6,7 +6,7 @@ from plexapi.config import PlexConfig, reset_base_headers import plexapi.const as const -from plexapi.utils import SecretsFilter, setDatetimeTimezone, DATETIME_TIMEZONE as GLOBAL_DATETIME_TIMEZONE +from plexapi.utils import SecretsFilter, setDatetimeTimezone # Load User Defined Config DEFAULT_CONFIG_PATH = os.path.expanduser('~/.config/plexapi/config.ini') From c5addc0ec0d39f88ae1810176bf23f20a56e08f1 Mon Sep 17 00:00:00 2001 From: Elias Benbourenane Date: Sat, 28 Feb 2026 09:51:18 -0500 Subject: [PATCH 17/17] style: Resolve flake8 E501 line too long --- plexapi/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plexapi/__init__.py b/plexapi/__init__.py index 0057a6c6d..db966485c 100644 --- a/plexapi/__init__.py +++ b/plexapi/__init__.py @@ -17,7 +17,7 @@ PROJECT = 'PlexAPI' VERSION = __version__ = const.__version__ TIMEOUT = CONFIG.get('plexapi.timeout', 30, int) -DATETIME_TIMEZONE = setDatetimeTimezone(CONFIG.get('plexapi.timezone', False)) # Sets and returns the DATETIME_TIMEZONE global variable +DATETIME_TIMEZONE = setDatetimeTimezone(CONFIG.get('plexapi.timezone', False)) X_PLEX_CONTAINER_SIZE = CONFIG.get('plexapi.container_size', 100, int) X_PLEX_ENABLE_FAST_CONNECT = CONFIG.get('plexapi.enable_fast_connect', False, bool)