两个日期之间的Pythonic年份差异?

97

这里有一种更加高效的方法来实现下面的操作吗?我想要计算两个日期之间的年份差作为一个单一的标量。欢迎任何建议。

from datetime import datetime
start_date = datetime(2010,4,28,12,33)
end_date = datetime(2010,5,5,23,14)
difference  = end_date - start_date
difference_in_years = (difference.days + difference.seconds/86400)/365.2425

10
要明确表达,请使用浮点常量而不是整数常量。您的最后一行应为 difference_in_years = (difference.days + difference.seconds/86400.0)/365.2425,以在 Python 2.X 中运行时得到预期的答案。 - John Machin
@John Machin 很好的观点,我没有想到。 - c00kiemonster
通过在整数末尾添加“.0”,代码立即变得独立且明确,无需考虑正在运行的Python版本以及已包含在模块顶部的废话。@Lennart Regebro - John Machin
如果不需要任何复杂术语,仍需要版本探讨。 - John Machin
1
NASA在1997年表示一年有365.2422天。https://pumas.nasa.gov/files/04_21_97_1.pdf - Marichyasana
显示剩余10条评论
15个回答

142
如果您想要精确的结果,我建议使用dateutil库。
from dateutil.relativedelta import relativedelta
difference_in_years = relativedelta(end_date, start_date).years

这是针对完整年份的计算(例如一个人的年龄)。如果您想要小数年份,则需要加上月份、天数、小时等,以达到所需的精度。


30

我使用以下方法来计算一个人的年龄:

import datetime
dob = datetime.date(1980, 10, 10)

def age():
    today = datetime.date.today()
    years = today.year - dob.year
    if today.month < dob.month or (today.month == dob.month and today.day < dob.day):
        years -= 1
    return years

def age2():
    today = datetime.date.today()
    this_year_birthday = datetime.date(today.year, dob.month, dob.day)
    if this_year_birthday < today:
        years = today.year - dob.year
    else:
        years = today.year - dob.year - 1
    return years

18

只需这样做:

from dateutil.relativedelta import relativedelta

myBirthday = datetime.datetime(1983,5,20,0,0,0,0)
now = datetime.datetime.now()



difference = relativedelta(now, myBirthday)
print("My years: "+str(difference.years))

9
更高效?不一定,但更正确可能性更大。但这取决于您想要多正确。日期并不是微不足道的事情。
年份长度不是恒定的。您想要闰年或普通年份之间的差异吗? :-) 在计算时,您总是会得到一个略有不正确的答案。一年有多长?你说1/365.2425。是的,在一千年内平均下来是这样的。但在其他情况下不是这样的。
所以这个问题实际上没有太多意义。
要正确,您必须这样做:
from datetime import datetime
from calendar import isleap
start_date = datetime(2005,4,28,12,33)
end_date = datetime(2010,5,5,23,14)
diffyears = end_date.year - start_date.year
difference  = end_date - start_date.replace(end_date.year)
days_in_year = isleap(end_date.year) and 366 or 365
difference_in_years = diffyears + (difference.days + difference.seconds/86400.0)/days_in_year

在这种情况下,考虑到这不是闰年,差异为0.0012322917425568528年或0.662天。(我们忽略微秒。呵呵。)

6
为了理解闰年,你几乎不得不将其分为两部分:整数年和小数部分。两者都需要处理闰年,但处理方式不同——整数部分需要处理2月29日作为起始日期,而小数部分必须处理一年中不同的天数。希望小数部分增加相等的量,直到在下一个周年纪念日达到1.0,因此它应该基于结束日期之后一年中的天数。
如果您的日期范围包括1900年或2100年怎么办?如果不包括,事情会变得更容易一些。
编辑:我用了很长时间来推理这个问题。基本问题是日历年份不是恒定大小,但您正在通过将它们设置为1.0来强制它们成为恒定大小。您想出的任何解决方案都会因此产生异常情况,并且您必须选择可以接受的异常情况。John Machin是正确的。
2008-02-28和2009-02-28之间有什么区别?大多数人都会认为应该正好是1.0年。2008-03-01和2009-03-01之间的差异如何?同样,大多数人都会认为应该正好是1.0年。如果您选择将日期表示为基于日期的年份加上一年的分数,则无法使这两个语句都为真。这适用于您原始的代码,该代码假定一天是一年的1/365.2425,或者对于任何假定每天恒定的一年的一部分的代码,即使一天的大小考虑了闰年。
我断言您需要将其分解为整数年和小数年,是试图解决此问题的尝试。如果您将每个先前的条件视为整数年,则只需决定要分配给任何剩余天数的分数即可。这种方案的问题在于,仍然无法理解(日期2-日期1)+日期3,因为分数无法以任何一致的方式解析回一天。
因此,我提出了另一种编码,基于每年包含366天,无论是否为闰年。异常情况首先是不能有一个日期恰好距离2月29日一年(或2年或3年)——“抱歉约翰尼,今年你没有生日,没有2月29日”并不总是可接受的。第二个是,如果您试图将这样的数字强制转换回日期,您将不得不考虑非闰年,并检查2月29日的特殊情况并将其转换,可能是3月1日。
from datetime import datetime
from datetime import timedelta
from calendar import isleap

size_of_day = 1. / 366.
size_of_second = size_of_day / (24. * 60. * 60.)

def date_as_float(dt):
    days_from_jan1 = dt - datetime(dt.year, 1, 1)
    if not isleap(dt.year) and days_from_jan1.days >= 31+28:
        days_from_jan1 += timedelta(1)
    return dt.year + days_from_jan1.days * size_of_day + days_from_jan1.seconds * size_of_second

start_date = datetime(2010,4,28,12,33)
end_date = datetime(2010,5,5,23,14)
difference_in_years = date_as_float(end_time) - date_as_float(start_time)

我并不是在说这是唯一的解决方案,因为我认为完美的解决方案是不存在的。但它具有一些可取之处:

  • 任何相同月份、相同日期和时间的两个日期之间的差值将是一个确切的年数。
  • 将一个差值添加到另一个日期将得到一个可以转换回有用日期的值。

2

这是基于Kostyantyn在他的“age2”函数中发布的内容的一个衍生版本。它略微更短/更简洁,同时也使用了“年龄”或年份差异的传统/口语含义:

def ageInYears( d ):
    today = datetime.date.today()
    currentYrAnniversary = datetime.date( today.year, d.month, d.day )
    return (today.year - d.year) - (1 if today < currentYrAnniversary else 0)

2
由于我们即将走到 2018 的尽头…
from dateutil import parser
from dateutil.relativedelta import relativedelta

rip = [
    ["Tim Bergling\t\t",         " 8 Sep 1989", "20 Apr 2018"], # Avicii Swedish musician
    ["Stephen Hillenburg\t",     "21 Aug 1961", "26 Nov 2018"], # Creator of Spongebob
    ["Stephen Hawking\t\t",      " 8 Jan 1942", "14 Mar 2018"], # Theoretical physicist
    ["Stan Lee\t\t",             "28 Dec 1922", "12 Nov 2018"], # American comic book writer
    ["Stefán Karl Stefánsson\t", "10 Jul 1975", "21 Aug 2018"]  # Robbie Rotten from LazyTown
    ]

for name,born,died in rip:
    print("%s %s\t %s\t died at %i"%(name,born,died,relativedelta(parser.parse(died),parser.parse(born)).years))

输出

Tim Bergling              8 Sep 1989     20 Apr 2018     died at 28
Stephen Hillenburg       21 Aug 1961     26 Nov 2018     died at 57
Stephen Hawking           8 Jan 1942     14 Mar 2018     died at 76
Stan Lee                 28 Dec 1922     12 Nov 2018     died at 95
Stefán Karl Stefánsson   10 Jul 1975     21 Aug 2018     died at 43

2

我想您正在寻找的是:

difference_in_years = difference.dt.days / 365.25

0
如果您已经将DOB作为字符串,则可以执行以下操作:
from datetime import datetime as dt

def get_age(dob_str):
    now_str = dt.strftime(dt.utcnow(), '%Y-%m-%d')
    return int(now_str[:4]) - int(dob_str[:4]) - int(dob_str[5:] > now_str[5:])

或者,如果您想编写一些单元测试,请将now_str设置为命名参数:

from datetime import datetime as dt

def get_age(dob_str, now_str=dt.strftime(dt.utcnow(), '%Y-%m-%d')):
    return int(now_str[:4]) - int(dob_str[:4]) - int(dob_str[5:] > now_str[5:])

0

这是我想出来的,没有使用外部依赖:

def year_diff(d1, d2):
    """Returns the number of years between the dates as a positive integer."""
    later = max(d1, d2)
    earlier = min(d1, d2)

    result = later.year - earlier.year
    if later.month < earlier.month or (later.month == earlier.month and later.day < earlier.day):
        result -= 1

    return result

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接