Преобразовать timedelta в годы?

Мне нужно проверить, прошло ли какое-то количество лет с какой-то даты. В настоящее время у меня есть timedelta из модуля datetime, и я не знаю, как преобразовать его в годы.


person Migol    schedule 19.04.2009    source источник
comment
см. этот ответ: stackoverflow.com/a/9754466/65387   -  person Adam    schedule 13.08.2014
comment
связанные: Как преобразовать годы в секунды   -  person jfs    schedule 11.12.2015


Ответы (20)


Вам нужно больше, чем timedelta, чтобы сказать, сколько лет прошло; вам также необходимо знать дату начала (или окончания). (Это високосный год.)

Лучше всего использовать dateutil.relativedelta объект, но это сторонний модуль. Если вы хотите узнать, сколько 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)

Первоначально в вашем вопросе говорилось, что вы хотите знать, сколько лет прошло с какой-то даты. Предполагая, что вам нужно целое число лет, вы можете угадать, исходя из 365,2425 дней в году, а затем проверить, используя любую из функций yearsago, определенных выше:

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
person Rick Copeland    schedule 19.04.2009
comment
Вы можете быть полностью точными с 365,2425 (вместо 365,25), которое учитывает 400-летнее исключение для григорианского календаря. - person brianary; 25.12.2009
comment
Ваша функция юридически не работает для таких стран, как Великобритания и Гонконг, потому что они округляются до 1 марта високосных лет. - person antihero; 04.09.2013
comment
@brianary: это не будет полностью точным, например, разница, вносимая местным часовым поясом (datetime.now()), больше, чем разница между средний юлианский (365.25) и григорианский (365.2425) годы.. Правильный подход предлагается в ответе @Adam Rosenfield - person jfs; 11.12.2015
comment
см. также this и this Оба являются отличным списком того, что не соответствует действительности относительно времени. - person gvoysey; 19.08.2016

Если вы пытаетесь проверить, достиг ли кто-то 18 лет, использование timedelta не будет работать правильно в некоторых крайних случаях из-за високосных лет. Например, человеку, родившемуся 1 января 2000 года, 1 января 2018 года исполнится 18 ровно на 6575 дней (включая 5 високосных лет), а человеку, родившемуся 1 января 2001 года, исполнится 18 ровно на 6574 дня позже, 1 января. 2019 г. (включая 4 високосных года). Таким образом, если кому-то ровно 6574 дня, вы не можете определить, 17 или 18 лет ему, не зная немного больше информации о дате его рождения.

Правильный способ сделать это - вычислить возраст непосредственно по датам, вычтя два года, а затем вычтя один, если текущий месяц / день предшествует месяцу / дню рождения.

person Adam Rosenfield    schedule 19.04.2009
comment
Это путь ... если вы не работаете в каком-либо секторе, который любит догадки: здоровье, геология и т. Д. - person Braiam; 31.05.2021
comment
Вычесть только годы - такое элегантное решение, но не очень очевидное. - person xyres; 28.06.2021

Во-первых, на самом детальном уровне проблема не может быть решена точно. Годы различаются по длине, и нет четкого «правильного выбора» в отношении продолжительности года.

Тем не менее, получите разницу в любых «естественных» единицах (вероятно, в секундах) и разделите на соотношение между ними и годами. Например.

delta_in_days / (365.25)
delta_in_seconds / (365.25*24*60*60)

... или что-то в этом роде. Избегайте месяцев, поскольку они даже менее четко определены, чем годы.

person MarkusQ    schedule 19.04.2009
comment
Это НЕ то, что кто-либо имеет в виду или использует, когда речь идет о том, сколько лет службы или достиг ли человек определенного возраста. - person John Machin; 30.11.2009
comment
Ваш 365,25 должен быть 365,2425, чтобы принять во внимание 400-летнее исключение григорианского календаря. - person brianary; 25.12.2009
comment
Что ж, проблему можно решить правильно - вы можете заранее сказать, в каких годах есть високосные дни, високосные секунды и все такое. Просто нет очень элегантного способа сделать это без вычитания лет, затем месяцев, затем дней и т. Д. В двух форматированных датах. - person Litherum; 14.01.2011

Вот обновленная функция DOB, которая вычисляет дни рождения так же, как и люди:

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)))
person antihero    schedule 10.08.2012
comment
Это прерывается, когда dob наступает 29 февраля, а текущий год не является високосным. - person Trey Hunner; 10.03.2013

Получите количество дней, затем разделите на 365,2425 (средний год по григорианскому календарю) за годы. Разделите месяцы на 30,436875 (средний месяц по григорианскому календарю).

person brianary    schedule 24.12.2009

Насколько точным он вам нужен? td.days / 365.25 поможет вам довольно близко, если вы беспокоитесь о високосных годах.

person eduffy    schedule 19.04.2009
comment
Меня очень беспокоят високосные годы. Следует проверить, старше ли человек 18 лет. - person Migol; 19.04.2009
comment
Тогда нет простого однострочника, вам нужно будет проанализировать две даты и выяснить, исполнилось ли человеку 18 лет или нет. - person eduffy; 19.04.2009

Еще одна сторонняя библиотека, не упомянутая здесь, - это mxDateTime (предшественник как python datetime, так и стороннего timeutil), который может использоваться для этой задачи.

Вышеупомянутый yearsago будет:

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)

Ожидается, что первым параметром будет экземпляр DateTime.

Чтобы преобразовать обычный datetime в DateTime, вы можете использовать это с точностью до 1 секунды):

def DT_from_dt_s(t):
    return DT.DateTimeFromTicks(time.mktime(t.timetuple()))

или это для точности в 1 микросекунду:

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)

И да, добавление зависимости для этой единственной рассматриваемой задачи определенно было бы излишним по сравнению даже с использованием timeutil (предложено Риком Коуплендом).

person Antony Hatchkins    schedule 24.12.2009

В конце концов, у вас возникла проблема с математикой. Если каждые 4 года у нас будет дополнительный день, а затем понизить timedelta в днях, не на 365, а на 365 * 4 + 1, то получится 4 года. Затем разделите его еще раз на 4. timedelta / ((365 * 4) +1) / 4 = timedelta * 4 / (365 * 4 +1)

person Norberto    schedule 15.07.2010
comment
Високосный год не применяется, когда годы делятся на 100, за исключением случаев, когда они делятся на 400. Итак, для 2000 года: - он делится на четыре, поэтому он должен быть високосным, но ... - он также делится. на сотню, так что это не должно быть високосным, но ... - оно делится на 400, так что на самом деле это был високосный год. Для 1900: - оно делится на 4, поэтому должно быть високосным. - он делится на 100, поэтому не должен быть скачкообразным. - он НЕ делится на 400, поэтому это правило не применяется. 1900 год не был високосным. - person Jblasco; 24.09.2013

Это решение, которое я разработал, надеюсь, может помочь ;-)

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
person pvilas    schedule 10.03.2011

`Простое решение!

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}') `

Завершено: relativedelta (годы = + 31, месяцы = + 3, дни = + 15)

лет: 31, месяцев: 3, дней: 15

person Sajid    schedule 16.05.2021

Несмотря на то, что этот поток уже мертв, могу ли я предложить рабочее решение той же самой проблемы, с которой я столкнулся. Вот она (дата - строка в формате дд-мм-гггг):

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
person Community    schedule 03.07.2009

эта функция возвращает разницу в годах между двумя датами (взятые как строки в формате ISO, но ее можно легко изменить, чтобы принять в любом формате)

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
person Mauro Bianchi    schedule 01.09.2009

Я предлагаю Pyfdate

Что такое pyfdate?

Учитывая цель Python - быть мощным и простым в использовании языком сценариев, его функции для работы с датой и временем не так удобны для пользователя, как должны быть. Цель pyfdate - исправить эту ситуацию, предоставив функции для работы с датой и временем, которые являются такими же мощными и простыми в использовании, как и остальная часть Python.

руководство

person twils    schedule 01.12.2009

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()

Это код, который оператор Carrousel запускал в фильме Logan's Run;)

https://en.wikipedia.org/wiki/Logan%27s_Run_ (фильм) < / а>

person Andres Hurtis    schedule 06.03.2020

Я наткнулся на этот вопрос и нашел, что Адамс отвечает на наиболее полезный https://stackoverflow.com/a/765862/2964689

Но не было примера его метода на Python, но вот что я в итоге использовал.

ввод: объект datetime

вывод: целочисленный возраст в целых годах

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
person Sam    schedule 09.06.2020

Мне понравилось решение Джона Ми из-за его простоты, и меня не особо беспокоит, как 28 февраля или 1 марта, когда это не високосный год, определять возраст людей, родившихся 29 февраля. Но вот небольшая поправка в его коде. который, я думаю, касается жалоб:

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
person Rick Graves    schedule 18.06.2020

Вместо этого я бы использовал тип данных datetime.date, так как это проще, когда дело доходит до проверки того, сколько лет, месяцев и дней прошло:

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")

Выход:

you are 27 years, 7 months and 11 days old

Я использую timedelta, когда мне нужно выполнить арифметические действия в определенный день:

age = now - birthday
print("addition of days to a date: ", birthday + timedelta(days=age.days))

Выход:

addition of days to a date:  2020-11-15
person Sebastian Meckovski    schedule 15.11.2020

Поздно на вечеринку, но это даст вам возраст (в годах) точно и легко:

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))
person Hebe Hilhorst    schedule 02.12.2020

Для моих целей, поскольку вычитание двух объектов datetime (по крайней мере, в Python 3.8) преобразуется в timedelta только с атрибутом days:

>>> admit = datetime.strptime('20130530', '%Y%m%d')
>>> birth = datetime.strptime('20010621', '%Y%m%d')
>>> age = (admit - birth).days/365.2425
>>> age
11.940012457476882
person jimh    schedule 03.06.2021

person    schedule
comment
Человек, родившийся 29 февраля, будет считаться достигшим 1-летнего возраста 28 февраля следующего года. - person John Machin; 30.11.2009
comment
Ok. Исправлено для учета 0,08% населения, родившегося 29-го числа, путем инвертирования теста с «день рождения после сегодняшнего дня» на «день рождения до сегодняшнего дня». Это решает проблему? - person John Mee; 01.12.2009
comment
Это правильно работает для вашего примера!?! Если я установлю сегодня на 28 февраля 2009 года, а дату рождения на 29 февраля 2008 года, он вернет ноль - по крайней мере, для меня; а не 1, как вы предлагаете (Python 2.5.2). Не нужно грубить мистеру Мачину. С какими именно свиданиями у вас проблемы? - person John Mee; 08.02.2010
comment
Я попробую еще раз: большинство людей будут считать, что человек, родившийся 29 февраля, достиг возраста 1 года 28 февраля следующего года. Ваш код производит 0 как до, так и после вашего исправления. Фактически, две версии вашего кода производят ТОЧНО одинаковый результат для ВСЕХ 9 вариантов ввода (месяц ‹==› X день ‹==›). Кстати, 100.0 / (4 * 365 + 1) дает 0,068, а не 0,08. - person John Machin; 09.02.2010
comment
О, ну вот и все. Я не знал законности, и вы не смогли сделать их недвусмысленными. Похоже, в первый раз было лучше. К сожалению, из комментария я не могу определить, какие у вас 9 сценариев. Но не пытайтесь объяснять; определенно пришло время поделиться с нами своим решением; тот, который работает так, как вам нравится. - person John Mee; 09.02.2010
comment
Вздох. (0) Решение проблем, связанных с 29 февраля, необходимо в арифметике любых дат; ты просто проигнорировал это. (1) Ваш код не стал лучше с первого раза; что вы не понимаете в том, чтобы производить ТОЧНО такой же результат? (2) Три возможных атомарных результата (‹, ==,›) при сравнении today.month и dob.month; сравнение трех возможных атомарных результатов today.day и dob.day; 3 * 3 == 9 (3) http://stackoverflow.com/questions/2217488/age-from-birthdate-in-python/2218654 - person John Machin; 09.02.2010