Cách làm tròn phút của đối tượng ngày giờ


102

I have a datetime object produced using strptime ().

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

Những gì tôi cần làm là làm tròn phút đến phút thứ 10 gần nhất. Những gì tôi đã làm cho đến thời điểm này là lấy giá trị phút và sử dụng round () trên đó.

min = round(tm.minute, -1)

Tuy nhiên, như với ví dụ trên, nó đưa ra thời gian không hợp lệ khi giá trị phút lớn hơn 56. tức là: 3:60

Cách tốt hơn để làm điều này là gì? Có datetimehỗ trợ điều này không?

Câu trả lời:


134

Điều này sẽ làm cho 'sàn' của một datetimeđối tượng được lưu trữ trong tm được làm tròn thành 10 phút trước đó tm.

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

Nếu bạn muốn làm tròn cổ điển đến mốc 10 phút gần nhất, hãy làm như sau:

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)

hoặc cái này:

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

94

Chức năng chung để làm tròn một ngày giờ bất kỳ lúc nào trôi đi trong vài giây:

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)

Các mẫu làm tròn 1 giờ & làm tròn 30 phút:

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

10
Thật không may, điều này không hoạt động với ngày giờ nhận biết tz. Người ta nên sử dụng dt.replace(hour=0, minute=0, second=0)thay vì dt.min.
skoval00

2
@ skoval00 + druska Đã chỉnh sửa theo lời khuyên của bạn để hỗ trợ ngày giờ nhận biết tz. Cảm ơn!
Le Droid

Cảm ơn @ skoval00 - tôi đã mất một lúc để tìm ra lý do tại sao hàm không hoạt động với dữ liệu của tôi
Mike Santos

1
Điều này không có tác dụng gì đối với tôi trong thời gian dài. ví dụ 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

Xem phần này để hiểu vấn đề:datetime.timedelta(100,1,2,3).seconds == 1
CPBL

15

Từ câu trả lời hay nhất mà tôi đã sửa đổi thành phiên bản thích ứng chỉ sử dụng các đối tượng datetime, điều này tránh phải thực hiện chuyển đổi thành giây và làm cho mã gọi dễ đọc hơn:

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)

Các mẫu có làm tròn 1 giờ & làm tròn 15 phút:

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
Cũng không tốt: print roundTime(datetime.datetime(2012,12,20,23,44,49),datetime.timedelta(days=15)) 2012-12-20 00:00:00trong khiprint roundTime(datetime.datetime(2012,12,21,23,44,49),datetime.timedelta(days=15)) 2012-12-21 00:00:00
CPBL

3
Tiếp theo ở trên: Chỉ chỉ ra rằng nó không hoạt động đối với các delta thời gian tùy ý, ví dụ như những delta hơn 1 ngày. Câu hỏi này là về số phút làm tròn, vì vậy đó là một hạn chế thích hợp, nhưng nó có thể rõ ràng hơn trong cách viết mã.
CPBL

15

Tôi đã sử dụng mã Stijn Nevens (cảm ơn bạn Stijn) và có một chút tiện ích bổ sung để chia sẻ. Làm tròn lên, xuống và làm tròn đến gần nhất.

cập nhật 2019-03-09 = bình luận Spinxz kết hợp; cảm ơn bạn.

cập nhật 2019-12-27 = bình luận Bart kết hợp; cảm ơn bạn.

Đã kiểm tra date_delta là "X giờ" hoặc "X phút" hoặc "X giây".

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

Điều này đã giúp tôi. Tôi muốn thêm điều đó nếu sử dụng nó trong PySpark, để phân tích cú pháp thời gian ngày tháng dưới dạng một chuỗi thay vì một đối tượng thời gian ngày tháng.
Tối đa

4
Việc làm tròn 'lên' có thể không làm được những gì mọi người mong đợi. Bạn sẽ làm tròn lên đến date_delta tiếp theo ngay cả khi dt sẽ không cần phải làm tròn: ví dụ 15: 30: 00.000 với round_to = 60 sẽ trở thành 15: 31: 00.000
spinxz

Việc uplàm tròn số dù sao cũng không chính xác với chức năng này; 2019-11-07 14:39:00.776980với date_deltabằng ví dụ 30 giây và to='up'kết quả là 2019-11-07 14:39:00.
Bart

1
Cảm ơn rất nhiều!! Mặc dù uplàm tròn có thể không phải là trường hợp sử dụng phổ biến, nhưng nó cần thiết khi bạn xử lý các ứng dụng bắt đầu ở ranh giới phút
Rahul Bharadwaj

5

Pandas có tính năng vòng ngày giờ, nhưng cũng như hầu hết mọi thứ ở Pandas, nó cần ở định dạng Series.

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

Tài liệu - Thay đổi chuỗi tần số nếu cần.


Để tham khảo, Timestampchứa floorceilcả
poulter7

3

nếu bạn không muốn sử dụng điều kiện, bạn có thể sử dụng modulotoán tử:

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

CẬP NHẬT

bạn có muốn một cái gì đó như thế này?

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

.. nếu bạn muốn kết quả là chuỗi. để có được kết quả ngày giờ, tốt hơn nên sử dụng thời gian - xem các phản hồi khác;)


À nhưng vấn đề ở đây là giờ cũng phải tăng lên
Lucas Manco

1
@Lucas Manco - Giải pháp của tôi cũng hoạt động tốt và tôi nghĩ có ý nghĩa hơn.
Omnifarious

2

tôi đang sử dụng cái này. nó có lợi thế là làm việc với thời gian nhận biết 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

nó có nhược điểm là chỉ hoạt động trong khoảng thời gian ít hơn một giờ.


2

Đây là một giải pháp tổng quát đơn giản hơn mà không có vấn đề về độ chính xác dấu chấm động và phụ thuộc thư viện bên ngoài:

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)

Trong trường hợp của bạn:

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

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

Dựa trên Stijn Nevens và được sửa đổi để Django sử dụng để làm tròn thời gian hiện tại chính xác đến 15 phút.

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

nếu bạn cần ngày và giờ đầy đủ, chỉ cần xóa .strftime('%H:%M:%S')


0

Không phải là tốc độ tốt nhất khi bắt được ngoại lệ, tuy nhiên điều này sẽ hoạt động.

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

Thời gian

%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

Một giải pháp trực quan hai dòng để làm tròn đến một đơn vị thời gian nhất định, ở đây là giây, cho một datetimeđối tượng t:

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

Nếu bạn muốn làm tròn thành một đơn vị khác, chỉ cần thay đổi format_str.

Cách tiếp cận này không làm tròn đến các khoảng thời gian tùy ý như các phương pháp trên, nhưng là một cách độc đáo của Pythonic để làm tròn đến một giờ, phút hoặc giây nhất định.


0

Giải pháp khác:

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

Hi vọng điêu nay co ich!


0

Con đường ngắn nhất mà tôi biết

min = tm.minute // 10 * 10


0

Những cái đó có vẻ quá phức tạp

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

0

Một cách tiếp cận đơn giản:

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

vâng, nếu dữ liệu của bạn thuộc cột DateTime trong chuỗi gấu trúc, bạn có thể làm tròn dữ liệu bằng cách sử dụng hàm pandas.Series.dt.round được tích hợp sẵn. Xem tài liệu tại đây trên pandas.Series.dt.round . Trong trường hợp của bạn làm tròn thành 10 phút, nó sẽ là Series.dt.round ('10 phút') hoặc Series.dt.round ('600s') như sau:

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

Chỉnh sửa để thêm Mã mẫu:

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]

Bạn có thể chỉ cách sử dụng những gì bạn đã đề xuất?
DanielM

Tôi không chắc rằng câu trả lời này bổ sung bất kỳ điều gì mới hoặc hữu ích. Đã có một câu trả lời giải thích điều tương tự: stackoverflow.com/a/56010357/7851470
Georgy

vâng, cảm ơn bạn đã chỉ ra cho tôi. Đó là sai lầm của tôi khi không đưa mã mẫu vào phản hồi của mình và cũng không kiểm tra tất cả các phản hồi của người khác. Tôi sẽ cố gắng cải thiện khía cạnh này.
Nguyen Bryan
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.