changeset 3879:46930301f0c1

tools: renamed module `sat.tools.datetime` to `date.tools.xmpp_datetime` to avoid conflict with Python's standard lib
author Goffi <goffi@goffi.org>
date Wed, 31 Aug 2022 13:18:56 +0200 (2022-08-31)
parents 32087d7c25d4
children 03761f8ba8bb
files sat/plugins/plugin_xep_0082.py sat/tools/datetime.py sat/tools/utils.py sat/tools/xmpp_datetime.py
diffstat 4 files changed, 200 insertions(+), 197 deletions(-) [+]
line wrap: on
line diff
--- a/sat/plugins/plugin_xep_0082.py	Wed Aug 31 13:11:26 2022 +0200
+++ b/sat/plugins/plugin_xep_0082.py	Wed Aug 31 13:18:56 2022 +0200
@@ -23,7 +23,7 @@
 from sat.core.constants import Const as C
 from sat.core.i18n import D_
 from sat.core.sat_main import SAT
-from sat.tools import datetime
+from sat.tools import xmpp_datetime
 
 
 __all__ = [  # pylint: disable=unused-variable
@@ -49,7 +49,7 @@
     """
     Implementation of the date and time profiles specified in XEP-0082 using Python's
     datetime module. The legacy format described in XEP-0082 section "4. Migration" is not
-    supported. Reexports of the functions in :mod:`sat.tools.datetime`.
+    supported. Reexports of the functions in :mod:`sat.tools.xmpp_datetime`.
 
     This is a passive plugin, i.e. it doesn't hook into any triggers to process stanzas
     actively, but offers API for other plugins to use.
@@ -60,9 +60,9 @@
         @param sat: The SAT instance.
         """
 
-    format_date = staticmethod(datetime.format_date)
-    parse_date = staticmethod(datetime.parse_date)
-    format_datetime = staticmethod(datetime.format_datetime)
-    parse_datetime = staticmethod(datetime.parse_datetime)
-    format_time = staticmethod(datetime.format_time)
-    parse_time = staticmethod(datetime.parse_time)
+    format_date = staticmethod(xmpp_datetime.format_date)
+    parse_date = staticmethod(xmpp_datetime.parse_date)
+    format_datetime = staticmethod(xmpp_datetime.format_datetime)
+    parse_datetime = staticmethod(xmpp_datetime.parse_datetime)
+    format_time = staticmethod(xmpp_datetime.format_time)
+    parse_time = staticmethod(xmpp_datetime.parse_time)
--- a/sat/tools/datetime.py	Wed Aug 31 13:11:26 2022 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,187 +0,0 @@
-#!/usr/bin/env python3
-
-# Libervia: XMPP Date and Time profiles as per XEP-0082
-# Copyright (C) 2022-2022 Tim Henkes (me@syndace.dev)
-
-# This program is free software: you can redistribute it and/or modify
-# it under the terms of the GNU Affero General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU Affero General Public License for more details.
-
-# You should have received a copy of the GNU Affero General Public License
-# along with this program.  If not, see <http://www.gnu.org/licenses/>.
-
-# Type-check with `mypy --strict`
-# Lint with `pylint`
-
-from sat.core import exceptions
-from datetime import date, datetime, time, timezone
-import re
-from typing import Optional, Tuple
-
-
-__all__ = [  # pylint: disable=unused-variable
-    "format_date",
-    "parse_date",
-    "format_datetime",
-    "parse_datetime",
-    "format_time",
-    "parse_time"
-]
-
-
-def __parse_fraction_of_a_second(value: str) -> Tuple[str, Optional[int]]:
-    """
-    datetime's strptime only supports up to six digits of the fraction of a seconds, while
-    the XEP-0082 specification allows for any number of digits. This function parses and
-    removes the optional fraction of a second from the input string.
-
-    @param value: The input string, containing a section of the format [.sss].
-    @return: The input string with the fraction of a second removed, and the fraction of a
-        second parsed with microsecond resolution. Returns the unaltered input string and
-        ``None`` if no fraction of a second was found in the input string.
-    """
-
-    #  The following regex matches the optional fraction of a seconds for manual
-    # processing.
-    match = re.search(r"\.(\d*)", value)
-    microsecond: Optional[int] = None
-    if match is not None:
-        # Remove the fraction of a second from the input string
-        value = value[:match.start()] + value[match.end():]
-
-        # datetime supports microsecond resolution for the fraction of a second, thus
-        # limit/pad the parsed fraction of a second to six digits
-        microsecond = int(match.group(1)[:6].ljust(6, '0'))
-
-    return value, microsecond
-
-
-def format_date(value: Optional[date] = None) -> str:
-    """
-    @param value: The date for format. Defaults to the current date in the UTC timezone.
-    @return: The date formatted according to the Date profile specified in XEP-0082.
-
-    @warning: Formatting of the current date in the local timezone may leak geographical
-        information of the sender. Thus, it is advised to only format the current date in
-        UTC.
-    """
-    # CCYY-MM-DD
-
-    # The Date profile of XEP-0082 is equal to the ISO 8601 format.
-    return (datetime.now(timezone.utc).date() if value is None else value).isoformat()
-
-
-def parse_date(value: str) -> date:
-    """
-    @param value: A string containing date information formatted according to the Date
-        profile specified in XEP-0082.
-    @return: The date parsed from the input string.
-    @raise ValueError: if the input string is not correctly formatted.
-    """
-    # CCYY-MM-DD
-
-    # The Date profile of XEP-0082 is equal to the ISO 8601 format.
-    return date.fromisoformat(value)
-
-
-def format_datetime(
-    value: Optional[datetime] = None,
-    include_microsecond: bool = False
-) -> str:
-    """
-    @param value: The datetime to format. Defaults to the current datetime.
-        must be an aware datetime object (timezone must be specified)
-    @param include_microsecond: Include the microsecond of the datetime in the output.
-    @return: The datetime formatted according to the DateTime profile specified in
-        XEP-0082. The datetime is always converted to UTC before formatting to avoid
-        leaking geographical information of the sender.
-    """
-    # CCYY-MM-DDThh:mm:ss[.sss]TZD
-
-    # We format the time in UTC, since the %z formatter of strftime doesn't include colons
-    # to separate hours and minutes which is required by XEP-0082. UTC allows us to put a
-    # simple letter 'Z' as the time zone definition.
-    if value is not None:
-        if value.tzinfo is None:
-            raise exceptions.InternalError(
-                "an aware datetime object must be used, but a naive one has been provided"
-            )
-        value = value.astimezone(timezone.utc)  # pylint: disable=no-member
-    else:
-        value = datetime.now(timezone.utc)
-
-    if include_microsecond:
-        return value.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
-
-    return value.strftime("%Y-%m-%dT%H:%M:%SZ")
-
-
-def parse_datetime(value: str) -> datetime:
-    """
-    @param value: A string containing datetime information formatted according to the
-        DateTime profile specified in XEP-0082.
-    @return: The datetime parsed from the input string.
-    @raise ValueError: if the input string is not correctly formatted.
-    """
-    # CCYY-MM-DDThh:mm:ss[.sss]TZD
-
-    value, microsecond = __parse_fraction_of_a_second(value)
-
-    result = datetime.strptime(value, "%Y-%m-%dT%H:%M:%S%z")
-
-    if microsecond is not None:
-        result = result.replace(microsecond=microsecond)
-
-    return result
-
-
-def format_time(value: Optional[time] = None, include_microsecond: bool = False) -> str:
-    """
-    @param value: The time to format. Defaults to the current time in the UTC timezone.
-    @param include_microsecond: Include the microsecond of the time in the output.
-    @return: The time formatted according to the Time profile specified in XEP-0082.
-
-    @warning: Since accurate timezone conversion requires the date to be known, this
-        function cannot convert input times to UTC before formatting. This means that
-        geographical information of the sender may be leaked if a time in local timezone
-        is formatted. Thus, when passing a time to format, it is advised to pass the time
-        in UTC if possible.
-    """
-    # hh:mm:ss[.sss][TZD]
-
-    if value is None:
-        # There is no time.now() method as one might expect, but the current time can be
-        # extracted from a datetime object including time zone information.
-        value = datetime.now(timezone.utc).timetz()
-
-    # The format created by time.isoformat complies with the XEP-0082 Time profile.
-    return value.isoformat("auto" if include_microsecond else "seconds")
-
-
-def parse_time(value: str) -> time:
-    """
-    @param value: A string containing time information formatted according to the Time
-        profile specified in XEP-0082.
-    @return: The time parsed from the input string.
-    @raise ValueError: if the input string is not correctly formatted.
-    """
-    # hh:mm:ss[.sss][TZD]
-
-    value, microsecond = __parse_fraction_of_a_second(value)
-
-    # The format parsed by time.fromisoformat mostly complies with the XEP-0082 Time
-    # profile, except that it doesn't handle the letter Z as time zone information for
-    # UTC. This can be fixed with a simple string replacement of 'Z' with "+00:00", which
-    # is another way to represent UTC.
-    result = time.fromisoformat(value.replace('Z', "+00:00"))
-
-    if microsecond is not None:
-        result = result.replace(microsecond=microsecond)
-
-    return result
--- a/sat/tools/utils.py	Wed Aug 31 13:11:26 2022 +0200
+++ b/sat/tools/utils.py	Wed Aug 31 13:18:56 2022 +0200
@@ -34,7 +34,7 @@
 from twisted.internet import defer
 from sat.core.constants import Const as C
 from sat.core.log import getLogger
-from sat.tools.datetime import format_date, format_datetime
+from sat.tools import xmpp_datetime
 
 log = getLogger(__name__)
 
@@ -157,7 +157,10 @@
         datetime.timezone.utc
     )
 
-    return format_datetime(dtime) if with_time else format_date(dtime.date())
+    return (
+        xmpp_datetime.format_datetime(dtime) if with_time
+        else xmpp_datetime.format_date(dtime.date())
+    )
 
 
 def generatePassword(vocabulary=None, size=20):
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/sat/tools/xmpp_datetime.py	Wed Aug 31 13:18:56 2022 +0200
@@ -0,0 +1,187 @@
+#!/usr/bin/env python3
+
+# Libervia: XMPP Date and Time profiles as per XEP-0082
+# Copyright (C) 2022-2022 Tim Henkes (me@syndace.dev)
+
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU Affero General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU Affero General Public License for more details.
+
+# You should have received a copy of the GNU Affero General Public License
+# along with this program.  If not, see <http://www.gnu.org/licenses/>.
+
+# Type-check with `mypy --strict`
+# Lint with `pylint`
+
+from sat.core import exceptions
+from datetime import date, datetime, time, timezone
+import re
+from typing import Optional, Tuple
+
+
+__all__ = [  # pylint: disable=unused-variable
+    "format_date",
+    "parse_date",
+    "format_datetime",
+    "parse_datetime",
+    "format_time",
+    "parse_time"
+]
+
+
+def __parse_fraction_of_a_second(value: str) -> Tuple[str, Optional[int]]:
+    """
+    datetime's strptime only supports up to six digits of the fraction of a seconds, while
+    the XEP-0082 specification allows for any number of digits. This function parses and
+    removes the optional fraction of a second from the input string.
+
+    @param value: The input string, containing a section of the format [.sss].
+    @return: The input string with the fraction of a second removed, and the fraction of a
+        second parsed with microsecond resolution. Returns the unaltered input string and
+        ``None`` if no fraction of a second was found in the input string.
+    """
+
+    #  The following regex matches the optional fraction of a seconds for manual
+    # processing.
+    match = re.search(r"\.(\d*)", value)
+    microsecond: Optional[int] = None
+    if match is not None:
+        # Remove the fraction of a second from the input string
+        value = value[:match.start()] + value[match.end():]
+
+        # datetime supports microsecond resolution for the fraction of a second, thus
+        # limit/pad the parsed fraction of a second to six digits
+        microsecond = int(match.group(1)[:6].ljust(6, '0'))
+
+    return value, microsecond
+
+
+def format_date(value: Optional[date] = None) -> str:
+    """
+    @param value: The date for format. Defaults to the current date in the UTC timezone.
+    @return: The date formatted according to the Date profile specified in XEP-0082.
+
+    @warning: Formatting of the current date in the local timezone may leak geographical
+        information of the sender. Thus, it is advised to only format the current date in
+        UTC.
+    """
+    # CCYY-MM-DD
+
+    # The Date profile of XEP-0082 is equal to the ISO 8601 format.
+    return (datetime.now(timezone.utc).date() if value is None else value).isoformat()
+
+
+def parse_date(value: str) -> date:
+    """
+    @param value: A string containing date information formatted according to the Date
+        profile specified in XEP-0082.
+    @return: The date parsed from the input string.
+    @raise ValueError: if the input string is not correctly formatted.
+    """
+    # CCYY-MM-DD
+
+    # The Date profile of XEP-0082 is equal to the ISO 8601 format.
+    return date.fromisoformat(value)
+
+
+def format_datetime(
+    value: Optional[datetime] = None,
+    include_microsecond: bool = False
+) -> str:
+    """
+    @param value: The datetime to format. Defaults to the current datetime.
+        must be an aware datetime object (timezone must be specified)
+    @param include_microsecond: Include the microsecond of the datetime in the output.
+    @return: The datetime formatted according to the DateTime profile specified in
+        XEP-0082. The datetime is always converted to UTC before formatting to avoid
+        leaking geographical information of the sender.
+    """
+    # CCYY-MM-DDThh:mm:ss[.sss]TZD
+
+    # We format the time in UTC, since the %z formatter of strftime doesn't include colons
+    # to separate hours and minutes which is required by XEP-0082. UTC allows us to put a
+    # simple letter 'Z' as the time zone definition.
+    if value is not None:
+        if value.tzinfo is None:
+            raise exceptions.InternalError(
+                "an aware datetime object must be used, but a naive one has been provided"
+            )
+        value = value.astimezone(timezone.utc)  # pylint: disable=no-member
+    else:
+        value = datetime.now(timezone.utc)
+
+    if include_microsecond:
+        return value.strftime("%Y-%m-%dT%H:%M:%S.%fZ")
+
+    return value.strftime("%Y-%m-%dT%H:%M:%SZ")
+
+
+def parse_datetime(value: str) -> datetime:
+    """
+    @param value: A string containing datetime information formatted according to the
+        DateTime profile specified in XEP-0082.
+    @return: The datetime parsed from the input string.
+    @raise ValueError: if the input string is not correctly formatted.
+    """
+    # CCYY-MM-DDThh:mm:ss[.sss]TZD
+
+    value, microsecond = __parse_fraction_of_a_second(value)
+
+    result = datetime.strptime(value, "%Y-%m-%dT%H:%M:%S%z")
+
+    if microsecond is not None:
+        result = result.replace(microsecond=microsecond)
+
+    return result
+
+
+def format_time(value: Optional[time] = None, include_microsecond: bool = False) -> str:
+    """
+    @param value: The time to format. Defaults to the current time in the UTC timezone.
+    @param include_microsecond: Include the microsecond of the time in the output.
+    @return: The time formatted according to the Time profile specified in XEP-0082.
+
+    @warning: Since accurate timezone conversion requires the date to be known, this
+        function cannot convert input times to UTC before formatting. This means that
+        geographical information of the sender may be leaked if a time in local timezone
+        is formatted. Thus, when passing a time to format, it is advised to pass the time
+        in UTC if possible.
+    """
+    # hh:mm:ss[.sss][TZD]
+
+    if value is None:
+        # There is no time.now() method as one might expect, but the current time can be
+        # extracted from a datetime object including time zone information.
+        value = datetime.now(timezone.utc).timetz()
+
+    # The format created by time.isoformat complies with the XEP-0082 Time profile.
+    return value.isoformat("auto" if include_microsecond else "seconds")
+
+
+def parse_time(value: str) -> time:
+    """
+    @param value: A string containing time information formatted according to the Time
+        profile specified in XEP-0082.
+    @return: The time parsed from the input string.
+    @raise ValueError: if the input string is not correctly formatted.
+    """
+    # hh:mm:ss[.sss][TZD]
+
+    value, microsecond = __parse_fraction_of_a_second(value)
+
+    # The format parsed by time.fromisoformat mostly complies with the XEP-0082 Time
+    # profile, except that it doesn't handle the letter Z as time zone information for
+    # UTC. This can be fixed with a simple string replacement of 'Z' with "+00:00", which
+    # is another way to represent UTC.
+    result = time.fromisoformat(value.replace('Z', "+00:00"))
+
+    if microsecond is not None:
+        result = result.replace(microsecond=microsecond)
+
+    return result