我使用dateutil.rerelativelta来计算年龄。我在核实结果时发现,在某些情况下,这是不正确的。仅当开始日期的日期为1,月底为31时才会出现此问题,但并非所有这些月都会出现此问题!奇怪的是,1月和8月的结果是正确的。我确实错过了一些东西,但无法确定是什么,在哪里。
我在Win32上使用python 2.7.10和dateutil 2.7.5(但2.6.1的问题相同(
要运行的代码:
from __future__ import print_function
import datetime
from dateutil.relativedelta import relativedelta
# Main function -- later on, its result is used in dateutil.rrule
# (all irrelevant code expurged)
def age(start, end, includeFirstDay=False):
# includeFirstDay: whether to include the start date in the result
if includeFirstDay:
start += relativedelta(days=-1)
# Documentation tells nothing about the order of arguments when the first
# two are dates. The following seems correct.
return relativedelta(end, start)
# Utility in external module
def eomonth(dt):
# from the beginning of month, add one month and substract one day
eom = dt + relativedelta(days=-dt.day+1) + relativedelta(months=+1, days=-1)
return eom
test_values = [
# the 3 following lines fail
(datetime.date(2018, 10, 1), datetime.date(2018, 10, 31)),
(datetime.date(2018, 10, 1), datetime.date(2018, 11, 1)+relativedelta(days=-1)),
(datetime.date(2018, 10, 1), eomonth(datetime.date(2018, 10, 1))),
# the following lines pass
(datetime.date(2018, 10, 5), eomonth(datetime.date(2018, 10, 5))),
(datetime.date(2016, 2, 1), datetime.date(2016, 2, 29)),
(datetime.date(2016, 2, 1), datetime.date(2016, 3, 1)+relativedelta(days=-1)),
(datetime.date(2016, 2, 1), eomonth(datetime.date(2016, 2, 1))),
]
def test(start, end, includeFirstDay=False):
rd = age(start, end, includeFirstDay=includeFirstDay)
# calculate end date from age
d = rd.days + (-1 if includeFirstDay else 0)
fin = start + relativedelta(years=rd.years, months=rd.months, days=d)
if fin != end: # i.e. AssertionError
print('expected %s, got %s (includeFirstDay=%s)' %(end, fin, includeFirstDay))
for start, end in test_values:
test(start, end, includeFirstDay=False)
test(start, end, includeFirstDay=True)
# trying with all months in a year
def make_test(year):
t = []
for month in range(1, 13):
start = datetime.date(year, month, 1)
# Three ways to find the end of the month
t.append((start, eomonth(start)))
t.append((start, start + relativedelta(months=+1, days=-1))) # only if start day == 1
for eom in [31, 30, 29, 28]: # brute force
try:
t.append((start, datetime.date(year, month, eom)))
break
except:
pass
return sorted(list(set(t)))
from pprint import pprint
print('n*** testing 2016 (leap year)')
test_values = make_test(2016)
pprint(test_values) # verify ends of months are correct
for start, end in test_values:
test(start, end, includeFirstDay=False)
test(start, end, includeFirstDay=True)
print('n*** testing 2017')
test_values = make_test(2017)
pprint(test_values) # verify ends of months are correct
for start, end in test_values:
test(start, end, includeFirstDay=False)
test(start, end, includeFirstDay=True)
输出:
expected 2018-10-31, got 2018-11-01 (includeFirstDay=True)
expected 2018-10-31, got 2018-11-01 (includeFirstDay=True)
expected 2018-10-31, got 2018-11-01 (includeFirstDay=True)
*** testing 2016 (leap year)
[(datetime.date(2016, 1, 1), datetime.date(2016, 1, 31)),
(datetime.date(2016, 2, 1), datetime.date(2016, 2, 29)),
(datetime.date(2016, 3, 1), datetime.date(2016, 3, 31)),
(datetime.date(2016, 4, 1), datetime.date(2016, 4, 30)),
(datetime.date(2016, 5, 1), datetime.date(2016, 5, 31)),
(datetime.date(2016, 6, 1), datetime.date(2016, 6, 30)),
(datetime.date(2016, 7, 1), datetime.date(2016, 7, 31)),
(datetime.date(2016, 8, 1), datetime.date(2016, 8, 31)),
(datetime.date(2016, 9, 1), datetime.date(2016, 9, 30)),
(datetime.date(2016, 10, 1), datetime.date(2016, 10, 31)),
(datetime.date(2016, 11, 1), datetime.date(2016, 11, 30)),
(datetime.date(2016, 12, 1), datetime.date(2016, 12, 31))]
expected 2016-03-31, got 2016-04-02 (includeFirstDay=True)
expected 2016-05-31, got 2016-06-01 (includeFirstDay=True)
expected 2016-07-31, got 2016-08-01 (includeFirstDay=True)
expected 2016-10-31, got 2016-11-01 (includeFirstDay=True)
expected 2016-12-31, got 2017-01-01 (includeFirstDay=True)
*** testing 2017
[(datetime.date(2017, 1, 1), datetime.date(2017, 1, 31)),
(datetime.date(2017, 2, 1), datetime.date(2017, 2, 28)),
(datetime.date(2017, 3, 1), datetime.date(2017, 3, 31)),
(datetime.date(2017, 4, 1), datetime.date(2017, 4, 30)),
(datetime.date(2017, 5, 1), datetime.date(2017, 5, 31)),
(datetime.date(2017, 6, 1), datetime.date(2017, 6, 30)),
(datetime.date(2017, 7, 1), datetime.date(2017, 7, 31)),
(datetime.date(2017, 8, 1), datetime.date(2017, 8, 31)),
(datetime.date(2017, 9, 1), datetime.date(2017, 9, 30)),
(datetime.date(2017, 10, 1), datetime.date(2017, 10, 31)),
(datetime.date(2017, 11, 1), datetime.date(2017, 11, 30)),
(datetime.date(2017, 12, 1), datetime.date(2017, 12, 31))]
expected 2017-03-31, got 2017-04-03 (includeFirstDay=True)
expected 2017-05-31, got 2017-06-01 (includeFirstDay=True)
expected 2017-07-31, got 2017-08-01 (includeFirstDay=True)
expected 2017-10-31, got 2017-11-01 (includeFirstDay=True)
expected 2017-12-31, got 2018-01-01 (includeFirstDay=True)
此外,如果可以的话,3月的结束日期甚至更错误(这个词存在(。
如果有人能启发我,我将不胜感激。提前谢谢。
实际上,我做错了事情。1月和8月表现"正常"的原因:对于这些月,前几个月也有31天。为什么三月对我来说很奇怪:二月比少2或3天
例如:
start = datetime.date(2018, 3, 1)
end = start + relativedelta(months=+1, days=-1)
a = age(start, end, True)
print 'start:', start
print 'end :', end
print 'age :', a
输出:
start: 2018-03-01
end : 2018-03-31
age : relativedelta(months=+1, days=+3)
我期待着类似relativelta(天=+31(的东西。
根据提供开始日期和结束日期的方式,relativelta返回不同的结果。示例:
relativedelta(datetime.date(2018, 4, 1), datetime.date(2018, 3, 1))
给出
relativedelta(months=+1)
和
relativedelta(datetime.date(2018, 3, 31), datetime.date(2018, 2, 28))
给出
relativedelta(months=+1, days=+3)
无论如何完成,开始日期和结束日期之间的差值应始终等于31天。
我的结论是,当计算其中一个日期时,不要使用relativelta来计算从第一天到最后一天(包括所有日子(之间的单个月的年龄。
或者修改函数age((,我可能会这么做
编辑
在不久的将来,我将删除这个问题,因为我认为它提出的观点应该包含在dateutil文档中的"角落案例"类别中。
from __future__ import print_function
import datetime
from datetime import date
from dateutil.relativedelta import relativedelta
today= date.today()
end_day=today.replace(day=1)
end_day += relativedelta(months=1)
end_day -= relativedelta(days=1)
print(end_day)
我的函数age((写得很糟糕。这里是正确的一个:
def age(start, end, includeFirstDay=False):
r = relativedelta(end, start)
if includeFirstDay:
r += relativedelta(days=1)
return r
解释:将start
抵消-1天,将日期移动到上个月,并愚弄了被我的意图弄糊涂的亲戚。