prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Cron fails to run
  The weekly cron for `create_payday_issue` is no longer running successfully at the scheduled time.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `liberapay/cron.py`
  Content:
  ```
  1 from collections import namedtuple
  2 from datetime import datetime, timedelta
  3 import logging
  4 import threading
  5 from time import sleep
  6 
  7 
  8 logger = logging.getLogger('liberapay.cron')
  9 
  10 
  11 Daily = namedtuple('Daily', 'hour')
  12 Weekly = namedtuple('Weekly', 'weekday hour')
  13 
  14 
  15 class Cron(object):
  16 
  17     def __init__(self, website):
  18         self.website = website
  19         self.conn = None
  20         self.has_lock = False
  21         self.exclusive_jobs = []
  22 
  23     def __call__(self, period, func, exclusive=False):
  24         if not self.website.env.run_cron_jobs:
  25             return
  26         if exclusive and not self.has_lock:
  27             self.exclusive_jobs.append((period, func))
  28             self._wait_for_lock()
  29             return
  30         def f():
  31             if isinstance(period, Weekly):
  32                 while True:
  33                     now = datetime.utcnow()
  34                     then = now.replace(hour=period.hour, minute=10, second=0)
  35                     days = (now.isoweekday() - period.weekday) % 7
  36                     if days:
  37                         then += timedelta(days=days)
  38                     seconds = (then - now).total_seconds()
  39                     if seconds > 0:
  40                         sleep(seconds)
  41                     elif seconds < -60:
  42                         sleep(86400 * 6)
  43                         continue
  44                     try:
  45                         func()
  46                     except Exception as e:
  47                         self.website.tell_sentry(e, {})
  48                     sleep(86400 * 6)
  49             elif isinstance(period, Daily):
  50                 while True:
  51                     now = datetime.utcnow()
  52                     then = now.replace(hour=period.hour, minute=5, second=0)
  53                     seconds = (then - now).total_seconds()
  54                     if seconds > 0:
  55                         # later today
  56                         sleep(seconds)
  57                     elif seconds < -60:
  58                         # tomorrow
  59                         sleep(3600 * 24 + seconds)
  60                     try:
  61                         func()
  62                     except Exception as e:
  63                         self.website.tell_sentry(e, {})
  64                         # retry in 5 minutes
  65                         sleep(300)
  66                     else:
  67                         # success, sleep until tomorrow
  68                         sleep(3600 * 23)
  69             else:
  70                 assert period >= 1
  71                 while True:
  72                     try:
  73                         func()
  74                     except Exception as e:
  75                         self.website.tell_sentry(e, {})
  76                     sleep(period)
  77         t = threading.Thread(target=f)
  78         t.daemon = True
  79         t.start()
  80 
  81     def _wait_for_lock(self):
  82         if self.conn:
  83             return  # Already waiting
  84         self.conn = self.website.db.get_connection().__enter__()
  85         def f():
  86             cursor = self.conn.cursor()
  87             while True:
  88                 if cursor.one("SELECT pg_try_advisory_lock(0)"):
  89                     self.has_lock = True
  90                     break
  91                 sleep(300)
  92             for job in self.exclusive_jobs:
  93                 self(*job, exclusive=True)
  94         t = threading.Thread(target=f)
  95         t.daemon = True
  96         t.start()
  97
  ```
  --- END FILES ---
  
  Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.
  
  Here is an example:
  
  ```diff
  diff --git a/examples/server_async.py b/examples/server_async.py
  --- a/examples/server_async.py
  +++ b/examples/server_async.py
  @@ -313,4 +313,4 @@
   
   
   if __name__ == "__main__":
  -    asyncio.run(run_async_server("."), debug=True)
  +    asyncio.run(run_async_server(), debug=True)
  diff --git a/examples/server_sync.py b/examples/server_sync.py
  --- a/examples/server_sync.py
  +++ b/examples/server_sync.py
  @@ -313,5 +313,5 @@
   
   
   if __name__ == "__main__":
  -    server = run_sync_server(".")
  +    server = run_sync_server()
       server.shutdown()
  
  ```
  
golden_diff: diff --git a/liberapay/cron.py b/liberapay/cron.py
  --- a/liberapay/cron.py
  +++ b/liberapay/cron.py
  @@ -21,7 +21,7 @@
           self.exclusive_jobs = []
   
       def __call__(self, period, func, exclusive=False):
  -        if not self.website.env.run_cron_jobs:
  +        if not self.website.env.run_cron_jobs or not period:
               return
           if exclusive and not self.has_lock:
               self.exclusive_jobs.append((period, func))
  @@ -32,7 +32,7 @@
                   while True:
                       now = datetime.utcnow()
                       then = now.replace(hour=period.hour, minute=10, second=0)
  -                    days = (now.isoweekday() - period.weekday) % 7
  +                    days = (period.weekday - now.isoweekday()) % 7
                       if days:
                           then += timedelta(days=days)
                       seconds = (then - now).total_seconds()
  
verification_info: {"golden_diff": "diff --git a/liberapay/cron.py b/liberapay/cron.py\n--- a/liberapay/cron.py\n+++ b/liberapay/cron.py\n@@ -21,7 +21,7 @@\n         self.exclusive_jobs = []\n \n     def __call__(self, period, func, exclusive=False):\n-        if not self.website.env.run_cron_jobs:\n+        if not self.website.env.run_cron_jobs or not period:\n             return\n         if exclusive and not self.has_lock:\n             self.exclusive_jobs.append((period, func))\n@@ -32,7 +32,7 @@\n                 while True:\n                     now = datetime.utcnow()\n                     then = now.replace(hour=period.hour, minute=10, second=0)\n-                    days = (now.isoweekday() - period.weekday) % 7\n+                    days = (period.weekday - now.isoweekday()) % 7\n                     if days:\n                         then += timedelta(days=days)\n                     seconds = (then - now).total_seconds()\n", "issue": "Cron fails to run\nThe weekly cron for `create_payday_issue` is no longer running successfully at the scheduled time.\n", "before_files": [{"content": "from collections import namedtuple\nfrom datetime import datetime, timedelta\nimport logging\nimport threading\nfrom time import sleep\n\n\nlogger = logging.getLogger('liberapay.cron')\n\n\nDaily = namedtuple('Daily', 'hour')\nWeekly = namedtuple('Weekly', 'weekday hour')\n\n\nclass Cron(object):\n\n    def __init__(self, website):\n        self.website = website\n        self.conn = None\n        self.has_lock = False\n        self.exclusive_jobs = []\n\n    def __call__(self, period, func, exclusive=False):\n        if not self.website.env.run_cron_jobs:\n            return\n        if exclusive and not self.has_lock:\n            self.exclusive_jobs.append((period, func))\n            self._wait_for_lock()\n            return\n        def f():\n            if isinstance(period, Weekly):\n                while True:\n                    now = datetime.utcnow()\n                    then = now.replace(hour=period.hour, minute=10, second=0)\n                    days = (now.isoweekday() - period.weekday) % 7\n                    if days:\n                        then += timedelta(days=days)\n                    seconds = (then - now).total_seconds()\n                    if seconds > 0:\n                        sleep(seconds)\n                    elif seconds < -60:\n                        sleep(86400 * 6)\n                        continue\n                    try:\n                        func()\n                    except Exception as e:\n                        self.website.tell_sentry(e, {})\n                    sleep(86400 * 6)\n            elif isinstance(period, Daily):\n                while True:\n                    now = datetime.utcnow()\n                    then = now.replace(hour=period.hour, minute=5, second=0)\n                    seconds = (then - now).total_seconds()\n                    if seconds > 0:\n                        # later today\n                        sleep(seconds)\n                    elif seconds < -60:\n                        # tomorrow\n                        sleep(3600 * 24 + seconds)\n                    try:\n                        func()\n                    except Exception as e:\n                        self.website.tell_sentry(e, {})\n                        # retry in 5 minutes\n                        sleep(300)\n                    else:\n                        # success, sleep until tomorrow\n                        sleep(3600 * 23)\n            else:\n                assert period >= 1\n                while True:\n                    try:\n                        func()\n                    except Exception as e:\n                        self.website.tell_sentry(e, {})\n                    sleep(period)\n        t = threading.Thread(target=f)\n        t.daemon = True\n        t.start()\n\n    def _wait_for_lock(self):\n        if self.conn:\n            return  # Already waiting\n        self.conn = self.website.db.get_connection().__enter__()\n        def f():\n            cursor = self.conn.cursor()\n            while True:\n                if cursor.one(\"SELECT pg_try_advisory_lock(0)\"):\n                    self.has_lock = True\n                    break\n                sleep(300)\n            for job in self.exclusive_jobs:\n                self(*job, exclusive=True)\n        t = threading.Thread(target=f)\n        t.daemon = True\n        t.start()\n", "path": "liberapay/cron.py"}], "after_files": [{"content": "from collections import namedtuple\nfrom datetime import datetime, timedelta\nimport logging\nimport threading\nfrom time import sleep\n\n\nlogger = logging.getLogger('liberapay.cron')\n\n\nDaily = namedtuple('Daily', 'hour')\nWeekly = namedtuple('Weekly', 'weekday hour')\n\n\nclass Cron(object):\n\n    def __init__(self, website):\n        self.website = website\n        self.conn = None\n        self.has_lock = False\n        self.exclusive_jobs = []\n\n    def __call__(self, period, func, exclusive=False):\n        if not self.website.env.run_cron_jobs or not period:\n            return\n        if exclusive and not self.has_lock:\n            self.exclusive_jobs.append((period, func))\n            self._wait_for_lock()\n            return\n        def f():\n            if isinstance(period, Weekly):\n                while True:\n                    now = datetime.utcnow()\n                    then = now.replace(hour=period.hour, minute=10, second=0)\n                    days = (period.weekday - now.isoweekday()) % 7\n                    if days:\n                        then += timedelta(days=days)\n                    seconds = (then - now).total_seconds()\n                    if seconds > 0:\n                        sleep(seconds)\n                    elif seconds < -60:\n                        sleep(86400 * 6)\n                        continue\n                    try:\n                        func()\n                    except Exception as e:\n                        self.website.tell_sentry(e, {})\n                    sleep(86400 * 6)\n            elif isinstance(period, Daily):\n                while True:\n                    now = datetime.utcnow()\n                    then = now.replace(hour=period.hour, minute=5, second=0)\n                    seconds = (then - now).total_seconds()\n                    if seconds > 0:\n                        # later today\n                        sleep(seconds)\n                    elif seconds < -60:\n                        # tomorrow\n                        sleep(3600 * 24 + seconds)\n                    try:\n                        func()\n                    except Exception as e:\n                        self.website.tell_sentry(e, {})\n                        # retry in 5 minutes\n                        sleep(300)\n                    else:\n                        # success, sleep until tomorrow\n                        sleep(3600 * 23)\n            else:\n                assert period >= 1\n                while True:\n                    try:\n                        func()\n                    except Exception as e:\n                        self.website.tell_sentry(e, {})\n                    sleep(period)\n        t = threading.Thread(target=f)\n        t.daemon = True\n        t.start()\n\n    def _wait_for_lock(self):\n        if self.conn:\n            return  # Already waiting\n        self.conn = self.website.db.get_connection().__enter__()\n        def f():\n            cursor = self.conn.cursor()\n            while True:\n                if cursor.one(\"SELECT pg_try_advisory_lock(0)\"):\n                    self.has_lock = True\n                    break\n                sleep(300)\n            for job in self.exclusive_jobs:\n                self(*job, exclusive=True)\n        t = threading.Thread(target=f)\n        t.daemon = True\n        t.start()\n", "path": "liberapay/cron.py"}]}