About time#
Your plugin may want to display dates and times in messages. For that, you can
always count on the datetime
built-in module. However, what if you would
like to respect the date-format for a given user or a given channel? Functions
of sopel.tools.time
can help you with that:
get_timezone()
will fetch the right timezone for youformat_time()
will format your aware datetime for you
Here is a full example of that, adapted from the built-in .t
command:
import datetime
from sopel import plugin
from sopel.tools.time import format_time, get_timezone
@plugin.command('.t')
@plugin.require_chanmsg
def my_command(bot, trigger):
"""Give time in a channel."""
time = datetime.datetime.now(datetime.timezone.utc)
timezone = get_timezone(
bot.db,
bot.settings,
nick=trigger.nick,
channel=trigger.sender,
)
formatted_time = format_time(
bot.db,
bot.settings,
timezone,
trigger.nick,
trigger.sender,
time,
)
bot.say(formatted_time)
Getting the time#
As mentioned earlier, Sopel relies on a Python built-in module:
datetime
. This module allows you to get the current time like this:
>>> import datetime
>>> datetime.datetime.now()
datetime.datetime(2021, 7, 26, 18, 7, 13, 491786)
>>> datetime.datetime.utcnow()
datetime.datetime(2021, 7, 26, 16, 7, 16, 496404)
As you can see at the moment of writing this documentation, there was a 2h
offset between the local time and UTC. To properly manage timezones and UTC
offsets, it is best to rely on pytz
, and work with UTC only:
>>> import pytz
>>> pytz.UTC.localize(datetime.datetime.utcnow())
datetime.datetime(2021, 7, 26, 16, 7, 19, 321828, tzinfo=<UTC>)
This way, you’ll always have an aware datetime to work with (store, compare, manipulate, etc.) that doesn’t depend on your local time, and you’ll only convert it to the proper timezone when you need to.
Note
You should always work with aware datetime objects. It’s also easier to always work with UTC+0 datetime as input and in storage, then convert to another timezone when displaying time to a user.
Getting the timezone#
Sopel uses pytz
to handle timezones to manipulate aware datetimes, i.e.
datetimes with timezone-related information such as the UTC Offset and DST
(Daylight Saving Time) status. When using this library, getting a timezone
is straightforward (as long as you know the IANA name of said timezone):
>>> paris = pytz.timezone('Europe/Paris')
Then you can get a UTC datetime:
>>> now = pytz.UTC.localize(datetime.datetime.utcnow())
Which you can convert to your timezone, or to another timezone:
>>> now.astimezone(paris) # convert to timezone
datetime.datetime(
2021, 7, 26, 18, 7, 49, 27970,
tzinfo=<DstTzInfo 'Europe/Paris' CEST+2:00:00 DST>)
>>> chicago = pytz.timezone('America/Chicago')
>>> now.astimezone(chicago) # convert to a different timezone
datetime.datetime(
2021, 7, 26, 11, 7, 58, 610998,
tzinfo=<DstTzInfo 'America/Chicago' CDT-1 day, 19:00:00 DST>
To get the IANA timezone for a given user or channel, you should use the
get_timezone()
function:
>>> from sopel.tools.time import get_timezone
>>> # assuming bot is an instance of sopel.bot.Sopel
>>> custom_tz = get_timezone(
... bot.db, bot.settings,
... zone=None, nick='Nick', channel='#sopel',
... ) # should be something like "Europe/Paris"
>>> local_now = now.astimezone(pytz.timezone(custom_tz))
This function does all the heavy lifting of looking for the right timezone, as configured for a user, a channel, or the bot itself.
See also
The pytz library is used by Sopel to manipulate timezone for aware datetimes. You can always assume it is available for your plugin since Sopel depends on this library.
Format time#
So far, you have:
an aware datetime in UTC+0
the user (or channel) timezone
And you want to:
display the time properly formatted for a user/channel
Then you have arrived at the last step of your journey, thanks to the
format_time()
function:
>>> from sopel.tools.time import format_time
>>> format_time(
... bot.db, bot.settings,
... zone=custom_tz, nick='Nick', channel='#sopel', time=now,
... )
'2021-07-26 - 18:07:49 (Europe/Paris)'
And voilà! You now have a string formatted aware datetime that uses the format defined for a user/channel/the bot, and can now rest and enjoy your own time.
Best practices#
So far, you have learned how to get a time formatted with the preferred timezone
and format of a user: this is perfect for a command like .time
that
displays the time for a specific user in mind. However, other commands, like
URL previews, are not related to users, and they should use a different
strategy to figure out the right timezone and format.
User specific time#
A user specific time is when the time is displayed for a specific user: a direct message, a reminder, the user’s time, etc.
In that case, the recommended order to select the appropriate timezone and time format is:
user’s preferred ones
channel’s preferred ones
bot’s timezone and format (from configuration)
default timezone and format
This can be done with get_timezone()
and
format_time()
:
>>> custom_tz = get_timezone(
... bot.db, bot.settings, zone=None,
... nick=nick_name, channel=channel_name,
... )
>>> display_time = format_time(
... bot.db, bot.settings, zone=custom_tz,
... nick=nick_name, channel=channel_name, time=user_time,
... )
Other times#
When displaying a time that is not for a specific user, it doesn’t make sense to display time with the user’s preferred format. For example, a URL preview plugin is not displaying a user specific time.
In that case, the recommended order to select the appropriate timezone and time format is:
channel’s preferred ones
bot’s timezone and format (from configuration)
default timezone and format
This can be done with get_timezone()
and
format_time()
:
>>> custom_tz = get_timezone(
... bot.db, bot.settings, zone=None,
... channel=channel_name,
... )
>>> display_time = plugin_defined_format or format_time(
... bot.db, bot.settings, zone=custom_tz,
... channel=channel_name, time=plugin_time,
... )
Note the absence of the nick
parameter in that snippet.