sopel.tools.time#

Tools for getting and displaying the time.

New in version 5.3.

Changed in version 6.0: Moved from willie namespace to sopel namespace for project rename.

class sopel.tools.time.Duration#

Named tuple representation of a duration.

This can be used as a tuple as well as an object:

>>> d = Duration(minutes=12, seconds=34)
>>> d.minutes
12
>>> d.seconds
34
>>> years, months, days, hours, minutes, seconds = d
>>> (years, months, days, hours, minutes, seconds)
(0, 0, 0, 0, 12, 34)
days: int#

Days spent.

hours: int#

Hours spent.

minutes: int#

Minutes spent.

months: int#

Months spent.

seconds: int#

Seconds spent.

years: int#

Years spent.

sopel.tools.time.format_time(
db: SopelDB | None = None,
config: Config | None = None,
zone: str | None = None,
nick: str | None = None,
channel: str | None = None,
time: datetime.datetime | None = None,
) str#

Return a formatted string of the given time in the given zone.

Parameters:
  • db – bot database object (optional)

  • config – bot config object (optional)

  • zone – name of timezone to use for output (optional)

  • nick – nick whose time format to use, if set (optional)

  • channel – channel whose time format to use, if set (optional)

  • time – the time value to format (optional)

time, if given, should be a ~datetime.datetime object, and will be treated as being in the UTC timezone if it is naïve. If time is not given, the current time will be used.

If zone is given it must be present in the IANA Time Zone Database; get_timezone can be helpful for this. If zone is not given, UTC will be assumed.

The format for the string is chosen in the following order:

  1. The format for the nick nick in db, if one is set and valid.

  2. The format for the channel channel in db, if one is set and valid.

  3. The default format in config, if one is set and valid.

  4. ISO-8601

If db is not given or is not set up, steps 1 and 2 are skipped. If config is not given, step 3 will be skipped.

sopel.tools.time.get_channel_timezone(db: SopelDB, channel: str) str | None#

Get a channel’s timezone from database.

Parameters:
  • db – Bot’s database handler (usually bot.db)

  • channel – IRC channel name

Returns:

the timezone associated with the channel

If a timezone cannot be found for channel, or if it is invalid, None will be returned.

New in version 7.0.

sopel.tools.time.get_nick_timezone(db: SopelDB, nick: str) str | None#

Get a nick’s timezone from database.

Parameters:
  • db – Bot’s database handler (usually bot.db)

  • nick – IRC nickname

Returns:

the timezone associated with the nick

If a timezone cannot be found for nick, or if it is invalid, None will be returned.

New in version 7.0.

sopel.tools.time.get_time_unit(
years: int = 0,
months: int = 0,
days: int = 0,
hours: int = 0,
minutes: int = 0,
seconds: int = 0,
) tuple[tuple[int, str], tuple[int, str], tuple[int, str], tuple[int, str], tuple[int, str], tuple[int, str]]#

Map a time in (y, m, d, h, min, s) to its labels.

Parameters:
  • years – number of years

  • months – number of months

  • days – number of days

  • hours – number of hours

  • minutes – number of minutes

  • seconds – number of seconds

Returns:

a tuple of 2-value tuples, each for a time amount and its label

This helper function takes a time split into years, months, days, hours, minutes, and seconds to return a tuple with the correct label for each unit. The label is pluralized according to whether the value is zero, one, or more than one:

>>> get_time_unit(days=1, hours=15, minutes=54, seconds=19)
(
    (0, 'years'),
    (0, 'months'),
    (1, 'day'),
    (15, 'hours'),
    (54, 'minutes'),
    (19, 'seconds'),
)

This function can be used with seconds_to_split():

>>> get_time_unit(*seconds_to_split(143659))
# ... same result as the example above

New in version 7.1.

Note

This function always returns a tuple with all time units, even when their amount is 0 (which is their default value).

sopel.tools.time.get_timezone(
db: SopelDB | None = None,
config: Config | None = None,
zone: str | None = None,
nick: str | None = None,
channel: str | None = None,
) str | None#

Find, and return, the appropriate timezone.

Parameters:
  • db – bot database object (optional)

  • config – bot config object (optional)

  • zone – preferred timezone name (optional)

  • nick – nick whose timezone to use, if set (optional)

  • channel – channel whose timezone to use, if set (optional)

Timezone is pulled in the following priority:

  1. zone, if it is valid

  2. The timezone for the channel or nick zone in db if one is set and valid.

  3. The timezone for the nick nick in db, if one is set and valid.

  4. The timezone for the channel channel in db, if one is set and valid.

  5. The default timezone in config, if one is set and valid.

If db is not given, or given but not set up, steps 2 and 3 will be skipped. If config is not given, step 4 will be skipped. If no step yields a valid timezone, None is returned.

Valid timezones are those present in the IANA Time Zone Database.

See also

The validate_timezone() function handles the validation and formatting of the timezone.

sopel.tools.time.seconds_to_human(
secs: timedelta | float | int,
granularity: int = 2,
) str#

Format timedelta as a human-readable relative time.

Parameters:
  • secs – time difference to format

  • granularity – number of time units to return (default to 2)

Inspiration for function structure from: https://gist.github.com/Highstaker/280a09591df4a5fb1363b0bbaf858f0d

Examples:

>>> seconds_to_human(65707200)
'2 years, 1 month ago'
>>> seconds_to_human(-17100)  # negative amount
'in 4 hours, 45 minutes'
>>> seconds_to_human(-709200)
'in 8 days, 5 hours'
>>> seconds_to_human(39441600, 1)  # 1 year + 3 months
'1 year ago'

This function can be used with a timedelta:

>>> from datetime import timedelta
>>> seconds_to_human(timedelta(days=42, seconds=278))
'1 month, 11 days ago'

The granularity argument controls how detailed the result is:

>>> seconds_to_human(3672)  # 2 by default
'1 hour, 1 minute ago'
>>> seconds_to_human(3672, granularity=3)
'1 hour, 1 minute, 12 seconds ago'
>>> seconds_to_human(3672, granularity=1)
'1 hour ago'

New in version 7.0.

sopel.tools.time.seconds_to_split(seconds: int) Duration#

Split an amount of seconds into years, months, days, etc.

Parameters:

seconds – amount of time in seconds

Returns:

the time split into a named tuple of years, months, days, hours, minutes, and seconds

Examples:

>>> seconds_to_split(7800)
Duration(years=0, months=0, days=0, hours=2, minutes=10, seconds=0)
>>> seconds_to_split(143659)
Duration(years=0, months=0, days=1, hours=15, minutes=54, seconds=19)

New in version 7.1.

Changed in version 8.0: This function returns a Duration named tuple.

sopel.tools.time.validate_format(tformat: str) str#

Validate a time format string.

Parameters:

tformat – the format string to validate

Returns:

the format string, if valid

Raises:

ValueError – when tformat is not a valid time format string

New in version 6.0.

sopel.tools.time.validate_timezone(zone: str | None) str#

Normalize and validate an IANA timezone name.

Parameters:

zone – in a strict or a human-friendly format

Returns:

the valid IANA timezone properly formatted

Raises:

ValueError – when zone is not a valid timezone (including empty string and None value)

Prior to checking timezones, two transformations are made to make the zone names more human-friendly:

  1. the string is split on ', ', the pieces reversed, and then joined with / (“New York, America” becomes “America/New York”)

  2. Remaining spaces are replaced with _

This means new york, america becomes America/New_York, and utc becomes UTC. In the majority of user-facing interactions, such case-insensitivity will be expected.

If the zone is not valid, ValueError will be raised.

New in version 6.0.

Changed in version 8.0: If zone is None, raises a ValueError as if it was an empty string or an invalid timezone instead of returning None.