We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
[BUG] validatorfuncs.duration() returns inaccurate datetime.timedelta
#### Describe the bug
`validatorfuncs.duration()` produces incorrect `datetime.timedelta`

#### To Reproduce
See https://github.com/evennia/evennia/pull/1967#pullrequestreview-301630347

```python
validatorfuncs.duration('1d 2s 3m 4h 5w 5y')
```


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `evennia/utils/validatorfuncs.py`
Content:
```
1 """
2 Contains all the validation functions.
3 
4 All validation functions must have a checker (probably a session) and entry arg.
5 
6 They can employ more paramters at your leisure.
7 
8 
9 """
10 
11 import re as _re
12 import pytz as _pytz
13 import datetime as _dt
14 from django.core.exceptions import ValidationError as _error
15 from django.core.validators import validate_email as _val_email
16 from evennia.utils.ansi import strip_ansi
17 from evennia.utils.utils import string_partial_matching as _partial
18 
19 _TZ_DICT = {str(tz): _pytz.timezone(tz) for tz in _pytz.common_timezones}
20 
21 
22 def text(entry, option_key="Text", **kwargs):
23     try:
24         return str(entry)
25     except Exception as err:
26         raise ValueError(f"Input could not be converted to text ({err})")
27 
28 
29 def color(entry, option_key="Color", **kwargs):
30     """
31     The color should be just a color character, so 'r' if red color is desired.
32     """
33     if not entry:
34         raise ValueError(f"Nothing entered for a {option_key}!")
35     test_str = strip_ansi(f"|{entry}|n")
36     if test_str:
37         raise ValueError(f"'{entry}' is not a valid {option_key}.")
38     return entry
39 
40 
41 def datetime(entry, option_key="Datetime", account=None, from_tz=None, **kwargs):
42     """
43     Process a datetime string in standard forms while accounting for the inputter's timezone.
44 
45     Args:
46         entry (str): A date string from a user.
47         option_key (str): Name to display this datetime as.
48         account (AccountDB): The Account performing this lookup. Unless from_tz is provided,
49             account's timezone will be used (if found) for local time and convert the results
50             to UTC.
51         from_tz (pytz): An instance of pytz from the user. If not provided, defaults to whatever
52             the Account uses. If neither one is provided, defaults to UTC.
53 
54     Returns:
55         datetime in utc.
56     """
57     if not entry:
58         raise ValueError(f"No {option_key} entered!")
59     if not from_tz:
60         from_tz = _pytz.UTC
61     utc = _pytz.UTC
62     now = _dt.datetime.utcnow().replace(tzinfo=utc)
63     cur_year = now.strftime("%Y")
64     split_time = entry.split(" ")
65     if len(split_time) == 3:
66         entry = f"{split_time[0]} {split_time[1]} {split_time[2]} {cur_year}"
67     elif len(split_time) == 4:
68         entry = f"{split_time[0]} {split_time[1]} {split_time[2]} {split_time[3]}"
69     else:
70         raise ValueError(
71             f"{option_key} must be entered in a 24-hour format such as: {now.strftime('%b %d %H:%M')}"
72         )
73     try:
74         local = _dt.datetime.strptime(entry, "%b %d %H:%M %Y")
75     except ValueError:
76         raise ValueError(
77             f"{option_key} must be entered in a 24-hour format such as: {now.strftime('%b %d %H:%M')}"
78         )
79     local_tz = from_tz.localize(local)
80     return local_tz.astimezone(utc)
81 
82 
83 def duration(entry, option_key="Duration", **kwargs):
84     """
85     Take a string and derive a datetime timedelta from it.
86 
87     Args:
88         entry (string): This is a string from user-input. The intended format is, for example: "5d 2w 90s" for
89                             'five days, two weeks, and ninety seconds.' Invalid sections are ignored.
90         option_key (str): Name to display this query as.
91 
92     Returns:
93         timedelta
94 
95     """
96     time_string = entry.lower().split(" ")
97     seconds = 0
98     minutes = 0
99     hours = 0
100     days = 0
101     weeks = 0
102 
103     for interval in time_string:
104         if _re.match(r"^[\d]+s$", interval):
105             seconds = +int(interval.rstrip("s"))
106         elif _re.match(r"^[\d]+m$", interval):
107             minutes = +int(interval.rstrip("m"))
108         elif _re.match(r"^[\d]+h$", interval):
109             hours = +int(interval.rstrip("h"))
110         elif _re.match(r"^[\d]+d$", interval):
111             days = +int(interval.rstrip("d"))
112         elif _re.match(r"^[\d]+w$", interval):
113             weeks = +int(interval.rstrip("w"))
114         elif _re.match(r"^[\d]+y$", interval):
115             days = +int(interval.rstrip("y")) * 365
116         else:
117             raise ValueError(f"Could not convert section '{interval}' to a {option_key}.")
118 
119     return _dt.timedelta(days, seconds, 0, 0, minutes, hours, weeks)
120 
121 
122 def future(entry, option_key="Future Datetime", from_tz=None, **kwargs):
123     time = datetime(entry, option_key, from_tz=from_tz)
124     if time < _dt.datetime.utcnow().replace(tzinfo=_dt.timezone.utc):
125         raise ValueError(f"That {option_key} is in the past! Must give a Future datetime!")
126     return time
127 
128 
129 def signed_integer(entry, option_key="Signed Integer", **kwargs):
130     if not entry:
131         raise ValueError(f"Must enter a whole number for {option_key}!")
132     try:
133         num = int(entry)
134     except ValueError:
135         raise ValueError(f"Could not convert '{entry}' to a whole number for {option_key}!")
136     return num
137 
138 
139 def positive_integer(entry, option_key="Positive Integer", **kwargs):
140     num = signed_integer(entry, option_key)
141     if not num >= 1:
142         raise ValueError(f"Must enter a whole number greater than 0 for {option_key}!")
143     return num
144 
145 
146 def unsigned_integer(entry, option_key="Unsigned Integer", **kwargs):
147     num = signed_integer(entry, option_key)
148     if not num >= 0:
149         raise ValueError(f"{option_key} must be a whole number greater than or equal to 0!")
150     return num
151 
152 
153 def boolean(entry, option_key="True/False", **kwargs):
154     """
155     Simplest check in computer logic, right? This will take user input to flick the switch on or off
156     Args:
157         entry (str): A value such as True, On, Enabled, Disabled, False, 0, or 1.
158         option_key (str): What kind of Boolean we are setting. What Option is this for?
159 
160     Returns:
161         Boolean
162     """
163     error = f"Must enter 0 (false) or 1 (true) for {option_key}. Also accepts True, False, On, Off, Yes, No, Enabled, and Disabled"
164     if not isinstance(entry, str):
165         raise ValueError(error)
166     entry = entry.upper()
167     if entry in ("1", "TRUE", "ON", "ENABLED", "ENABLE", "YES"):
168         return True
169     if entry in ("0", "FALSE", "OFF", "DISABLED", "DISABLE", "NO"):
170         return False
171     raise ValueError(error)
172 
173 
174 def timezone(entry, option_key="Timezone", **kwargs):
175     """
176     Takes user input as string, and partial matches a Timezone.
177 
178     Args:
179         entry (str): The name of the Timezone.
180         option_key (str): What this Timezone is used for.
181 
182     Returns:
183         A PYTZ timezone.
184     """
185     if not entry:
186         raise ValueError(f"No {option_key} entered!")
187     found = _partial(list(_TZ_DICT.keys()), entry, ret_index=False)
188     if len(found) > 1:
189         raise ValueError(
190             f"That matched: {', '.join(str(t) for t in found)}. Please be more specific!"
191         )
192     if found:
193         return _TZ_DICT[found[0]]
194     raise ValueError(f"Could not find timezone '{entry}' for {option_key}!")
195 
196 
197 def email(entry, option_key="Email Address", **kwargs):
198     if not entry:
199         raise ValueError("Email address field empty!")
200     try:
201         _val_email(str(entry))  # offloading the hard work to Django!
202     except _error:
203         raise ValueError(f"That isn't a valid {option_key}!")
204     return entry
205 
206 
207 def lock(entry, option_key="locks", access_options=None, **kwargs):
208     entry = entry.strip()
209     if not entry:
210         raise ValueError(f"No {option_key} entered to set!")
211     for locksetting in entry.split(";"):
212         access_type, lockfunc = locksetting.split(":", 1)
213         if not access_type:
214             raise ValueError("Must enter an access type!")
215         if access_options:
216             if access_type not in access_options:
217                 raise ValueError(f"Access type must be one of: {', '.join(access_options)}")
218         if not lockfunc:
219             raise ValueError("Lock func not entered.")
220     return entry
221
```
--- 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/evennia/utils/validatorfuncs.py b/evennia/utils/validatorfuncs.py
--- a/evennia/utils/validatorfuncs.py
+++ b/evennia/utils/validatorfuncs.py
@@ -102,17 +102,17 @@
 
     for interval in time_string:
         if _re.match(r"^[\d]+s$", interval):
-            seconds = +int(interval.rstrip("s"))
+            seconds += int(interval.rstrip("s"))
         elif _re.match(r"^[\d]+m$", interval):
-            minutes = +int(interval.rstrip("m"))
+            minutes += int(interval.rstrip("m"))
         elif _re.match(r"^[\d]+h$", interval):
-            hours = +int(interval.rstrip("h"))
+            hours += int(interval.rstrip("h"))
         elif _re.match(r"^[\d]+d$", interval):
-            days = +int(interval.rstrip("d"))
+            days += int(interval.rstrip("d"))
         elif _re.match(r"^[\d]+w$", interval):
-            weeks = +int(interval.rstrip("w"))
+            weeks += int(interval.rstrip("w"))
         elif _re.match(r"^[\d]+y$", interval):
-            days = +int(interval.rstrip("y")) * 365
+            days += int(interval.rstrip("y")) * 365
         else:
             raise ValueError(f"Could not convert section '{interval}' to a {option_key}.")

{"golden_diff": "diff --git a/evennia/utils/validatorfuncs.py b/evennia/utils/validatorfuncs.py\n--- a/evennia/utils/validatorfuncs.py\n+++ b/evennia/utils/validatorfuncs.py\n@@ -102,17 +102,17 @@\n \n     for interval in time_string:\n         if _re.match(r\"^[\\d]+s$\", interval):\n-            seconds = +int(interval.rstrip(\"s\"))\n+            seconds += int(interval.rstrip(\"s\"))\n         elif _re.match(r\"^[\\d]+m$\", interval):\n-            minutes = +int(interval.rstrip(\"m\"))\n+            minutes += int(interval.rstrip(\"m\"))\n         elif _re.match(r\"^[\\d]+h$\", interval):\n-            hours = +int(interval.rstrip(\"h\"))\n+            hours += int(interval.rstrip(\"h\"))\n         elif _re.match(r\"^[\\d]+d$\", interval):\n-            days = +int(interval.rstrip(\"d\"))\n+            days += int(interval.rstrip(\"d\"))\n         elif _re.match(r\"^[\\d]+w$\", interval):\n-            weeks = +int(interval.rstrip(\"w\"))\n+            weeks += int(interval.rstrip(\"w\"))\n         elif _re.match(r\"^[\\d]+y$\", interval):\n-            days = +int(interval.rstrip(\"y\")) * 365\n+            days += int(interval.rstrip(\"y\")) * 365\n         else:\n             raise ValueError(f\"Could not convert section '{interval}' to a {option_key}.\")\n", "issue": "[BUG] validatorfuncs.duration() returns inaccurate datetime.timedelta\n#### Describe the bug\r\n`validatorfuncs.duration()` produces incorrect `datetime.timedelta`\r\n\r\n#### To Reproduce\r\nSee https://github.com/evennia/evennia/pull/1967#pullrequestreview-301630347\r\n\r\n```python\r\nvalidatorfuncs.duration('1d 2s 3m 4h 5w 5y')\r\n```\r\n\n", "before_files": [{"content": "\"\"\"\nContains all the validation functions.\n\nAll validation functions must have a checker (probably a session) and entry arg.\n\nThey can employ more paramters at your leisure.\n\n\n\"\"\"\n\nimport re as _re\nimport pytz as _pytz\nimport datetime as _dt\nfrom django.core.exceptions import ValidationError as _error\nfrom django.core.validators import validate_email as _val_email\nfrom evennia.utils.ansi import strip_ansi\nfrom evennia.utils.utils import string_partial_matching as _partial\n\n_TZ_DICT = {str(tz): _pytz.timezone(tz) for tz in _pytz.common_timezones}\n\n\ndef text(entry, option_key=\"Text\", **kwargs):\n    try:\n        return str(entry)\n    except Exception as err:\n        raise ValueError(f\"Input could not be converted to text ({err})\")\n\n\ndef color(entry, option_key=\"Color\", **kwargs):\n    \"\"\"\n    The color should be just a color character, so 'r' if red color is desired.\n    \"\"\"\n    if not entry:\n        raise ValueError(f\"Nothing entered for a {option_key}!\")\n    test_str = strip_ansi(f\"|{entry}|n\")\n    if test_str:\n        raise ValueError(f\"'{entry}' is not a valid {option_key}.\")\n    return entry\n\n\ndef datetime(entry, option_key=\"Datetime\", account=None, from_tz=None, **kwargs):\n    \"\"\"\n    Process a datetime string in standard forms while accounting for the inputter's timezone.\n\n    Args:\n        entry (str): A date string from a user.\n        option_key (str): Name to display this datetime as.\n        account (AccountDB): The Account performing this lookup. Unless from_tz is provided,\n            account's timezone will be used (if found) for local time and convert the results\n            to UTC.\n        from_tz (pytz): An instance of pytz from the user. If not provided, defaults to whatever\n            the Account uses. If neither one is provided, defaults to UTC.\n\n    Returns:\n        datetime in utc.\n    \"\"\"\n    if not entry:\n        raise ValueError(f\"No {option_key} entered!\")\n    if not from_tz:\n        from_tz = _pytz.UTC\n    utc = _pytz.UTC\n    now = _dt.datetime.utcnow().replace(tzinfo=utc)\n    cur_year = now.strftime(\"%Y\")\n    split_time = entry.split(\" \")\n    if len(split_time) == 3:\n        entry = f\"{split_time[0]} {split_time[1]} {split_time[2]} {cur_year}\"\n    elif len(split_time) == 4:\n        entry = f\"{split_time[0]} {split_time[1]} {split_time[2]} {split_time[3]}\"\n    else:\n        raise ValueError(\n            f\"{option_key} must be entered in a 24-hour format such as: {now.strftime('%b %d %H:%M')}\"\n        )\n    try:\n        local = _dt.datetime.strptime(entry, \"%b %d %H:%M %Y\")\n    except ValueError:\n        raise ValueError(\n            f\"{option_key} must be entered in a 24-hour format such as: {now.strftime('%b %d %H:%M')}\"\n        )\n    local_tz = from_tz.localize(local)\n    return local_tz.astimezone(utc)\n\n\ndef duration(entry, option_key=\"Duration\", **kwargs):\n    \"\"\"\n    Take a string and derive a datetime timedelta from it.\n\n    Args:\n        entry (string): This is a string from user-input. The intended format is, for example: \"5d 2w 90s\" for\n                            'five days, two weeks, and ninety seconds.' Invalid sections are ignored.\n        option_key (str): Name to display this query as.\n\n    Returns:\n        timedelta\n\n    \"\"\"\n    time_string = entry.lower().split(\" \")\n    seconds = 0\n    minutes = 0\n    hours = 0\n    days = 0\n    weeks = 0\n\n    for interval in time_string:\n        if _re.match(r\"^[\\d]+s$\", interval):\n            seconds = +int(interval.rstrip(\"s\"))\n        elif _re.match(r\"^[\\d]+m$\", interval):\n            minutes = +int(interval.rstrip(\"m\"))\n        elif _re.match(r\"^[\\d]+h$\", interval):\n            hours = +int(interval.rstrip(\"h\"))\n        elif _re.match(r\"^[\\d]+d$\", interval):\n            days = +int(interval.rstrip(\"d\"))\n        elif _re.match(r\"^[\\d]+w$\", interval):\n            weeks = +int(interval.rstrip(\"w\"))\n        elif _re.match(r\"^[\\d]+y$\", interval):\n            days = +int(interval.rstrip(\"y\")) * 365\n        else:\n            raise ValueError(f\"Could not convert section '{interval}' to a {option_key}.\")\n\n    return _dt.timedelta(days, seconds, 0, 0, minutes, hours, weeks)\n\n\ndef future(entry, option_key=\"Future Datetime\", from_tz=None, **kwargs):\n    time = datetime(entry, option_key, from_tz=from_tz)\n    if time < _dt.datetime.utcnow().replace(tzinfo=_dt.timezone.utc):\n        raise ValueError(f\"That {option_key} is in the past! Must give a Future datetime!\")\n    return time\n\n\ndef signed_integer(entry, option_key=\"Signed Integer\", **kwargs):\n    if not entry:\n        raise ValueError(f\"Must enter a whole number for {option_key}!\")\n    try:\n        num = int(entry)\n    except ValueError:\n        raise ValueError(f\"Could not convert '{entry}' to a whole number for {option_key}!\")\n    return num\n\n\ndef positive_integer(entry, option_key=\"Positive Integer\", **kwargs):\n    num = signed_integer(entry, option_key)\n    if not num >= 1:\n        raise ValueError(f\"Must enter a whole number greater than 0 for {option_key}!\")\n    return num\n\n\ndef unsigned_integer(entry, option_key=\"Unsigned Integer\", **kwargs):\n    num = signed_integer(entry, option_key)\n    if not num >= 0:\n        raise ValueError(f\"{option_key} must be a whole number greater than or equal to 0!\")\n    return num\n\n\ndef boolean(entry, option_key=\"True/False\", **kwargs):\n    \"\"\"\n    Simplest check in computer logic, right? This will take user input to flick the switch on or off\n    Args:\n        entry (str): A value such as True, On, Enabled, Disabled, False, 0, or 1.\n        option_key (str): What kind of Boolean we are setting. What Option is this for?\n\n    Returns:\n        Boolean\n    \"\"\"\n    error = f\"Must enter 0 (false) or 1 (true) for {option_key}. Also accepts True, False, On, Off, Yes, No, Enabled, and Disabled\"\n    if not isinstance(entry, str):\n        raise ValueError(error)\n    entry = entry.upper()\n    if entry in (\"1\", \"TRUE\", \"ON\", \"ENABLED\", \"ENABLE\", \"YES\"):\n        return True\n    if entry in (\"0\", \"FALSE\", \"OFF\", \"DISABLED\", \"DISABLE\", \"NO\"):\n        return False\n    raise ValueError(error)\n\n\ndef timezone(entry, option_key=\"Timezone\", **kwargs):\n    \"\"\"\n    Takes user input as string, and partial matches a Timezone.\n\n    Args:\n        entry (str): The name of the Timezone.\n        option_key (str): What this Timezone is used for.\n\n    Returns:\n        A PYTZ timezone.\n    \"\"\"\n    if not entry:\n        raise ValueError(f\"No {option_key} entered!\")\n    found = _partial(list(_TZ_DICT.keys()), entry, ret_index=False)\n    if len(found) > 1:\n        raise ValueError(\n            f\"That matched: {', '.join(str(t) for t in found)}. Please be more specific!\"\n        )\n    if found:\n        return _TZ_DICT[found[0]]\n    raise ValueError(f\"Could not find timezone '{entry}' for {option_key}!\")\n\n\ndef email(entry, option_key=\"Email Address\", **kwargs):\n    if not entry:\n        raise ValueError(\"Email address field empty!\")\n    try:\n        _val_email(str(entry))  # offloading the hard work to Django!\n    except _error:\n        raise ValueError(f\"That isn't a valid {option_key}!\")\n    return entry\n\n\ndef lock(entry, option_key=\"locks\", access_options=None, **kwargs):\n    entry = entry.strip()\n    if not entry:\n        raise ValueError(f\"No {option_key} entered to set!\")\n    for locksetting in entry.split(\";\"):\n        access_type, lockfunc = locksetting.split(\":\", 1)\n        if not access_type:\n            raise ValueError(\"Must enter an access type!\")\n        if access_options:\n            if access_type not in access_options:\n                raise ValueError(f\"Access type must be one of: {', '.join(access_options)}\")\n        if not lockfunc:\n            raise ValueError(\"Lock func not entered.\")\n    return entry\n", "path": "evennia/utils/validatorfuncs.py"}], "after_files": [{"content": "\"\"\"\nContains all the validation functions.\n\nAll validation functions must have a checker (probably a session) and entry arg.\n\nThey can employ more paramters at your leisure.\n\n\n\"\"\"\n\nimport re as _re\nimport pytz as _pytz\nimport datetime as _dt\nfrom django.core.exceptions import ValidationError as _error\nfrom django.core.validators import validate_email as _val_email\nfrom evennia.utils.ansi import strip_ansi\nfrom evennia.utils.utils import string_partial_matching as _partial\n\n_TZ_DICT = {str(tz): _pytz.timezone(tz) for tz in _pytz.common_timezones}\n\n\ndef text(entry, option_key=\"Text\", **kwargs):\n    try:\n        return str(entry)\n    except Exception as err:\n        raise ValueError(f\"Input could not be converted to text ({err})\")\n\n\ndef color(entry, option_key=\"Color\", **kwargs):\n    \"\"\"\n    The color should be just a color character, so 'r' if red color is desired.\n    \"\"\"\n    if not entry:\n        raise ValueError(f\"Nothing entered for a {option_key}!\")\n    test_str = strip_ansi(f\"|{entry}|n\")\n    if test_str:\n        raise ValueError(f\"'{entry}' is not a valid {option_key}.\")\n    return entry\n\n\ndef datetime(entry, option_key=\"Datetime\", account=None, from_tz=None, **kwargs):\n    \"\"\"\n    Process a datetime string in standard forms while accounting for the inputter's timezone.\n\n    Args:\n        entry (str): A date string from a user.\n        option_key (str): Name to display this datetime as.\n        account (AccountDB): The Account performing this lookup. Unless from_tz is provided,\n            account's timezone will be used (if found) for local time and convert the results\n            to UTC.\n        from_tz (pytz): An instance of pytz from the user. If not provided, defaults to whatever\n            the Account uses. If neither one is provided, defaults to UTC.\n\n    Returns:\n        datetime in utc.\n    \"\"\"\n    if not entry:\n        raise ValueError(f\"No {option_key} entered!\")\n    if not from_tz:\n        from_tz = _pytz.UTC\n    utc = _pytz.UTC\n    now = _dt.datetime.utcnow().replace(tzinfo=utc)\n    cur_year = now.strftime(\"%Y\")\n    split_time = entry.split(\" \")\n    if len(split_time) == 3:\n        entry = f\"{split_time[0]} {split_time[1]} {split_time[2]} {cur_year}\"\n    elif len(split_time) == 4:\n        entry = f\"{split_time[0]} {split_time[1]} {split_time[2]} {split_time[3]}\"\n    else:\n        raise ValueError(\n            f\"{option_key} must be entered in a 24-hour format such as: {now.strftime('%b %d %H:%M')}\"\n        )\n    try:\n        local = _dt.datetime.strptime(entry, \"%b %d %H:%M %Y\")\n    except ValueError:\n        raise ValueError(\n            f\"{option_key} must be entered in a 24-hour format such as: {now.strftime('%b %d %H:%M')}\"\n        )\n    local_tz = from_tz.localize(local)\n    return local_tz.astimezone(utc)\n\n\ndef duration(entry, option_key=\"Duration\", **kwargs):\n    \"\"\"\n    Take a string and derive a datetime timedelta from it.\n\n    Args:\n        entry (string): This is a string from user-input. The intended format is, for example: \"5d 2w 90s\" for\n                            'five days, two weeks, and ninety seconds.' Invalid sections are ignored.\n        option_key (str): Name to display this query as.\n\n    Returns:\n        timedelta\n\n    \"\"\"\n    time_string = entry.lower().split(\" \")\n    seconds = 0\n    minutes = 0\n    hours = 0\n    days = 0\n    weeks = 0\n\n    for interval in time_string:\n        if _re.match(r\"^[\\d]+s$\", interval):\n            seconds += int(interval.rstrip(\"s\"))\n        elif _re.match(r\"^[\\d]+m$\", interval):\n            minutes += int(interval.rstrip(\"m\"))\n        elif _re.match(r\"^[\\d]+h$\", interval):\n            hours += int(interval.rstrip(\"h\"))\n        elif _re.match(r\"^[\\d]+d$\", interval):\n            days += int(interval.rstrip(\"d\"))\n        elif _re.match(r\"^[\\d]+w$\", interval):\n            weeks += int(interval.rstrip(\"w\"))\n        elif _re.match(r\"^[\\d]+y$\", interval):\n            days += int(interval.rstrip(\"y\")) * 365\n        else:\n            raise ValueError(f\"Could not convert section '{interval}' to a {option_key}.\")\n\n    return _dt.timedelta(days, seconds, 0, 0, minutes, hours, weeks)\n\n\ndef future(entry, option_key=\"Future Datetime\", from_tz=None, **kwargs):\n    time = datetime(entry, option_key, from_tz=from_tz)\n    if time < _dt.datetime.utcnow().replace(tzinfo=_dt.timezone.utc):\n        raise ValueError(f\"That {option_key} is in the past! Must give a Future datetime!\")\n    return time\n\n\ndef signed_integer(entry, option_key=\"Signed Integer\", **kwargs):\n    if not entry:\n        raise ValueError(f\"Must enter a whole number for {option_key}!\")\n    try:\n        num = int(entry)\n    except ValueError:\n        raise ValueError(f\"Could not convert '{entry}' to a whole number for {option_key}!\")\n    return num\n\n\ndef positive_integer(entry, option_key=\"Positive Integer\", **kwargs):\n    num = signed_integer(entry, option_key)\n    if not num >= 1:\n        raise ValueError(f\"Must enter a whole number greater than 0 for {option_key}!\")\n    return num\n\n\ndef unsigned_integer(entry, option_key=\"Unsigned Integer\", **kwargs):\n    num = signed_integer(entry, option_key)\n    if not num >= 0:\n        raise ValueError(f\"{option_key} must be a whole number greater than or equal to 0!\")\n    return num\n\n\ndef boolean(entry, option_key=\"True/False\", **kwargs):\n    \"\"\"\n    Simplest check in computer logic, right? This will take user input to flick the switch on or off\n    Args:\n        entry (str): A value such as True, On, Enabled, Disabled, False, 0, or 1.\n        option_key (str): What kind of Boolean we are setting. What Option is this for?\n\n    Returns:\n        Boolean\n    \"\"\"\n    error = f\"Must enter 0 (false) or 1 (true) for {option_key}. Also accepts True, False, On, Off, Yes, No, Enabled, and Disabled\"\n    if not isinstance(entry, str):\n        raise ValueError(error)\n    entry = entry.upper()\n    if entry in (\"1\", \"TRUE\", \"ON\", \"ENABLED\", \"ENABLE\", \"YES\"):\n        return True\n    if entry in (\"0\", \"FALSE\", \"OFF\", \"DISABLED\", \"DISABLE\", \"NO\"):\n        return False\n    raise ValueError(error)\n\n\ndef timezone(entry, option_key=\"Timezone\", **kwargs):\n    \"\"\"\n    Takes user input as string, and partial matches a Timezone.\n\n    Args:\n        entry (str): The name of the Timezone.\n        option_key (str): What this Timezone is used for.\n\n    Returns:\n        A PYTZ timezone.\n    \"\"\"\n    if not entry:\n        raise ValueError(f\"No {option_key} entered!\")\n    found = _partial(list(_TZ_DICT.keys()), entry, ret_index=False)\n    if len(found) > 1:\n        raise ValueError(\n            f\"That matched: {', '.join(str(t) for t in found)}. Please be more specific!\"\n        )\n    if found:\n        return _TZ_DICT[found[0]]\n    raise ValueError(f\"Could not find timezone '{entry}' for {option_key}!\")\n\n\ndef email(entry, option_key=\"Email Address\", **kwargs):\n    if not entry:\n        raise ValueError(\"Email address field empty!\")\n    try:\n        _val_email(str(entry))  # offloading the hard work to Django!\n    except _error:\n        raise ValueError(f\"That isn't a valid {option_key}!\")\n    return entry\n\n\ndef lock(entry, option_key=\"locks\", access_options=None, **kwargs):\n    entry = entry.strip()\n    if not entry:\n        raise ValueError(f\"No {option_key} entered to set!\")\n    for locksetting in entry.split(\";\"):\n        access_type, lockfunc = locksetting.split(\":\", 1)\n        if not access_type:\n            raise ValueError(\"Must enter an access type!\")\n        if access_options:\n            if access_type not in access_options:\n                raise ValueError(f\"Access type must be one of: {', '.join(access_options)}\")\n        if not lockfunc:\n            raise ValueError(\"Lock func not entered.\")\n    return entry\n", "path": "evennia/utils/validatorfuncs.py"}]}