在Python / dateutil中将两个时间跨度相互划分

时间:2013-03-29 15:09:46

标签: python python-dateutil

请考虑这个问题:

我有一个过去从我开始添加句号的日期。当添加这些句点导致日期大于今天时,我想停下来,并检查最后一个日期是什么。

这是用于计算会员资格中的借记日期的功能。例如,一名成员加入,2007-01-31。他每月都会被扣款。我们今天说的是2013-03-29(它实际上是atm)。所以我需要从2007-01-31开始计算几个月,当我过了今天的日期时,我需要停下来。然后我可以看到下一个借记日期是2013-03-31。

我正在使用dateutil库来实现这一点,在while循环中添加relativedelta,直到我超过当前日期。 (我知道它可能不是最好的方式,但我仍然是Python的新手,这是一个概念验证)。问题是,当我在2007-01-31添加一个月时,下一个日期是2007-02-28,这是正确的。但是下一次迭代的日期是2007-03-28,因为dateutil不会将第28天识别为该月的最后一天,以保持其完好无损并迭代到3月的最后一天。当然,这是一个完全有效的实施。然后我尝试使用dateutils rrule对象,但它具有相同的原理。它输出一个日期列表,但它只是跳过没有足够日期的月份。

period = rrule(MONTHLY, interval=1, dtstart=datetime.date(2012, 5, 31), until=datetime.date(2013, 3, 29))
print(list(period))

然后我想到了另一种方法:

如果我可以计算2007-01-31和2013-03-29之间的时间段内的时段数,我可以将这些时段添加到startdate,而dateutil将返回正确的日期。
问题是这段时间并不总是一个月。例如,它也可以是四周,四分之一或一年。

我无法找到一种方法来取一个相对的delta并将它与另一个相对的delta分开,以便后者在第一个中获得多次。

如果有人能指出我正确的方向,我会很感激。例如,是否存在可以执行此操作的库(将时间间隔相互划分,并在给定的时间块中输出结果,如数月或数周)?是否有一个约会函数接受一个句点作为输入(我知道例如in vbscript you can get the difference between two dates in whatever period you want,无论是几周,几个月,几天,等等)。是否有完全不同的解决方案?

为了完整起见,我将包含代码,但我认为文本已经解释了所有内容:

def calculate(self, testdate=datetime.date.today()):
    self._testdate = testdate


    while self.next < self._testdate:
        self.next += self._ddinterval
    self.previous = self.next - self._ddinterval
    return self.next

谢谢,

埃里克

编辑:我现在有一个解决方案能够达到预期效果,但它几乎不是Pythonic,优雅或快速的。所以问题仍然存在,如果有人能提出更好的解决方案,请做。这就是我想出的:

def calculate(self, testdate=datetime.date.today()):
    self._testdate = testdate

    start = self.next
    count = 0
    while self.next < self._testdate:
        count += 1
        self.next = start + (count * self._ddinterval)
    self.previous = self.next - self._ddinterval
    return self.next

2 个答案:

答案 0 :(得分:2)

在添加句点作为下一个循环的起点后,不要使用新值,而是创建一个不断增加的 delta

today = datetime.date.today()
start = datetime.date(2007, 1, 31)
period = relativedelta(months=1)

delta = period
while start + delta < today:
    delta += period

next = start + delta

这导致:

>>> import datetime
>>> from dateutil.relativedelta import relativedelta
>>> today = datetime.date.today()
>>> start = datetime.date(2007, 1, 31)
>>> period = relativedelta(months=1)
>>> delta = period
>>> while start + delta < today:
...     delta += period
... 
>>> delta
relativedelta(years=+6, months=+3)
>>> start + delta
datetime.date(2013, 4, 30)

适用于任何可变周期长度,包括季度和年份。对于以精确数周或天计算的时段,您也可以使用timedelta()对象,但这是一种通用解决方案。

使用可变宽度期间(例如月份和季度)时,不能“划分”时间段。对于整天测量的时间段,您可以简单地将两个日期之间的差异转换为天数(从时间delta),然后获得模数:

period = datetime.timedelta(days=28)  # 4 weeks
delta = today - start
remainder = delta.days % period.days
end = today + datetime.timedelta(days=remainder)

给出:

>>> period = datetime.timedelta(days=28)  # 4 weeks
>>> delta = today - start
>>> remainder = delta.days % period.days
>>> today + datetime.timedelta(days=remainder)
datetime.date(2013, 4, 17)

答案 1 :(得分:0)

如果您的delta相对于基准时间是可变的(即1个月可能意味着28-31天中的任何一个),那么您就会陷入循环。

但是,如果delta是常数日计数,则可以通过转换为整数并进行模运算来回避迭代。