Geography: Add 'localtime' command

This commit is contained in:
Valentin Lorentz 2021-11-09 23:10:55 +01:00
parent 36ade18319
commit 93a407a9ac
2 changed files with 111 additions and 29 deletions

View File

@ -30,7 +30,7 @@
import datetime import datetime
from supybot import utils, plugins, ircutils, callbacks from supybot import conf, utils, plugins, ircutils, callbacks
from supybot.commands import * from supybot.commands import *
from supybot.i18n import PluginInternationalization from supybot.i18n import PluginInternationalization
@ -40,6 +40,27 @@ from . import wikidata
_ = PluginInternationalization("Geography") _ = 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): class Geography(callbacks.Plugin):
"""Provides geography facts, such as timezones. """Provides geography facts, such as timezones.
@ -49,6 +70,38 @@ class Geography(callbacks.Plugin):
threaded = True threaded = True
@wrap(["text"])
def localtime(self, irc, msg, args, query):
"""<location name to search>
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"]) @wrap(["text"])
def timezone(self, irc, msg, args, query): def timezone(self, irc, msg, args, query):
"""<location name to search> """<location name to search>
@ -68,24 +121,7 @@ class Geography(callbacks.Plugin):
continue continue
# Get the timezone object (and handle various errors) # Get the timezone object (and handle various errors)
try: timezone = timezone_from_uri(irc, uri)
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)
# Extract a human-friendly name, depending on the type of # Extract a human-friendly name, depending on the type of
# the timezone object: # the timezone object:

View File

@ -29,6 +29,7 @@
### ###
import datetime import datetime
import functools
import contextlib import contextlib
from unittest import skipIf from unittest import skipIf
from unittest.mock import patch from unittest.mock import patch
@ -50,31 +51,38 @@ from . import wikidata
from . import nominatim 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",) plugins = ("Geography",)
@skipIf(not pytz, "pytz is not available") @skipIf(not pytz, "pytz is not available")
@patch.object(nominatim, "search_osmids", return_value=[42]) @mock
@patch.object(wikidata, "uri_from_osmid", return_value="foo") def testTimezonePytz(self):
def testTimezonePytz(self, _, __):
tz = pytz.timezone("Europe/Paris") tz = pytz.timezone("Europe/Paris")
with patch.object(wikidata, "timezone_from_uri", return_value=tz): with patch.object(wikidata, "timezone_from_uri", return_value=tz):
self.assertResponse("timezone Foo Bar", "Europe/Paris") self.assertResponse("timezone Foo Bar", "Europe/Paris")
@skipIf(not zoneinfo, "Python is older than 3.9") @skipIf(not zoneinfo, "Python is older than 3.9")
@patch.object(nominatim, "search_osmids", return_value=[42]) @mock
@patch.object(wikidata, "uri_from_osmid", return_value="foo") def testTimezoneZoneinfo(self):
def testTimezoneZoneinfo(self, _, __):
tz = zoneinfo.ZoneInfo("Europe/Paris") tz = zoneinfo.ZoneInfo("Europe/Paris")
with patch.object(wikidata, "timezone_from_uri", return_value=tz): with patch.object(wikidata, "timezone_from_uri", return_value=tz):
self.assertResponse("timezone Foo Bar", "Europe/Paris") self.assertResponse("timezone Foo Bar", "Europe/Paris")
@skipIf(not zoneinfo, "Python is older than 3.9") @skipIf(not zoneinfo, "Python is older than 3.9")
@patch.object(nominatim, "search_osmids", return_value=[42]) @mock
@patch.object(wikidata, "uri_from_osmid", return_value="foo") def testTimezoneAbsolute(self):
def testTimezoneAbsolute(self, _, __):
tz = datetime.timezone(datetime.timedelta(hours=4)) tz = datetime.timezone(datetime.timedelta(hours=4))
with patch.object(wikidata, "timezone_from_uri", return_value=tz): 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") 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): class GeographyWikidataTestCase(SupyTestCase):
@skipIf(not network, "Network test") @skipIf(not network, "Network test")
def testOsmidToTimezone(self): def testOsmidToTimezone(self):