[tor-commits] [bridgedb/master] Rewrite bridgedb.Time module.

isis at torproject.org isis at torproject.org
Fri May 16 18:52:53 UTC 2014


commit ff5980fe8bab178bc113964a0e6c31a531387c2f
Author: Isis Lovecruft <isis at torproject.org>
Date:   Tue May 13 23:17:01 2014 +0000

    Rewrite bridgedb.Time module.
---
 lib/bridgedb/Time.py |  308 +++++++++++++++++++++++++++++++++-----------------
 1 file changed, 204 insertions(+), 104 deletions(-)

diff --git a/lib/bridgedb/Time.py b/lib/bridgedb/Time.py
index 707bf9b..4c4a2e2 100644
--- a/lib/bridgedb/Time.py
+++ b/lib/bridgedb/Time.py
@@ -10,157 +10,257 @@
 """This module implements functions for dividing time into chunks."""
 
 import calendar
-import time
+
+from datetime import datetime
 
 from zope import interface
 from zope.interface import implements
+from zope.interface import Attribute
+
+
+#: The known time intervals (or *periods*) for dividing time by.
+KNOWN_INTERVALS = ["second", "minute", "hour", "day", "week", "month"]
+
+
+class UnknownInterval(ValueError):
+    """Raised if an interval isn't one of the :data:`KNOWN_INTERVALS`."""
+
 
-KNOWN_INTERVALS = [ "hour", "day", "week", "month" ]
+def toUnixSeconds(timestruct):
+    """Convert a datetime struct to a Unix timestamp in seconds.
+
+    :param timestruct: A ``datetime.datetime`` object to convert into a
+        timestamp in Unix Era seconds.
+    :rtype: int
+    """
+    return calendar.timegm(timestruct)
+
+def fromUnixSeconds(timestamp):
+    """Convert a Unix timestamp to a datetime struct.
+
+    :param int timestamp: A timestamp in Unix Era seconds.
+    :rtype: :type:`datetime.datetime`
+    """
+    return datetime.fromtimestamp(timestamp)
 
 
 class ISchedule(interface.Interface):
     """A ``Interface`` specification for a Schedule."""
 
-    def intervalStart(when):
-        """Set the start time of the current interval to **when**."""
+    intervalPeriod = Attribute(
+        "The type of period which this Schedule's intervals will rotate by.")
+    intervalCount = Attribute(
+        "Number of **intervalPeriod**s before rotation to the next interval")
+
+    def intervalStart(when=None):
+        """Get the start time of the interval that contains **when**."""
 
     def getInterval(when=None):
         """Get the interval which includes an arbitrary **when**."""
 
-    def nextIntervalStarts():
-        """Get the start time for the next interval."""
+    def nextIntervalStarts(when=None):
+        """Get the start of the interval after the one containing **when**."""
 
 
-class ScheduleBase(object):
-    """Base class for all ``Schedule`` classes."""
+class Unscheduled(object):
+    """A base ``Schedule`` that has only one period that contains all time."""
 
     implements(ISchedule)
 
-    def intervalStart(self, when):
-        pass
+    def __init__(self, period=None, count=None):
+        """Create a schedule for dividing time into intervals.
 
-    def getInterval(self, when=None):
-        pass
+        :param str period: One of the periods in :data:`KNOWN_INTERVALS`.
+        :param int count: The number of **period**s in an interval.
+        """
+        self.intervalCount = count
+        self.intervalPeriod = period
 
-    def nextIntervalStarts(self):
-        pass
+    def intervalStart(self, when=0):
+        """Get the start time of the interval that contains **when**.
 
+        :param int when: The time which we're trying to find the corresponding
+            interval for.
+        :rtype: int
+        :returns: The Unix epoch timestamp for the start time of the interval
+            that contains **when**.
+        """
+        return toUnixSeconds(datetime.min.timetuple())
+
+    def getInterval(self, when=0):
+        """Get the interval that contains the time **when**.
 
-class IntervalSchedule(ScheduleBase):
-    """An IntervalSchedule splits time into somewhat natural periods, based on
+        :param int when: The time which we're trying to find the corresponding
+            interval for.
+        :rtype: str
+        :returns: A timestamp in the form ``YEAR-MONTH[-DAY[-HOUR]]``. It's
+            specificity depends on what type of interval we're using. For
+            example, if using ``"month"``, the return value would be something
+            like ``"2013-12"``.
+        """
+        return fromUnixSeconds(when).strftime('%04Y-%02m-%02d %02H:%02M:%02S')
+
+    def nextIntervalStarts(self, when=0):
+        """Return the start time of the interval starting _after_ when.
+
+        :rtype: int
+        :returns: Return the Y10K bug.
+        """
+        return toUnixSeconds(datetime.max.timetuple())
+
+
+class ScheduledInterval(Unscheduled):
+    """An class that splits time into periods, based on seconds, minutes,
     hours, days, weeks, or months.
 
-    :ivar str itype: One of "month", "day", "hour".
-    :ivar int count: How many of the units in :ivar:`itype` belong to each period.
+    :ivar str intervalPeriod: One of the :data:`KNOWN_INTERVALS`.
+    :ivar int intervalCount: The number of times **intervalPeriod** should be
+        repeated within an interval.
     """
+    implements(ISchedule)
+
+    def __init__(self, period=None, count=None):
+        """Create a schedule for dividing time into intervals.
+
+        :param str period: One of the periods in :data:`KNOWN_INTERVALS`.
+        :param int count: The number of **period**s in an interval.
+        """
+        super(ScheduledInterval, self).__init__(period, count)
+        self._setIntervalCount(count)
+        self._setIntervalPeriod(period)
 
-    def __init__(self, intervaltype, count):
-        """Divide time into intervals of **count** number of **intervaltype**.
+    def _setIntervalCount(self, count=None):
+        """Set our :ivar:`intervalCount`.
 
-        :param str intervaltype: One of ``'month'``, ``'week'``, ``'day'``,
-            or ``'hour'``.
+        .. attention:: This method should be called _before_
+            :meth:`_setIntervalPeriod`, because the latter may change the
+            count, if it decides to change the period (for example, to
+            simplify things by changing weeks into days).
 
-        :param int count: How many of the units in **intervaltype** belong to
-            each period.
+        :param int count: The number of times the :ivar:`intervalPeriod`
+            should be repeated during the interval. Defaults to ``1``.
+        :raises UnknownInterval: if the specified **count** was invalid.
+        """
+        try:
+            if not count > 0:
+                count = 1
+            count = int(count)
+        except (TypeError, ValueError):
+            raise UnknownInterval("%s.intervalCount: %r ist not an integer."
+                                  % (self.__class__.__name__, count))
+        self.intervalCount = count
+
+    def _setIntervalPeriod(self, period=None):
+        """Set our :ivar:`intervalPeriod`.
+
+        :param str period: One of the :data:`KNOWN_INTERVALS`, or its
+            plural. Defaults to ``'hour'``.
+        :raises UnknownInterval: if the specified **period** is unknown.
         """
-        it = intervaltype.lower()
-        if it.endswith("s"): it = it[:-1]
-        if it not in KNOWN_INTERVALS:
-            raise TypeError("What's a %s?"%it)
-        assert count > 0
-        if it == 'week':
-            it = 'day'
-            count *= 7
-        self.itype = it
-        self.count = count
-
-    def intervalStart(self, when):
+        if not period:
+            period = 'hour'
+        try:
+            period = period.lower()
+            # Depluralise the period if necessary, i.e., "months" -> "month".
+            if period.endswith('s'):
+                period = period[:-1]
+
+            if not period in KNOWN_INTERVALS:
+                raise ValueError
+        except (TypeError, AttributeError, ValueError):
+            raise UnknownInterval("%s doesn't know about the %r interval type."
+                                  % (self.__class__.__name__, period))
+        self.intervalPeriod = period
+
+        if period == 'week':
+            self.intervalPeriod = 'day'
+            self.intervalCount *= 7
+
+    def intervalStart(self, when=0):
         """Get the start time of the interval that contains **when**.
 
+        :param int when: The time which we're trying to determine the start of
+            interval that contains it. This should be given in Unix seconds,
+            for example, taken from :func:`calendar.timegm`.
         :rtype: int
         :returns: The Unix epoch timestamp for the start time of the interval
             that contains **when**.
         """
-        if self.itype == 'month':
+        if self.intervalPeriod == 'month':
             # For months, we always start at the beginning of the month.
-            tm = time.gmtime(when)
-            n = tm.tm_year * 12 + tm.tm_mon - 1
-            n -= (n % self.count)
-            month = n%12 + 1
-            return calendar.timegm((n//12, month, 1, 0, 0, 0))
-        elif self.itype == 'day':
+            date = fromUnixSeconds(when)
+            months = (date.year * 12) + (date.month - 1)
+            months -= (months % self.intervalCount)
+            month = months % 12 + 1
+            return toUnixSeconds((months // 12, month, 1, 0, 0, 0))
+        elif self.intervalPeriod == 'day':
             # For days, we start at the beginning of a day.
-            when -= when % (86400 * self.count)
+            when -= when % (86400 * self.intervalCount)
             return when
-        elif self.itype == 'hour':
+        elif self.intervalPeriod == 'hour':
             # For hours, we start at the beginning of an hour.
-            when -= when % (3600 * self.count)
+            when -= when % (3600 * self.intervalCount)
+            return when
+        elif self.intervalPeriod == 'minute':
+            when -= when % (60 * self.intervalCount)
+            return when
+        elif self.intervalPeriod == 'second':
+            when -= when % self.intervalCount
             return when
-        else:
-            assert False
 
-    def getInterval(self, when):
+    def getInterval(self, when=0):
         """Get the interval that contains the time **when**.
 
         >>> import calendar
-        >>> from bridgedb.Time import IntervalSchedule
-        >>> t = calendar.timegm((2007, 12, 12, 0, 0, 0))
-        >>> I = IntervalSchedule('month', 1)
-        >>> I.getInterval(t)
+        >>> from bridgedb.Time import ScheduledInterval
+        >>> sched = ScheduledInterval('month', 1)
+        >>> when = calendar.timegm((2007, 12, 12, 0, 0, 0))
+        >>> sched.getInterval(when)
         '2007-12'
+        >>> then = calendar.timegm((2014, 05, 13, 20, 25, 13))
+        >>> sched.getInterval(then)
+        '2014-05'
 
         :param int when: The time which we're trying to find the corresponding
-                         interval for.
+            interval for. Given in Unix seconds, for example, taken from
+            :func:`calendar.timegm`.
         :rtype: str
         :returns: A timestamp in the form ``YEAR-MONTH[-DAY[-HOUR]]``. It's
-                  specificity depends on what type of interval we're
-                  using. For example, if using ``"month"``, the return value
-                  would be something like ``"2013-12"``.
+            specificity depends on what type of interval we're using. For
+            example, if using ``"month"``, the return value would be something
+            like ``"2013-12"``.
+        """
+        date = fromUnixSeconds(self.intervalStart(when))
+
+        fstr = "%04Y-%02m"
+        if self.intervalPeriod != 'month':
+            fstr += "-%02d"
+            if self.intervalPeriod != 'day':
+                fstr += " %02H"
+                if self.intervalPeriod != 'hour':
+                    fstr += ":%02M"
+                    if self.intervalPeriod == 'minute':
+                        fstr += ":%02S"
+
+        return date.strftime(fstr)
+
+    def nextIntervalStarts(self, when=0):
+        """Return the start time of the interval starting _after_ when.
+
+        :returns: The Unix epoch timestamp for the start time of the interval
+            that contains **when**.
         """
-        if self.itype == 'month':
-            tm = time.gmtime(when)
-            n = tm.tm_year * 12 + tm.tm_mon - 1
-            n -= (n % self.count)
-            month = n%12 + 1
-            return "%04d-%02d" % (n // 12, month)
-        elif self.itype == 'day':
-            when = self.intervalStart(when) + 7200 #slop
-            tm = time.gmtime(when)
-            return "%04d-%02d-%02d" % (tm.tm_year, tm.tm_mon, tm.tm_mday)
-        elif self.itype == 'hour':
-            when = self.intervalStart(when) + 120 #slop
-            tm = time.gmtime(when)
-            return "%04d-%02d-%02d %02d" % (tm.tm_year, tm.tm_mon, tm.tm_mday,
-                                            tm.tm_hour)
-        else:
-            assert False
-
-    def nextIntervalStarts(self, when):
-        """Return the start time of the interval starting _after_ when."""
-        if self.itype == 'month':
-            tm = time.gmtime(when)
-            n = tm.tm_year * 12 + tm.tm_mon - 1
-            n -= (n % self.count)
-            month = n%12 + 1
-            tm = (n // 12, month+self.count, 1, 0,0,0)
-            return calendar.timegm(tm)
-        elif self.itype == 'day':
-            return self.intervalStart(when) + 86400 * self.count
-        elif self.itype == 'hour':
-            return self.intervalStart(when) + 3600 * self.count
-
-
-class NoSchedule(ScheduleBase):
-    """A Schedule that has only one period for all time."""
-
-    def __init__(self):
-        pass
-
-    def intervalStart(self, when):
-        return 0
-
-    def getInterval(self, when):
-        return "1970"
-
-    def nextIntervalStarts(self, when):
-        return 2147483647L # INT32_MAX
+        seconds = self.intervalStart(when)
+
+        if self.intervalPeriod == 'month':
+            date = fromUnixSeconds(seconds)
+            months = date.month + self.intervalCount
+            return toUnixSeconds((date.year, months, 1, 0, 0, 0))
+        elif self.intervalPeriod == 'day':
+            return seconds + (86400 * self.intervalCount)
+        elif self.intervalPeriod == 'hour':
+            return seconds + (3600 * self.intervalCount)
+        elif self.intervalPeriod == 'minute':
+            return seconds + (60 * self.intervalCount)
+        elif self.intervalPeriod == 'second':
+            return seconds + self.intervalCount





More information about the tor-commits mailing list