Python timedelta в годах


147

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


4
см. этот ответ: stackoverflow.com/a/9754466/65387
Адам,

Ответы:


163

Вам нужно больше, чем a, 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,25 дней в году, а затем проверить, используя любую из yearsagoфункций, определенных выше:

def num_years(begin, end=None):
    if end is None:
        end = datetime.now()
    num_years = int((end - begin).days / 365.25)
    if begin > yearsago(num_years, end):
        return num_years - 1
    else:
        return num_years

28
Вы можете быть полностью точными с 365,2425 (вместо 365,25), что учитывает 400-летнее исключение для григорианского календаря.
brianary

3
Ваша функция юридически не действует для таких стран, как Великобритания и Гонконг, потому что они «округляют» до 1 марта високосные годы.
antihero

2
@brianary: это не будет «полностью точным», например, разница,datetime.now()365.25365.2425 вносимая местным часовым поясом ( ), больше, чем разница между средними юлианскими ( ) и григорианскими ( ) годами. . Правильный подход предлагается в ответе @Adam Rosenfield
jfs

3
см. также это и это Оба являются отличными списками того, что не соответствует действительности относительно времени.
gvoysey

49

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

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


9

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

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

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

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


2
Это НЕ то, что кто-либо имеет в виду или использует, когда речь идет о том, сколько лет службы или достиг ли человек определенного возраста.
Джон Мачин,

3
Ваш 365,25 должен быть 365,2425, чтобы принять во внимание 400-летнее исключение григорианского календаря.
brianary

1
Что ж, проблему можно решить правильно - заранее можно сказать, в каких годах есть високосные дни, високосные секунды и все такое. Просто нет чрезвычайно элегантного способа сделать это без вычитания лет, затем месяцев, затем дней и т. Д. В двух форматированных датах
Litherum

8

Вот обновленная функция 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)))

Это прерывается, когда dob 29 февраля, а текущий год не является високосным.
Trey Hunner

4

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


2

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


Меня очень волнуют високосные годы. Следует проверить, старше ли человек 18 лет.
Migol

Тогда нет простого однострочника, вам нужно будет проанализировать две даты и выяснить, исполнилось ли человеку 18 лет или нет.
eduffy

2
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

Человек, родившийся 29 февраля, будет считаться достигшим 1 года 28 февраля следующего года.
John Machin

ОК. Исправлено с учетом 0,08% населения, родившегося 29-го числа, путем инвертирования теста с «день рождения после сегодняшнего дня» на «день рождения до сегодняшнего дня». Это решает проблему?
Джон Ми,

Это правильно работает для вашего примера!?! Если я установлю «сегодня» на 28 февраля 2009 г., а дату рождения на 29 февраля 2008 г., он вернет ноль - по крайней мере для меня; а не 1, как вы предлагаете (Python 2.5.2). Не нужно грубить мистеру Мачину. С какими именно свиданиями у вас проблемы?
Джон Ми

Я попробую еще раз: большинство людей будут рассматривать человека, родившегося 29 февраля, как человека, достигшего 1 года 28 февраля следующего года. Ваш код производит 0 как до, так и после вашего «исправления». Фактически, две версии вашего кода производят ТОЧНО одинаковый вывод для ВСЕХ 9 вариантов ввода (месяц <==> X день <==>). Кстати, 100.0 / (4 * 365 + 1) дает 0,068, а не 0,08.
Джон Мачин

2
Вздох. (0) Решение проблем, связанных с 29 февраля, необходимо в арифметике любых дат; ты просто проигнорировал это. (1) Ваш код не стал лучше с первого раза; что вы не понимаете в слове «производить ТОЧНО такой же результат»? (2) Три возможных атомарных результата (<, ==,>) при сравнении today.month и dob.month; сравнение трех возможных атомарных результатов today.day и dob.day; 3 * 3 == 9 (3) stackoverflow.com/questions/2217488/…
Джон Мачин,

1

Еще одна сторонняя библиотека, не упомянутая здесь, - это 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 (предложено Риком Коуплендом).


1

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


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

1

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

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

0

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

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

0

эта функция возвращает разницу в годах между двумя датами (взятые как строки в формате 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

0

Я предлагаю Pyfdate

Что такое pyfdate?

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

учебник


0
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_(film)


0

Я наткнулся на этот вопрос и нашел, что ответ Адамса самый полезный 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

0

Мне понравилось решение Джона Ми из-за его простоты, и меня не особо беспокоит, как 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
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.