我需要检查自某个日期以来是否已有几年。目前我已经timedelta
从datetime
模块中获得,我不知道如何将其转换为年。
21 回答
你需要不止一个timedelta
来判断已经过去了多少年;您还需要知道开始(或结束)日期。(这是闰年的事情。)
您最好的选择是使用dateutil.relativedelta
object,但这是一个 3rd 方模块。如果您想知道从某个日期算起的datetime
年份n
(默认为现在),您可以执行以下操作:
from dateutil.relativedelta import relativedelta
def yearsago(years, from_date=None):
if from_date is None:
from_date = datetime.now()
return from_date - relativedelta(years=years)
如果您宁愿坚持使用标准库,答案会稍微复杂一点::
from datetime import datetime
def yearsago(years, from_date=None):
if from_date is None:
from_date = datetime.now()
try:
return from_date.replace(year=from_date.year - years)
except ValueError:
# Must be 2/29!
assert from_date.month == 2 and from_date.day == 29 # can be removed
return from_date.replace(month=2, day=28,
year=from_date.year-years)
如果是 2/29,而 18 年前没有 2/29,则此函数将返回 2/28。如果您宁愿返回 3/1,只需将最后一条return
语句更改为::
return from_date.replace(month=3, day=1,
year=from_date.year-years)
Your question originally said you wanted to know how many years it's been since some date. Assuming you want an integer number of years, you can guess based on 365.2425 days per year and then check using either of the yearsago
functions defined above::
def num_years(begin, end=None):
if end is None:
end = datetime.now()
num_years = int((end - begin).days / 365.2425)
if begin > yearsago(num_years, end):
return num_years - 1
else:
return num_years
如果您要检查某人是否年满 18 岁,timedelta
由于闰年,在某些边缘情况下使用将无法正常工作。例如,2000 年 1 月 1 日出生的人,将在 2018 年 1 月 1 日(包括 5 个闰年)正好在 6575 天后满 18 岁,但在 2001 年 1 月 1 日出生的人,将在 1 月 1 日正好 6574 天后满 18 岁, 2019 年(包括 4 个闰年)。因此,如果某人正好是 6574 天,如果不知道更多关于他们生日的信息,您就无法确定他们是 17 岁还是 18 岁。
正确的方法是直接从日期计算年龄,减去两年,如果当前月/日早于出生月/日,则减去一。
首先,在最详细的层面上,问题无法完全解决。年份的长度各不相同,而且年份长度没有明确的“正确选择”。
也就是说,获取任何“自然”单位(可能是秒)的差异,然后除以该单位与年份之间的比率。例如
delta_in_days / (365.25)
delta_in_seconds / (365.25*24*60*60)
...管他呢。远离几个月,因为它们的定义甚至不如几年。
Here's a updated DOB function, which calculates birthdays the same way humans do:
import datetime
import locale
# Source: https://en.wikipedia.org/wiki/February_29
PRE = [
'US',
'TW',
]
POST = [
'GB',
'HK',
]
def get_country():
code, _ = locale.getlocale()
try:
return code.split('_')[1]
except IndexError:
raise Exception('Country cannot be ascertained from locale.')
def get_leap_birthday(year):
country = get_country()
if country in PRE:
return datetime.date(year, 2, 28)
elif country in POST:
return datetime.date(year, 3, 1)
else:
raise Exception('It is unknown whether your country treats leap year '
+ 'birthdays as being on the 28th of February or '
+ 'the 1st of March. Please consult your country\'s '
+ 'legal code for in order to ascertain an answer.')
def age(dob):
today = datetime.date.today()
years = today.year - dob.year
try:
birthday = datetime.date(today.year, dob.month, dob.day)
except ValueError as e:
if dob.month == 2 and dob.day == 29:
birthday = get_leap_birthday(today.year)
else:
raise e
if today < birthday:
years -= 1
return years
print(age(datetime.date(1988, 2, 29)))
Get the number of days, then divide by 365.2425 (the mean Gregorian year) for years. Divide by 30.436875 (the mean Gregorian month) for months.
你需要它有多精确? td.days / 365.25
如果您担心闰年,会让您非常接近。
def age(dob):
import datetime
today = datetime.date.today()
if today.month < dob.month or \
(today.month == dob.month and today.day < dob.day):
return today.year - dob.year - 1
else:
return today.year - dob.year
>>> import datetime
>>> datetime.date.today()
datetime.date(2009, 12, 1)
>>> age(datetime.date(2008, 11, 30))
1
>>> age(datetime.date(2008, 12, 1))
1
>>> age(datetime.date(2008, 12, 2))
0
Yet another 3rd party lib not mentioned here is mxDateTime (predecessor of both python datetime
and 3rd party timeutil
) could be used for this task.
The aforementioned yearsago
would be:
from mx.DateTime import now, RelativeDateTime
def years_ago(years, from_date=None):
if from_date == None:
from_date = now()
return from_date-RelativeDateTime(years=years)
First parameter is expected to be a DateTime
instance.
To convert ordinary datetime
to DateTime
you could use this for 1 second precision):
def DT_from_dt_s(t):
return DT.DateTimeFromTicks(time.mktime(t.timetuple()))
or this for 1 microsecond precision:
def DT_from_dt_u(t):
return DT.DateTime(t.year, t.month, t.day, t.hour,
t.minute, t.second + t.microsecond * 1e-6)
And yes, adding the dependency for this single task in question would definitely be an overkill compared even with using timeutil (suggested by Rick Copeland).
In the end what you have is a maths issue. If every 4 years we have an extra day lets then dived the timedelta in days, not by 365 but 365*4 + 1, that would give you the amount of 4 years. Then divide it again by 4. timedelta / ((365*4) +1) / 4 = timedelta * 4 / (365*4 +1)
This is the solution I worked out, I hope can help ;-)
def menor_edad_legal(birthday):
""" returns true if aged<18 in days """
try:
today = time.localtime()
fa_divuit_anys=date(year=today.tm_year-18, month=today.tm_mon, day=today.tm_mday)
if birthday>fa_divuit_anys:
return True
else:
return False
except Exception, ex_edad:
logging.error('Error menor de edad: %s' % ex_edad)
return True
` Simple solution!
import datetime as dt
from dateutil.relativedelta import relativedelta
dt1 = dt.datetime(1990,2,1)
dt2 = dt.datetime(2021,5,16)
out = relativedelta(dt2, dt1)
print(f'Complete: {out}')
print(f'years:{out.years}, months:{out.months}, days:{out.days}') `
Complete: relativedelta(years=+31, months=+3, days=+15)
years:31, months:3, days:15
This method is a hack of the datetime library:
def calculate_age(birthday_date, fmt="%Y-%m-%d"):
birthday = datetime.datetime.strptime(birthday_date, fmt)
age = datetime.datetime.now() - birthday
# first datime valid is (1, 1, 1), I use (1, 12, 31) => (2, 0, 0) to hack the lib
age = (datetime.datetime(1, 12, 31) + age)
return age.year - 2
This solution is odd, so I shared it with you. It's probably not the most elegant function.
now : 2019-09-21
print(calculate_age("2019-09-21")) => 2 (Done)
-> age + (1,12,31) = 0004-01-01 23:42:17.767031
print(calculate_age("2020-09-21")) => 0 (Undone)
-> age + (1,12,31) = 0002-12-31 23:46:39.144091
The difference is one day. I understand the reason for the difference is due to the bisextile (leap) year.
To correct this undesired behaviour, you need to use the birthday year to add the result.
Even though this thread is already dead, might i suggest a working solution for this very same problem i was facing. Here it is (date is a string in the format dd-mm-yyyy):
def validatedate(date):
parts = date.strip().split('-')
if len(parts) == 3 and False not in [x.isdigit() for x in parts]:
birth = datetime.date(int(parts[2]), int(parts[1]), int(parts[0]))
today = datetime.date.today()
b = (birth.year * 10000) + (birth.month * 100) + (birth.day)
t = (today.year * 10000) + (today.month * 100) + (today.day)
if (t - 18 * 10000) >= b:
return True
return False
this function returns the difference in years between two dates (taken as strings in ISO format, but it can easily modified to take in any format)
import time
def years(earlydateiso, laterdateiso):
"""difference in years between two dates in ISO format"""
ed = time.strptime(earlydateiso, "%Y-%m-%d")
ld = time.strptime(laterdateiso, "%Y-%m-%d")
#switch dates if needed
if ld < ed:
ld, ed = ed, ld
res = ld[0] - ed [0]
if res > 0:
if ld[1]< ed[1]:
res -= 1
elif ld[1] == ed[1]:
if ld[2]< ed[2]:
res -= 1
return res
I'll suggest Pyfdate
What is pyfdate?
Given Python's goal to be a powerful and easy-to-use scripting language, its features for working with dates and times are not as user-friendly as they should be. The purpose of pyfdate is to remedy that situation by providing features for working with dates and times that are as powerful and easy-to-use as the rest of Python.
the tutorial
import datetime
def check_if_old_enough(years_needed, old_date):
limit_date = datetime.date(old_date.year + years_needed, old_date.month, old_date.day)
today = datetime.datetime.now().date()
old_enough = False
if limit_date <= today:
old_enough = True
return old_enough
def test_ages():
years_needed = 30
born_date_Logan = datetime.datetime(1988, 3, 5)
if check_if_old_enough(years_needed, born_date_Logan):
print("Logan is old enough")
else:
print("Logan is not old enough")
born_date_Jessica = datetime.datetime(1997, 3, 6)
if check_if_old_enough(years_needed, born_date_Jessica):
print("Jessica is old enough")
else:
print("Jessica is not old enough")
test_ages()
This is the code that the Carrousel operator was running in Logan's Run film ;)
I came across this question and found Adams answer the most helpful https://stackoverflow.com/a/765862/2964689
But there was no python example of his method but here's what I ended up using.
input: datetime object
output: integer age in whole years
def age(birthday):
birthday = birthday.date()
today = date.today()
years = today.year - birthday.year
if (today.month < birthday.month or
(today.month == birthday.month and today.day < birthday.day)):
years = years - 1
return years
I liked John Mee's solution for its simplicity, and I am not that concerned about how, on Feb 28 or March 1 when it is not a leap year, to determine age of people born on Feb 29. But here is a tweak of his code which I think addresses the complaints:
def age(dob):
import datetime
today = datetime.date.today()
age = today.year - dob.year
if ( today.month == dob.month == 2 and
today.day == 28 and dob.day == 29 ):
pass
elif today.month < dob.month or \
(today.month == dob.month and today.day < dob.day):
age -= 1
return age
I would use datetime.date
data type instead, as it is simpler when it comes to checking how many years, months and days have passed:
now = date.today()
birthday = date(1993, 4, 4)
print("you are", now.year - birthday.year, "years,", now.month - birthday.month, "months and",
now.day - birthday.day, "days old")
Output:
you are 27 years, 7 months and 11 days old
I use timedelta
when I need to perform arithmetic on a specific date:
age = now - birthday
print("addition of days to a date: ", birthday + timedelta(days=age.days))
Output:
addition of days to a date: 2020-11-15
Late to the party, but this will give you the age (in years) accurately and easily:
b = birthday
today = datetime.datetime.today()
age = today.year - b.year + (today.month - b.month > 0 or
(today.month == b.month > 0 and
today.day - b.day > 0))
For my purposes since subtraction of two datetime
objects (at least in Python 3.8) converts to a timedelta
with only a days
attribute:
>>> admit = datetime.strptime('20130530', '%Y%m%d')
>>> birth = datetime.strptime('20010621', '%Y%m%d')
>>> age = (admit - birth).days/365.2425
>>> age
11.940012457476882