aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/exts/info/information.py8
-rw-r--r--bot/exts/moderation/defcon.py6
-rw-r--r--bot/exts/moderation/infraction/_utils.py2
-rw-r--r--bot/exts/moderation/infraction/management.py18
-rw-r--r--bot/exts/moderation/stream.py13
-rw-r--r--bot/exts/recruitment/talentpool/_review.py9
-rw-r--r--bot/exts/utils/reminders.py27
-rw-r--r--bot/exts/utils/utils.py2
-rw-r--r--bot/utils/time.py85
-rw-r--r--tests/bot/exts/info/test_information.py8
-rw-r--r--tests/bot/exts/moderation/infraction/test_utils.py4
-rw-r--r--tests/bot/utils/test_time.py73
12 files changed, 126 insertions, 129 deletions
diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py
index 1b1243118..2c89d39e8 100644
--- a/bot/exts/info/information.py
+++ b/bot/exts/info/information.py
@@ -17,7 +17,7 @@ from bot.decorators import in_whitelist
from bot.pagination import LinePaginator
from bot.utils.channel import is_mod_channel, is_staff_channel
from bot.utils.checks import cooldown_with_role_bypass, has_no_roles_check, in_whitelist_check
-from bot.utils.time import humanize_delta, time_since
+from bot.utils.time import TimestampFormats, discord_timestamp, humanize_delta
log = logging.getLogger(__name__)
@@ -154,7 +154,7 @@ class Information(Cog):
"""Returns an embed full of server information."""
embed = Embed(colour=Colour.blurple(), title="Server Information")
- created = time_since(ctx.guild.created_at, precision="days")
+ created = discord_timestamp(ctx.guild.created_at, TimestampFormats.RELATIVE)
region = ctx.guild.region
num_roles = len(ctx.guild.roles) - 1 # Exclude @everyone
@@ -224,7 +224,7 @@ class Information(Cog):
"""Creates an embed containing information on the `user`."""
on_server = bool(ctx.guild.get_member(user.id))
- created = time_since(user.created_at, max_units=3)
+ created = discord_timestamp(user.created_at, TimestampFormats.RELATIVE)
name = str(user)
if on_server and user.nick:
@@ -242,7 +242,7 @@ class Information(Cog):
badges.append(emoji)
if on_server:
- joined = time_since(user.joined_at, max_units=3)
+ joined = discord_timestamp(user.joined_at, TimestampFormats.RELATIVE)
roles = ", ".join(role.mention for role in user.roles[1:])
membership = {"Joined": joined, "Verified": not user.pending, "Roles": roles or None}
if not is_mod_channel(ctx.channel):
diff --git a/bot/exts/moderation/defcon.py b/bot/exts/moderation/defcon.py
index dfb1afd19..9801d45ad 100644
--- a/bot/exts/moderation/defcon.py
+++ b/bot/exts/moderation/defcon.py
@@ -19,7 +19,9 @@ from bot.converters import DurationDelta, Expiry
from bot.exts.moderation.modlog import ModLog
from bot.utils.messages import format_user
from bot.utils.scheduling import Scheduler
-from bot.utils.time import humanize_delta, parse_duration_string, relativedelta_to_timedelta
+from bot.utils.time import (
+ TimestampFormats, discord_timestamp, humanize_delta, parse_duration_string, relativedelta_to_timedelta
+)
log = logging.getLogger(__name__)
@@ -150,7 +152,7 @@ class Defcon(Cog):
colour=Colour.blurple(), title="DEFCON Status",
description=f"""
**Threshold:** {humanize_delta(self.threshold) if self.threshold else "-"}
- **Expires in:** {humanize_delta(relativedelta(self.expiry, datetime.utcnow())) if self.expiry else "-"}
+ **Expires:** {discord_timestamp(self.expiry, TimestampFormats.RELATIVE) if self.expiry else "-"}
**Verification level:** {ctx.guild.verification_level.name}
"""
)
diff --git a/bot/exts/moderation/infraction/_utils.py b/bot/exts/moderation/infraction/_utils.py
index cfb238fa3..92e0596df 100644
--- a/bot/exts/moderation/infraction/_utils.py
+++ b/bot/exts/moderation/infraction/_utils.py
@@ -164,7 +164,7 @@ async def notify_infraction(
text = INFRACTION_DESCRIPTION_TEMPLATE.format(
type=infr_type.title(),
- expires=f"{expires_at} UTC" if expires_at else "N/A",
+ expires=expires_at or "N/A",
reason=reason or "No reason provided."
)
diff --git a/bot/exts/moderation/infraction/management.py b/bot/exts/moderation/infraction/management.py
index b3783cd60..4b0cb78a5 100644
--- a/bot/exts/moderation/infraction/management.py
+++ b/bot/exts/moderation/infraction/management.py
@@ -3,7 +3,9 @@ import textwrap
import typing as t
from datetime import datetime
+import dateutil.parser
import discord
+from dateutil.relativedelta import relativedelta
from discord.ext import commands
from discord.ext.commands import Context
from discord.utils import escape_markdown
@@ -16,6 +18,7 @@ from bot.exts.moderation.modlog import ModLog
from bot.pagination import LinePaginator
from bot.utils import messages, time
from bot.utils.channel import is_mod_channel
+from bot.utils.time import humanize_delta, until_expiration
log = logging.getLogger(__name__)
@@ -164,8 +167,8 @@ class ModManagement(commands.Cog):
self.infractions_cog.schedule_expiration(new_infraction)
log_text += f"""
- Previous expiry: {infraction['expires_at'] or "Permanent"}
- New expiry: {new_infraction['expires_at'] or "Permanent"}
+ Previous expiry: {until_expiration(infraction['expires_at']) or "Permanent"}
+ New expiry: {until_expiration(new_infraction['expires_at']) or "Permanent"}
""".rstrip()
changes = ' & '.join(confirm_messages)
@@ -288,10 +291,11 @@ class ModManagement(commands.Cog):
remaining = "Inactive"
if expires_at is None:
- expires = "*Permanent*"
+ duration = "*Permanent*"
else:
- date_from = datetime.strptime(created, time.INFRACTION_FORMAT)
- expires = time.format_infraction_with_duration(expires_at, date_from)
+ date_from = datetime.fromtimestamp(float(time.DISCORD_TIMESTAMP_REGEX.match(created).group(1)))
+ date_to = dateutil.parser.isoparse(expires_at).replace(tzinfo=None)
+ duration = humanize_delta(relativedelta(date_to, date_from))
lines = textwrap.dedent(f"""
{"**===============**" if active else "==============="}
@@ -300,8 +304,8 @@ class ModManagement(commands.Cog):
Type: **{infraction["type"]}**
Shadow: {infraction["hidden"]}
Created: {created}
- Expires: {expires}
- Remaining: {remaining}
+ Expires: {remaining}
+ Duration: {duration}
Actor: <@{infraction["actor"]["id"]}>
ID: `{infraction["id"]}`
Reason: {infraction["reason"] or "*None*"}
diff --git a/bot/exts/moderation/stream.py b/bot/exts/moderation/stream.py
index fd856a7f4..07ee4099e 100644
--- a/bot/exts/moderation/stream.py
+++ b/bot/exts/moderation/stream.py
@@ -13,7 +13,7 @@ from bot.constants import Colours, Emojis, Guild, MODERATION_ROLES, Roles, STAFF
from bot.converters import Expiry
from bot.pagination import LinePaginator
from bot.utils.scheduling import Scheduler
-from bot.utils.time import format_infraction_with_duration
+from bot.utils.time import discord_timestamp, format_infraction_with_duration
log = logging.getLogger(__name__)
@@ -134,16 +134,7 @@ class Stream(commands.Cog):
await member.add_roles(discord.Object(Roles.video), reason="Temporary streaming access granted")
- # Use embed as embed timestamps do timezone conversions.
- embed = discord.Embed(
- description=f"{Emojis.check_mark} {member.mention} can now stream.",
- colour=Colours.soft_green
- )
- embed.set_footer(text=f"Streaming permission has been given to {member} until")
- embed.timestamp = duration
-
- # Mention in content as mentions in embeds don't ping
- await ctx.send(content=member.mention, embed=embed)
+ await ctx.send(f"{Emojis.check_mark} {member.mention} can now stream until {discord_timestamp(duration)}.")
# Convert here for nicer logging
revoke_time = format_infraction_with_duration(str(duration))
diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py
index c4c68dbc3..aebb401e0 100644
--- a/bot/exts/recruitment/talentpool/_review.py
+++ b/bot/exts/recruitment/talentpool/_review.py
@@ -10,7 +10,6 @@ from datetime import datetime, timedelta
from typing import List, Optional, Union
from dateutil.parser import isoparse
-from dateutil.relativedelta import relativedelta
from discord import Embed, Emoji, Member, Message, NoMoreItems, PartialMessage, TextChannel
from discord.ext.commands import Context
@@ -19,7 +18,7 @@ from bot.bot import Bot
from bot.constants import Channels, Colours, Emojis, Guild, Roles
from bot.utils.messages import count_unique_users_reaction, pin_no_system_message
from bot.utils.scheduling import Scheduler
-from bot.utils.time import get_time_delta, humanize_delta, time_since
+from bot.utils.time import get_time_delta, time_since
if typing.TYPE_CHECKING:
from bot.exts.recruitment.talentpool._cog import TalentPool
@@ -255,9 +254,9 @@ class Reviewer:
last_channel = user_activity["top_channel_activity"][-1]
channels += f", and {last_channel[1]} in {last_channel[0]}"
- time_on_server = humanize_delta(relativedelta(datetime.utcnow(), member.joined_at), max_units=2)
+ joined_at_formatted = time_since(member.join_at)
review = (
- f"{member.name} has been on the server for **{time_on_server}**"
+ f"{member.name} joined the server **{joined_at_formatted}**"
f" and has **{messages} messages**{channels}."
)
@@ -347,7 +346,7 @@ class Reviewer:
nomination_times = f"{num_entries} times" if num_entries > 1 else "once"
rejection_times = f"{len(history)} times" if len(history) > 1 else "once"
- end_time = time_since(isoparse(history[0]['ended_at']).replace(tzinfo=None), max_units=2)
+ end_time = time_since(isoparse(history[0]['ended_at']).replace(tzinfo=None))
review = (
f"They were nominated **{nomination_times}** before"
diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py
index 6c21920a1..c7ce8b9e9 100644
--- a/bot/exts/utils/reminders.py
+++ b/bot/exts/utils/reminders.py
@@ -3,12 +3,11 @@ import logging
import random
import textwrap
import typing as t
-from datetime import datetime, timedelta
+from datetime import datetime
from operator import itemgetter
import discord
from dateutil.parser import isoparse
-from dateutil.relativedelta import relativedelta
from discord.ext.commands import Cog, Context, Greedy, group
from bot.bot import Bot
@@ -19,7 +18,7 @@ from bot.utils.checks import has_any_role_check, has_no_roles_check
from bot.utils.lock import lock_arg
from bot.utils.messages import send_denial
from bot.utils.scheduling import Scheduler
-from bot.utils.time import humanize_delta
+from bot.utils.time import TimestampFormats, discord_timestamp, time_since
log = logging.getLogger(__name__)
@@ -62,8 +61,7 @@ class Reminders(Cog):
# If the reminder is already overdue ...
if remind_at < now:
- late = relativedelta(now, remind_at)
- await self.send_reminder(reminder, late)
+ await self.send_reminder(reminder, remind_at)
else:
self.schedule_reminder(reminder)
@@ -174,7 +172,7 @@ class Reminders(Cog):
self.schedule_reminder(reminder)
@lock_arg(LOCK_NAMESPACE, "reminder", itemgetter("id"), raise_error=True)
- async def send_reminder(self, reminder: dict, late: relativedelta = None) -> None:
+ async def send_reminder(self, reminder: dict, expected_time: datetime = None) -> None:
"""Send the reminder."""
is_valid, user, channel = self.ensure_valid_reminder(reminder)
if not is_valid:
@@ -188,16 +186,17 @@ class Reminders(Cog):
name="It has arrived!"
)
- embed.description = f"Here's your reminder: `{reminder['content']}`."
+ # Let's not use a codeblock to keep emojis and mentions working. Embeds are safe anyway.
+ embed.description = f"Here's your reminder: {reminder['content']}."
if reminder.get("jump_url"): # keep backward compatibility
embed.description += f"\n[Jump back to when you created the reminder]({reminder['jump_url']})"
- if late:
+ if expected_time:
embed.colour = discord.Colour.red()
embed.set_author(
icon_url=Icons.remind_red,
- name=f"Sorry it arrived {humanize_delta(late, max_units=2)} late!"
+ name=f"Sorry it should have arrived {time_since(expected_time)} !"
)
additional_mentions = ' '.join(
@@ -270,9 +269,7 @@ class Reminders(Cog):
}
)
- now = datetime.utcnow() - timedelta(seconds=1)
- humanized_delta = humanize_delta(relativedelta(expiration, now))
- mention_string = f"Your reminder will arrive in {humanized_delta}"
+ mention_string = f"Your reminder will arrive {discord_timestamp(expiration, TimestampFormats.RELATIVE)}"
if mentions:
mention_string += f" and will mention {len(mentions)} other(s)"
@@ -297,8 +294,6 @@ class Reminders(Cog):
params={'author__id': str(ctx.author.id)}
)
- now = datetime.utcnow()
-
# Make a list of tuples so it can be sorted by time.
reminders = sorted(
(
@@ -313,7 +308,7 @@ class Reminders(Cog):
for content, remind_at, id_, mentions in reminders:
# Parse and humanize the time, make it pretty :D
remind_datetime = isoparse(remind_at).replace(tzinfo=None)
- time = humanize_delta(relativedelta(remind_datetime, now))
+ time = discord_timestamp(remind_datetime, TimestampFormats.RELATIVE)
mentions = ", ".join(
# Both Role and User objects have the `name` attribute
@@ -322,7 +317,7 @@ class Reminders(Cog):
mention_string = f"\n**Mentions:** {mentions}" if mentions else ""
text = textwrap.dedent(f"""
- **Reminder #{id_}:** *expires in {time}* (ID: {id_}){mention_string}
+ **Reminder #{id_}:** *expires {time}* (ID: {id_}){mention_string}
{content}
""").strip()
diff --git a/bot/exts/utils/utils.py b/bot/exts/utils/utils.py
index 3b8564aee..2831e30cc 100644
--- a/bot/exts/utils/utils.py
+++ b/bot/exts/utils/utils.py
@@ -175,7 +175,7 @@ class Utils(Cog):
lines = []
for snowflake in snowflakes:
created_at = snowflake_time(snowflake)
- lines.append(f"**{snowflake}**\nCreated at {created_at} ({time_since(created_at, max_units=3)}).")
+ lines.append(f"**{snowflake}**\nCreated at {created_at} ({time_since(created_at)}).")
await LinePaginator.paginate(
lines,
diff --git a/bot/utils/time.py b/bot/utils/time.py
index d55a0e532..8cf7d623b 100644
--- a/bot/utils/time.py
+++ b/bot/utils/time.py
@@ -1,12 +1,13 @@
import datetime
import re
-from typing import Optional
+from enum import Enum
+from typing import Optional, Union
import dateutil.parser
from dateutil.relativedelta import relativedelta
RFC1123_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"
-INFRACTION_FORMAT = "%Y-%m-%d %H:%M"
+DISCORD_TIMESTAMP_REGEX = re.compile(r"<t:(\d+):f>")
_DURATION_REGEX = re.compile(
r"((?P<years>\d+?) ?(years|year|Y|y) ?)?"
@@ -19,6 +20,25 @@ _DURATION_REGEX = re.compile(
)
+ValidTimestamp = Union[int, datetime.datetime, datetime.date, datetime.timedelta, relativedelta]
+
+
+class TimestampFormats(Enum):
+ """
+ Represents the different formats possible for Discord timestamps.
+
+ Examples are given in epoch time.
+ """
+
+ DATE_TIME = "f" # January 1, 1970 1:00 AM
+ DAY_TIME = "F" # Thursday, January 1, 1970 1:00 AM
+ DATE_SHORT = "d" # 01/01/1970
+ DATE = "D" # January 1, 1970
+ TIME = "t" # 1:00 AM
+ TIME_SECONDS = "T" # 1:00:00 AM
+ RELATIVE = "R" # 52 years ago
+
+
def _stringify_time_unit(value: int, unit: str) -> str:
"""
Returns a string to represent a value and time unit, ensuring that it uses the right plural form of the unit.
@@ -40,6 +60,24 @@ def _stringify_time_unit(value: int, unit: str) -> str:
return f"{value} {unit}"
+def discord_timestamp(timestamp: ValidTimestamp, format: TimestampFormats = TimestampFormats.DATE_TIME) -> str:
+ """Create and format a Discord flavored markdown timestamp."""
+ if format not in TimestampFormats:
+ raise ValueError(f"Format can only be one of {', '.join(TimestampFormats.args)}, not {format}.")
+
+ # Convert each possible timestamp class to an integer.
+ if isinstance(timestamp, datetime.datetime):
+ timestamp = (timestamp.replace(tzinfo=None) - datetime.datetime.utcfromtimestamp(0)).total_seconds()
+ elif isinstance(timestamp, datetime.date):
+ timestamp = (timestamp - datetime.date.fromtimestamp(0)).total_seconds()
+ elif isinstance(timestamp, datetime.timedelta):
+ timestamp = timestamp.total_seconds()
+ elif isinstance(timestamp, relativedelta):
+ timestamp = timestamp.seconds
+
+ return f"<t:{int(timestamp)}:{format.value}>"
+
+
def humanize_delta(delta: relativedelta, precision: str = "seconds", max_units: int = 6) -> str:
"""
Returns a human-readable version of the relativedelta.
@@ -87,7 +125,7 @@ def humanize_delta(delta: relativedelta, precision: str = "seconds", max_units:
def get_time_delta(time_string: str) -> str:
"""Returns the time in human-readable time delta format."""
date_time = dateutil.parser.isoparse(time_string).replace(tzinfo=None)
- time_delta = time_since(date_time, precision="minutes", max_units=1)
+ time_delta = time_since(date_time)
return time_delta
@@ -123,19 +161,9 @@ def relativedelta_to_timedelta(delta: relativedelta) -> datetime.timedelta:
return utcnow + delta - utcnow
-def time_since(past_datetime: datetime.datetime, precision: str = "seconds", max_units: int = 6) -> str:
- """
- Takes a datetime and returns a human-readable string that describes how long ago that datetime was.
-
- precision specifies the smallest unit of time to include (e.g. "seconds", "minutes").
- max_units specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).
- """
- now = datetime.datetime.utcnow()
- delta = abs(relativedelta(now, past_datetime))
-
- humanized = humanize_delta(delta, precision, max_units)
-
- return f"{humanized} ago"
+def time_since(past_datetime: datetime.datetime) -> str:
+ """Takes a datetime and returns a discord timestamp that describes how long ago that datetime was."""
+ return discord_timestamp(past_datetime, TimestampFormats.RELATIVE)
def parse_rfc1123(stamp: str) -> datetime.datetime:
@@ -144,8 +172,8 @@ def parse_rfc1123(stamp: str) -> datetime.datetime:
def format_infraction(timestamp: str) -> str:
- """Format an infraction timestamp to a more readable ISO 8601 format."""
- return dateutil.parser.isoparse(timestamp).strftime(INFRACTION_FORMAT)
+ """Format an infraction timestamp to a discord timestamp."""
+ return discord_timestamp(dateutil.parser.isoparse(timestamp))
def format_infraction_with_duration(
@@ -155,11 +183,7 @@ def format_infraction_with_duration(
absolute: bool = True
) -> Optional[str]:
"""
- Return `date_to` formatted as a readable ISO-8601 with the humanized duration since `date_from`.
-
- `date_from` must be an ISO-8601 formatted timestamp. The duration is calculated as from
- `date_from` until `date_to` with a precision of seconds. If `date_from` is unspecified, the
- current time is used.
+ Return `date_to` formatted as a discord timestamp with the timestamp duration since `date_from`.
`max_units` specifies the maximum number of units of time to include in the duration. For
example, a value of 1 may include days but not hours.
@@ -186,25 +210,22 @@ def format_infraction_with_duration(
def until_expiration(
- expiry: Optional[str],
- now: Optional[datetime.datetime] = None,
- max_units: int = 2
+ expiry: Optional[str]
) -> Optional[str]:
"""
- Get the remaining time until infraction's expiration, in a human-readable version of the relativedelta.
+ Get the remaining time until infraction's expiration, in a discord timestamp.
Returns a human-readable version of the remaining duration between datetime.utcnow() and an expiry.
- Unlike `humanize_delta`, this function will force the `precision` to be `seconds` by not passing it.
- `max_units` specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).
- By default, max_units is 2.
+ Similar to time_since, except that this function doesn't error on a null input
+ and return null if the expiry is in the paste
"""
if not expiry:
return None
- now = now or datetime.datetime.utcnow()
+ now = datetime.datetime.utcnow()
since = dateutil.parser.isoparse(expiry).replace(tzinfo=None, microsecond=0)
if since < now:
return None
- return humanize_delta(relativedelta(since, now), max_units=max_units)
+ return discord_timestamp(since, TimestampFormats.RELATIVE)
diff --git a/tests/bot/exts/info/test_information.py b/tests/bot/exts/info/test_information.py
index 770660fe3..ced3a2449 100644
--- a/tests/bot/exts/info/test_information.py
+++ b/tests/bot/exts/info/test_information.py
@@ -347,7 +347,7 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase):
self.assertEqual(
textwrap.dedent(f"""
- Created: {"1 year ago"}
+ Created: {"<t:1:R>"}
Profile: {user.mention}
ID: {user.id}
""").strip(),
@@ -356,7 +356,7 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase):
self.assertEqual(
textwrap.dedent(f"""
- Joined: {"1 year ago"}
+ Joined: {"<t:1:R>"}
Verified: {"True"}
Roles: &Moderators
""").strip(),
@@ -379,7 +379,7 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase):
self.assertEqual(
textwrap.dedent(f"""
- Created: {"1 year ago"}
+ Created: {"<t:1:R>"}
Profile: {user.mention}
ID: {user.id}
""").strip(),
@@ -388,7 +388,7 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase):
self.assertEqual(
textwrap.dedent(f"""
- Joined: {"1 year ago"}
+ Joined: {"<t:1:R>"}
Roles: &Moderators
""").strip(),
embed.fields[1].value
diff --git a/tests/bot/exts/moderation/infraction/test_utils.py b/tests/bot/exts/moderation/infraction/test_utils.py
index c6ae76984..5f95ced9f 100644
--- a/tests/bot/exts/moderation/infraction/test_utils.py
+++ b/tests/bot/exts/moderation/infraction/test_utils.py
@@ -137,7 +137,7 @@ class ModerationUtilsTests(unittest.IsolatedAsyncioTestCase):
title=utils.INFRACTION_TITLE,
description=utils.INFRACTION_DESCRIPTION_TEMPLATE.format(
type="Ban",
- expires="2020-02-26 09:20 (23 hours and 59 minutes) UTC",
+ expires="2020-02-26 09:20 (23 hours and 59 minutes)",
reason="No reason provided."
),
colour=Colours.soft_red,
@@ -193,7 +193,7 @@ class ModerationUtilsTests(unittest.IsolatedAsyncioTestCase):
title=utils.INFRACTION_TITLE,
description=utils.INFRACTION_DESCRIPTION_TEMPLATE.format(
type="Mute",
- expires="2020-02-26 09:20 (23 hours and 59 minutes) UTC",
+ expires="2020-02-26 09:20 (23 hours and 59 minutes)",
reason="Test"
),
colour=Colours.soft_red,
diff --git a/tests/bot/utils/test_time.py b/tests/bot/utils/test_time.py
index 115ddfb0d..8edffd1c9 100644
--- a/tests/bot/utils/test_time.py
+++ b/tests/bot/utils/test_time.py
@@ -52,7 +52,7 @@ class TimeTests(unittest.TestCase):
def test_format_infraction(self):
"""Testing format_infraction."""
- self.assertEqual(time.format_infraction('2019-12-12T00:01:00Z'), '2019-12-12 00:01')
+ self.assertEqual(time.format_infraction('2019-12-12T00:01:00Z'), '<t:1576108860:f>')
def test_format_infraction_with_duration_none_expiry(self):
"""format_infraction_with_duration should work for None expiry."""
@@ -72,10 +72,10 @@ class TimeTests(unittest.TestCase):
def test_format_infraction_with_duration_custom_units(self):
"""format_infraction_with_duration should work for custom max_units."""
test_cases = (
- ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 5, 5), 6,
- '2019-12-12 00:01 (11 hours, 55 minutes and 55 seconds)'),
- ('2019-11-23T20:09:00Z', datetime(2019, 4, 25, 20, 15), 20,
- '2019-11-23 20:09 (6 months, 28 days, 23 hours and 54 minutes)')
+ ('3000-12-12T00:01:00Z', datetime(3000, 12, 11, 12, 5, 5), 6,
+ '<t:32533488060:f> (11 hours, 55 minutes and 55 seconds)'),
+ ('3000-11-23T20:09:00Z', datetime(3000, 4, 25, 20, 15), 20,
+ '<t:32531918940:f> (6 months, 28 days, 23 hours and 54 minutes)')
)
for expiry, date_from, max_units, expected in test_cases:
@@ -85,16 +85,16 @@ class TimeTests(unittest.TestCase):
def test_format_infraction_with_duration_normal_usage(self):
"""format_infraction_with_duration should work for normal usage, across various durations."""
test_cases = (
- ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 0, 5), 2, '2019-12-12 00:01 (12 hours and 55 seconds)'),
- ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 0, 5), 1, '2019-12-12 00:01 (12 hours)'),
- ('2019-12-12T00:00:00Z', datetime(2019, 12, 11, 23, 59), 2, '2019-12-12 00:00 (1 minute)'),
- ('2019-11-23T20:09:00Z', datetime(2019, 11, 15, 20, 15), 2, '2019-11-23 20:09 (7 days and 23 hours)'),
- ('2019-11-23T20:09:00Z', datetime(2019, 4, 25, 20, 15), 2, '2019-11-23 20:09 (6 months and 28 days)'),
- ('2019-11-23T20:58:00Z', datetime(2019, 11, 23, 20, 53), 2, '2019-11-23 20:58 (5 minutes)'),
- ('2019-11-24T00:00:00Z', datetime(2019, 11, 23, 23, 59, 0), 2, '2019-11-24 00:00 (1 minute)'),
- ('2019-11-23T23:59:00Z', datetime(2017, 7, 21, 23, 0), 2, '2019-11-23 23:59 (2 years and 4 months)'),
+ ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 0, 5), 2, '<t:1576108860:f> (12 hours and 55 seconds)'),
+ ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 0, 5), 1, '<t:1576108860:f> (12 hours)'),
+ ('2019-12-12T00:00:00Z', datetime(2019, 12, 11, 23, 59), 2, '<t:1576108800:f> (1 minute)'),
+ ('2019-11-23T20:09:00Z', datetime(2019, 11, 15, 20, 15), 2, '<t:1574539740:f> (7 days and 23 hours)'),
+ ('2019-11-23T20:09:00Z', datetime(2019, 4, 25, 20, 15), 2, '<t:1574539740:f> (6 months and 28 days)'),
+ ('2019-11-23T20:58:00Z', datetime(2019, 11, 23, 20, 53), 2, '<t:1574542680:f> (5 minutes)'),
+ ('2019-11-24T00:00:00Z', datetime(2019, 11, 23, 23, 59, 0), 2, '<t:1574553600:f> (1 minute)'),
+ ('2019-11-23T23:59:00Z', datetime(2017, 7, 21, 23, 0), 2, '<t:1574553540:f> (2 years and 4 months)'),
('2019-11-23T23:59:00Z', datetime(2019, 11, 23, 23, 49, 5), 2,
- '2019-11-23 23:59 (9 minutes and 55 seconds)'),
+ '<t:1574553540:f> (9 minutes and 55 seconds)'),
(None, datetime(2019, 11, 23, 23, 49, 5), 2, None),
)
@@ -104,45 +104,30 @@ class TimeTests(unittest.TestCase):
def test_until_expiration_with_duration_none_expiry(self):
"""until_expiration should work for None expiry."""
- test_cases = (
- (None, None, None, None),
-
- # To make sure that now and max_units are not touched
- (None, 'Why hello there!', None, None),
- (None, None, float('inf'), None),
- (None, 'Why hello there!', float('inf'), None),
- )
-
- for expiry, now, max_units, expected in test_cases:
- with self.subTest(expiry=expiry, now=now, max_units=max_units, expected=expected):
- self.assertEqual(time.until_expiration(expiry, now, max_units), expected)
+ self.assertEqual(time.until_expiration(None), None)
def test_until_expiration_with_duration_custom_units(self):
"""until_expiration should work for custom max_units."""
test_cases = (
- ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 5, 5), 6, '11 hours, 55 minutes and 55 seconds'),
- ('2019-11-23T20:09:00Z', datetime(2019, 4, 25, 20, 15), 20, '6 months, 28 days, 23 hours and 54 minutes')
+ ('3000-12-12T00:01:00Z', '<t:32533488060:R>'),
+ ('3000-11-23T20:09:00Z', '<t:32531918940:R>')
)
- for expiry, now, max_units, expected in test_cases:
- with self.subTest(expiry=expiry, now=now, max_units=max_units, expected=expected):
- self.assertEqual(time.until_expiration(expiry, now, max_units), expected)
+ for expiry, expected in test_cases:
+ with self.subTest(expiry=expiry, expected=expected):
+ self.assertEqual(time.until_expiration(expiry,), expected)
def test_until_expiration_normal_usage(self):
"""until_expiration should work for normal usage, across various durations."""
test_cases = (
- ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 0, 5), 2, '12 hours and 55 seconds'),
- ('2019-12-12T00:01:00Z', datetime(2019, 12, 11, 12, 0, 5), 1, '12 hours'),
- ('2019-12-12T00:00:00Z', datetime(2019, 12, 11, 23, 59), 2, '1 minute'),
- ('2019-11-23T20:09:00Z', datetime(2019, 11, 15, 20, 15), 2, '7 days and 23 hours'),
- ('2019-11-23T20:09:00Z', datetime(2019, 4, 25, 20, 15), 2, '6 months and 28 days'),
- ('2019-11-23T20:58:00Z', datetime(2019, 11, 23, 20, 53), 2, '5 minutes'),
- ('2019-11-24T00:00:00Z', datetime(2019, 11, 23, 23, 59, 0), 2, '1 minute'),
- ('2019-11-23T23:59:00Z', datetime(2017, 7, 21, 23, 0), 2, '2 years and 4 months'),
- ('2019-11-23T23:59:00Z', datetime(2019, 11, 23, 23, 49, 5), 2, '9 minutes and 55 seconds'),
- (None, datetime(2019, 11, 23, 23, 49, 5), 2, None),
+ ('3000-12-12T00:01:00Z', '<t:32533488060:R>'),
+ ('3000-12-12T00:01:00Z', '<t:32533488060:R>'),
+ ('3000-12-12T00:00:00Z', '<t:32533488000:R>'),
+ ('3000-11-23T20:09:00Z', '<t:32531918940:R>'),
+ ('3000-11-23T20:09:00Z', '<t:32531918940:R>'),
+ (None, None),
)
- for expiry, now, max_units, expected in test_cases:
- with self.subTest(expiry=expiry, now=now, max_units=max_units, expected=expected):
- self.assertEqual(time.until_expiration(expiry, now, max_units), expected)
+ for expiry, expected in test_cases:
+ with self.subTest(expiry=expiry, expected=expected):
+ self.assertEqual(time.until_expiration(expiry), expected)