2009-04-19 2 views
96

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

+3

см этот ответ: http://stackoverflow.com/a/9754466/65387 – Adam

+0

связанные: [Как конвертировать года до второй ] (http://stackoverflow.com/a/32658742/4279) – jfs

ответ

111

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

Ваш лучший выбор - использовать dateutil.relativedeltaobject, но это сторонний модуль. Если вы хотите знать, что 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 
+19

Вы можете быть полностью точным с 365.2425 (вместо 365.25), что учитывает исключение 400 лет для григорианского календаря. – brianary

+2

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

+0

@brianary: он не будет «полностью точным», например [разница, введенная локальным часовым поясом ('datetime.now()') больше, чем разница между * average * Julian ('365.25') и григорианской ('365.2425') лет.] (Http://stackoverflow.com/a/32658742/4279). Правильный подход предлагается в ответе [@Adam Rosenfield] (http://stackoverflow.com/a/765862/4279) – jfs

0

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

+0

Я действительно переживаю за високосные годы. Он должен проверить, не превышает ли человек 18 лет. – Migol

+0

Тогда нет простой однострочной линии, вам придется разобрать две даты и выяснить, прошел ли человек 18-летний юбилей или нет. – eduffy

8

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

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

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

... или что-то еще. Держитесь подальше от месяцев, поскольку они еще менее четко определены, чем годы.

+1

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

+2

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

+1

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

42

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

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

+1

: [Возраст от даты рождения в python] (http: // stackoverflow. com/a/9754466/4279) – jfs

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 
-2

Ну, вопрос кажется довольно простым. Вам нужно проверить количество «полных» лет, и только если он равен 18, вам нужно беспокоиться месяцами и днями. Случай край: endDate.year - startDate.year == 18 и она распадается на два случая: startDate.month != endDate.month и startDate.month == endDate.month, когда вы просто должны проверить дни:

def isOfAge(birthDate, age=18): 
    endDate = date.today() 
    years = endDate.year - birthDate.year 
    if years == age: 
     return (birthDate.month < endDate.month or 
        (birthDate.month == endDate.month and birthDate.day < endDate.day))   
    return years > age 

Это все еще больше, чем один вкладыш лямбда, но это все еще довольно короткий, и, кажется, быстрый в исполнении.

1
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 
+0

Лицо, родившееся 29 февраля, будет считаться имеющим возраст 1 следующего 28 февраля. –

+0

Хорошо. Исправлено для размещения 0,08% населения, родившегося 29-го, путем инвертирования теста с «день рождения после сегодняшнего дня» до «день рождения до сегодняшнего дня». Решает ли это? –

+0

Нет, это не решает проблему. Вы не верите в тестирование? –

0

Я предлагаю Pyfdate

Что pyfdate?

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

tutorial

1

Еще одна третья сторона Lib не упоминается здесь mxDateTime (предшественник как питон datetime и 3-й партии 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 (предложенный Риком Коуплэнд).

3

Получите количество дней, а затем разделите их на 365.2425 (средний григорианский год) в течение многих лет. Разделите на 30.436875 (средний григорианский месяц) месяцами.

1

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

+0

Значение високосного года не применяется, если годы делятся на 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 
5

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

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

Смежные вопросы