Repository: opsdroid/opsdroid

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.


Discussion:

Resolving patch:
diff --git a/opsdroid/matchers.py b/opsdroid/matchers.py
--- a/opsdroid/matchers.py
+++ b/opsdroid/matchers.py
@@ -45,14 +45,14 @@ def matcher(func):
     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 @@ async def parse_crontab(opsdroid):
     # 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.")


Test patch:
diff --git a/tests/test_parser_crontab.py b/tests/test_parser_crontab.py
--- a/tests/test_parser_crontab.py
+++ b/tests/test_parser_crontab.py
@@ -30,6 +30,18 @@ async def test_parse_crontab(self):
                 await parse_crontab(opsdroid)
                 self.assertTrue(mock_skill.called)
 
+    async def test_parse_crontab_timezone(self):
+        with OpsDroid() as opsdroid:
+            self.not_first_run_flag = True
+            opsdroid.eventloop.is_running = self.true_once
+            with amock.patch('asyncio.sleep'):
+                mock_skill = amock.CoroutineMock()
+                match_crontab("* * * * *",
+                              timezone="Europe/London")(mock_skill)
+
+                await parse_crontab(opsdroid)
+                self.assertTrue(mock_skill.called)
+
     async def test_parse_crontab_raises(self):
         with OpsDroid() as opsdroid:
             self.not_first_run_flag = True