diff --git a/plugins/Geography/plugin.py b/plugins/Geography/plugin.py index fca150e40..07c75122a 100644 --- a/plugins/Geography/plugin.py +++ b/plugins/Geography/plugin.py @@ -30,7 +30,7 @@ import datetime -from supybot import utils, plugins, ircutils, callbacks +from supybot import conf, utils, plugins, ircutils, callbacks from supybot.commands import * from supybot.i18n import PluginInternationalization @@ -40,6 +40,27 @@ from . import wikidata _ = PluginInternationalization("Geography") +def timezone_from_uri(irc, uri): + try: + return wikidata.timezone_from_uri(uri) + except utils.time.UnknownTimeZone as e: + irc.error( + format(_("Could not understand timezone: %s"), e.args[0]), + Raise=True, + ) + except utils.time.MissingTimezoneLibrary: + irc.error( + _( + "Timezone-related commands are not available. " + "Your administrator need to either upgrade Python to " + "version 3.9 or greater, or install pytz." + ), + Raise=True, + ) + except utils.time.TimezoneException as e: + irc.error(e.args[0], Raise=True) + + class Geography(callbacks.Plugin): """Provides geography facts, such as timezones. @@ -49,6 +70,38 @@ class Geography(callbacks.Plugin): threaded = True + @wrap(["text"]) + def localtime(self, irc, msg, args, query): + """ + + Returns the current used in the given location. For example, + the name could be "Paris" or "Paris, France". The response is + formatted according to supybot.reply.format.time + This uses data from Wikidata and Nominatim.""" + osmids = nominatim.search_osmids(query) + if not osmids: + irc.error(_("Could not find the location"), Raise=True) + + for osmid in osmids: + uri = wikidata.uri_from_osmid(osmid) + if not uri: + continue + + # Get the timezone object (and handle various errors) + timezone = timezone_from_uri(irc, uri) + + # Get the local time + now = datetime.datetime.now(tz=timezone) + + format_ = conf.supybot.reply.format.time.getSpecific( + channel=msg.channel, network=irc.network + )() + + # Return it + irc.reply(now.strftime(format_)) + + return + @wrap(["text"]) def timezone(self, irc, msg, args, query): """ @@ -68,24 +121,7 @@ class Geography(callbacks.Plugin): continue # Get the timezone object (and handle various errors) - try: - timezone = wikidata.timezone_from_uri(uri) - except utils.time.UnknownTimeZone as e: - irc.error( - format(_("Could not understand timezone: %s"), e.args[0]), - Raise=True, - ) - except utils.time.MissingTimezoneLibrary: - irc.error( - _( - "Timezone-related commands are not available. " - "Your administrator need to either upgrade Python to " - "version 3.9 or greater, or install pytz." - ), - Raise=True, - ) - except utils.time.TimezoneException as e: - irc.error(e.args[0], Raise=True) + timezone = timezone_from_uri(irc, uri) # Extract a human-friendly name, depending on the type of # the timezone object: diff --git a/plugins/Geography/test.py b/plugins/Geography/test.py index 035e424f2..7accfa44b 100644 --- a/plugins/Geography/test.py +++ b/plugins/Geography/test.py @@ -29,6 +29,7 @@ ### import datetime +import functools import contextlib from unittest import skipIf from unittest.mock import patch @@ -50,31 +51,38 @@ from . import wikidata from . import nominatim -class GeographyTestCase(PluginTestCase): +def mock(f): + @functools.wraps(f) + def newf(self): + with patch.object(wikidata, "uri_from_osmid", return_value="foo"): + with patch.object(nominatim, "search_osmids", return_value=[42]): + f(self) + + return newf + + +class GeographyTimezoneTestCase(PluginTestCase): plugins = ("Geography",) @skipIf(not pytz, "pytz is not available") - @patch.object(nominatim, "search_osmids", return_value=[42]) - @patch.object(wikidata, "uri_from_osmid", return_value="foo") - def testTimezonePytz(self, _, __): + @mock + def testTimezonePytz(self): tz = pytz.timezone("Europe/Paris") with patch.object(wikidata, "timezone_from_uri", return_value=tz): self.assertResponse("timezone Foo Bar", "Europe/Paris") @skipIf(not zoneinfo, "Python is older than 3.9") - @patch.object(nominatim, "search_osmids", return_value=[42]) - @patch.object(wikidata, "uri_from_osmid", return_value="foo") - def testTimezoneZoneinfo(self, _, __): + @mock + def testTimezoneZoneinfo(self): tz = zoneinfo.ZoneInfo("Europe/Paris") with patch.object(wikidata, "timezone_from_uri", return_value=tz): self.assertResponse("timezone Foo Bar", "Europe/Paris") @skipIf(not zoneinfo, "Python is older than 3.9") - @patch.object(nominatim, "search_osmids", return_value=[42]) - @patch.object(wikidata, "uri_from_osmid", return_value="foo") - def testTimezoneAbsolute(self, _, __): + @mock + def testTimezoneAbsolute(self): tz = datetime.timezone(datetime.timedelta(hours=4)) with patch.object(wikidata, "timezone_from_uri", return_value=tz): @@ -91,6 +99,44 @@ class GeographyTestCase(PluginTestCase): self.assertResponse("timezone Saint-Denis, La RĂ©union", "UTC+04:00") +class GeographyLocaltimeTestCase(PluginTestCase): + plugins = ("Geography",) + + @skipIf(not pytz, "pytz is not available") + @mock + def testLocaltimePytz(self): + tz = pytz.timezone("Europe/Paris") + + with patch.object(wikidata, "timezone_from_uri", return_value=tz): + self.assertRegexp("localtime Foo Bar", r".*\+0[12]00$") + + @skipIf(not zoneinfo, "Python is older than 3.9") + @mock + def testLocaltimeZoneinfo(self): + tz = zoneinfo.ZoneInfo("Europe/Paris") + + with patch.object(wikidata, "timezone_from_uri", return_value=tz): + self.assertRegexp("localtime Foo Bar", r".*\+0[12]00$") + + @skipIf(not zoneinfo, "Python is older than 3.9") + @mock + def testLocaltimeAbsolute(self): + tz = datetime.timezone(datetime.timedelta(hours=4)) + + with patch.object(wikidata, "timezone_from_uri", return_value=tz): + self.assertRegexp("localtime Foo Bar", r".*\+0400$") + + tz = datetime.timezone(datetime.timedelta(hours=4, minutes=30)) + + with patch.object(wikidata, "timezone_from_uri", return_value=tz): + self.assertRegexp("localtime Foo Bar", r".*\+0430$") + + @skipIf(not network, "Network test") + def testLocaltimeIntegration(self): + self.assertRegexp("localtime Metz, France", r".*\+0[12]00$") + self.assertRegexp("localtime Saint-Denis, La RĂ©union", r".*\+0400$") + + class GeographyWikidataTestCase(SupyTestCase): @skipIf(not network, "Network test") def testOsmidToTimezone(self):