[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]
[tor-commits] [bridgedb/master] Rewrite bridgedb.Time module.
commit ff5980fe8bab178bc113964a0e6c31a531387c2f
Author: Isis Lovecruft <isis@xxxxxxxxxxxxxx>
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
_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits