Cara membulatkan menit dari objek datetime

102

I have a datetime object produced using strptime ().

>>> tm
datetime.datetime(2010, 6, 10, 3, 56, 23)

Yang perlu saya lakukan adalah membulatkan menit ke menit ke-10 terdekat. Apa yang telah saya lakukan hingga saat ini adalah mengambil nilai menit dan menggunakan round () di atasnya.

min = round(tm.minute, -1)

Namun, seperti contoh di atas, memberikan waktu tidak valid ketika nilai menit lebih besar dari 56. yaitu: 3:60

Apa cara yang lebih baik untuk melakukan ini? Apakah datetimemendukung ini?

Lucas Manco
sumber

Jawaban:

134

Ini akan membuat 'lantai' dari sebuah datetimeobjek yang disimpan dalam tm dibulatkan ke tanda 10 menit sebelumnya tm.

tm = tm - datetime.timedelta(minutes=tm.minute % 10,
                             seconds=tm.second,
                             microseconds=tm.microsecond)

Jika Anda ingin pembulatan klasik ke tanda 10 menit terdekat, lakukan ini:

discard = datetime.timedelta(minutes=tm.minute % 10,
                             seconds=tm.second,
                             microseconds=tm.microsecond)
tm -= discard
if discard >= datetime.timedelta(minutes=5):
    tm += datetime.timedelta(minutes=10)

atau ini:

tm += datetime.timedelta(minutes=5)
tm -= datetime.timedelta(minutes=tm.minute % 10,
                         seconds=tm.second,
                         microseconds=tm.microsecond)
Beraneka ragam
sumber
94

Fungsi umum untuk membulatkan datetime pada selang waktu dalam hitungan detik:

def roundTime(dt=None, roundTo=60):
   """Round a datetime object to any time lapse in seconds
   dt : datetime.datetime object, default now.
   roundTo : Closest number of seconds to round to, default 1 minute.
   Author: Thierry Husson 2012 - Use it as you want but don't blame me.
   """
   if dt == None : dt = datetime.datetime.now()
   seconds = (dt.replace(tzinfo=None) - dt.min).seconds
   rounding = (seconds+roundTo/2) // roundTo * roundTo
   return dt + datetime.timedelta(0,rounding-seconds,-dt.microsecond)

Sampel dengan pembulatan 1 jam & pembulatan 30 menit:

print roundTime(datetime.datetime(2012,12,31,23,44,59,1234),roundTo=60*60)
2013-01-01 00:00:00

print roundTime(datetime.datetime(2012,12,31,23,44,59,1234),roundTo=30*60)
2012-12-31 23:30:00
Le Droid
sumber
10
Sayangnya ini tidak berfungsi dengan tz-aware datetime. Satu harus menggunakan dt.replace(hour=0, minute=0, second=0)bukan dt.min.
skoval00
2
@ skoval00 + druska Diedit mengikuti saran Anda untuk mendukung waktu yang sadar-tz. Terima kasih!
Le Droid
Terima kasih @ skoval00 - butuh beberapa saat bagi saya untuk mencari tahu mengapa fungsi tersebut tidak berfungsi dengan data saya
Mike Santos
1
Ini sama sekali tidak berhasil bagi saya untuk waktu yang lama. misalnya roundTime(datetime.datetime(2012,12,31,23,44,59,1234),roundTo=60*60*24*7)vsroundTime(datetime.datetime(2012,12,30,23,44,59,1234),roundTo=60*60*24*7)
CPBL
Lihat ini untuk memahami masalahnya:datetime.timedelta(100,1,2,3).seconds == 1
CPBL
15

Dari jawaban terbaik yang saya modifikasi ke versi yang diadaptasi hanya menggunakan objek datetime, ini menghindari keharusan melakukan konversi ke detik dan membuat kode panggilan lebih mudah dibaca:

def roundTime(dt=None, dateDelta=datetime.timedelta(minutes=1)):
    """Round a datetime object to a multiple of a timedelta
    dt : datetime.datetime object, default now.
    dateDelta : timedelta object, we round to a multiple of this, default 1 minute.
    Author: Thierry Husson 2012 - Use it as you want but don't blame me.
            Stijn Nevens 2014 - Changed to use only datetime objects as variables
    """
    roundTo = dateDelta.total_seconds()

    if dt == None : dt = datetime.datetime.now()
    seconds = (dt - dt.min).seconds
    # // is a floor division, not a comment on following line:
    rounding = (seconds+roundTo/2) // roundTo * roundTo
    return dt + datetime.timedelta(0,rounding-seconds,-dt.microsecond)

Sampel dengan pembulatan 1 jam & pembulatan 15 menit:

print roundTime(datetime.datetime(2012,12,31,23,44,59),datetime.timedelta(hour=1))
2013-01-01 00:00:00

print roundTime(datetime.datetime(2012,12,31,23,44,49),datetime.timedelta(minutes=15))
2012-12-31 23:30:00
Stijn Nevens
sumber
1
Juga tidak bagus: print roundTime(datetime.datetime(2012,12,20,23,44,49),datetime.timedelta(days=15)) 2012-12-20 00:00:00sementaraprint roundTime(datetime.datetime(2012,12,21,23,44,49),datetime.timedelta(days=15)) 2012-12-21 00:00:00
CPBL
3
Tindak lanjut di atas: Hanya menunjukkan bahwa itu tidak berfungsi untuk delta waktu sewenang-wenang, misalnya yang lebih dari 1 hari. Pertanyaan ini adalah tentang pembulatan menit, jadi itu adalah batasan yang sesuai, tetapi bisa lebih jelas dalam cara kode ditulis.
CPBL
15

Saya menggunakan kode Stijn Nevens (terima kasih Stijn) dan memiliki sedikit add-on untuk dibagikan. Pembulatan ke atas, bawah, dan pembulatan ke terdekat.

perbarui 2019-03-09 = komentar Spinxz dimasukkan; Terima kasih.

perbarui 2019-12-27 = komentar Bart dimasukkan; Terima kasih.

Diuji untuk date_delta dari "X jam" atau "X menit" atau "X detik".

import datetime

def round_time(dt=None, date_delta=datetime.timedelta(minutes=1), to='average'):
    """
    Round a datetime object to a multiple of a timedelta
    dt : datetime.datetime object, default now.
    dateDelta : timedelta object, we round to a multiple of this, default 1 minute.
    from:  http://stackoverflow.com/questions/3463930/how-to-round-the-minute-of-a-datetime-object-python
    """
    round_to = date_delta.total_seconds()
    if dt is None:
        dt = datetime.now()
    seconds = (dt - dt.min).seconds

    if seconds % round_to == 0 and dt.microsecond == 0:
        rounding = (seconds + round_to / 2) // round_to * round_to
    else:
        if to == 'up':
            # // is a floor division, not a comment on following line (like in javascript):
            rounding = (seconds + dt.microsecond/1000000 + round_to) // round_to * round_to
        elif to == 'down':
            rounding = seconds // round_to * round_to
        else:
            rounding = (seconds + round_to / 2) // round_to * round_to

    return dt + datetime.timedelta(0, rounding - seconds, - dt.microsecond)

# test data
print(round_time(datetime.datetime(2019,11,1,14,39,00), date_delta=datetime.timedelta(seconds=30), to='up'))
print(round_time(datetime.datetime(2019,11,2,14,39,00,1), date_delta=datetime.timedelta(seconds=30), to='up'))
print(round_time(datetime.datetime(2019,11,3,14,39,00,776980), date_delta=datetime.timedelta(seconds=30), to='up'))
print(round_time(datetime.datetime(2019,11,4,14,39,29,776980), date_delta=datetime.timedelta(seconds=30), to='up'))
print(round_time(datetime.datetime(2018,11,5,14,39,00,776980), date_delta=datetime.timedelta(seconds=30), to='down'))
print(round_time(datetime.datetime(2018,11,6,14,38,59,776980), date_delta=datetime.timedelta(seconds=30), to='down'))
print(round_time(datetime.datetime(2017,11,7,14,39,15), date_delta=datetime.timedelta(seconds=30), to='average'))
print(round_time(datetime.datetime(2017,11,8,14,39,14,999999), date_delta=datetime.timedelta(seconds=30), to='average'))
print(round_time(datetime.datetime(2019,11,9,14,39,14,999999), date_delta=datetime.timedelta(seconds=30), to='up'))
print(round_time(datetime.datetime(2012,12,10,23,44,59,7769),to='average'))
print(round_time(datetime.datetime(2012,12,11,23,44,59,7769),to='up'))
print(round_time(datetime.datetime(2010,12,12,23,44,59,7769),to='down',date_delta=datetime.timedelta(seconds=1)))
print(round_time(datetime.datetime(2011,12,13,23,44,59,7769),to='up',date_delta=datetime.timedelta(seconds=1)))
print(round_time(datetime.datetime(2012,12,14,23,44,59),date_delta=datetime.timedelta(hours=1),to='down'))
print(round_time(datetime.datetime(2012,12,15,23,44,59),date_delta=datetime.timedelta(hours=1),to='up'))
print(round_time(datetime.datetime(2012,12,16,23,44,59),date_delta=datetime.timedelta(hours=1)))
print(round_time(datetime.datetime(2012,12,17,23,00,00),date_delta=datetime.timedelta(hours=1),to='down'))
print(round_time(datetime.datetime(2012,12,18,23,00,00),date_delta=datetime.timedelta(hours=1),to='up'))
print(round_time(datetime.datetime(2012,12,19,23,00,00),date_delta=datetime.timedelta(hours=1)))
MZA
sumber
Ini membantu saya. Saya ingin menambahkan bahwa jika menggunakannya di PySpark, untuk mengurai tanggal waktu sebagai string daripada objek waktu tanggal.
Maks.
4
Pembulatan 'ke atas' mungkin tidak melakukan apa yang diharapkan kebanyakan orang. Anda akan membulatkan ke date_delta berikutnya meskipun dt tidak memerlukan pembulatan: misalnya 15: 30: 00.000 dengan round_to = 60 akan menjadi 15: 31:
00.000
The uppembulatan bagaimanapun akurat dengan fungsi ini; 2019-11-07 14:39:00.776980dengan date_deltasama dengan misalnya 30 detik dan to='up'hasilnya 2019-11-07 14:39:00.
Bart
1
Terima kasih banyak!! Meskipun uppembulatan mungkin bukan kasus penggunaan umum, pembulatan ini diperlukan saat Anda menangani aplikasi yang dimulai pada batas menit
Rahul Bharadwaj
5

Pandas memiliki fitur putaran waktu, tetapi seperti kebanyakan hal di Pandas, fitur ini harus dalam format Seri.

>>> ts = pd.Series(pd.date_range(Dt(2019,1,1,1,1),Dt(2019,1,1,1,4),periods=8))
>>> print(ts)
0   2019-01-01 01:01:00.000000000
1   2019-01-01 01:01:25.714285714
2   2019-01-01 01:01:51.428571428
3   2019-01-01 01:02:17.142857142
4   2019-01-01 01:02:42.857142857
5   2019-01-01 01:03:08.571428571
6   2019-01-01 01:03:34.285714285
7   2019-01-01 01:04:00.000000000
dtype: datetime64[ns]

>>> ts.dt.round('1min')
0   2019-01-01 01:01:00
1   2019-01-01 01:01:00
2   2019-01-01 01:02:00
3   2019-01-01 01:02:00
4   2019-01-01 01:03:00
5   2019-01-01 01:03:00
6   2019-01-01 01:04:00
7   2019-01-01 01:04:00
dtype: datetime64[ns]

Docs - Ubah string frekuensi sesuai kebutuhan.

Erock618
sumber
Untuk referensi, Timestampberisi floordan ceiljuga
poulter7
3

Jika Anda tidak ingin menggunakan kondisi, Anda dapat menggunakan modulooperator:

minutes = int(round(tm.minute, -1)) % 60

MEMPERBARUI

apakah kamu menginginkan sesuatu seperti ini?

def timeround10(dt):
    a, b = divmod(round(dt.minute, -1), 60)
    return '%i:%02i' % ((dt.hour + a) % 24, b)

timeround10(datetime.datetime(2010, 1, 1, 0, 56, 0)) # 0:56
# -> 1:00

timeround10(datetime.datetime(2010, 1, 1, 23, 56, 0)) # 23:56
# -> 0:00

.. Jika Anda ingin hasil sebagai string. untuk mendapatkan hasil datetime, lebih baik menggunakan timedelta - lihat tanggapan lain;)

mykhal
sumber
Ah, tetapi masalahnya di sini adalah waktunya harus meningkat juga
Lucas Manco
1
@Lucas Manco - Solusi saya juga berfungsi dengan baik dan menurut saya lebih masuk akal.
Omnifarious
2

saya menggunakan ini. itu memiliki keuntungan bekerja dengan datetimes yang sadar tz.

def round_minutes(some_datetime: datetime, step: int):
    """ round up to nearest step-minutes """
    if step > 60:
        raise AttrbuteError("step must be less than 60")

    change = timedelta(
        minutes= some_datetime.minute % step,
        seconds=some_datetime.second,
        microseconds=some_datetime.microsecond
    )

    if change > timedelta():
        change -= timedelta(minutes=step)

    return some_datetime - change

Ini memiliki kelemahan hanya bekerja untuk penjepit waktu kurang dari satu jam.

David Chan
sumber
2

Berikut adalah solusi umum yang lebih sederhana tanpa masalah presisi floating point dan dependensi library eksternal:

import datetime as dt

def time_mod(time, delta, epoch=None):
    if epoch is None:
        epoch = dt.datetime(1970, 1, 1, tzinfo=time.tzinfo)
    return (time - epoch) % delta

def time_round(time, delta, epoch=None):
    mod = time_mod(time, delta, epoch)
    if mod < (delta / 2):
       return time - mod
    return time + (delta - mod)

Dalam kasus Anda:

>>> tm
datetime.datetime(2010, 6, 10, 3, 56, 23)
>>> time_round(tm, dt.timedelta(minutes=10))
datetime.datetime(2010, 6, 10, 4, 0)
ofo
sumber
0
def get_rounded_datetime(self, dt, freq, nearest_type='inf'):

    if freq.lower() == '1h':
        round_to = 3600
    elif freq.lower() == '3h':
        round_to = 3 * 3600
    elif freq.lower() == '6h':
        round_to = 6 * 3600
    else:
        raise NotImplementedError("Freq %s is not handled yet" % freq)

    # // is a floor division, not a comment on following line:
    seconds_from_midnight = dt.hour * 3600 + dt.minute * 60 + dt.second
    if nearest_type == 'inf':
        rounded_sec = int(seconds_from_midnight / round_to) * round_to
    elif nearest_type == 'sup':
        rounded_sec = (int(seconds_from_midnight / round_to) + 1) * round_to
    else:
        raise IllegalArgumentException("nearest_type should be  'inf' or 'sup'")

    dt_midnight = datetime.datetime(dt.year, dt.month, dt.day)

    return dt_midnight + datetime.timedelta(0, rounded_sec)
JulienV
sumber
0

Berdasarkan Stijn Nevens dan dimodifikasi untuk penggunaan Django untuk membulatkan waktu saat ini ke 15 menit terdekat.

from datetime import date, timedelta, datetime, time

    def roundTime(dt=None, dateDelta=timedelta(minutes=1)):

        roundTo = dateDelta.total_seconds()

        if dt == None : dt = datetime.now()
        seconds = (dt - dt.min).seconds
        # // is a floor division, not a comment on following line:
        rounding = (seconds+roundTo/2) // roundTo * roundTo
        return dt + timedelta(0,rounding-seconds,-dt.microsecond)

    dt = roundTime(datetime.now(),timedelta(minutes=15)).strftime('%H:%M:%S')

 dt = 11:45:00

jika Anda membutuhkan tanggal dan waktu penuh, hapus saja .strftime('%H:%M:%S')

WayBehind
sumber
0

Bukan yang terbaik untuk kecepatan saat pengecualian ditangkap, namun ini akan berhasil.

def _minute10(dt=datetime.utcnow()):
    try:
        return dt.replace(minute=round(dt.minute, -1))
    except ValueError:
        return dt.replace(minute=0) + timedelta(hours=1)

Pengaturan waktu

%timeit _minute10(datetime(2016, 12, 31, 23, 55))
100000 loops, best of 3: 5.12 µs per loop

%timeit _minute10(datetime(2016, 12, 31, 23, 31))
100000 loops, best of 3: 2.21 µs per loop
James
sumber
0

Solusi intuitif dua baris untuk membulatkan ke satuan waktu tertentu, di sini detik, untuk suatu datetimeobjek t:

format_str = '%Y-%m-%d %H:%M:%S'
t_rounded = datetime.strptime(datetime.strftime(t, format_str), format_str)

Jika Anda ingin membulatkan ke unit lain, cukup ubah format_str.

Pendekatan ini tidak membulatkan ke jumlah waktu sewenang-wenang seperti metode di atas, tetapi merupakan cara Pythonic yang bagus untuk membulatkan ke jam, menit atau detik tertentu.

Barnhillec.dll
sumber
0

Solusi lain:

def round_time(timestamp=None, lapse=0):
    """
    Round a timestamp to a lapse according to specified minutes

    Usage:

    >>> import datetime, math
    >>> round_time(datetime.datetime(2010, 6, 10, 3, 56, 23), 0)
    datetime.datetime(2010, 6, 10, 3, 56)
    >>> round_time(datetime.datetime(2010, 6, 10, 3, 56, 23), 1)
    datetime.datetime(2010, 6, 10, 3, 57)
    >>> round_time(datetime.datetime(2010, 6, 10, 3, 56, 23), -1)
    datetime.datetime(2010, 6, 10, 3, 55)
    >>> round_time(datetime.datetime(2019, 3, 11, 9, 22, 11), 3)
    datetime.datetime(2019, 3, 11, 9, 24)
    >>> round_time(datetime.datetime(2019, 3, 11, 9, 22, 11), 3*60)
    datetime.datetime(2019, 3, 11, 12, 0)
    >>> round_time(datetime.datetime(2019, 3, 11, 10, 0, 0), 3)
    datetime.datetime(2019, 3, 11, 10, 0)

    :param timestamp: Timestamp to round (default: now)
    :param lapse: Lapse to round in minutes (default: 0)
    """
    t = timestamp or datetime.datetime.now()  # type: Union[datetime, Any]
    surplus = datetime.timedelta(seconds=t.second, microseconds=t.microsecond)
    t -= surplus
    try:
        mod = t.minute % lapse
    except ZeroDivisionError:
        return t
    if mod:  # minutes % lapse != 0
        t += datetime.timedelta(minutes=math.ceil(t.minute / lapse) * lapse - t.minute)
    elif surplus != datetime.timedelta() or lapse < 0:
        t += datetime.timedelta(minutes=(t.minute / lapse + 1) * lapse - t.minute)
    return t

Semoga ini membantu!

juliocesar.dll
sumber
0

Cara terpendek yang saya tahu

min = tm.minute // 10 * 10

mLuzgin.dll
sumber
0

Itu tampak terlalu rumit

def round_down_to():
    num = int(datetime.utcnow().replace(second=0, microsecond=0).minute)
    return num - (num%10)
Ashton Honnecke
sumber
0

Pendekatan langsung:

def round_time(dt, round_to_seconds=60):
    """Round a datetime object to any number of seconds
    dt: datetime.datetime object
    round_to_seconds: closest number of seconds for rounding, Default 1 minute.
    """
    rounded_epoch = round(dt.timestamp() / round_to_seconds) * round_to_seconds
    rounded_dt = datetime.datetime.fromtimestamp(rounded_epoch).astimezone(dt.tzinfo)
    return rounded_dt
banting
sumber
0

ya, jika data Anda termasuk dalam kolom DateTime dalam seri pandas, Anda bisa membulatkannya menggunakan fungsi pandas.Series.dt.round bawaan. Lihat dokumentasi di sini di pandas.Series.dt.round . Dalam kasus Anda pembulatan ke 10 menit itu akan menjadi Series.dt.round ('10min') atau Series.dt.round ('600s') seperti ini:

pandas.Series(tm).dt.round('10min')

Edit untuk menambahkan kode Contoh:

import datetime
import pandas

tm = datetime.datetime(2010, 6, 10, 3, 56, 23)
tm_rounded = pandas.Series(tm).dt.round('10min')
print(tm_rounded)

>>> 0   2010-06-10 04:00:00
dtype: datetime64[ns]
Nguyen Bryan
sumber
Dapatkah Anda menunjukkan cara menggunakan saran Anda?
DanielM
Saya tidak yakin jawaban ini menambahkan sesuatu yang baru atau berguna. Sudah ada jawaban yang menjelaskan hal yang sama: stackoverflow.com/a/56010357/7851470
Georgy
ya, terima kasih telah menunjukkan ini kepada saya. Ini adalah kesalahan saya karena tidak menyertakan kode contoh ke dalam tanggapan saya, dan juga tidak memeriksa semua tanggapan orang lain. Saya akan mencoba meningkatkan aspek ini.
Nguyen Bryan