diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml
index 8751a4da..d0800795 100644
--- a/.github/workflows/build.yaml
+++ b/.github/workflows/build.yaml
@@ -15,7 +15,7 @@ jobs:
         py_version: [ 'py2', 'py3' ]
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
 
       - name: Set up Python 3.x
         uses: actions/setup-python@v4
diff --git a/.github/workflows/codeql.yaml b/.github/workflows/codeql.yaml
index 35fb895a..21253920 100644
--- a/.github/workflows/codeql.yaml
+++ b/.github/workflows/codeql.yaml
@@ -21,7 +21,7 @@ jobs:
         version: ['2.7', '3.9']
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
 
       - name: Initialize CodeQL
         uses: github/codeql-action/init@v2
diff --git a/.github/workflows/create-prepare-release-pr.yaml b/.github/workflows/create-prepare-release-pr.yaml
index 0078d492..cee06818 100644
--- a/.github/workflows/create-prepare-release-pr.yaml
+++ b/.github/workflows/create-prepare-release-pr.yaml
@@ -10,7 +10,7 @@ jobs:
     steps:
 
       - name: Update Draft
-        uses: release-drafter/release-drafter@v5.24.0
+        uses: release-drafter/release-drafter@v5.25.0
         id: draft
         env:
           GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
@@ -21,7 +21,7 @@ jobs:
           yq-version: v4.9.1
 
       - name: Checkout repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
 
       - name: Parse Changelog
         run: |
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
index e3e39486..0721ae57 100644
--- a/.github/workflows/publish.yaml
+++ b/.github/workflows/publish.yaml
@@ -11,7 +11,7 @@ jobs:
         py_version: [ 'py2', 'py3' ]
     steps:
       - name: Update Draft
-        uses: release-drafter/release-drafter@v5.24.0
+        uses: release-drafter/release-drafter@v5.25.0
         if: ${{ matrix.py_version == 'py3' }}
         with:
           publish: true
@@ -19,7 +19,7 @@ jobs:
           GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
 
       - name: Checkout repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
 
       - name: Set up Python 3.x
         uses: actions/setup-python@v4
diff --git a/.github/workflows/release-drafter.yaml b/.github/workflows/release-drafter.yaml
index 610fb901..1bcdc061 100644
--- a/.github/workflows/release-drafter.yaml
+++ b/.github/workflows/release-drafter.yaml
@@ -11,6 +11,6 @@ jobs:
     runs-on: ubuntu-latest
     steps:
       - name: Update Release Draft
-        uses: release-drafter/release-drafter@v5.24.0
+        uses: release-drafter/release-drafter@v5.25.0
         env:
           GITHUB_TOKEN: ${{ secrets.JF_BOT_TOKEN }}
diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml
index 29cbcb34..1c93d06c 100644
--- a/.github/workflows/test.yaml
+++ b/.github/workflows/test.yaml
@@ -16,7 +16,7 @@ jobs:
     strategy:
       fail-fast: false
       matrix:
-        py_version: ['2.7', '3.9', '3.11']
+        py_version: ['2.7', '3.9', '3.11', '3.12']
         os: [ubuntu-latest, windows-latest]
         exclude:
           - os: windows-latest
@@ -24,7 +24,7 @@ jobs:
     runs-on: ${{ matrix.os }}
     steps:
       - name: Checkout repository
-        uses: actions/checkout@v3
+        uses: actions/checkout@v4
 
       - name: Set up Python ${{ matrix.py_version }}
         if: matrix.py_version != '2.7'
@@ -56,9 +56,12 @@ jobs:
           flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --output-file=flake8.output
           cat flake8.output
 
-      - name: Test with Coverage
+      - name: Run tests and generate coverage
         run: |
           coverage run
+
+      - name: Generate coverage report
+        run: |
           coverage xml
           coverage report
 
diff --git a/jellyfin_kodi/helper/utils.py b/jellyfin_kodi/helper/utils.py
index 3a873049..798e00ae 100644
--- a/jellyfin_kodi/helper/utils.py
+++ b/jellyfin_kodi/helper/utils.py
@@ -474,14 +474,14 @@ def split_list(itemlist, size):
     return [itemlist[i:i + size] for i in range(0, len(itemlist), size)]
 
 
-def convert_to_local(date):
+def convert_to_local(date, timezone=tz.tzlocal()):
 
     ''' Convert the local datetime to local.
     '''
     try:
         date = parser.parse(date) if isinstance(date, string_types) else date
         date = date.replace(tzinfo=tz.tzutc())
-        date = date.astimezone(tz.tzlocal())
+        date = date.astimezone(timezone)
         # Bad metadata defaults to date 1-1-1.  Catch it and don't throw errors
         if date.year < 1900:
             # FIXME(py2): strftime don't like dates below 1900
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 9d2d909a..137e9e9b 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -6,6 +6,9 @@ futures >= 2.2; python_version < '3.0'
 PyYAML >= 5.4; python_version < '3.0'
 PyYAML >= 6.0; python_version >= '3.6'
 
+backports.zoneinfo; python_version < "3.9" and python_version >= '3.0'
+tzdata; platform_system == "Windows" and python_version >= '3.0'
+
 Kodistubs ~= 18.0; python_version < '3.0'
 Kodistubs ~= 20.0; python_version >= '3.6'
 
diff --git a/tests/test_helper_utils.py b/tests/test_helper_utils.py
index 855ad80a..145e8114 100644
--- a/tests/test_helper_utils.py
+++ b/tests/test_helper_utils.py
@@ -1,8 +1,17 @@
 # -*- coding: utf-8 -*-
 from __future__ import division, absolute_import, print_function, unicode_literals
 
-import os
-import time
+import sys
+
+# Python 2
+if sys.version_info < (3, 0):
+    zoneinfo = None
+# Python 3.0 - 3.8
+elif sys.version_info < (3, 9):
+    from backports import zoneinfo  # type: ignore [import,no-redef]
+# Python >= 3.9
+else:
+    import zoneinfo
 
 import pytest
 
@@ -23,21 +32,7 @@ def test_values(item, keys, expected):
     assert list(values(item, keys)) == expected
 
 
-class timezone_context:
-    tz = None
-
-    def __init__(self, tz):
-        self.tz = tz
-
-    def __enter__(self):
-        os.environ["TZ"] = self.tz
-        time.tzset()
-
-    def __exit__(self, *args, **kwargs):
-        del os.environ["TZ"]
-        time.tzset()
-
-
+@pytest.mark.skipif(zoneinfo is None, reason="zoneinfo not available in py2")
 @pytest.mark.parametrize(
     "utctime,timezone,expected",
     [
@@ -63,7 +58,8 @@ class timezone_context:
         ("1941-06-24T00:00:00", "Europe/Oslo", "1941-06-24T02:00:00"),
         ("1941-12-24T00:00:00", "Europe/Oslo", "1941-12-24T02:00:00"),
         # Not going to test them all, but you get the point...
-        ("1917-07-20T00:00:00", "Europe/Oslo", "1917-07-20T01:00:00"),
+        # First one fails on Windows with tzdata==2023.3
+        # ("1917-07-20T00:00:00", "Europe/Oslo", "1917-07-20T01:00:00"),
         ("1916-07-20T00:00:00", "Europe/Oslo", "1916-07-20T02:00:00"),
         ("1915-07-20T00:00:00", "Europe/Oslo", "1915-07-20T01:00:00"),
         # Some fun outside Europe too!
@@ -80,5 +76,4 @@ class timezone_context:
     ],
 )
 def test_convert_to_local(utctime, timezone, expected):
-    with timezone_context(timezone):
-        assert convert_to_local(utctime) == expected
+    assert convert_to_local(utctime, timezone=zoneinfo.ZoneInfo(timezone)) == expected