วิธีการปัดเศษนาทีของวัตถุวันที่และเวลา


104

ฉันมีdatetimeวัตถุที่ผลิตโดยใช้strptime()ไฟล์.

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

สิ่งที่ฉันต้องทำคือปัดเศษนาทีให้ใกล้ที่สุดนาทีที่ 10 สิ่งที่ฉันทำจนถึงจุดนี้คือการหาค่านาทีและใช้ round () กับมัน

min = round(tm.minute, -1)

อย่างไรก็ตามเช่นเดียวกับตัวอย่างข้างต้นจะให้เวลาที่ไม่ถูกต้องเมื่อค่านาทีมากกว่า 56 เช่น 3:60

วิธีที่ดีกว่านี้คืออะไร? ไม่datetimeสนับสนุนเรื่องนี้?

คำตอบ:


137

สิ่งนี้จะทำให้ 'พื้น' ของdatetimeวัตถุที่จัดเก็บใน tm ถูกปัดเศษเป็นเครื่องหมาย 10 นาทีก่อนหน้าtmนี้

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

หากคุณต้องการให้การปัดเศษแบบคลาสสิกเป็นเครื่องหมาย 10 นาทีที่ใกล้ที่สุดให้ทำดังนี้

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)

หรือสิ่งนี้:

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

97

ฟังก์ชั่นทั่วไปในการปัดเศษวันที่และเวลาใด ๆ ที่ล่วงเลยเป็นวินาที:

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)

ตัวอย่างที่มีการปัดเศษ 1 ชั่วโมงและการปัดเศษ 30 นาที:

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

11
น่าเสียดายที่สิ่งนี้ใช้ไม่ได้กับวันที่และเวลาที่รับรู้ของ tz หนึ่งควรใช้dt.replace(hour=0, minute=0, second=0)แทนdt.min.
skoval00

2
@ skoval00 + druska แก้ไขตามคำแนะนำของคุณเพื่อรองรับ tz-awareness datetime ขอบคุณ!
Le Droid

ขอบคุณ @ skoval00 - ฉันใช้เวลาสักพักในการคิดว่าเหตุใดฟังก์ชันจึงไม่ทำงานกับข้อมูลของฉัน
Michel Mesquita

1
สิ่งนี้ไม่ได้ผลเลยสำหรับฉันเป็นเวลานาน เช่น roundTime(datetime.datetime(2012,12,31,23,44,59,1234),roundTo=60*60*24*7)เทียบกับroundTime(datetime.datetime(2012,12,30,23,44,59,1234),roundTo=60*60*24*7)
CPBL

ดูสิ่งนี้เพื่อทำความเข้าใจปัญหา:datetime.timedelta(100,1,2,3).seconds == 1
CPBL

16

ฉันใช้รหัส Stijn Nevens (ขอบคุณ Stijn) และมีส่วนเสริมเล็ก ๆ น้อย ๆ ที่จะแบ่งปัน ปัดขึ้นลงและปัดเศษให้ใกล้ที่สุด

อัปเดต 2019-03-09 = แสดงความคิดเห็น Spinxz รวมอยู่ด้วย; ขอบคุณ.

อัปเดต 2019-12-27 = ความคิดเห็นรวม Bart; ขอบคุณ.

ทดสอบ date_delta ของ "X ชั่วโมง" หรือ "X นาที" หรือ "X วินาที"

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

สิ่งนี้ช่วยฉันได้ ฉันต้องการเพิ่มว่าถ้าใช้ใน PySpark เพื่อแยกวิเคราะห์วันที่เวลาเป็นสตริงแทนที่จะเป็นอ็อบเจ็กต์วันที่และเวลา
สูงสุด

4
การปัดเศษ 'ขึ้น' อาจไม่ได้ทำตามที่คนส่วนใหญ่คาดหวัง คุณจะปัดเศษขึ้นไปที่ date_delta ถัดไปแม้ว่า dt จะไม่ต้องการการปัดเศษ: เช่น 15: 30: 00.000 โดย round_to = 60 จะกลายเป็น 15: 31: 00.000
spinxz

การupปัดเศษไม่ถูกต้องกับฟังก์ชันนี้ 2019-11-07 14:39:00.776980มีdate_deltaค่าเท่ากับ 30 วินาทีและได้to='up'ผลลัพธ์2019-11-07 14:39:00เป็น
บาร์ต

1
ขอบคุณมาก!! แม้ว่าupการปัดเศษอาจไม่ใช่กรณีการใช้งานทั่วไป แต่ก็จำเป็นเมื่อคุณจัดการกับแอปพลิเคชันซึ่งเริ่มต้นที่ขอบเขตนาที
Rahul Bharadwaj

15

จากคำตอบที่ดีที่สุดที่ฉันแก้ไขเป็นเวอร์ชันดัดแปลงโดยใช้ออบเจ็กต์วันที่และเวลาเท่านั้นสิ่งนี้จะหลีกเลี่ยงการแปลงเป็นวินาทีและทำให้รหัสการโทรอ่านได้ง่ายขึ้น:

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)

ตัวอย่างที่มีการปัดเศษ 1 ชั่วโมงและการปัดเศษ 15 นาที:

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

1
ก็ไม่ดีด้วย: print roundTime(datetime.datetime(2012,12,20,23,44,49),datetime.timedelta(days=15)) 2012-12-20 00:00:00ในขณะที่print roundTime(datetime.datetime(2012,12,21,23,44,49),datetime.timedelta(days=15)) 2012-12-21 00:00:00
CPBL

3
การติดตามผลด้านบน: เพียงแค่ชี้ให้เห็นว่ามันใช้ไม่ได้กับเดลต้าเวลาโดยพลการเช่นที่เกิน 1 วัน คำถามนี้เกี่ยวกับการปัดเศษนาทีดังนั้นจึงเป็นข้อ จำกัด ที่เหมาะสม แต่อาจชัดเจนกว่าในวิธีการเขียนโค้ด
CPBL

5

Pandas มีคุณลักษณะรอบวันที่และเวลา แต่เช่นเดียวกับสิ่งต่างๆใน Pandas จำเป็นต้องอยู่ในรูปแบบซีรี่ส์

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

เอกสาร - เปลี่ยนสตริงความถี่ตามต้องการ


สำหรับการอ้างอิงTimestampมีfloorและceilด้วย
poulter7

3

หากคุณไม่ต้องการใช้เงื่อนไขคุณสามารถใช้moduloโอเปอเรเตอร์:

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

อัปเดต

คุณต้องการสิ่งนี้หรือไม่?

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

.. ถ้าคุณต้องการผลลัพธ์เป็นสตริง. เพื่อให้ได้ผลลัพธ์วันที่และเวลาควรใช้ timedelta - ดูคำตอบอื่น ๆ ;)


อา แต่ปัญหาก็คือชั่วโมงก็ต้องเพิ่มขึ้นเช่นกัน
Lucas Manco

1
@Lucas Manco - วิธีแก้ปัญหาของฉันก็ใช้ได้ดีและฉันคิดว่าสมเหตุสมผลกว่า
Omnifarious

2

ฉันกำลังใช้สิ่งนี้ มีข้อได้เปรียบในการทำงานกับวันที่ที่ทราบ 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

มีข้อเสียในการทำงานเฉพาะช่วงเวลาน้อยกว่าหนึ่งชั่วโมง


2

นี่คือวิธีแก้ปัญหาทั่วไปที่ง่ายกว่าโดยไม่มีปัญหาความแม่นยำของจุดลอยตัวและการพึ่งพาไลบรารีภายนอก:

import datetime

def time_mod(time, delta, epoch=None):
    if epoch is None:
        epoch = datetime.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)

ในกรณีของคุณ:

>>> tm = datetime.datetime(2010, 6, 10, 3, 56, 23)
>>> time_round(tm, datetime.timedelta(minutes=10))
datetime.datetime(2010, 6, 10, 4, 0)

1

แนวทางที่ตรงไปตรงมา:

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

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)

0

ขึ้นอยู่กับ Stijn Nevens และดัดแปลงสำหรับ Django ใช้เพื่อปัดเศษเวลาปัจจุบันเป็น 15 นาทีที่ใกล้ที่สุด

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

หากคุณต้องการวันที่และเวลาแบบเต็มเพียงแค่ลบไฟล์ .strftime('%H:%M:%S')


0

ไม่ใช่วิธีที่ดีที่สุดสำหรับความเร็วเมื่อพบข้อยกเว้น แต่จะได้ผล

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

การกำหนดเวลา

%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

0

โซลูชันที่ใช้งานง่ายสองบรรทัดเพื่อปัดเศษเป็นหน่วยเวลาที่กำหนดซึ่งเป็นวินาทีสำหรับdatetimeวัตถุt:

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

format_strหากคุณต้องการให้รอบไปยังหน่วยที่แตกต่างกันเพียงแค่ปรับเปลี่ยน

วิธีนี้ไม่ได้ปัดเศษเป็นจำนวนเวลาตามอำเภอใจเหมือนวิธีการข้างต้น แต่เป็นวิธี Pythonic ที่ดีในการปัดเศษเป็นชั่วโมงนาทีหรือวินาทีที่กำหนด


0

วิธีแก้ปัญหาอื่น ๆ :

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

หวังว่านี่จะช่วยได้!



0

สิ่งเหล่านี้ดูซับซ้อนเกินไป

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

0

ใช่ถ้าข้อมูลของคุณอยู่ในคอลัมน์ DateTime ในชุดแพนด้าคุณสามารถปัดเศษได้โดยใช้ฟังก์ชัน pandas.Series.dt.round ในตัว ดูเอกสารที่นี่ในpandas.Series.dt.round ในกรณีของคุณปัดเศษเป็น 10 นาทีจะเป็น Series.dt.round ('10min') หรือ Series.dt.round ('600s') ดังนี้:

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

แก้ไขเพื่อเพิ่มรหัสตัวอย่าง:

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]

ฉันไม่แน่ใจว่าคำตอบนี้เพิ่มอะไรใหม่หรือเป็นประโยชน์ มีคำตอบที่อธิบายสิ่งเดียวกันอยู่แล้ว: stackoverflow.com/a/56010357/7851470
Georgy

ใช่ขอขอบคุณที่ชี้ให้ฉันทราบ เป็นความผิดพลาดของฉันที่ไม่รวมโค้ดตัวอย่างในการตอบกลับของฉันและยังไม่ตรวจสอบคำตอบของคนอื่นทั้งหมด ฉันจะพยายามปรับปรุงในด้านนี้
เหงียนไบรอัน
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.