Python timedelta dalam beberapa tahun

Jawaban:

163

Anda membutuhkan lebih dari satu timedeltauntuk mengetahui berapa tahun telah berlalu; Anda juga perlu mengetahui tanggal awal (atau akhir). (Ini adalah tahun kabisat.)

Taruhan terbaik Anda adalah menggunakan dateutil.relativedelta objek , tapi itu adalah modul pihak ke-3. Jika Anda ingin mengetahui datetimebahwa itu adalah ntahun dari suatu tanggal (default sekarang), Anda dapat melakukan hal berikut:

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)

Jika Anda lebih suka menggunakan pustaka standar, jawabannya sedikit lebih rumit ::

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)

Jika 2/29, dan 18 tahun yang lalu tidak ada 2/29, fungsi ini akan mengembalikan 2/28. Jika Anda lebih suka mengembalikan 3/1, ubah saja returnpernyataan terakhir menjadi:

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

Pertanyaan Anda awalnya mengatakan Anda ingin tahu sudah berapa tahun sejak suatu tanggal. Dengan asumsi Anda menginginkan bilangan bulat tahun, Anda dapat menebak berdasarkan 365,25 hari per tahun dan kemudian memeriksa menggunakan salah satu yearsagofungsi yang ditentukan di atas ::

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
Rick Copeland
sumber
28
Anda dapat sepenuhnya akurat dengan 365,2425 (bukan 365,25), yang memperhitungkan pengecualian 400 tahun untuk kalender Gregorian.
Brianary
3
Fungsi Anda melanggar hukum untuk negara-negara seperti Inggris dan Hong Kong, karena mereka "membulatkan" ke tanggal 1 Maret untuk tahun kabisat.
antihero
3
lihat juga ini dan ini Keduanya adalah daftar yang sangat bagus tentang hal-hal yang tidak benar tentang waktu.
gvoysey
49

Jika Anda mencoba memeriksa apakah seseorang berusia 18 tahun, penggunaan timedeltatidak akan berfungsi dengan benar pada beberapa kasus edge karena tahun kabisat. Misalnya, seseorang yang lahir pada tanggal 1 Januari 2000, akan berusia 18 tahun tepat 6575 hari kemudian pada tanggal 1 Januari 2018 (termasuk 5 tahun kabisat), tetapi seseorang yang lahir pada tanggal 1 Januari 2001, akan berusia 18 tahun tepat 6574 hari kemudian pada tanggal 1 Januari, 2019 (termasuk 4 tahun kabisat). Jadi, jika seseorang berusia tepat 6574 hari, Anda tidak dapat menentukan apakah mereka berusia 17 atau 18 tahun tanpa mengetahui lebih banyak informasi tentang tanggal lahir mereka.

Cara yang benar untuk melakukan ini adalah menghitung usia secara langsung dari tanggal, dengan mengurangi dua tahun, dan kemudian menguranginya jika bulan / hari saat ini mendahului bulan / hari kelahiran.

Adam Rosenfield
sumber
9

Pertama, pada tingkat yang paling mendetail, masalah tidak dapat diselesaikan dengan tepat. Panjang tahun bervariasi, dan tidak ada "pilihan tepat" yang jelas untuk lamanya tahun.

Meskipun demikian, dapatkan perbedaan dalam satuan apa pun yang "alami" (mungkin detik) dan bagi dengan rasio antara satuan tersebut dan tahun. Misalnya

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

...atau terserah. Hindari berbulan-bulan, karena mereka bahkan kurang didefinisikan dengan baik daripada tahun.

MarkusQ
sumber
2
Itu BUKAN apa yang orang maksud atau gunakan ketika itu adalah pertanyaan tentang berapa tahun masa kerja atau sudahkah seseorang mencapai usia tertentu.
John Machin
3
365.25 Anda harus 365.2425 untuk memperhitungkan pengecualian 400 tahun dari kalender Gregorian.
Brianary
1
Nah, masalahnya bisa diselesaikan dengan benar - Anda bisa tahu sebelumnya tahun mana yang memiliki hari kabisat dan detik kabisat dan sebagainya. Hanya saja tidak ada cara yang sangat elegan untuk melakukannya tanpa mengurangi tahun, lalu bulan, lalu hari, dll ... dalam dua tanggal yang diformat
Litherum
8

Berikut adalah fungsi DOB yang diperbarui, yang menghitung ulang tahun dengan cara yang sama seperti manusia:

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)))
anti hero
sumber
Ini rusak ketika dob adalah 29 Februari dan tahun ini bukan tahun kabisat.
Trey Hunner
4

Dapatkan jumlah hari, lalu bagi dengan 365,2425 (tahun rata-rata Gregorian) untuk tahun. Bagilah dengan 30,436875 (rata-rata bulan Gregorian) untuk bulan.

brianari
sumber
2

Seberapa tepat Anda membutuhkannya? td.days / 365.25akan membuat Anda cukup dekat, jika Anda khawatir tentang tahun kabisat.

eduffy
sumber
Saya sangat khawatir tentang tahun kabisat. Ini harus memeriksa apakah seseorang berusia di atas 18 tahun.
Migol
Maka tidak ada satu kalimat yang mudah, Anda harus mengurai kedua tanggal tersebut dan mencari tahu apakah orang tersebut telah melewati ulang tahun ke-18 atau belum.
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
John Mee
sumber
Seseorang yang lahir pada tanggal 29 Februari akan dianggap telah mencapai usia 1 pada tanggal 28 Februari berikutnya.
John Machin
Baik. Dikoreksi untuk mengakomodasi 0,08% populasi yang lahir pada tanggal 29 dengan membalik tes dari "adalah ulang tahun setelah hari ini" menjadi "ulang tahun sebelum hari ini". Apakah itu menyelesaikannya?
John Mee
Ini berfungsi dengan benar untuk contoh Anda!?! Jika saya menetapkan "hari ini" ke 28 Februari 2009, dan tanggal lahir ke 29 Februari 2008 mengembalikan Nol - setidaknya untuk saya; bukan 1 seperti yang Anda sarankan (Python 2.5.2). Tidak perlu kasar, Tuan Machin. Tepatnya pada dua tanggal apa Anda bermasalah?
John Mee
Saya akan coba lagi: Seseorang yang lahir pada tanggal 29 Februari akan diperlakukan oleh kebanyakan orang untuk sebagian besar tujuan hukum karena telah mencapai usia 1 pada tanggal 28 Februari berikutnya. Kode Anda menghasilkan 0, baik sebelum dan sesudah "koreksi" Anda. Faktanya, dua versi kode Anda menghasilkan output yang PERSIS sama untuk semua 9 kemungkinan input (bulan <==> X hari <==>). BTW, 100.0 / (4 * 365 + 1) menghasilkan 0,068, bukan 0,08.
John Machin
2
Mendesah. (0) Mengatasi masalah 29 Februari penting dalam aritmatika tanggal apa pun; Anda hanya mengabaikannya. (1) Kode Anda tidak lebih baik pertama kali; Apa yang tidak Anda pahami dalam "menghasilkan output yang PERSIS sama"? (2) Tiga kemungkinan hasil atom (<, ==,>) membandingkan hari ini. Bulan dan bulan bulan; tiga kemungkinan hasil atom membandingkan hari ini.hari dan dob.day; 3 * 3 == 9 (3) stackoverflow.com/questions/2217488/…
John Machin
1

Namun lib pihak ketiga lain yang tidak disebutkan di sini adalah mxDateTime (pendahulu python datetimedan pihak ketiga timeutil) dapat digunakan untuk tugas ini.

Yang tersebut di atas yearsagoadalah:

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)

Parameter pertama diharapkan menjadi sebuah DateTimeinstance.

Untuk mengonversi biasa datetimemenjadi DateTimeAnda dapat menggunakan ini selama 1 detik presisi):

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

atau ini untuk presisi 1 mikrodetik:

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)

Dan ya, menambahkan ketergantungan untuk tugas tunggal ini pasti akan menjadi berlebihan dibandingkan dengan menggunakan timeutil (disarankan oleh Rick Copeland).

Antony Hatchkins
sumber
1

Pada akhirnya yang Anda miliki adalah masalah matematika. Jika setiap 4 tahun kita memiliki satu hari ekstra, mari kita selami timedelta dalam beberapa hari, bukan 365 tetapi 365 * 4 + 1, itu akan memberi Anda jumlah 4 tahun. Kemudian bagi lagi dengan 4. timedelta / ((365 * 4) +1) / 4 = timedelta * 4 / (365 * 4 +1)

Norberto
sumber
Hal tahun kabisat tidak berlaku bila tahun habis habis 100, kecuali habis habis 400. Jadi, untuk tahun 2000: - habis dibagi empat, jadi harus lompatan tapi ... - bisa habis juga oleh seratus, jadi itu tidak harus lompatan tapi ... - itu habis dibagi 400, jadi itu sebenarnya tahun kabisat. Untuk 1900: - habis dibagi 4 jadi harus lompatan. - dapat dibagi 100, jadi tidak boleh lompatan. - TIDAK habis dibagi 400, jadi aturan ini tidak berlaku. 1900 bukanlah tahun kabisat.
Jblasco
1

Ini adalah solusi yang saya berhasil, saya harap dapat membantu ;-)

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
pvilas.dll
sumber
0

Meskipun utas ini sudah mati, bolehkah saya menyarankan solusi yang berfungsi untuk masalah yang sama yang saya hadapi ini. Ini dia (tanggal adalah string dalam format dd-mm-yyyy):

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

sumber
0

fungsi ini mengembalikan perbedaan tahun antara dua tanggal (diambil sebagai string dalam format ISO, tetapi dapat dengan mudah dimodifikasi untuk menggunakan format apa pun)

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
Mauro Bianchi
sumber
0

Saya akan menyarankan Pyfdate

Apa itu pyfdate?

Mengingat tujuan Python untuk menjadi bahasa skrip yang kuat dan mudah digunakan, fitur-fiturnya untuk bekerja dengan tanggal dan waktu tidak semudah yang seharusnya. Tujuan pyfdate adalah untuk memperbaiki situasi tersebut dengan menyediakan fitur untuk bekerja dengan tanggal dan waktu yang sama kuat dan mudah digunakannya seperti Python lainnya.

yang tutorial

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

Ini adalah kode yang dijalankan oleh operator Carrousel di film Logan's Run;)

https://en.wikipedia.org/wiki/Logan%27s_Run_(film)

Andres Hurtis
sumber
0

Saya menemukan pertanyaan ini dan menemukan jawaban Adams https://stackoverflow.com/a/765862/2964689 yang paling membantu

Tapi tidak ada contoh python dari metodenya tapi inilah yang akhirnya saya gunakan.

input: objek datetime

keluaran: usia integer dalam satu tahun penuh

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
Sam
sumber
0

Saya menyukai solusi John Mee karena kesederhanaannya, dan saya tidak terlalu peduli tentang bagaimana, pada 28 Februari atau 1 Maret ketika ini bukan tahun kabisat, untuk menentukan usia orang yang lahir pada 29 Februari. Namun berikut ini adalah perubahan kodenya yang menurut saya mengatasi keluhan:

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
Rick Graves
sumber