We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Write unit tests for `bot/utils/time.py`
Write unit tests for [`bot/utils/time.py`](../blob/master/bot/utils/time.py). This file already has some unit tests, but they are written for `pytest`. The tests are currently located in [`tests/utils/test_time.py`](../blob/master/tests/utils/test_time.py), but should be moved to the appropriate location in the folder hierarchy, `tests/bot/utils/test_time.py` after they have been migrated to the `unittest` framework.

## Implementation details
Please make sure to read the general information in the [meta issue](553) and the [testing README](../blob/master/tests/README.md). We are aiming for a 100% [branch coverage](https://coverage.readthedocs.io/en/stable/branch.html) for this file, but if you think that is not possible, please discuss that in this issue.

## Additional information
If you want to work on this issue, **please make sure that you get assigned to it** by one of the core devs before starting to work on it. We would like to prevent the situation that multiple people are working on the same issue. To get assigned, leave a comment showing your interesting in tackling this issue.


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `bot/utils/time.py`
Content:
```
1 import asyncio
2 import datetime
3 from typing import Optional
4 
5 import dateutil.parser
6 from dateutil.relativedelta import relativedelta
7 
8 RFC1123_FORMAT = "%a, %d %b %Y %H:%M:%S GMT"
9 INFRACTION_FORMAT = "%Y-%m-%d %H:%M"
10 
11 
12 def _stringify_time_unit(value: int, unit: str) -> str:
13     """
14     Returns a string to represent a value and time unit, ensuring that it uses the right plural form of the unit.
15 
16     >>> _stringify_time_unit(1, "seconds")
17     "1 second"
18     >>> _stringify_time_unit(24, "hours")
19     "24 hours"
20     >>> _stringify_time_unit(0, "minutes")
21     "less than a minute"
22     """
23     if value == 1:
24         return f"{value} {unit[:-1]}"
25     elif value == 0:
26         return f"less than a {unit[:-1]}"
27     else:
28         return f"{value} {unit}"
29 
30 
31 def humanize_delta(delta: relativedelta, precision: str = "seconds", max_units: int = 6) -> str:
32     """
33     Returns a human-readable version of the relativedelta.
34 
35     precision specifies the smallest unit of time to include (e.g. "seconds", "minutes").
36     max_units specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).
37     """
38     if max_units <= 0:
39         raise ValueError("max_units must be positive")
40 
41     units = (
42         ("years", delta.years),
43         ("months", delta.months),
44         ("days", delta.days),
45         ("hours", delta.hours),
46         ("minutes", delta.minutes),
47         ("seconds", delta.seconds),
48     )
49 
50     # Add the time units that are >0, but stop at accuracy or max_units.
51     time_strings = []
52     unit_count = 0
53     for unit, value in units:
54         if value:
55             time_strings.append(_stringify_time_unit(value, unit))
56             unit_count += 1
57 
58         if unit == precision or unit_count >= max_units:
59             break
60 
61     # Add the 'and' between the last two units, if necessary
62     if len(time_strings) > 1:
63         time_strings[-1] = f"{time_strings[-2]} and {time_strings[-1]}"
64         del time_strings[-2]
65 
66     # If nothing has been found, just make the value 0 precision, e.g. `0 days`.
67     if not time_strings:
68         humanized = _stringify_time_unit(0, precision)
69     else:
70         humanized = ", ".join(time_strings)
71 
72     return humanized
73 
74 
75 def time_since(past_datetime: datetime.datetime, precision: str = "seconds", max_units: int = 6) -> str:
76     """
77     Takes a datetime and returns a human-readable string that describes how long ago that datetime was.
78 
79     precision specifies the smallest unit of time to include (e.g. "seconds", "minutes").
80     max_units specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).
81     """
82     now = datetime.datetime.utcnow()
83     delta = abs(relativedelta(now, past_datetime))
84 
85     humanized = humanize_delta(delta, precision, max_units)
86 
87     return f"{humanized} ago"
88 
89 
90 def parse_rfc1123(stamp: str) -> datetime.datetime:
91     """Parse RFC1123 time string into datetime."""
92     return datetime.datetime.strptime(stamp, RFC1123_FORMAT).replace(tzinfo=datetime.timezone.utc)
93 
94 
95 # Hey, this could actually be used in the off_topic_names and reddit cogs :)
96 async def wait_until(time: datetime.datetime, start: Optional[datetime.datetime] = None) -> None:
97     """
98     Wait until a given time.
99 
100     :param time: A datetime.datetime object to wait until.
101     :param start: The start from which to calculate the waiting duration. Defaults to UTC time.
102     """
103     delay = time - (start or datetime.datetime.utcnow())
104     delay_seconds = delay.total_seconds()
105 
106     # Incorporate a small delay so we don't rapid-fire the event due to time precision errors
107     if delay_seconds > 1.0:
108         await asyncio.sleep(delay_seconds)
109 
110 
111 def format_infraction(timestamp: str) -> str:
112     """Format an infraction timestamp to a more readable ISO 8601 format."""
113     return dateutil.parser.isoparse(timestamp).strftime(INFRACTION_FORMAT)
114 
115 
116 def format_infraction_with_duration(
117     expiry: Optional[str],
118     date_from: datetime.datetime = None,
119     max_units: int = 2
120 ) -> Optional[str]:
121     """
122     Format an infraction timestamp to a more readable ISO 8601 format WITH the duration.
123 
124     Returns a human-readable version of the duration between datetime.utcnow() and an expiry.
125     Unlike `humanize_delta`, this function will force the `precision` to be `seconds` by not passing it.
126     `max_units` specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).
127     By default, max_units is 2.
128     """
129     if not expiry:
130         return None
131 
132     date_from = date_from or datetime.datetime.utcnow()
133     date_to = dateutil.parser.isoparse(expiry).replace(tzinfo=None, microsecond=0)
134 
135     expiry_formatted = format_infraction(expiry)
136 
137     duration = humanize_delta(relativedelta(date_to, date_from), max_units=max_units)
138     duration_formatted = f" ({duration})" if duration else ''
139 
140     return f"{expiry_formatted}{duration_formatted}"
141 
142 
143 def until_expiration(expiry: Optional[str], max_units: int = 2) -> Optional[str]:
144     """
145     Get the remaining time until infraction's expiration, in a human-readable version of the relativedelta.
146 
147     Unlike `humanize_delta`, this function will force the `precision` to be `seconds` by not passing it.
148     `max_units` specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).
149     By default, max_units is 2.
150     """
151     if not expiry:
152         return None
153 
154     now = datetime.datetime.utcnow()
155     since = dateutil.parser.isoparse(expiry).replace(tzinfo=None, microsecond=0)
156 
157     if since < now:
158         return None
159 
160     return humanize_delta(relativedelta(since, now), max_units=max_units)
161
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/bot/utils/time.py b/bot/utils/time.py
--- a/bot/utils/time.py
+++ b/bot/utils/time.py
@@ -115,7 +115,7 @@
 
 def format_infraction_with_duration(
     expiry: Optional[str],
-    date_from: datetime.datetime = None,
+    date_from: Optional[datetime.datetime] = None,
     max_units: int = 2
 ) -> Optional[str]:
     """
@@ -140,10 +140,15 @@
     return f"{expiry_formatted}{duration_formatted}"
 
 
-def until_expiration(expiry: Optional[str], max_units: int = 2) -> Optional[str]:
+def until_expiration(
+    expiry: Optional[str],
+    now: Optional[datetime.datetime] = None,
+    max_units: int = 2
+) -> Optional[str]:
     """
     Get the remaining time until infraction's expiration, in a human-readable version of the relativedelta.
 
+    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.
@@ -151,7 +156,7 @@
     if not expiry:
         return None
 
-    now = datetime.datetime.utcnow()
+    now = now or datetime.datetime.utcnow()
     since = dateutil.parser.isoparse(expiry).replace(tzinfo=None, microsecond=0)
 
     if since < now:

{"golden_diff": "diff --git a/bot/utils/time.py b/bot/utils/time.py\n--- a/bot/utils/time.py\n+++ b/bot/utils/time.py\n@@ -115,7 +115,7 @@\n \n def format_infraction_with_duration(\n     expiry: Optional[str],\n-    date_from: datetime.datetime = None,\n+    date_from: Optional[datetime.datetime] = None,\n     max_units: int = 2\n ) -> Optional[str]:\n     \"\"\"\n@@ -140,10 +140,15 @@\n     return f\"{expiry_formatted}{duration_formatted}\"\n \n \n-def until_expiration(expiry: Optional[str], max_units: int = 2) -> Optional[str]:\n+def until_expiration(\n+    expiry: Optional[str],\n+    now: Optional[datetime.datetime] = None,\n+    max_units: int = 2\n+) -> Optional[str]:\n     \"\"\"\n     Get the remaining time until infraction's expiration, in a human-readable version of the relativedelta.\n \n+    Returns a human-readable version of the remaining duration between datetime.utcnow() and an expiry.\n     Unlike `humanize_delta`, this function will force the `precision` to be `seconds` by not passing it.\n     `max_units` specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).\n     By default, max_units is 2.\n@@ -151,7 +156,7 @@\n     if not expiry:\n         return None\n \n-    now = datetime.datetime.utcnow()\n+    now = now or datetime.datetime.utcnow()\n     since = dateutil.parser.isoparse(expiry).replace(tzinfo=None, microsecond=0)\n \n     if since < now:\n", "issue": "Write unit tests for `bot/utils/time.py`\nWrite unit tests for [`bot/utils/time.py`](../blob/master/bot/utils/time.py). This file already has some unit tests, but they are written for `pytest`. The tests are currently located in [`tests/utils/test_time.py`](../blob/master/tests/utils/test_time.py), but should be moved to the appropriate location in the folder hierarchy, `tests/bot/utils/test_time.py` after they have been migrated to the `unittest` framework.\r\n\r\n## Implementation details\r\nPlease make sure to read the general information in the [meta issue](553) and the [testing README](../blob/master/tests/README.md). We are aiming for a 100% [branch coverage](https://coverage.readthedocs.io/en/stable/branch.html) for this file, but if you think that is not possible, please discuss that in this issue.\r\n\r\n## Additional information\r\nIf you want to work on this issue, **please make sure that you get assigned to it** by one of the core devs before starting to work on it. We would like to prevent the situation that multiple people are working on the same issue. To get assigned, leave a comment showing your interesting in tackling this issue.\r\n\n", "before_files": [{"content": "import asyncio\nimport datetime\nfrom typing import Optional\n\nimport dateutil.parser\nfrom dateutil.relativedelta import relativedelta\n\nRFC1123_FORMAT = \"%a, %d %b %Y %H:%M:%S GMT\"\nINFRACTION_FORMAT = \"%Y-%m-%d %H:%M\"\n\n\ndef _stringify_time_unit(value: int, unit: str) -> str:\n    \"\"\"\n    Returns a string to represent a value and time unit, ensuring that it uses the right plural form of the unit.\n\n    >>> _stringify_time_unit(1, \"seconds\")\n    \"1 second\"\n    >>> _stringify_time_unit(24, \"hours\")\n    \"24 hours\"\n    >>> _stringify_time_unit(0, \"minutes\")\n    \"less than a minute\"\n    \"\"\"\n    if value == 1:\n        return f\"{value} {unit[:-1]}\"\n    elif value == 0:\n        return f\"less than a {unit[:-1]}\"\n    else:\n        return f\"{value} {unit}\"\n\n\ndef humanize_delta(delta: relativedelta, precision: str = \"seconds\", max_units: int = 6) -> str:\n    \"\"\"\n    Returns a human-readable version of the relativedelta.\n\n    precision specifies the smallest unit of time to include (e.g. \"seconds\", \"minutes\").\n    max_units specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).\n    \"\"\"\n    if max_units <= 0:\n        raise ValueError(\"max_units must be positive\")\n\n    units = (\n        (\"years\", delta.years),\n        (\"months\", delta.months),\n        (\"days\", delta.days),\n        (\"hours\", delta.hours),\n        (\"minutes\", delta.minutes),\n        (\"seconds\", delta.seconds),\n    )\n\n    # Add the time units that are >0, but stop at accuracy or max_units.\n    time_strings = []\n    unit_count = 0\n    for unit, value in units:\n        if value:\n            time_strings.append(_stringify_time_unit(value, unit))\n            unit_count += 1\n\n        if unit == precision or unit_count >= max_units:\n            break\n\n    # Add the 'and' between the last two units, if necessary\n    if len(time_strings) > 1:\n        time_strings[-1] = f\"{time_strings[-2]} and {time_strings[-1]}\"\n        del time_strings[-2]\n\n    # If nothing has been found, just make the value 0 precision, e.g. `0 days`.\n    if not time_strings:\n        humanized = _stringify_time_unit(0, precision)\n    else:\n        humanized = \", \".join(time_strings)\n\n    return humanized\n\n\ndef time_since(past_datetime: datetime.datetime, precision: str = \"seconds\", max_units: int = 6) -> str:\n    \"\"\"\n    Takes a datetime and returns a human-readable string that describes how long ago that datetime was.\n\n    precision specifies the smallest unit of time to include (e.g. \"seconds\", \"minutes\").\n    max_units specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).\n    \"\"\"\n    now = datetime.datetime.utcnow()\n    delta = abs(relativedelta(now, past_datetime))\n\n    humanized = humanize_delta(delta, precision, max_units)\n\n    return f\"{humanized} ago\"\n\n\ndef parse_rfc1123(stamp: str) -> datetime.datetime:\n    \"\"\"Parse RFC1123 time string into datetime.\"\"\"\n    return datetime.datetime.strptime(stamp, RFC1123_FORMAT).replace(tzinfo=datetime.timezone.utc)\n\n\n# Hey, this could actually be used in the off_topic_names and reddit cogs :)\nasync def wait_until(time: datetime.datetime, start: Optional[datetime.datetime] = None) -> None:\n    \"\"\"\n    Wait until a given time.\n\n    :param time: A datetime.datetime object to wait until.\n    :param start: The start from which to calculate the waiting duration. Defaults to UTC time.\n    \"\"\"\n    delay = time - (start or datetime.datetime.utcnow())\n    delay_seconds = delay.total_seconds()\n\n    # Incorporate a small delay so we don't rapid-fire the event due to time precision errors\n    if delay_seconds > 1.0:\n        await asyncio.sleep(delay_seconds)\n\n\ndef format_infraction(timestamp: str) -> str:\n    \"\"\"Format an infraction timestamp to a more readable ISO 8601 format.\"\"\"\n    return dateutil.parser.isoparse(timestamp).strftime(INFRACTION_FORMAT)\n\n\ndef format_infraction_with_duration(\n    expiry: Optional[str],\n    date_from: datetime.datetime = None,\n    max_units: int = 2\n) -> Optional[str]:\n    \"\"\"\n    Format an infraction timestamp to a more readable ISO 8601 format WITH the duration.\n\n    Returns a human-readable version of the duration between datetime.utcnow() and an expiry.\n    Unlike `humanize_delta`, this function will force the `precision` to be `seconds` by not passing it.\n    `max_units` specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).\n    By default, max_units is 2.\n    \"\"\"\n    if not expiry:\n        return None\n\n    date_from = date_from or datetime.datetime.utcnow()\n    date_to = dateutil.parser.isoparse(expiry).replace(tzinfo=None, microsecond=0)\n\n    expiry_formatted = format_infraction(expiry)\n\n    duration = humanize_delta(relativedelta(date_to, date_from), max_units=max_units)\n    duration_formatted = f\" ({duration})\" if duration else ''\n\n    return f\"{expiry_formatted}{duration_formatted}\"\n\n\ndef until_expiration(expiry: Optional[str], max_units: int = 2) -> Optional[str]:\n    \"\"\"\n    Get the remaining time until infraction's expiration, in a human-readable version of the relativedelta.\n\n    Unlike `humanize_delta`, this function will force the `precision` to be `seconds` by not passing it.\n    `max_units` specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).\n    By default, max_units is 2.\n    \"\"\"\n    if not expiry:\n        return None\n\n    now = datetime.datetime.utcnow()\n    since = dateutil.parser.isoparse(expiry).replace(tzinfo=None, microsecond=0)\n\n    if since < now:\n        return None\n\n    return humanize_delta(relativedelta(since, now), max_units=max_units)\n", "path": "bot/utils/time.py"}], "after_files": [{"content": "import asyncio\nimport datetime\nfrom typing import Optional\n\nimport dateutil.parser\nfrom dateutil.relativedelta import relativedelta\n\nRFC1123_FORMAT = \"%a, %d %b %Y %H:%M:%S GMT\"\nINFRACTION_FORMAT = \"%Y-%m-%d %H:%M\"\n\n\ndef _stringify_time_unit(value: int, unit: str) -> str:\n    \"\"\"\n    Returns a string to represent a value and time unit, ensuring that it uses the right plural form of the unit.\n\n    >>> _stringify_time_unit(1, \"seconds\")\n    \"1 second\"\n    >>> _stringify_time_unit(24, \"hours\")\n    \"24 hours\"\n    >>> _stringify_time_unit(0, \"minutes\")\n    \"less than a minute\"\n    \"\"\"\n    if value == 1:\n        return f\"{value} {unit[:-1]}\"\n    elif value == 0:\n        return f\"less than a {unit[:-1]}\"\n    else:\n        return f\"{value} {unit}\"\n\n\ndef humanize_delta(delta: relativedelta, precision: str = \"seconds\", max_units: int = 6) -> str:\n    \"\"\"\n    Returns a human-readable version of the relativedelta.\n\n    precision specifies the smallest unit of time to include (e.g. \"seconds\", \"minutes\").\n    max_units specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).\n    \"\"\"\n    if max_units <= 0:\n        raise ValueError(\"max_units must be positive\")\n\n    units = (\n        (\"years\", delta.years),\n        (\"months\", delta.months),\n        (\"days\", delta.days),\n        (\"hours\", delta.hours),\n        (\"minutes\", delta.minutes),\n        (\"seconds\", delta.seconds),\n    )\n\n    # Add the time units that are >0, but stop at accuracy or max_units.\n    time_strings = []\n    unit_count = 0\n    for unit, value in units:\n        if value:\n            time_strings.append(_stringify_time_unit(value, unit))\n            unit_count += 1\n\n        if unit == precision or unit_count >= max_units:\n            break\n\n    # Add the 'and' between the last two units, if necessary\n    if len(time_strings) > 1:\n        time_strings[-1] = f\"{time_strings[-2]} and {time_strings[-1]}\"\n        del time_strings[-2]\n\n    # If nothing has been found, just make the value 0 precision, e.g. `0 days`.\n    if not time_strings:\n        humanized = _stringify_time_unit(0, precision)\n    else:\n        humanized = \", \".join(time_strings)\n\n    return humanized\n\n\ndef time_since(past_datetime: datetime.datetime, precision: str = \"seconds\", max_units: int = 6) -> str:\n    \"\"\"\n    Takes a datetime and returns a human-readable string that describes how long ago that datetime was.\n\n    precision specifies the smallest unit of time to include (e.g. \"seconds\", \"minutes\").\n    max_units specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).\n    \"\"\"\n    now = datetime.datetime.utcnow()\n    delta = abs(relativedelta(now, past_datetime))\n\n    humanized = humanize_delta(delta, precision, max_units)\n\n    return f\"{humanized} ago\"\n\n\ndef parse_rfc1123(stamp: str) -> datetime.datetime:\n    \"\"\"Parse RFC1123 time string into datetime.\"\"\"\n    return datetime.datetime.strptime(stamp, RFC1123_FORMAT).replace(tzinfo=datetime.timezone.utc)\n\n\n# Hey, this could actually be used in the off_topic_names and reddit cogs :)\nasync def wait_until(time: datetime.datetime, start: Optional[datetime.datetime] = None) -> None:\n    \"\"\"\n    Wait until a given time.\n\n    :param time: A datetime.datetime object to wait until.\n    :param start: The start from which to calculate the waiting duration. Defaults to UTC time.\n    \"\"\"\n    delay = time - (start or datetime.datetime.utcnow())\n    delay_seconds = delay.total_seconds()\n\n    # Incorporate a small delay so we don't rapid-fire the event due to time precision errors\n    if delay_seconds > 1.0:\n        await asyncio.sleep(delay_seconds)\n\n\ndef format_infraction(timestamp: str) -> str:\n    \"\"\"Format an infraction timestamp to a more readable ISO 8601 format.\"\"\"\n    return dateutil.parser.isoparse(timestamp).strftime(INFRACTION_FORMAT)\n\n\ndef format_infraction_with_duration(\n    expiry: Optional[str],\n    date_from: Optional[datetime.datetime] = None,\n    max_units: int = 2\n) -> Optional[str]:\n    \"\"\"\n    Format an infraction timestamp to a more readable ISO 8601 format WITH the duration.\n\n    Returns a human-readable version of the duration between datetime.utcnow() and an expiry.\n    Unlike `humanize_delta`, this function will force the `precision` to be `seconds` by not passing it.\n    `max_units` specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).\n    By default, max_units is 2.\n    \"\"\"\n    if not expiry:\n        return None\n\n    date_from = date_from or datetime.datetime.utcnow()\n    date_to = dateutil.parser.isoparse(expiry).replace(tzinfo=None, microsecond=0)\n\n    expiry_formatted = format_infraction(expiry)\n\n    duration = humanize_delta(relativedelta(date_to, date_from), max_units=max_units)\n    duration_formatted = f\" ({duration})\" if duration else ''\n\n    return f\"{expiry_formatted}{duration_formatted}\"\n\n\ndef until_expiration(\n    expiry: Optional[str],\n    now: Optional[datetime.datetime] = None,\n    max_units: int = 2\n) -> Optional[str]:\n    \"\"\"\n    Get the remaining time until infraction's expiration, in a human-readable version of the relativedelta.\n\n    Returns a human-readable version of the remaining duration between datetime.utcnow() and an expiry.\n    Unlike `humanize_delta`, this function will force the `precision` to be `seconds` by not passing it.\n    `max_units` specifies the maximum number of units of time to include (e.g. 1 may include days but not hours).\n    By default, max_units is 2.\n    \"\"\"\n    if not expiry:\n        return None\n\n    now = now or datetime.datetime.utcnow()\n    since = dateutil.parser.isoparse(expiry).replace(tzinfo=None, microsecond=0)\n\n    if since < now:\n        return None\n\n    return humanize_delta(relativedelta(since, now), max_units=max_units)\n", "path": "bot/utils/time.py"}]}