Питоническая разница между двумя датами в годах? - PullRequest
39 голосов
/ 14 декабря 2010

Есть ли более эффективный способ сделать это ниже? Я хочу иметь разницу в годах между двумя датами как один скаляр. Любые предложения приветствуются.

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

Ответы [ 11 ]

61 голосов
/ 23 января 2012

Если вы хотите получить точные результаты, я рекомендую использовать библиотеку dateutil .

from dateutil.relativedelta import relativedelta
difference_in_years = relativedelta(end_date, start_date).years

Это для полных лет (например, возраст человека).Если вы хотите использовать дробные годы, то прибавьте месяцы, дни, часы и т. Д. До требуемой точности.

11 голосов
/ 13 июня 2015

Я использую один из них для расчета возраста человека:

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
7 голосов
/ 24 октября 2017

Просто сделайте это:

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))
7 голосов
/ 16 декабря 2010

Более эффективно? Нет, но правильнее, наверное. Но это зависит от того, насколько правильно вы хотите быть. Даты не являются тривиальными вещами.

Годы не имеют постоянной длины. Хотите разницу в високосных или нормальных годах? :-) При подсчете вы всегда получите немного неправильный ответ. И как долго день в годах? Вы говорите 1 / 365.2425. Ну, в среднем за тысячу лет, да. Но в противном случае нет.

Так что вопрос не имеет особого смысла.

Чтобы быть правильным, вы должны сделать это:

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

В этом случае разница составляет 0,0012322917425568528 лет или 0,662 дня, учитывая, что это не високосный год.

(а потом мы игнорируем микросекунды. Хех.)

5 голосов
/ 16 декабря 2010

Чтобы понять смысл високосных лет, вы почти вынуждены разбить это на две части: целое число лет и дробная часть.Оба должны иметь дело с високосными годами, но по-разному - интеграл должен иметь дело с датой начала 29 февраля, а дробный должен иметь дело с различным числом дней в году.Вы хотите, чтобы дробная часть увеличивалась в равных количествах до тех пор, пока она не станет равной 1,0 на следующую годовщину, поэтому она должна основываться на количестве дней в году после даты окончания.

Вы хотите, чтобы ваш диапазон дат включал 1900 или 2100?Все становится немного проще, если вы этого не сделаете.


Редактировать: Мне потребовалось много времени, чтобы объяснить это.Основная проблема заключается в том, что календарные годы не являются постоянными, но вы заставляете их быть постоянными, устанавливая их на 1,0.Любое решение, которое вы придумаете, будет иметь аномалии из-за этого, и вам придется выбирать, с какими аномалиями вы можете жить.Джон Мачин был прав.

В чем разница между 2008-02-28 и 2009-02-28?Большинство людей согласятся, что это должно быть ровно 1,0 года.Как насчет разницы между 2008-03-01 и 2009-03-01?Опять же, большинство людей согласится, что это должно быть ровно 1,0 года.Если вы решите представить дату как год плюс часть года в зависимости от дня, невозможно сделать оба этих утверждения правдивыми.Это относится к вашему исходному коду, который предполагал, что день был 1 / 365.2425 года, или даже к любому коду, который предполагает постоянную долю года в день, даже если размер дня составляет годы, которые являются високоснымилет.

Мое утверждение о том, что вам нужно разбить это на целые годы и дробные годы, было попыткой обойти эту проблему.Если вы рассматриваете каждое из предыдущих условий как целостный год, все, что вам нужно сделать, это решить, какую дробь назначить на любое количество оставшихся дней.Проблема с этой схемой заключается в том, что вы все еще не можете понять (date2-date1) + date3, потому что дробь не может быть разрешена обратно в день с какой-либо согласованностью.

Таким образом, я предлагаю ещедругая кодировка, основанная на каждом году, содержащая 366 дней, независимо от того, високосный или нет.Во-первых, аномалии состоят в том, что не может быть даты, которая ровно год (или 2 или 3) с 29 февраля - «Извините, Джонни, у вас нет дня рождения в этом году, 29 февраля нет».не всегда приемлемоВо-вторых, если вы попытаетесь привести такое число к дате, вам придется учитывать не високосные годы, проверить особый случай 29 февраля и преобразовать его, вероятно, в 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)

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

  • Разница между любыми датами с одним и тем же месяцем и днем ​​и временем будет точным числом лет.
  • Добавление разницы к другой дате приведет кпривести к значению, которое можно преобразовать обратно в полезную дату.
1 голос
/ 07 мая 2018

Вот пример того, что Константин опубликовал в своей функции 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)
0 голосов
/ 06 февраля 2019

Перед установкой библиотеки:

choco upgrade Python -y
python pip install python-dateutil

В одну строку в Python в Cmder (windows):

python -c "import datetime; from dateutil.relativedelta import relativedelta; myBirthday = datetime.datetime(2019,2,6,11,0,0,0); now = datetime.datetime.utcnow(); diff = relativedelta(now, myBirthday); print ("'My'+'" "'+'year'+'" "'+':'+'" "'+'%d''" "''and''" "''%d''" "''microseconds'" % (diff.years, diff.microseconds))"

В процентах побега в пакетном режиме с %%:

python -c "import datetime; from dateutil.relativedelta import relativedelta; myBirthday = datetime.datetime(2019,2,6,11,0,0,0); now = datetime.datetime.utcnow(); diff = relativedelta(now, myBirthday); print ("'My'+'" "'+'year'+'" "'+':'+'" "'+'%%d''" "''and''" "''%%d''" "''microseconds'" %% (diff.years, diff.microseconds))"
0 голосов
/ 28 декабря 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))

вывод

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
0 голосов
/ 07 декабря 2018

Более надежная функция - вычисляет разницу в годах (возрасте) и днях:

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

некоторые юнит-тесты:

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)))
0 голосов
/ 14 ноября 2018

Вот то, что я придумал, без использования внешней зависимости:

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
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...