Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-django-celery-beat for 
openSUSE:Factory checked in at 2026-04-01 19:52:36
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-django-celery-beat (Old)
 and      /work/SRC/openSUSE:Factory/.python-django-celery-beat.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-django-celery-beat"

Wed Apr  1 19:52:36 2026 rev:7 rq:1344087 version:2.9.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-django-celery-beat/python-django-celery-beat.changes
      2026-01-05 16:04:16.039222391 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-django-celery-beat.new.21863/python-django-celery-beat.changes
   2026-04-01 19:54:13.274084022 +0200
@@ -1,0 +2,15 @@
+Wed Apr  1 10:08:42 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 2.9.0:
+  * Added DeepWiki to README
+  * refactor: use in operator instead of regex operator in
+    crontab query to fix mssql regression
+  * refactor: tzaware crontab is due method
+  * feat: enable translation to PeriodicTaskInline verbose name
+  * Remove Python2 'next' assignment in scheduler
+  * Avoid the breaking change in dependency cron_descriptor v2
+  * GitHub Actions: Test on Python 3.14 release candidate 2
+  * Remove upper bound on Django version
+  * Add Django 6.0 support
+
+-------------------------------------------------------------------

Old:
----
  django_celery_beat-2.8.1.tar.gz

New:
----
  django_celery_beat-2.9.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-django-celery-beat.spec ++++++
--- /var/tmp/diff_new_pack.HqJAEQ/_old  2026-04-01 19:54:13.886109443 +0200
+++ /var/tmp/diff_new_pack.HqJAEQ/_new  2026-04-01 19:54:13.894109775 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           python-django-celery-beat
-Version:        2.8.1
+Version:        2.9.0
 Release:        0
 Summary:        Database-backed Periodic Tasks
 License:        BSD-3-Clause

++++++ django_celery_beat-2.8.1.tar.gz -> django_celery_beat-2.9.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django_celery_beat-2.8.1/Changelog 
new/django_celery_beat-2.9.0/Changelog
--- old/django_celery_beat-2.8.1/Changelog      2025-05-13 08:57:51.000000000 
+0200
+++ new/django_celery_beat-2.9.0/Changelog      2026-02-28 17:44:56.000000000 
+0100
@@ -7,6 +7,24 @@
 Next
 ====
 
+.. _version-2.9.0:
+
+2.9.0
+=====
+:release-date: 2026-02-24
+:release-by: Asif Saif Uddin (@auvipy)
+
+- Added DeepWiki to README
+- refactor: use in operator instead of regex operator in crontab query to fix 
mssql regression
+- refactor: tzaware crontab is due method
+- feat: enable translation to PeriodicTaskInline verbose name
+- Remove Python2 'next' assignment in scheduler
+- Avoid the breaking change in dependency cron_descriptor v2
+- GitHub Actions: Test on Python 3.14 release candidate 2
+- Remove upper bound on Django version
+- Add Django 6.0 support
+
+
 .. _version-2.8.1:
 
 2.8.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django_celery_beat-2.8.1/PKG-INFO 
new/django_celery_beat-2.9.0/PKG-INFO
--- old/django_celery_beat-2.8.1/PKG-INFO       2025-05-13 08:57:54.326757700 
+0200
+++ new/django_celery_beat-2.9.0/PKG-INFO       2026-02-28 17:44:59.304603600 
+0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: django-celery-beat
-Version: 2.8.1
+Version: 2.9.0
 Summary: Database-backed Periodic Tasks.
 Home-page: https://github.com/celery/django-celery-beat
 Author: Asif Saif Uddin, Ask Solem
@@ -18,6 +18,7 @@
 Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: 3.12
 Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Framework :: Django
@@ -27,6 +28,7 @@
 Classifier: Framework :: Django :: 5.0
 Classifier: Framework :: Django :: 5.1
 Classifier: Framework :: Django :: 5.2
+Classifier: Framework :: Django :: 6.0
 Classifier: Operating System :: OS Independent
 Classifier: Topic :: Communications
 Classifier: Topic :: System :: Distributed Computing
@@ -41,8 +43,8 @@
 Requires-Dist: backports.zoneinfo; python_version < "3.9"
 Requires-Dist: tzdata
 Requires-Dist: python-crontab>=2.3.4
-Requires-Dist: cron-descriptor>=1.2.32
-Requires-Dist: Django<6.0,>=2.2
+Requires-Dist: cron-descriptor<2.0.0,>=1.2.32
+Requires-Dist: Django<6.1,>=2.2
 Dynamic: author
 Dynamic: author-email
 Dynamic: classifier
@@ -67,6 +69,7 @@
 :Web: http://django-celery-beat.readthedocs.io/
 :Download: http://pypi.python.org/pypi/django-celery-beat
 :Source: http://github.com/celery/django-celery-beat
+:DeepWiki: |deepwiki|
 :Keywords: django, celery, beat, periodic task, cron, scheduling
 
 About
@@ -404,6 +407,11 @@
     :alt: Support Python implementations.
     :target: http://pypi.python.org/pypi/django-celery-beat/
 
+.. |deepwiki| image:: https://devin.ai/assets/deepwiki-badge.png
+    :alt: Ask http://DeepWiki.com
+    :target: https://deepwiki.com/celery/django-celery-beat
+    :width: 125px
+
 django-celery-beat as part of the Tidelift Subscription
 -------------------------------------------------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django_celery_beat-2.8.1/README.rst 
new/django_celery_beat-2.9.0/README.rst
--- old/django_celery_beat-2.8.1/README.rst     2025-05-13 08:57:51.000000000 
+0200
+++ new/django_celery_beat-2.9.0/README.rst     2026-02-28 17:44:56.000000000 
+0100
@@ -8,6 +8,7 @@
 :Web: http://django-celery-beat.readthedocs.io/
 :Download: http://pypi.python.org/pypi/django-celery-beat
 :Source: http://github.com/celery/django-celery-beat
+:DeepWiki: |deepwiki|
 :Keywords: django, celery, beat, periodic task, cron, scheduling
 
 About
@@ -345,6 +346,11 @@
     :alt: Support Python implementations.
     :target: http://pypi.python.org/pypi/django-celery-beat/
 
+.. |deepwiki| image:: https://devin.ai/assets/deepwiki-badge.png
+    :alt: Ask http://DeepWiki.com
+    :target: https://deepwiki.com/celery/django-celery-beat
+    :width: 125px
+
 django-celery-beat as part of the Tidelift Subscription
 -------------------------------------------------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django_celery_beat-2.8.1/django_celery_beat/__init__.py 
new/django_celery_beat-2.9.0/django_celery_beat/__init__.py
--- old/django_celery_beat-2.8.1/django_celery_beat/__init__.py 2025-05-13 
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat/__init__.py 2026-02-28 
17:44:56.000000000 +0100
@@ -5,7 +5,7 @@
 import re
 from collections import namedtuple
 
-__version__ = '2.8.1'
+__version__ = '2.9.0'
 __author__ = 'Asif Saif Uddin, Ask Solem'
 __contact__ = '[email protected], [email protected]'
 __homepage__ = 'https://github.com/celery/django-celery-beat'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django_celery_beat-2.8.1/django_celery_beat/admin.py 
new/django_celery_beat-2.9.0/django_celery_beat/admin.py
--- old/django_celery_beat-2.8.1/django_celery_beat/admin.py    2025-05-13 
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat/admin.py    2026-02-28 
17:44:56.000000000 +0100
@@ -281,7 +281,7 @@
     can_delete = False
     extra = 0
     show_change_link = True
-    verbose_name = "Periodic Tasks Using This Schedule"
+    verbose_name = _("Periodic Tasks Using This Schedule")
     verbose_name_plural = verbose_name
 
     def has_add_permission(self, request, obj):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django_celery_beat-2.8.1/django_celery_beat/apps.py 
new/django_celery_beat-2.9.0/django_celery_beat/apps.py
--- old/django_celery_beat-2.8.1/django_celery_beat/apps.py     2025-05-13 
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat/apps.py     2026-02-28 
17:44:56.000000000 +0100
@@ -14,5 +14,5 @@
     default_auto_field = 'django.db.models.AutoField'
 
     def ready(self):
-        from .signals import signals_connect
+        from .signals import signals_connect  # noqa: PLC0415
         signals_connect()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django_celery_beat-2.8.1/django_celery_beat/clockedschedule.py 
new/django_celery_beat-2.9.0/django_celery_beat/clockedschedule.py
--- old/django_celery_beat-2.8.1/django_celery_beat/clockedschedule.py  
2025-05-13 08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat/clockedschedule.py  
2026-02-28 17:44:56.000000000 +0100
@@ -6,7 +6,7 @@
 from .utils import NEVER_CHECK_TIMEOUT
 
 
-class clocked(schedules.BaseSchedule):
+class clocked(schedules.BaseSchedule):  # noqa: PLW1641
     """clocked schedule.
 
     Depends on PeriodicTask one_off=True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django_celery_beat-2.8.1/django_celery_beat/schedulers.py 
new/django_celery_beat-2.9.0/django_celery_beat/schedulers.py
--- old/django_celery_beat-2.8.1/django_celery_beat/schedulers.py       
2025-05-13 08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat/schedulers.py       
2026-02-28 17:44:56.000000000 +0100
@@ -167,7 +167,6 @@
         self.model.total_run_count += 1
         self.model.no_changes = True
         return self.__class__(self.model)
-    next = __next__  # for 2to3
 
     def save(self):
         # Object may not be synchronized, so only
@@ -175,7 +174,6 @@
         obj = type(self.model)._default_manager.get(pk=self.model.pk)
         for field in self.save_fields:
             setattr(obj, field, getattr(self.model, field))
-
         obj.save()
 
     @classmethod
@@ -314,13 +312,10 @@
         ]
         hours_to_include += [4]  # celery's default cleanup task
 
-        # Regex pattern to match only numbers
-        # This ensures we only process numeric hour values
-        numeric_hour_pattern = r'^\d+$'
-
         # Get all tasks with a simple numeric hour value
+        valid_numeric_hours = self._get_valid_hour_formats()
         numeric_hour_tasks = CrontabSchedule.objects.filter(
-            hour__regex=numeric_hour_pattern
+            hour__in=valid_numeric_hours
         )
 
         # Annotate these tasks with their server-hour equivalent
@@ -359,6 +354,15 @@
 
         return exclude_query
 
+    def _get_valid_hour_formats(self):
+        """
+        Return a list of all valid hour values (0-23).
+        Both zero-padded ("00"–"09") and non-padded ("0"–"23")
+        """
+        return [str(hour) for hour in range(24)] + [
+            f"{hour:02d}" for hour in range(10)
+        ]
+
     def _get_unique_timezone_names(self):
         """Get a list of all unique timezone names used in CrontabSchedule"""
         return CrontabSchedule.objects.values_list(
@@ -426,7 +430,7 @@
             return False
 
         try:
-            if ts and ts > (last if last else ts):
+            if ts and ts > (last or ts):
                 return True
         finally:
             self._last_timestamp = ts
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django_celery_beat-2.8.1/django_celery_beat/signals.py 
new/django_celery_beat-2.9.0/django_celery_beat/signals.py
--- old/django_celery_beat-2.8.1/django_celery_beat/signals.py  2025-05-13 
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat/signals.py  2026-02-28 
17:44:56.000000000 +0100
@@ -3,10 +3,11 @@
 
 def signals_connect():
     """Connect to signals."""
-    from django.db.models import signals
+    from django.db.models import signals  # noqa: PLC0415
 
-    from .models import (ClockedSchedule, CrontabSchedule, IntervalSchedule,
-                         PeriodicTask, PeriodicTasks, SolarSchedule)
+    from .models import (ClockedSchedule, CrontabSchedule,  # noqa: PLC0415
+                         IntervalSchedule, PeriodicTask, PeriodicTasks,
+                         SolarSchedule)
 
     signals.pre_save.connect(
         PeriodicTasks.changed, sender=PeriodicTask
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django_celery_beat-2.8.1/django_celery_beat/tzcrontab.py 
new/django_celery_beat-2.9.0/django_celery_beat/tzcrontab.py
--- old/django_celery_beat-2.8.1/django_celery_beat/tzcrontab.py        
2025-05-13 08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat/tzcrontab.py        
2026-02-28 17:44:56.000000000 +0100
@@ -7,7 +7,7 @@
 schedstate = namedtuple('schedstate', ('is_due', 'next'))
 
 
-class TzAwareCrontab(schedules.crontab):
+class TzAwareCrontab(schedules.crontab):  # noqa: PLW1641
     """Timezone Aware Crontab."""
 
     def __init__(
@@ -38,13 +38,7 @@
         # convert last_run_at to the schedule timezone
         last_run_at = last_run_at.astimezone(self.tz)
 
-        rem_delta = self.remaining_estimate(last_run_at)
-        rem = max(rem_delta.total_seconds(), 0)
-        due = rem == 0
-        if due:
-            rem_delta = self.remaining_estimate(self.now())
-            rem = max(rem_delta.total_seconds(), 0)
-        return schedstate(due, rem)
+        return super().is_due(last_run_at)
 
     # Needed to support pickling
     def __repr__(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django_celery_beat-2.8.1/django_celery_beat/utils.py 
new/django_celery_beat-2.9.0/django_celery_beat/utils.py
--- old/django_celery_beat-2.8.1/django_celery_beat/utils.py    2025-05-13 
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat/utils.py    2026-02-28 
17:44:56.000000000 +0100
@@ -57,9 +57,9 @@
     """Return true if Celery is configured to use the db scheduler."""
     if not scheduler:
         return False
-    from kombu.utils import symbol_by_name
+    from kombu.utils import symbol_by_name  # noqa: PLC0415
 
-    from .schedulers import DatabaseScheduler
+    from .schedulers import DatabaseScheduler  # noqa: PLC0415
     return (
         scheduler == 'django'
         or issubclass(symbol_by_name(scheduler), DatabaseScheduler)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django_celery_beat-2.8.1/django_celery_beat.egg-info/PKG-INFO 
new/django_celery_beat-2.9.0/django_celery_beat.egg-info/PKG-INFO
--- old/django_celery_beat-2.8.1/django_celery_beat.egg-info/PKG-INFO   
2025-05-13 08:57:54.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat.egg-info/PKG-INFO   
2026-02-28 17:44:59.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: django-celery-beat
-Version: 2.8.1
+Version: 2.9.0
 Summary: Database-backed Periodic Tasks.
 Home-page: https://github.com/celery/django-celery-beat
 Author: Asif Saif Uddin, Ask Solem
@@ -18,6 +18,7 @@
 Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: 3.12
 Classifier: Programming Language :: Python :: 3.13
+Classifier: Programming Language :: Python :: 3.14
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Classifier: Framework :: Django
@@ -27,6 +28,7 @@
 Classifier: Framework :: Django :: 5.0
 Classifier: Framework :: Django :: 5.1
 Classifier: Framework :: Django :: 5.2
+Classifier: Framework :: Django :: 6.0
 Classifier: Operating System :: OS Independent
 Classifier: Topic :: Communications
 Classifier: Topic :: System :: Distributed Computing
@@ -41,8 +43,8 @@
 Requires-Dist: backports.zoneinfo; python_version < "3.9"
 Requires-Dist: tzdata
 Requires-Dist: python-crontab>=2.3.4
-Requires-Dist: cron-descriptor>=1.2.32
-Requires-Dist: Django<6.0,>=2.2
+Requires-Dist: cron-descriptor<2.0.0,>=1.2.32
+Requires-Dist: Django<6.1,>=2.2
 Dynamic: author
 Dynamic: author-email
 Dynamic: classifier
@@ -67,6 +69,7 @@
 :Web: http://django-celery-beat.readthedocs.io/
 :Download: http://pypi.python.org/pypi/django-celery-beat
 :Source: http://github.com/celery/django-celery-beat
+:DeepWiki: |deepwiki|
 :Keywords: django, celery, beat, periodic task, cron, scheduling
 
 About
@@ -404,6 +407,11 @@
     :alt: Support Python implementations.
     :target: http://pypi.python.org/pypi/django-celery-beat/
 
+.. |deepwiki| image:: https://devin.ai/assets/deepwiki-badge.png
+    :alt: Ask http://DeepWiki.com
+    :target: https://deepwiki.com/celery/django-celery-beat
+    :width: 125px
+
 django-celery-beat as part of the Tidelift Subscription
 -------------------------------------------------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django_celery_beat-2.8.1/django_celery_beat.egg-info/requires.txt 
new/django_celery_beat-2.9.0/django_celery_beat.egg-info/requires.txt
--- old/django_celery_beat-2.8.1/django_celery_beat.egg-info/requires.txt       
2025-05-13 08:57:54.000000000 +0200
+++ new/django_celery_beat-2.9.0/django_celery_beat.egg-info/requires.txt       
2026-02-28 17:44:59.000000000 +0100
@@ -2,8 +2,8 @@
 django-timezone-field>=5.0
 tzdata
 python-crontab>=2.3.4
-cron-descriptor>=1.2.32
-Django<6.0,>=2.2
+cron-descriptor<2.0.0,>=1.2.32
+Django<6.1,>=2.2
 
 [:python_version < "3.8"]
 importlib-metadata<5.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django_celery_beat-2.8.1/pyproject.toml 
new/django_celery_beat-2.9.0/pyproject.toml
--- old/django_celery_beat-2.8.1/pyproject.toml 2025-05-13 08:57:51.000000000 
+0200
+++ new/django_celery_beat-2.9.0/pyproject.toml 2026-02-28 17:44:56.000000000 
+0100
@@ -1,6 +1,5 @@
 [tool.ruff]
 target-version = "py38"
-
 lint.select = [
   "A",     # flake8-builtins
   "AIR",   # Airflow
@@ -126,26 +125,24 @@
   "int",
   "str",
 ]
-lint.pylint.max-args = 8 # Default: 5
-
-[tool.coverage.run]
-branch = true
-cover_pylib = false
-include = [ "*django_celery_beat/*" ]
-omit = [ "django_celery_beat.tests.*" ]
+lint.pylint.max-args = 8  # Default: 5
 
-[tool.coverage.report]
-exclude_lines = [
-  "pragma: no cover",
-  "if TYPE_CHECKING:",
+[tool.coverage]
+run.branch = true
+run.cover_pylib = false
+run.include = [ "*django_celery_beat/*" ]
+run.omit = [ "django_celery_beat.tests.*" ]
+report.exclude_lines = [
   "except ImportError:",
+  "if TYPE_CHECKING:",
+  "pragma: no cover",
 ]
-omit = [
-  "*/python?.?/*",
-  "*/site-packages/*",
-  "*/pypy/*",
+report.omit = [
   "*/.tox/*",
   "*/docker/*",
   "*/docs/*",
+  "*/pypy/*",
+  "*/python?.?/*",
+  "*/site-packages/*",
   "*/test_*.py",
 ]
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django_celery_beat-2.8.1/requirements/default.txt 
new/django_celery_beat-2.9.0/requirements/default.txt
--- old/django_celery_beat-2.8.1/requirements/default.txt       2025-05-13 
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/requirements/default.txt       2026-02-28 
17:44:56.000000000 +0100
@@ -4,4 +4,4 @@
 backports.zoneinfo; python_version<"3.9"
 tzdata
 python-crontab>=2.3.4
-cron-descriptor>=1.2.32
+cron-descriptor>=1.2.32,<2.0.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django_celery_beat-2.8.1/requirements/docs.txt 
new/django_celery_beat-2.9.0/requirements/docs.txt
--- old/django_celery_beat-2.8.1/requirements/docs.txt  2025-05-13 
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/requirements/docs.txt  2026-02-28 
17:44:56.000000000 +0100
@@ -1,4 +1,4 @@
-Django>=2.2,<6.0
+Django>=2.2,<6.1
 sphinxcontrib-django
 https://github.com/celery/sphinx_celery/archive/master.zip
 https://github.com/celery/kombu/zipball/main#egg=kombu
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django_celery_beat-2.8.1/requirements/runtime.txt 
new/django_celery_beat-2.9.0/requirements/runtime.txt
--- old/django_celery_beat-2.8.1/requirements/runtime.txt       2025-05-13 
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/requirements/runtime.txt       2026-02-28 
17:44:56.000000000 +0100
@@ -1 +1 @@
-Django>=2.2,<6.0
+Django>=2.2,<6.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/django_celery_beat-2.8.1/requirements/test-django.txt 
new/django_celery_beat-2.9.0/requirements/test-django.txt
--- old/django_celery_beat-2.8.1/requirements/test-django.txt   2025-05-13 
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/requirements/test-django.txt   2026-02-28 
17:44:56.000000000 +0100
@@ -1 +1 @@
-Django>=3.2,<6.0
+Django>=3.2,<6.1
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django_celery_beat-2.8.1/setup.py 
new/django_celery_beat-2.9.0/setup.py
--- old/django_celery_beat-2.8.1/setup.py       2025-05-13 08:57:51.000000000 
+0200
+++ new/django_celery_beat-2.9.0/setup.py       2026-02-28 17:44:56.000000000 
+0100
@@ -22,6 +22,7 @@
     Programming Language :: Python :: 3.11
     Programming Language :: Python :: 3.12
     Programming Language :: Python :: 3.13
+    Programming Language :: Python :: 3.14
     Programming Language :: Python :: Implementation :: CPython
     Programming Language :: Python :: Implementation :: PyPy
     Framework :: Django
@@ -31,6 +32,7 @@
     Framework :: Django :: 5.0
     Framework :: Django :: 5.1
     Framework :: Django :: 5.2
+    Framework :: Django :: 6.0
     Operating System :: OS Independent
     Topic :: Communications
     Topic :: System :: Distributed Computing
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django_celery_beat-2.8.1/t/unit/conftest.py 
new/django_celery_beat-2.9.0/t/unit/conftest.py
--- old/django_celery_beat-2.8.1/t/unit/conftest.py     2025-05-13 
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/t/unit/conftest.py     2026-02-28 
17:44:56.000000000 +0100
@@ -18,7 +18,8 @@
 
 @pytest.fixture(scope='session', autouse=True)
 def setup_default_app_trap():
-    from celery._state import set_default_app
+    from celery._state import set_default_app  # noqa: PLC0415
+
     set_default_app(Trap())
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/django_celery_beat-2.8.1/t/unit/test_schedulers.py 
new/django_celery_beat-2.9.0/t/unit/test_schedulers.py
--- old/django_celery_beat-2.8.1/t/unit/test_schedulers.py      2025-05-13 
08:57:51.000000000 +0200
+++ new/django_celery_beat-2.9.0/t/unit/test_schedulers.py      2026-02-28 
17:44:56.000000000 +0100
@@ -2,6 +2,7 @@
 import os
 import time
 from datetime import datetime, timedelta
+from datetime import timezone as dt_timezone
 from itertools import count
 from time import monotonic
 from unittest.mock import patch
@@ -11,6 +12,8 @@
 except ImportError:
     from backports.zoneinfo import ZoneInfo  # Python 3.8
 
+from unittest.mock import MagicMock
+
 import pytest
 from celery.schedules import crontab, schedule, solar
 from django.contrib.admin.sites import AdminSite
@@ -24,6 +27,7 @@
 from django_celery_beat.models import (DAYS, ClockedSchedule, CrontabSchedule,
                                        IntervalSchedule, PeriodicTask,
                                        PeriodicTasks, SolarSchedule)
+from django_celery_beat.tzcrontab import TzAwareCrontab
 from django_celery_beat.utils import NEVER_CHECK_TIMEOUT, make_aware
 
 _ids = count(0)
@@ -120,6 +124,167 @@
 
 
 @pytest.mark.django_db
+class test_TzAwareCrontab_beat_cron_starting_deadline(SchedulerCase):
+    """Tests for TzAwareCrontab with beat_cron_starting_deadline."""
+
+    @override_settings(DJANGO_CELERY_BEAT_TZ_AWARE=True)
+    @patch("django_celery_beat.tzcrontab.datetime")
+    def test_due_when_within_starting_deadline(self, mock_datetime):
+        """
+        Test that a task is due if last_run_at is within
+        beat_cron_starting_deadline.
+        """
+        # Create a mock app with 5 minute beat_cron_starting_deadline
+        app = MagicMock()
+        app.conf.beat_cron_starting_deadline = 300  # 5 minutes in seconds
+
+        # Set current time to 12:05:50
+        mock_now_utc = datetime(
+            2023, 10, 26, 12, 5, 50, tzinfo=dt_timezone.utc
+        )
+        mock_datetime.now.return_value = mock_now_utc
+
+        # Create a schedule that runs every 5 minutes (12:00, 12:05, etc.)
+        schedule = TzAwareCrontab(app=app, tz=dt_timezone.utc, minute="*/5")
+
+        # Last run was 290 seconds ago (12:01:00) - within the deadline window
+        last_run_at_utc = mock_now_utc - timedelta(seconds=290)
+
+        # Next scheduled run should be 12:05:00, but current time is 12:05:50
+        # So the task is due, and the deadline check doesn't prevent execution
+        due_status, next_check_delta = schedule.is_due(last_run_at_utc)
+        assert due_status is True
+
+    @override_settings(DJANGO_CELERY_BEAT_TZ_AWARE=True)
+    @patch("django_celery_beat.tzcrontab.datetime")
+    def test_not_due_when_outside_starting_deadline(self, mock_datetime):
+        """
+        Test that a task is NOT due if last_run_at is outside (older than)
+        beat_cron_starting_deadline.
+        """
+        # Create a mock app with 2 minute beat_cron_starting_deadline
+        app = MagicMock()
+        app.conf.beat_cron_starting_deadline = 120  # 2 minutes in seconds
+
+        # Set current time to 12:09:50
+        mock_now_utc = datetime(
+            2023, 10, 26, 12, 9, 50, tzinfo=dt_timezone.utc
+        )
+        mock_datetime.now.return_value = mock_now_utc
+
+        # Create a schedule that runs every 5 minutes (12:00, 12:05, etc.)
+        schedule_utc = TzAwareCrontab(
+            app=app, tz=dt_timezone.utc, minute="*/5"
+        )
+
+        # Last run was 310 seconds ago (12:04:40) - outside the deadline window
+        last_run_at_utc = mock_now_utc - timedelta(seconds=310)
+
+        # Next scheduled run after 12:04:40 would be 12:05:00
+        # This is in the past relative to current time, so normally due
+        # BUT since last_run_at is before the deadline window, it's NOT due
+        due_status, next_check_delta = schedule_utc.is_due(last_run_at_utc)
+        assert due_status is False
+
+    @override_settings(DJANGO_CELERY_BEAT_TZ_AWARE=True)
+    @patch("django_celery_beat.tzcrontab.datetime")
+    def test_not_due_with_recent_run(self, mock_datetime):
+        """
+        Test that a task is not due if last_run_at is recent,
+        even with a starting_deadline set.
+        """
+        # Create a mock app with 5 minute beat_cron_starting_deadline
+        app = MagicMock()
+        app.conf.beat_cron_starting_deadline = 300  # 5 minutes in seconds
+
+        # Create a schedule that runs every 5 minutes (12:00, 12:05, etc.)
+        schedule = TzAwareCrontab(app=app, tz=dt_timezone.utc, minute="*/5")
+
+        # Set current time to 12:04:30 (before next scheduled execution)
+        mock_now_utc_early = datetime(
+            2023, 10, 26, 12, 4, 30, tzinfo=dt_timezone.utc
+        )
+        mock_datetime.now.return_value = mock_now_utc_early
+
+        # Last run was at 12:04:00
+        # The next schedule would be at 12:05:00, which is in the future
+        last_run_at_recent = datetime(
+            2023, 10, 26, 12, 4, 0, tzinfo=dt_timezone.utc
+        )
+
+        # Calculate if the task is due
+        # Since the next execution time is in the future, the task is not due
+        # The deadline check doesn't matter for tasks not yet scheduled to run
+        due_status, next_check_delta = schedule.is_due(last_run_at_recent)
+        assert due_status is False
+
+    @override_settings(DJANGO_CELERY_BEAT_TZ_AWARE=True)
+    @patch("django_celery_beat.tzcrontab.datetime")
+    def test_due_with_no_starting_deadline_set(self, mock_datetime):
+        """
+        Test that a task is due if last_run_at is old and no deadline is set.
+        """
+        # Create a mock app with no beat_cron_starting_deadline
+        app = MagicMock()
+        app.conf.beat_cron_starting_deadline = None
+
+        # Set current time to 12:10:00
+        mock_now_utc = datetime(
+            2023, 10, 26, 12, 10, 0, tzinfo=dt_timezone.utc
+        )
+        mock_datetime.now.return_value = mock_now_utc
+
+        # Create a schedule that runs every 5 minutes (12:00, 12:05, etc.)
+        schedule_utc = TzAwareCrontab(
+            app=app, tz=dt_timezone.utc, minute="*/5"
+        )
+
+        # Last run was 310 seconds ago (12:04:50)
+        # With no deadline, age of the last run doesn't matter
+        last_run_at_utc = mock_now_utc - timedelta(seconds=310)
+
+        # Next scheduled time after 12:04:50 would be 12:05:00
+        # Current time is 12:10:00, so this is in the past
+        # With no deadline check, the task is due to run
+        due_status, next_check_delta = schedule_utc.is_due(
+            last_run_at_utc
+        )
+        assert due_status is True
+
+    @override_settings(DJANGO_CELERY_BEAT_TZ_AWARE=True)
+    @patch("django_celery_beat.tzcrontab.datetime")
+    def test_due_with_starting_deadline_non_utc_timezone(self, mock_datetime):
+        """
+        Test with a non-UTC timezone for the schedule.
+        """
+        # Create a mock app with 5 minute beat_cron_starting_deadline
+        app = MagicMock()
+        app.conf.beat_cron_starting_deadline = 300  # 5 minutes in seconds
+        app.timezone = ZoneInfo("America/New_York")
+
+        # Use New York timezone for the schedule
+        schedule_tz = ZoneInfo("America/New_York")
+
+        # Set current time to 08:05:00 New York time
+        mock_now_ny = datetime(2023, 10, 26, 8, 5, 0, tzinfo=schedule_tz)
+        mock_datetime.now.return_value = mock_now_ny
+
+        # Create a schedule that runs every 5 minutes in NY time
+        schedule_ny = TzAwareCrontab(app=app, tz=schedule_tz, minute="*/5")
+
+        # Last run was 290 seconds ago - within deadline window
+        # The deadline window starts at 08:00:00 (current time - 5 minutes)
+        # Since 08:00:10 is after 08:00:00, it's within the deadline window
+        last_run_at_ny = mock_now_ny - timedelta(seconds=290)
+
+        # Next scheduled time after 08:00:10 would be 08:05:00
+        # Current time is 08:05:00, so this is due
+        # The deadline check doesn't prevent execution
+        due_status, next_check_delta = schedule_ny.is_due(last_run_at_ny)
+        assert due_status is True
+
+
[email protected]_db
 class test_ModelEntry(SchedulerCase):
     Entry = EntryTrackSave
 
@@ -149,7 +314,7 @@
         e2 = self.Entry(m2, app=self.app)
         assert e2.last_run_at is right_now
 
-        e3 = e2.next()
+        e3 = next(e2)
         assert e3.last_run_at > e2.last_run_at
         assert e3.total_run_count == 1
 
@@ -1064,7 +1229,6 @@
     @patch('django.utils.timezone.get_current_timezone')
     def test_crontab_timezone_conversion(self, mock_get_tz, mock_aware_now):
         # Set up mocks for server timezone and current time
-        from datetime import datetime
         server_tz = ZoneInfo("Asia/Tokyo")
 
         mock_get_tz.return_value = server_tz
@@ -1141,8 +1305,6 @@
         self, mock_aware_now, mock_get_tz
     ):
         # Set up mocks for server timezone and current time
-        from datetime import datetime
-
         server_tz = ZoneInfo("UTC")
 
         mock_get_tz.return_value = server_tz
@@ -1212,6 +1374,41 @@
             "Paris outside window task should be excluded"
         )
 
+    def test_scheduler_valid_hours(self):
+        """Test the _get_valid_hour_formats method."""
+        # Create an instance of DatabaseScheduler
+        s = self.Scheduler(app=self.app)
+
+        # Get the valid hours list
+        valid_hours = s._get_valid_hour_formats()
+
+        # Basic validations
+        # 24 regular hours + 10 zero-padded hours
+        assert len(valid_hours) == 34
+
+        # Check both regular and zero-padded formats are present
+        assert "0" in valid_hours
+        assert "00" in valid_hours
+        assert "9" in valid_hours
+        assert "09" in valid_hours
+        assert "23" in valid_hours
+
+        # Verify all hours 0-23 are included
+        for hour in range(24):
+            assert str(hour) in valid_hours
+
+        # Verify zero-padded hours 00-09 are included
+        for hour in range(10):
+            assert f"{hour:02d}" in valid_hours
+
+        # Check for duplicates (set should have same length as list)
+        assert len(set(valid_hours)) == len(valid_hours)
+
+        # Verify all entries are valid hour representations
+        for hour_str in valid_hours:
+            hour_value = int(hour_str)
+            assert 0 <= hour_value <= 23
+
 
 @pytest.mark.django_db
 class test_models(SchedulerCase):
@@ -1465,11 +1662,14 @@
 
     @patch("django_celery_beat.schedulers.aware_now")
     def test_server_timezone_handling_with_zoneinfo(self, mock_aware_now):
-        """Test handling when server timezone is already a ZoneInfo 
instance."""
+        """Test handling when server timezone
+        is already a ZoneInfo instance."""
 
         # Create a mock scheduler with only the methods we need to test
         class MockScheduler:
-            _get_timezone_offset = 
schedulers.DatabaseScheduler._get_timezone_offset
+            _get_timezone_offset = (
+                schedulers.DatabaseScheduler._get_timezone_offset
+            )
 
         s = MockScheduler()
 
@@ -1490,7 +1690,9 @@
         """Test handling when timezone_name parameter is a ZoneInfo object."""
 
         class MockScheduler:
-            _get_timezone_offset = 
schedulers.DatabaseScheduler._get_timezone_offset
+            _get_timezone_offset = (
+                schedulers.DatabaseScheduler._get_timezone_offset
+            )
 
         s = MockScheduler()
 

Reply via email to