2009-04-19 8 views
96

Muszę sprawdzić, czy od pewnej daty upłynęła pewna liczba lat. Obecnie mam timedelta z modułu datetime i nie wiem jak przekonwertować go na lata.Python timedelta w latach

+3

zobaczyć tę odpowiedź: http://stackoverflow.com/a/9754466/65387 – Adam

+0

related: [Jak przekonwertować lat do sekundy ] (http://stackoverflow.com/a/32658742/4279) – jfs

Odpowiedz

111

Potrzebujesz więcej niż timedelta, aby powiedzieć, ile minęło lat; musisz również znać datę początkową (lub końcową). (To jest rok przestępny.)

Najlepiej jest użyć dateutil.relativedeltaobject, ale to moduł innej firmy. Jeśli chcesz wiedzieć datetime że był n lat od jakiegoś terminu (zalegających do teraz), można wykonać następujące czynności ::

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) 

Jeśli wolisz pozostać przy standardowej biblioteki, odpowiedź jest trochę bardziej skomplikowane ::

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) 

Jeśli to 2/29, a 18 lat temu nie było 2/29, funkcja ta zwróci 2/28. Jeśli wolisz wrócić 3/1, wystarczy zmienić ostatni return oświadczenie czytać ::

return from_date.replace(month=3, day=1, 
          year=from_date.year-years) 

Twoje pytanie pierwotnie powiedział, że chciał wiedzieć, ile lat minęło od jakiegoś terminu. Zakładając, że chcesz całkowitą liczbę lat, można się domyślać na podstawie 365,25 dni w roku, a następnie sprawdzić, korzystając z jednej z yearsago funkcjami zdefiniowanymi powyżej ::

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

Możesz być w pełni dokładny z 365.2425 (zamiast 365.25), który bierze pod uwagę wyjątek 400 lat dla kalendarza gregoriańskiego. – brianary

+2

Twoja funkcja legalnie łamie się w krajach takich jak Wielka Brytania i Hongkong, ponieważ "zbliżają się" do 1 marca w latach przestępnych. – antihero

+0

@brianary: nie będzie "w pełni dokładny", np. [Różnica wprowadzona przez lokalną strefę czasową ('datetime.now()') jest większa niż różnica między * średnią * Julian ('365.25') a gregoriańskim ('365.2425') years.] (Http://stackoverflow.com/a/32658742/4279). Prawidłowe podejście jest zasugerowane w odpowiedzi [@Adam Rosenfield] (http://stackoverflow.com/a/765862/4279) – jfs

0

Jak dokładnie powinno to być? td.days/365.25 będzie Ci bardzo bliski, jeśli martwisz się o lata przestępne.

+0

Naprawdę martwię się o lata przestępne. Powinien sprawdzić, czy osoba ma ponad 18 lat. – Migol

+0

Wtedy nie ma łatwego jedno-liniowego, będziesz musiał przeanalizować dwie daty i zorientował się, czy osoba przeszła 18 urodziny, czy nie. – eduffy

8

Po pierwsze, na najbardziej szczegółowym poziomie problemu nie da się dokładnie rozwiązać. Lata różnią się długością i nie ma wyraźnego "właściwego wyboru" na rok.

Powiedziawszy to, dostań różnicę w jednostkach, które są "naturalne" (prawdopodobnie sekundy) i podziel przez stosunek między tym a latami. Na przykład.

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

... lub cokolwiek innego. Trzymaj się z dala od miesięcy, ponieważ są one jeszcze mniej zdefiniowane niż lata.

+1

NIE JEST to, co ktokolwiek oznacza lub używa, gdy jest to kwestia tego, ile lat usługi lub osoba osiągnęła określony wiek. –

+2

Twój 365.25 powinien być 365.2425, aby wziąć pod uwagę 400-letni wyjątek od kalendarza gregoriańskiego. – brianary

+1

No cóż, problem * można * rozwiązać poprawnie - z wyprzedzeniem można przewidzieć, które lata mają dni przestępne i sekundy przestępne i tak dalej. Chodzi o to, że nie ma wyjątkowo eleganckiego sposobu robienia tego bez odejmowania lat, potem miesięcy, dni, itd ... w dwóch sformatowanych datach: – Litherum

42

Jeśli próbujesz sprawdzić, czy ktoś ma 18 lat, użycie timedelta nie będzie działać poprawnie w niektórych przypadkach z powodu lata przestępnej. Na przykład ktoś urodzony 1 stycznia 2000 roku skończy 18 lat dokładnie 6575 dni później 1 stycznia 2018 roku (w tym 5 lat przestępnych), ale osoba urodzona 1 stycznia 2001 roku skończy 18 lat dokładnie 6574 dni później, 1 stycznia, 2019 (w tym 4 lata przestępne). Tak więc, jeśli ktoś ma dokładnie 6574 dni, nie można ustalić, czy ma on 17 lub 18 lat, nie znając nieco więcej informacji na temat ich daty urodzenia.

Prawidłowym sposobem jest obliczenie wieku bezpośrednio z dat, odjęcie dwóch lat, a następnie odjęcie jednego, jeśli bieżący miesiąc/dzień poprzedza miesiąc/dzień urodzenia.

+1

: [Wiek od daty urodzenia w pytonie] (http: // stackoverflow. com/a/9754466/4279) – jfs

0

Chociaż ten wątek jest już martwy, mogę zaproponować rozwiązanie robocze dla tego samego problemu, z jakim miałem do czynienia.Oto ona (data jest ciągiem w formacie dd-mm-rrrr):

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

ta funkcja zwraca różnicę w latach między dwiema datami (rozumiana jako ciągi znaków w formacie ISO, ale można go łatwo modyfikowane w celu podjęcia w dowolnym formacie)

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

Cóż, pytanie wydaje się dość łatwe. Musisz sprawdzić liczbę pełnych lat i tylko jeśli jest równa 18, musisz zawracać sobie głowę miesiącami i dniami. Sprawa krawędź jest: endDate.year - startDate.year == 18 i dzieli na dwa przypadki: startDate.month != endDate.month i startDate.month == endDate.month, kiedy po prostu trzeba sprawdzić dzień:

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 

To wciąż więcej niż jeden-liner-lambda, ale nadal jest dość krótki, a wydaje szybkie wykonanie.

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

Osoba urodzona 29 lutego będzie traktowana jak osoba, która osiągnęła wiek 1 następnego dnia 28 lutego. –

+0

Ok. Skorygowano, aby pomieścić 0,08% populacji urodzonej 29-tego dnia, odwracając test z "jest urodziny po dniu dzisiejszym", "to urodziny przed dniem dzisiejszym". Czy to rozwiązuje? –

+0

Nie, to nie rozwiąże problemu. Nie wierzysz w testowanie? –

0

będę sugerować Pyfdate

Co jest pyfdate?

Biorąc pod uwagę cel Pythona być potężnym i łatwym w użyciu skryptów język, jego funkcje do pracy z datami i czasem nie są tak łatwy w obsłudze, jak powinno być. Celem usługi pyfdate jest naprawienie tej sytuacji za pomocą funkcji przy użyciu dat i czasów, które są , tak potężne i łatwe w użyciu, jak reszta Pythona w wersji .

tutorial

1

Kolejny 3-ga lib strona nie wspomniano tutaj jest mxDateTime (poprzednik zarówno python datetime i 3rd party timeutil) mogą być wykorzystane do tego zadania.

wyżej wymienionych yearsago to: Oczekuje

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) 

Pierwszy parametr, który jest wystąpienie DateTime.

Aby przekształcić zwykły datetime do DateTime można to wykorzystać do 1 sekundy precyzji):

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

czy to dla precyzji 1 mikrosekund:

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) 

I tak, dodając zależność na ten pojedynczy Zadanie to z pewnością byłoby przesadą w porównaniu nawet z użyciem timeutil (sugeruje Rick Copeland).

3

Zdobądź liczbę dni, a następnie podziel przez 365.2425 (średni rok gregoriański) na lata. Podziel przez 30,436875 (średni miesiąc gregoriański) na miesiące.

1

W końcu to, co masz, jest problemem matematycznym. Jeśli co 4 lata mamy dodatkowy dzień, to potem zanurkujemy w timedelta w dniach, a nie w 365, ale 365 * 4 + 1, co dałoby ci 4 lata. Następnie podziel go ponownie przez 4. timedelta/((365 * 4) +1)/4 = timedelta * 4/(365 * 4 +1)

+0

Rzecz o rok przestępny nie ma zastosowania, gdy lata są podzielne przez 100, chyba że są podzielne przez 400. Więc, dla roku 2000: - jest podzielny przez cztery, więc powinien być skok, ale ... - jest również podzielny przez sto, więc nie powinien być skokiem, ale ... - jest podzielny przez 400, więc był to rok przestępny. Dla 1900: - jest podzielny przez 4, więc należy przeskoczyć. - jest podzielny przez 100, więc nie powinien być skok. - NIE jest podzielna przez 400, więc ta reguła nie ma zastosowania. 1900 nie był rokiem przestępnym. – Jblasco

1

To rozwiązanie wypracowałem, mam nadzieję, że może pomóc ;-)

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

Oto zaktualizowana funkcja DOB, który oblicza Urodziny ten sam sposób ludzie zrobić:

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

To zrywa, gdy dob jest 29 lutego, a bieżący rok nie jest rokiem przestępnym. –

Powiązane problemy