以年为单位的两个日期之间的 Pythonic 差异?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/4436957/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Pythonic difference between two dates in years?
提问by c00kiemonster
Is there a more efficient way of doing this below? I want to have the difference in years between two dates as a single scalar. Any suggestions are welcome.
下面有更有效的方法吗?我想将两个日期之间的年差作为单个标量。欢迎任何建议。
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
回答by dan_waterworth
If you mean efficient in terms of code space then no, that's about the most efficient way to do that.
如果您的意思是在代码空间方面高效,那么不,那是最有效的方法。
回答by Lennart Regebro
More efficient? No, but more correct, probably. But it depends on how correct you want to be. Dates are not trivial things.
更高效?不,但可能更正确。但这取决于您想要的正确程度。日期不是小事。
Years do not have a constant length. Do you want the difference in leap years or normal years? :-) As you calculate you are always going to get a slightly incorrect answer. And how long is a day in years? You say 1/365.2425. Well, yeah, averaged over a thousand years, yeah. But otherwise not.
年没有固定的长度。你想要闰年或正常年的差异吗?:-) 当你计算时,你总是会得到一个稍微不正确的答案。一天是多少年?你说 1/365.2425。嗯,是的,平均超过一千年,是的。但否则不会。
So the question doesn't really make much sense.
所以这个问题真的没有多大意义。
To be correct you have to do this:
要正确,你必须这样做:
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
In this case that's a difference of 0.0012322917425568528 years, or 0.662 days, considering that this is not a leap year.
在这种情况下,考虑到这不是闰年,这是 0.0012322917425568528 年或 0.662 天的差异。
(and then we are ignoring microseconds. Heh.)
(然后我们忽略了微秒。呵呵。)
回答by Mark Ransom
To make sense of leap years, you are almost forced to break this into two parts: an integral number of years, and a fractional part. Both need to deal with leap years, but in different ways - the integral needs to deal with a starting date of February 29, and the fractional must deal with the differing number of days in a year. You want the fractional part to increment in equal amounts until it equals 1.0 at the next anniversary date, so it should be based on the number of days in the year afterthe end date.
为了理解闰年,您几乎不得不将其分为两部分:整数年数和小数部分。两者都需要处理闰年,但方式不同——积分需要处理 2 月 29 日的开始日期,而小数必须处理一年中不同的天数。您希望小数部分以相等的量递增,直到它在下一个周年日期等于 1.0,因此它应该基于结束日期之后一年中的天数。
Do you want your date range to include 1900 or 2100? Things get a little easier if you don't.
您希望日期范围包括 1900 年还是 2100 年?如果你不这样做,事情会变得容易一些。
Edit:编辑:我花了很长时间才明白这一点。基本问题是日历年的大小不是恒定的,但是您通过将它们设置为 1.0 来强制它们保持恒定。因此,您提出的任何解决方案都会出现异常,您将不得不选择可以接受哪些异常。约翰·马钦是对的。
What's the difference between 2008-02-28 and 2009-02-28? Most people would agree that it should be exactly 1.0 years. How about the difference between 2008-03-01 and 2009-03-01? Again, most people would agree that it should be exactly 1.0 years. If you choose to represent a date as a year plus a fraction of a year based on the day, it is impossible to make both of these statements true. This is the case for your original code which assumed a day was 1/365.2425 of a year, or indeed for any code which assumes a constant fraction of a year per day, even if the size of a day accounts for the years which are leap years.
2008-02-28 和 2009-02-28 有什么区别?大多数人会同意它应该正好是 1.0 年。2008-03-01 和 2009-03-01 有什么区别?同样,大多数人会同意它应该正好是 1.0 年。如果您选择将日期表示为一年加上基于日期的一年的一小部分,则不可能使这两个陈述都为真。这是假设一天是一年的 1/365.2425 的原始代码的情况,或者实际上对于假设每天是一年的恒定分数的任何代码,即使一天的大小占了闰年年。
My assertion that you needed to break this down into integral years and fractional years was an attempt to get around this problem. If you treat each of the previous conditions as an integral year, all you have to do is decide on which fraction to assign to any number of days left over. The problem with this scheme is that you still can't make sense of (date2-date1)+date3, because the fraction can't be resolved back to a day with any consistency.
我断言你需要把它分解成整数年和小数年是为了解决这个问题。如果您将前面的每个条件都视为一个完整的年份,您所要做的就是决定将哪个分数分配给剩余的任意天数。这个方案的问题是你仍然无法理解(date2-date1)+date3,因为分数不能以任何一致性解决回到一天。
Thus I am proposing yet another encoding, based on each year containing 366 days whether it is a leap year or not. The anomalies will firstly be that there can't be a date which is exactly a year (or 2 or 3) from Feb. 29 - "Sorry Johnny, you don't get a birthday this year, there's no Feb. 29" isn't always acceptable. Second is that if you try to coerce such a number back to a date, you'll have to account for non-leap years and check for the special case of Feb. 29 and convert it, probably to Mar. 1.
因此,我提出了另一种编码,基于每年包含 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)
I'm not suggesting that this is thesolution, because I don't think a perfect solution is possible. But it has some desirable properties:
我并不是说,这是该解决方案,因为我不认为一个完美的解决方案是可能的。但它有一些理想的特性:
- The difference between any dates with the same month and day and time will be an exact number of years.
- Adding a difference to another date will result in a value that can be converted back into a useful date.
- 具有相同月份、日期和时间的任何日期之间的差异将是精确的年数。
- 将差异添加到另一个日期将产生一个可以转换回有用日期的值。
回答by Karl Bartel
If you want precise results, I recommend using the dateutillibrary.
如果您想要精确的结果,我建议使用dateutil库。
from dateutil.relativedelta import relativedelta
difference_in_years = relativedelta(end_date, start_date).years
This is for complete years (e.g. a person's age). If you want fractional years, then add months, days, hours, ... up to the desired precision.
这是完整的年份(例如一个人的年龄)。如果您想要小数年,则添加月、日、小时……直到所需的精度。
回答by Kostyantyn
I use one of these to calculate person's age:
我使用其中之一来计算人的年龄:
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
回答by Michel Fernandes
Just do this:
只需这样做:
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))
回答by BuvinJ
Here's a spin off of what Kostyantyn posted in his "age2" function. It's slightly shorter/cleaner and uses the traditional/colloquial meaning of an "age" or difference in years as well:
这是 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)
回答by Aidan Feldman
Here's what I came up with, without using an external dependency:
这是我想出的,不使用外部依赖:
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
回答by Robert Lujo
More robust function - calculates difference in years (age) and days:
更强大的功能 - 计算年(年龄)和天数的差异:
def get_diff_in_years_and_days(from_date, to_date):
try:
from_in_this_year = date(to_date.year, from_date.month, from_date.day)
except:
from_in_this_year = date(to_date.year, from_date.month, from_date.day-1) # today is feb in leap year
if from_in_this_year <= to_date:
years = to_date.year - from_date.year
days = (to_date - from_in_this_year).days
else:
years = to_date.year - from_date.year - 1
try:
from_in_prev_year = date(to_date.year-1, from_date.month, from_date.day)
except:
from_in_prev_year = date(to_date.year-1, from_date.month, from_date.day-1) # today is feb in leap year
days = (to_date - from_in_prev_year).days
assert days>=0 and days<=365, days
assert years>=0, years
return years, days
some unit-tests:
一些单元测试:
self.assertEqual((0, 0), get_diff_in_years_and_days(date(2018,1, 1), date(2018,1, 1)))
self.assertEqual((1, 0), get_diff_in_years_and_days(date(2017,1, 1), date(2018,1, 1)))
self.assertEqual((1, 1), get_diff_in_years_and_days(date(2017,1, 1), date(2018,1, 2)))
self.assertEqual((2, 0), get_diff_in_years_and_days(date(2016,2,29), date(2018,2,28)))
self.assertEqual((2, 1), get_diff_in_years_and_days(date(2014,2,28), date(2016,2,29)))
self.assertEqual((1,364), get_diff_in_years_and_days(date(2014,2,28), date(2016, 2,27)))
self.assertEqual((3,30) , get_diff_in_years_and_days(date(2015,10,1), date(2018,10,31)))
self.assertEqual((10,30), get_diff_in_years_and_days(date(2010,10,1), date(2020,10,31)))
self.assertEqual((3,31) , get_diff_in_years_and_days(date(2015,10,1), date(2018,11, 1)))
self.assertEqual((2,364), get_diff_in_years_and_days(date(2015,10,1), date(2018, 9,30)))
回答by Puddle
Since we're coming to the end of 2018...
由于我们即将到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))
output
输出
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

