Xqt submitted this change.

View Change

Approvals: Xqt: Verified; Looks good to me, approved
[IMPR] Move time parts from archivebot to time module

Change-Id: I612e7c39a8bd03cd7cffe788137e5aae213fa9aa
---
M pywikibot/time_.py
2 files changed, 263 insertions(+), 266 deletions(-)

diff --git a/pywikibot/time_.py b/pywikibot/time_.py
index c57c2d2..4a495ff 100644
--- a/pywikibot/time_.py
+++ b/pywikibot/time_.py
@@ -1,4 +1,266 @@
-<<<<<<< HEAD (4a0216 [IMPR] move archivebot time parts to time module (part 1))
+"""Time handling module."""
+#
+# (C) Pywikibot team, 2009-2022
+#
+# Distributed under the terms of the MIT license.
+#
+import datetime
+import re
+from typing import Type, Union
+
+from pywikibot.tools import classproperty
+
+__all__ = ['Timestamp']
+
+
+class Timestamp(datetime.datetime):
+
+ """Class for handling MediaWiki timestamps.
+
+ This inherits from datetime.datetime, so it can use all of the methods
+ and operations of a datetime object. To ensure that the results of any
+ operation are also a Timestamp object, be sure to use only Timestamp
+ objects (and datetime.timedeltas) in any operation.
+
+ Use Timestamp.fromISOformat() and Timestamp.fromtimestampformat() to
+ create Timestamp objects from MediaWiki string formats.
+ As these constructors are typically used to create objects using data
+ passed provided by site and page methods, some of which return a Timestamp
+ when previously they returned a MediaWiki string representation, these
+ methods also accept a Timestamp object, in which case they return a clone.
+
+ Alternatively, Timestamp.set_timestamp() can create Timestamp objects from
+ Timestamp, datetime.datetime object, or strings compliant with ISO8601,
+ MW, or POSIX formats.
+
+ Use Site.server_time() for the current time; this is more reliable
+ than using Timestamp.utcnow().
+ """
+
+ mediawikiTSFormat = '%Y%m%d%H%M%S' # noqa: N815
+ _ISO8601Format_new = '{0:+05d}-{1:02d}-{2:02d}T{3:02d}:{4:02d}:{5:02d}Z'
+
+ @classmethod
+ def set_timestamp(cls: Type['Timestamp'],
+ ts: Union[str, datetime.datetime, 'Timestamp']
+ ) -> 'Timestamp':
+ """Set Timestamp from input object.
+
+ ts is converted to a datetime naive object representing UTC time.
+ String shall be compliant with:
+ - Mediwiki timestamp format: YYYYMMDDHHMMSS
+ - ISO8601 format: YYYY-MM-DD[T ]HH:MM:SS[Z|±HH[MM[SS[.ffffff]]]]
+ - POSIX format: seconds from Unix epoch S{1,13}[.ffffff]]
+
+ :param ts: Timestamp, datetime.datetime or str
+ :return: Timestamp object
+ :raises ValuError: conversion failed
+ """
+ if isinstance(ts, cls):
+ return ts
+ if isinstance(ts, datetime.datetime):
+ return cls._from_datetime(ts)
+ if isinstance(ts, str):
+ return cls._from_string(ts)
+
+ @staticmethod
+ def _from_datetime(dt: datetime.datetime) -> 'Timestamp':
+ """Convert a datetime.datetime timestamp to a Timestamp object."""
+ return Timestamp(dt.year, dt.month, dt.day, dt.hour,
+ dt.minute, dt.second, dt.microsecond,
+ dt.tzinfo)
+
+ @classmethod
+ def _from_mw(cls: Type['Timestamp'], timestr: str) -> 'Timestamp':
+ """Convert a string in MW format to a Timestamp object.
+
+ Mediwiki timestamp format: YYYYMMDDHHMMSS
+ """
+ RE_MW = r'\d{14}$' # noqa: N806
+ m = re.match(RE_MW, timestr)
+
+ if not m:
+ msg = "time data '{timestr}' does not match MW format."
+ raise ValueError(msg.format(timestr=timestr))
+
+ return cls.strptime(timestr, cls.mediawikiTSFormat)
+
+ @classmethod
+ def _from_iso8601(cls: Type['Timestamp'], timestr: str) -> 'Timestamp':
+ """Convert a string in ISO8601 format to a Timestamp object.
+
+ ISO8601 format:
+ - YYYY-MM-DD[T ]HH:MM:SS[[.,]ffffff][Z|±HH[MM[SS[.ffffff]]]]
+ """
+ RE_ISO8601 = (r'(?:\d{4}-\d{2}-\d{2})(?P<sep>[T ])' # noqa: N806
+ r'(?:\d{2}:\d{2}:\d{2})(?P<u>[.,]\d{1,6})?'
+ r'(?P<tz>Z|[+\-]\d{2}:?\d{,2})?$'
+ )
+ m = re.match(RE_ISO8601, timestr)
+
+ if not m:
+ msg = "time data '{timestr}' does not match ISO8601 format."
+ raise ValueError(msg.format(timestr=timestr))
+
+ strpfmt = '%Y-%m-%d{sep}%H:%M:%S'.format(sep=m.group('sep'))
+ strpstr = timestr[:19]
+
+ if m.group('u'):
+ strpfmt += '.%f'
+ strpstr += m.group('u').replace(',', '.') # .ljust(7, '0')
+
+ if m.group('tz'):
+ if m.group('tz') == 'Z':
+ strpfmt += 'Z'
+ strpstr += 'Z'
+ else:
+ strpfmt += '%z'
+ # strptime wants HHMM, without ':'
+ strpstr += (m.group('tz').replace(':', '')).ljust(5, '0')
+
+ ts = cls.strptime(strpstr, strpfmt)
+ if ts.tzinfo is not None:
+ ts = ts.astimezone(datetime.timezone.utc).replace(tzinfo=None)
+ # why pytest in py35/py37 fails without this?
+ ts = cls._from_datetime(ts)
+
+ return ts
+
+ @classmethod
+ def _from_posix(cls: Type['Timestamp'], timestr: str) -> 'Timestamp':
+ """Convert a string in POSIX format to a Timestamp object.
+
+ POSIX format: SECONDS[.ffffff]]
+ """
+ RE_POSIX = r'(?P<S>-?\d{1,13})(?:\.(?P<u>\d{1,6}))?$' # noqa: N806
+ m = re.match(RE_POSIX, timestr)
+
+ if not m:
+ msg = "time data '{timestr}' does not match POSIX format."
+ raise ValueError(msg.format(timestr=timestr))
+
+ sec = int(m.group('S'))
+ usec = m.group('u')
+ usec = int(usec.ljust(6, '0')) if usec else 0
+ if sec < 0 and usec > 0:
+ sec = sec - 1
+ usec = 1000000 - usec
+
+ ts = (cls(1970, 1, 1)
+ + datetime.timedelta(seconds=sec, microseconds=usec))
+ return ts
+
+ @classmethod
+ def _from_string(cls: Type['Timestamp'], timestr: str) -> 'Timestamp':
+ """Convert a string to a Timestamp object."""
+ handlers = [
+ cls._from_mw,
+ cls._from_iso8601,
+ cls._from_posix,
+ ]
+
+ for handler in handlers:
+ try:
+ return handler(timestr)
+ except ValueError:
+ continue
+
+ msg = "time data '{timestr}' does not match any format."
+ raise ValueError(msg.format(timestr=timestr))
+
+ def clone(self) -> datetime.datetime:
+ """Clone this instance."""
+ return self.replace(microsecond=self.microsecond)
+
+ @classproperty
+ def ISO8601Format(cls: Type['Timestamp']) -> str: # noqa: N802
+ """ISO8601 format string class property for compatibility purpose."""
+ return cls._ISO8601Format()
+
+ @classmethod
+ def _ISO8601Format(cls: Type['Timestamp'], # noqa: N802
+ sep: str = 'T') -> str:
+ """ISO8601 format string.
+
+ :param sep: one-character separator, placed between the date and time
+ :return: ISO8601 format string
+ """
+ assert len(sep) == 1
+ return '%Y-%m-%d{}%H:%M:%SZ'.format(sep)
+
+ @classmethod
+ def fromISOformat(cls: Type['Timestamp'], # noqa: N802
+ ts: Union[str, 'Timestamp'],
+ sep: str = 'T') -> 'Timestamp':
+ """Convert an ISO 8601 timestamp to a Timestamp object.
+
+ :param ts: ISO 8601 timestamp or a Timestamp object already
+ :param sep: one-character separator, placed between the date and time
+ :return: Timestamp object
+ """
+ # If inadvertently passed a Timestamp object, use replace()
+ # to create a clone.
+ if isinstance(ts, cls):
+ return ts.clone()
+ _ts = '{pre}{sep}{post}'.format(pre=ts[:10], sep=sep, post=ts[11:])
+ return cls._from_iso8601(_ts)
+
+ @classmethod
+ def fromtimestampformat(cls: Type['Timestamp'], ts: Union[str, 'Timestamp']
+ ) -> 'Timestamp':
+ """Convert a MediaWiki internal timestamp to a Timestamp object."""
+ # If inadvertently passed a Timestamp object, use replace()
+ # to create a clone.
+ if isinstance(ts, cls):
+ return ts.clone()
+ if len(ts) == 8: # year, month and day are given only
+ ts += '000000'
+ return cls._from_mw(ts)
+
+ def isoformat(self, sep: str = 'T') -> str: # type: ignore[override]
+ """
+ Convert object to an ISO 8601 timestamp accepted by MediaWiki.
+
+ datetime.datetime.isoformat does not postfix the ISO formatted date
+ with a 'Z' unless a timezone is included, which causes MediaWiki
+ ~1.19 and earlier to fail.
+ """
+ return self.strftime(self._ISO8601Format(sep))
+
+ def totimestampformat(self) -> str:
+ """Convert object to a MediaWiki internal timestamp."""
+ return self.strftime(self.mediawikiTSFormat)
+
+ def posix_timestamp(self) -> float:
+ """
+ Convert object to a POSIX timestamp.
+
+ See Note in datetime.timestamp().
+ """
+ return self.replace(tzinfo=datetime.timezone.utc).timestamp()
+
+ def posix_timestamp_format(self) -> str:
+ """Convert object to a POSIX timestamp format."""
+ return '{ts:.6f}'.format(ts=self.posix_timestamp())
+
+ def __str__(self) -> str:
+ """Return a string format recognized by the API."""
+ return self.isoformat()
+
+ def __add__(self, other: datetime.timedelta) -> 'Timestamp':
+ """Perform addition, returning a Timestamp instead of datetime."""
+ newdt = super().__add__(other)
+ if isinstance(newdt, datetime.datetime):
+ return self._from_datetime(newdt)
+ return newdt
+
+ def __sub__(self, other: datetime.timedelta # type: ignore[override]
+ ) -> 'Timestamp':
+ """Perform subtraction, returning a Timestamp instead of datetime."""
+ newdt = super().__sub__(other)
+ if isinstance(newdt, datetime.datetime):
+ return self._from_datetime(newdt)
+ return newdt
#!/usr/bin/python3
"""
archivebot.py - discussion page archiving bot.
@@ -913,268 +1175,3 @@

if __name__ == '__main__':
main()
-=======
-"""Time handling module."""
-#
-# (C) Pywikibot team, 2009-2022
-#
-# Distributed under the terms of the MIT license.
-#
-import datetime
-import re
-from typing import Type, Union
-
-from pywikibot.tools import classproperty
-
-__all__ = ['Timestamp']
-
-
-class Timestamp(datetime.datetime):
-
- """Class for handling MediaWiki timestamps.
-
- This inherits from datetime.datetime, so it can use all of the methods
- and operations of a datetime object. To ensure that the results of any
- operation are also a Timestamp object, be sure to use only Timestamp
- objects (and datetime.timedeltas) in any operation.
-
- Use Timestamp.fromISOformat() and Timestamp.fromtimestampformat() to
- create Timestamp objects from MediaWiki string formats.
- As these constructors are typically used to create objects using data
- passed provided by site and page methods, some of which return a Timestamp
- when previously they returned a MediaWiki string representation, these
- methods also accept a Timestamp object, in which case they return a clone.
-
- Alternatively, Timestamp.set_timestamp() can create Timestamp objects from
- Timestamp, datetime.datetime object, or strings compliant with ISO8601,
- MW, or POSIX formats.
-
- Use Site.server_time() for the current time; this is more reliable
- than using Timestamp.utcnow().
- """
-
- mediawikiTSFormat = '%Y%m%d%H%M%S' # noqa: N815
- _ISO8601Format_new = '{0:+05d}-{1:02d}-{2:02d}T{3:02d}:{4:02d}:{5:02d}Z'
-
- @classmethod
- def set_timestamp(cls: Type['Timestamp'],
- ts: Union[str, datetime.datetime, 'Timestamp']
- ) -> 'Timestamp':
- """Set Timestamp from input object.
-
- ts is converted to a datetime naive object representing UTC time.
- String shall be compliant with:
- - Mediwiki timestamp format: YYYYMMDDHHMMSS
- - ISO8601 format: YYYY-MM-DD[T ]HH:MM:SS[Z|±HH[MM[SS[.ffffff]]]]
- - POSIX format: seconds from Unix epoch S{1,13}[.ffffff]]
-
- :param ts: Timestamp, datetime.datetime or str
- :return: Timestamp object
- :raises ValuError: conversion failed
- """
- if isinstance(ts, cls):
- return ts
- if isinstance(ts, datetime.datetime):
- return cls._from_datetime(ts)
- if isinstance(ts, str):
- return cls._from_string(ts)
-
- @staticmethod
- def _from_datetime(dt: datetime.datetime) -> 'Timestamp':
- """Convert a datetime.datetime timestamp to a Timestamp object."""
- return Timestamp(dt.year, dt.month, dt.day, dt.hour,
- dt.minute, dt.second, dt.microsecond,
- dt.tzinfo)
-
- @classmethod
- def _from_mw(cls: Type['Timestamp'], timestr: str) -> 'Timestamp':
- """Convert a string in MW format to a Timestamp object.
-
- Mediwiki timestamp format: YYYYMMDDHHMMSS
- """
- RE_MW = r'\d{14}$' # noqa: N806
- m = re.match(RE_MW, timestr)
-
- if not m:
- msg = "time data '{timestr}' does not match MW format."
- raise ValueError(msg.format(timestr=timestr))
-
- return cls.strptime(timestr, cls.mediawikiTSFormat)
-
- @classmethod
- def _from_iso8601(cls: Type['Timestamp'], timestr: str) -> 'Timestamp':
- """Convert a string in ISO8601 format to a Timestamp object.
-
- ISO8601 format:
- - YYYY-MM-DD[T ]HH:MM:SS[[.,]ffffff][Z|±HH[MM[SS[.ffffff]]]]
- """
- RE_ISO8601 = (r'(?:\d{4}-\d{2}-\d{2})(?P<sep>[T ])' # noqa: N806
- r'(?:\d{2}:\d{2}:\d{2})(?P<u>[.,]\d{1,6})?'
- r'(?P<tz>Z|[+\-]\d{2}:?\d{,2})?$'
- )
- m = re.match(RE_ISO8601, timestr)
-
- if not m:
- msg = "time data '{timestr}' does not match ISO8601 format."
- raise ValueError(msg.format(timestr=timestr))
-
- strpfmt = '%Y-%m-%d{sep}%H:%M:%S'.format(sep=m.group('sep'))
- strpstr = timestr[:19]
-
- if m.group('u'):
- strpfmt += '.%f'
- strpstr += m.group('u').replace(',', '.') # .ljust(7, '0')
-
- if m.group('tz'):
- if m.group('tz') == 'Z':
- strpfmt += 'Z'
- strpstr += 'Z'
- else:
- strpfmt += '%z'
- # strptime wants HHMM, without ':'
- strpstr += (m.group('tz').replace(':', '')).ljust(5, '0')
-
- ts = cls.strptime(strpstr, strpfmt)
- if ts.tzinfo is not None:
- ts = ts.astimezone(datetime.timezone.utc).replace(tzinfo=None)
- # why pytest in py35/py37 fails without this?
- ts = cls._from_datetime(ts)
-
- return ts
-
- @classmethod
- def _from_posix(cls: Type['Timestamp'], timestr: str) -> 'Timestamp':
- """Convert a string in POSIX format to a Timestamp object.
-
- POSIX format: SECONDS[.ffffff]]
- """
- RE_POSIX = r'(?P<S>-?\d{1,13})(?:\.(?P<u>\d{1,6}))?$' # noqa: N806
- m = re.match(RE_POSIX, timestr)
-
- if not m:
- msg = "time data '{timestr}' does not match POSIX format."
- raise ValueError(msg.format(timestr=timestr))
-
- sec = int(m.group('S'))
- usec = m.group('u')
- usec = int(usec.ljust(6, '0')) if usec else 0
- if sec < 0 and usec > 0:
- sec = sec - 1
- usec = 1000000 - usec
-
- ts = (cls(1970, 1, 1)
- + datetime.timedelta(seconds=sec, microseconds=usec))
- return ts
-
- @classmethod
- def _from_string(cls: Type['Timestamp'], timestr: str) -> 'Timestamp':
- """Convert a string to a Timestamp object."""
- handlers = [
- cls._from_mw,
- cls._from_iso8601,
- cls._from_posix,
- ]
-
- for handler in handlers:
- try:
- return handler(timestr)
- except ValueError:
- continue
-
- msg = "time data '{timestr}' does not match any format."
- raise ValueError(msg.format(timestr=timestr))
-
- def clone(self) -> datetime.datetime:
- """Clone this instance."""
- return self.replace(microsecond=self.microsecond)
-
- @classproperty
- def ISO8601Format(cls: Type['Timestamp']) -> str: # noqa: N802
- """ISO8601 format string class property for compatibility purpose."""
- return cls._ISO8601Format()
-
- @classmethod
- def _ISO8601Format(cls: Type['Timestamp'], # noqa: N802
- sep: str = 'T') -> str:
- """ISO8601 format string.
-
- :param sep: one-character separator, placed between the date and time
- :return: ISO8601 format string
- """
- assert len(sep) == 1
- return '%Y-%m-%d{}%H:%M:%SZ'.format(sep)
-
- @classmethod
- def fromISOformat(cls: Type['Timestamp'], # noqa: N802
- ts: Union[str, 'Timestamp'],
- sep: str = 'T') -> 'Timestamp':
- """Convert an ISO 8601 timestamp to a Timestamp object.
-
- :param ts: ISO 8601 timestamp or a Timestamp object already
- :param sep: one-character separator, placed between the date and time
- :return: Timestamp object
- """
- # If inadvertently passed a Timestamp object, use replace()
- # to create a clone.
- if isinstance(ts, cls):
- return ts.clone()
- _ts = '{pre}{sep}{post}'.format(pre=ts[:10], sep=sep, post=ts[11:])
- return cls._from_iso8601(_ts)
-
- @classmethod
- def fromtimestampformat(cls: Type['Timestamp'], ts: Union[str, 'Timestamp']
- ) -> 'Timestamp':
- """Convert a MediaWiki internal timestamp to a Timestamp object."""
- # If inadvertently passed a Timestamp object, use replace()
- # to create a clone.
- if isinstance(ts, cls):
- return ts.clone()
- if len(ts) == 8: # year, month and day are given only
- ts += '000000'
- return cls._from_mw(ts)
-
- def isoformat(self, sep: str = 'T') -> str: # type: ignore[override]
- """
- Convert object to an ISO 8601 timestamp accepted by MediaWiki.
-
- datetime.datetime.isoformat does not postfix the ISO formatted date
- with a 'Z' unless a timezone is included, which causes MediaWiki
- ~1.19 and earlier to fail.
- """
- return self.strftime(self._ISO8601Format(sep))
-
- def totimestampformat(self) -> str:
- """Convert object to a MediaWiki internal timestamp."""
- return self.strftime(self.mediawikiTSFormat)
-
- def posix_timestamp(self) -> float:
- """
- Convert object to a POSIX timestamp.
-
- See Note in datetime.timestamp().
- """
- return self.replace(tzinfo=datetime.timezone.utc).timestamp()
-
- def posix_timestamp_format(self) -> str:
- """Convert object to a POSIX timestamp format."""
- return '{ts:.6f}'.format(ts=self.posix_timestamp())
-
- def __str__(self) -> str:
- """Return a string format recognized by the API."""
- return self.isoformat()
-
- def __add__(self, other: datetime.timedelta) -> 'Timestamp':
- """Perform addition, returning a Timestamp instead of datetime."""
- newdt = super().__add__(other)
- if isinstance(newdt, datetime.datetime):
- return self._from_datetime(newdt)
- return newdt
-
- def __sub__(self, other: datetime.timedelta # type: ignore[override]
- ) -> 'Timestamp':
- """Perform subtraction, returning a Timestamp instead of datetime."""
- newdt = super().__sub__(other)
- if isinstance(newdt, datetime.datetime):
- return self._from_datetime(newdt)
- return newdt
->>>>>>> BRANCH (1bd999 [IMPR] move archivebot time parts to time module (part 1))

To view, visit change 811261. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-Project: pywikibot/core
Gerrit-Branch: master
Gerrit-Change-Id: I612e7c39a8bd03cd7cffe788137e5aae213fa9aa
Gerrit-Change-Number: 811261
Gerrit-PatchSet: 1
Gerrit-Owner: Xqt <info@gno.de>
Gerrit-Reviewer: Xqt <info@gno.de>
Gerrit-Reviewer: jenkins-bot
Gerrit-MessageType: merged