prompt: We are currently solving the following issue within our repository. Here is the issue text:
  --- BEGIN ISSUE ---
  Make crontab parser timezone aware
  The crontab matcher should take a timezone as a kwarg. It should also be possible to set a global timezone in the config. Default should be UTC.
  
  --- END ISSUE ---
  
  Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
  --- BEGIN FILES ---
  Path: `opsdroid/matchers.py`
  Content:
  ```
  1 """Decorator functions to use when creating skill modules."""
  2 
  3 import logging
  4 
  5 from opsdroid.helper import get_opsdroid
  6 from opsdroid.web import Web
  7 
  8 
  9 _LOGGER = logging.getLogger(__name__)
  10 
  11 
  12 def match_regex(regex):
  13     """Return regex match decorator."""
  14     def matcher(func):
  15         """Add decorated function to skills list for regex matching."""
  16         opsdroid = get_opsdroid()
  17         opsdroid.skills.append({"regex": regex, "skill": func,
  18                                 "config":
  19                                 opsdroid.loader.current_import_config})
  20         return func
  21     return matcher
  22 
  23 
  24 def match_apiai_action(action):
  25     """Return apiai action match decorator."""
  26     def matcher(func):
  27         """Add decorated function to skills list for apiai matching."""
  28         opsdroid = get_opsdroid()
  29         opsdroid.skills.append({"apiai_action": action, "skill": func,
  30                                 "config":
  31                                 opsdroid.loader.current_import_config})
  32         return func
  33     return matcher
  34 
  35 
  36 def match_apiai_intent(intent):
  37     """Return apiai intent match decorator."""
  38     def matcher(func):
  39         """Add decorated function to skills list for apiai matching."""
  40         opsdroid = get_opsdroid()
  41         opsdroid.skills.append({"apiai_intent": intent, "skill": func,
  42                                 "config":
  43                                 opsdroid.loader.current_import_config})
  44         return func
  45     return matcher
  46 
  47 
  48 def match_crontab(crontab):
  49     """Return crontab match decorator."""
  50     def matcher(func):
  51         """Add decorated function to skills list for crontab matching."""
  52         opsdroid = get_opsdroid()
  53         opsdroid.skills.append({"crontab": crontab, "skill": func,
  54                                 "config":
  55                                 opsdroid.loader.current_import_config})
  56         return func
  57     return matcher
  58 
  59 
  60 def match_webhook(webhook):
  61     """Return webhook match decorator."""
  62     def matcher(func):
  63         """Add decorated function to skills list for webhook matching."""
  64         opsdroid = get_opsdroid()
  65         config = opsdroid.loader.current_import_config
  66         opsdroid.skills.append({"webhook": webhook, "skill": func,
  67                                 "config": config})
  68 
  69         async def wrapper(req, opsdroid=opsdroid, config=config):
  70             """Wrap up the aiohttp handler."""
  71             _LOGGER.info("Running skill %s via webhook", webhook)
  72             opsdroid.stats["webhooks_called"] = \
  73                 opsdroid.stats["webhooks_called"] + 1
  74             await func(opsdroid, config, req)
  75             return Web.build_response(200, {"called_skill": webhook})
  76 
  77         opsdroid.web_server.web_app.router.add_post(
  78             "/skill/{}/{}".format(config["name"], webhook), wrapper)
  79         opsdroid.web_server.web_app.router.add_post(
  80             "/skill/{}/{}/".format(config["name"], webhook), wrapper)
  81 
  82         return func
  83     return matcher
  84
  ```
  
  Path: `opsdroid/parsers/crontab.py`
  Content:
  ```
  1 """A helper function for parsing and executing crontab skills."""
  2 
  3 import logging
  4 import asyncio
  5 from datetime import datetime
  6 
  7 import pycron
  8 
  9 
  10 _LOGGER = logging.getLogger(__name__)
  11 
  12 
  13 async def parse_crontab(opsdroid):
  14     """Parse all crontab skills against the current time."""
  15     # pylint: disable=broad-except
  16     # We want to catch all exceptions coming from a skill module and not
  17     # halt the application. If a skill throws an exception it just doesn't
  18     # give a response to the user, so an error response should be given.
  19     while opsdroid.eventloop.is_running():
  20         await asyncio.sleep(60 - datetime.now().time().second)
  21         _LOGGER.debug("Running crontab skills")
  22         for skill in opsdroid.skills:
  23             if "crontab" in skill and pycron.is_now(skill["crontab"]):
  24                 try:
  25                     await skill["skill"](opsdroid, skill["config"], None)
  26                 except Exception:
  27                     _LOGGER.exception("Exception when executing cron skill.")
  28
  ```
  --- 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/opsdroid/matchers.py b/opsdroid/matchers.py
  --- a/opsdroid/matchers.py
  +++ b/opsdroid/matchers.py
  @@ -45,14 +45,14 @@
       return matcher
   
   
  -def match_crontab(crontab):
  +def match_crontab(crontab, timezone=None):
       """Return crontab match decorator."""
       def matcher(func):
           """Add decorated function to skills list for crontab matching."""
           opsdroid = get_opsdroid()
  +        config = opsdroid.loader.current_import_config
           opsdroid.skills.append({"crontab": crontab, "skill": func,
  -                                "config":
  -                                opsdroid.loader.current_import_config})
  +                                "config": config, "timezone": timezone})
           return func
       return matcher
   
  diff --git a/opsdroid/parsers/crontab.py b/opsdroid/parsers/crontab.py
  --- a/opsdroid/parsers/crontab.py
  +++ b/opsdroid/parsers/crontab.py
  @@ -1,9 +1,9 @@
   """A helper function for parsing and executing crontab skills."""
   
  -import logging
   import asyncio
  -from datetime import datetime
  +import logging
   
  +import arrow
   import pycron
   
   
  @@ -17,11 +17,17 @@
       # halt the application. If a skill throws an exception it just doesn't
       # give a response to the user, so an error response should be given.
       while opsdroid.eventloop.is_running():
  -        await asyncio.sleep(60 - datetime.now().time().second)
  +        await asyncio.sleep(60 - arrow.now().time().second)
           _LOGGER.debug("Running crontab skills")
           for skill in opsdroid.skills:
  -            if "crontab" in skill and pycron.is_now(skill["crontab"]):
  -                try:
  -                    await skill["skill"](opsdroid, skill["config"], None)
  -                except Exception:
  -                    _LOGGER.exception("Exception when executing cron skill.")
  +            if "crontab" in skill:
  +                if skill["timezone"] is not None:
  +                    timezone = skill["timezone"]
  +                else:
  +                    timezone = opsdroid.config.get("timezone", "UTC")
  +                if pycron.is_now(skill["crontab"], arrow.now(tz=timezone)):
  +                    try:
  +                        await skill["skill"](opsdroid, skill["config"], None)
  +                    except Exception:
  +                        _LOGGER.exception(
  +                            "Exception when executing cron skill.")
  
verification_info: {"golden_diff": "diff --git a/opsdroid/matchers.py b/opsdroid/matchers.py\n--- a/opsdroid/matchers.py\n+++ b/opsdroid/matchers.py\n@@ -45,14 +45,14 @@\n     return matcher\n \n \n-def match_crontab(crontab):\n+def match_crontab(crontab, timezone=None):\n     \"\"\"Return crontab match decorator.\"\"\"\n     def matcher(func):\n         \"\"\"Add decorated function to skills list for crontab matching.\"\"\"\n         opsdroid = get_opsdroid()\n+        config = opsdroid.loader.current_import_config\n         opsdroid.skills.append({\"crontab\": crontab, \"skill\": func,\n-                                \"config\":\n-                                opsdroid.loader.current_import_config})\n+                                \"config\": config, \"timezone\": timezone})\n         return func\n     return matcher\n \ndiff --git a/opsdroid/parsers/crontab.py b/opsdroid/parsers/crontab.py\n--- a/opsdroid/parsers/crontab.py\n+++ b/opsdroid/parsers/crontab.py\n@@ -1,9 +1,9 @@\n \"\"\"A helper function for parsing and executing crontab skills.\"\"\"\n \n-import logging\n import asyncio\n-from datetime import datetime\n+import logging\n \n+import arrow\n import pycron\n \n \n@@ -17,11 +17,17 @@\n     # halt the application. If a skill throws an exception it just doesn't\n     # give a response to the user, so an error response should be given.\n     while opsdroid.eventloop.is_running():\n-        await asyncio.sleep(60 - datetime.now().time().second)\n+        await asyncio.sleep(60 - arrow.now().time().second)\n         _LOGGER.debug(\"Running crontab skills\")\n         for skill in opsdroid.skills:\n-            if \"crontab\" in skill and pycron.is_now(skill[\"crontab\"]):\n-                try:\n-                    await skill[\"skill\"](opsdroid, skill[\"config\"], None)\n-                except Exception:\n-                    _LOGGER.exception(\"Exception when executing cron skill.\")\n+            if \"crontab\" in skill:\n+                if skill[\"timezone\"] is not None:\n+                    timezone = skill[\"timezone\"]\n+                else:\n+                    timezone = opsdroid.config.get(\"timezone\", \"UTC\")\n+                if pycron.is_now(skill[\"crontab\"], arrow.now(tz=timezone)):\n+                    try:\n+                        await skill[\"skill\"](opsdroid, skill[\"config\"], None)\n+                    except Exception:\n+                        _LOGGER.exception(\n+                            \"Exception when executing cron skill.\")\n", "issue": "Make crontab parser timezone aware\nThe crontab matcher should take a timezone as a kwarg. It should also be possible to set a global timezone in the config. Default should be UTC.\n", "before_files": [{"content": "\"\"\"Decorator functions to use when creating skill modules.\"\"\"\n\nimport logging\n\nfrom opsdroid.helper import get_opsdroid\nfrom opsdroid.web import Web\n\n\n_LOGGER = logging.getLogger(__name__)\n\n\ndef match_regex(regex):\n    \"\"\"Return regex match decorator.\"\"\"\n    def matcher(func):\n        \"\"\"Add decorated function to skills list for regex matching.\"\"\"\n        opsdroid = get_opsdroid()\n        opsdroid.skills.append({\"regex\": regex, \"skill\": func,\n                                \"config\":\n                                opsdroid.loader.current_import_config})\n        return func\n    return matcher\n\n\ndef match_apiai_action(action):\n    \"\"\"Return apiai action match decorator.\"\"\"\n    def matcher(func):\n        \"\"\"Add decorated function to skills list for apiai matching.\"\"\"\n        opsdroid = get_opsdroid()\n        opsdroid.skills.append({\"apiai_action\": action, \"skill\": func,\n                                \"config\":\n                                opsdroid.loader.current_import_config})\n        return func\n    return matcher\n\n\ndef match_apiai_intent(intent):\n    \"\"\"Return apiai intent match decorator.\"\"\"\n    def matcher(func):\n        \"\"\"Add decorated function to skills list for apiai matching.\"\"\"\n        opsdroid = get_opsdroid()\n        opsdroid.skills.append({\"apiai_intent\": intent, \"skill\": func,\n                                \"config\":\n                                opsdroid.loader.current_import_config})\n        return func\n    return matcher\n\n\ndef match_crontab(crontab):\n    \"\"\"Return crontab match decorator.\"\"\"\n    def matcher(func):\n        \"\"\"Add decorated function to skills list for crontab matching.\"\"\"\n        opsdroid = get_opsdroid()\n        opsdroid.skills.append({\"crontab\": crontab, \"skill\": func,\n                                \"config\":\n                                opsdroid.loader.current_import_config})\n        return func\n    return matcher\n\n\ndef match_webhook(webhook):\n    \"\"\"Return webhook match decorator.\"\"\"\n    def matcher(func):\n        \"\"\"Add decorated function to skills list for webhook matching.\"\"\"\n        opsdroid = get_opsdroid()\n        config = opsdroid.loader.current_import_config\n        opsdroid.skills.append({\"webhook\": webhook, \"skill\": func,\n                                \"config\": config})\n\n        async def wrapper(req, opsdroid=opsdroid, config=config):\n            \"\"\"Wrap up the aiohttp handler.\"\"\"\n            _LOGGER.info(\"Running skill %s via webhook\", webhook)\n            opsdroid.stats[\"webhooks_called\"] = \\\n                opsdroid.stats[\"webhooks_called\"] + 1\n            await func(opsdroid, config, req)\n            return Web.build_response(200, {\"called_skill\": webhook})\n\n        opsdroid.web_server.web_app.router.add_post(\n            \"/skill/{}/{}\".format(config[\"name\"], webhook), wrapper)\n        opsdroid.web_server.web_app.router.add_post(\n            \"/skill/{}/{}/\".format(config[\"name\"], webhook), wrapper)\n\n        return func\n    return matcher\n", "path": "opsdroid/matchers.py"}, {"content": "\"\"\"A helper function for parsing and executing crontab skills.\"\"\"\n\nimport logging\nimport asyncio\nfrom datetime import datetime\n\nimport pycron\n\n\n_LOGGER = logging.getLogger(__name__)\n\n\nasync def parse_crontab(opsdroid):\n    \"\"\"Parse all crontab skills against the current time.\"\"\"\n    # pylint: disable=broad-except\n    # We want to catch all exceptions coming from a skill module and not\n    # halt the application. If a skill throws an exception it just doesn't\n    # give a response to the user, so an error response should be given.\n    while opsdroid.eventloop.is_running():\n        await asyncio.sleep(60 - datetime.now().time().second)\n        _LOGGER.debug(\"Running crontab skills\")\n        for skill in opsdroid.skills:\n            if \"crontab\" in skill and pycron.is_now(skill[\"crontab\"]):\n                try:\n                    await skill[\"skill\"](opsdroid, skill[\"config\"], None)\n                except Exception:\n                    _LOGGER.exception(\"Exception when executing cron skill.\")\n", "path": "opsdroid/parsers/crontab.py"}], "after_files": [{"content": "\"\"\"Decorator functions to use when creating skill modules.\"\"\"\n\nimport logging\n\nfrom opsdroid.helper import get_opsdroid\nfrom opsdroid.web import Web\n\n\n_LOGGER = logging.getLogger(__name__)\n\n\ndef match_regex(regex):\n    \"\"\"Return regex match decorator.\"\"\"\n    def matcher(func):\n        \"\"\"Add decorated function to skills list for regex matching.\"\"\"\n        opsdroid = get_opsdroid()\n        opsdroid.skills.append({\"regex\": regex, \"skill\": func,\n                                \"config\":\n                                opsdroid.loader.current_import_config})\n        return func\n    return matcher\n\n\ndef match_apiai_action(action):\n    \"\"\"Return apiai action match decorator.\"\"\"\n    def matcher(func):\n        \"\"\"Add decorated function to skills list for apiai matching.\"\"\"\n        opsdroid = get_opsdroid()\n        opsdroid.skills.append({\"apiai_action\": action, \"skill\": func,\n                                \"config\":\n                                opsdroid.loader.current_import_config})\n        return func\n    return matcher\n\n\ndef match_apiai_intent(intent):\n    \"\"\"Return apiai intent match decorator.\"\"\"\n    def matcher(func):\n        \"\"\"Add decorated function to skills list for apiai matching.\"\"\"\n        opsdroid = get_opsdroid()\n        opsdroid.skills.append({\"apiai_intent\": intent, \"skill\": func,\n                                \"config\":\n                                opsdroid.loader.current_import_config})\n        return func\n    return matcher\n\n\ndef match_crontab(crontab, timezone=None):\n    \"\"\"Return crontab match decorator.\"\"\"\n    def matcher(func):\n        \"\"\"Add decorated function to skills list for crontab matching.\"\"\"\n        opsdroid = get_opsdroid()\n        config = opsdroid.loader.current_import_config\n        opsdroid.skills.append({\"crontab\": crontab, \"skill\": func,\n                                \"config\": config, \"timezone\": timezone})\n        return func\n    return matcher\n\n\ndef match_webhook(webhook):\n    \"\"\"Return webhook match decorator.\"\"\"\n    def matcher(func):\n        \"\"\"Add decorated function to skills list for webhook matching.\"\"\"\n        opsdroid = get_opsdroid()\n        config = opsdroid.loader.current_import_config\n        opsdroid.skills.append({\"webhook\": webhook, \"skill\": func,\n                                \"config\": config})\n\n        async def wrapper(req, opsdroid=opsdroid, config=config):\n            \"\"\"Wrap up the aiohttp handler.\"\"\"\n            _LOGGER.info(\"Running skill %s via webhook\", webhook)\n            opsdroid.stats[\"webhooks_called\"] = \\\n                opsdroid.stats[\"webhooks_called\"] + 1\n            await func(opsdroid, config, req)\n            return Web.build_response(200, {\"called_skill\": webhook})\n\n        opsdroid.web_server.web_app.router.add_post(\n            \"/skill/{}/{}\".format(config[\"name\"], webhook), wrapper)\n        opsdroid.web_server.web_app.router.add_post(\n            \"/skill/{}/{}/\".format(config[\"name\"], webhook), wrapper)\n\n        return func\n    return matcher\n", "path": "opsdroid/matchers.py"}, {"content": "\"\"\"A helper function for parsing and executing crontab skills.\"\"\"\n\nimport asyncio\nimport logging\n\nimport arrow\nimport pycron\n\n\n_LOGGER = logging.getLogger(__name__)\n\n\nasync def parse_crontab(opsdroid):\n    \"\"\"Parse all crontab skills against the current time.\"\"\"\n    # pylint: disable=broad-except\n    # We want to catch all exceptions coming from a skill module and not\n    # halt the application. If a skill throws an exception it just doesn't\n    # give a response to the user, so an error response should be given.\n    while opsdroid.eventloop.is_running():\n        await asyncio.sleep(60 - arrow.now().time().second)\n        _LOGGER.debug(\"Running crontab skills\")\n        for skill in opsdroid.skills:\n            if \"crontab\" in skill:\n                if skill[\"timezone\"] is not None:\n                    timezone = skill[\"timezone\"]\n                else:\n                    timezone = opsdroid.config.get(\"timezone\", \"UTC\")\n                if pycron.is_now(skill[\"crontab\"], arrow.now(tz=timezone)):\n                    try:\n                        await skill[\"skill\"](opsdroid, skill[\"config\"], None)\n                    except Exception:\n                        _LOGGER.exception(\n                            \"Exception when executing cron skill.\")\n", "path": "opsdroid/parsers/crontab.py"}]}