Как округлить минуты Python объекта datetime

У меня есть объект datetime, созданный с использованием strptime().

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

Что мне нужно сделать, это от минуты до ближайшей 10-й минуты. До этого момента я принимал значение минут и использовал round().

min = round(tm.minute, -1)

Однако, как и в приведенном выше примере, он дает недопустимое время, когда значение минуты больше 56. т.е. 3:60

Какой лучший способ сделать это? Datetime поддерживает это?

22 ответа

Это получит "пол" datetime объект хранится в тм округляется до 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)

Общая функция для округления даты и времени в любой момент времени в секундах:

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

Я использовал код Stijn Nevens для большого эффекта (спасибо Stijn), и у меня было небольшое дополнение, которым можно поделиться. Округление вверх, вниз и округление до ближайшего.

def round_time(dt=None, date_delta=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://stackru.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 to == 'up':
        # // is a floor division, not a comment on following line (like in javascript):
        rounding = (seconds + 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 + timedelta(0, rounding - seconds, -dt.microsecond)

От лучшего ответа, который я изменил, до адаптированной версии, использующей только объекты datetime, это избавляет от необходимости выполнять преобразование в секунды и делает вызывающий код более читабельным:

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

Вот более простое обобщенное решение без проблем с точностью с плавающей запятой и зависимостей от внешних библиотек:

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)

В твоем случае:

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

У Pandas есть функция округления даты и времени, но, как и у большинства вещей в Pandas, она должна быть в формате 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]

Документы - измените строку частоты по мере необходимости.

Если вы не хотите использовать условие, вы можете использовать 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

.. если вы хотите, чтобы результат в виде строки. для получения результата datetime лучше использовать timedelta - см. другие ответы;)

Я использую это. у него есть преимущество работы с tz-datetime.

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

его недостаток заключается в том, что он работает только с временными срезами менее часа.

Это сделает это, я думаю, что он использует очень полезное приложение раунда.

      from typing import Literal
import math

def round_datetime(dt: datetime.datetime, step: datetime.timedelta, d: Literal['no', 'up', 'down'] = 'no'):
    step = step.seconds
    round_f = {'no': round, 'up': math.ceil, 'down': math.floor}
    return datetime.datetime.fromtimestamp(step * round_f[d](dt.timestamp() / step))

date = datetime.datetime(year=2022, month=11, day=16, hour=10, minute=2, second=30, microsecond=424242)#
print('Original:', date)
print('Standard:', round_datetime(date, datetime.timedelta(minutes=5)))
print('Down:    ', round_datetime(date, datetime.timedelta(minutes=5), d='down'))
print('Up:      ', round_datetime(date, datetime.timedelta(minutes=5), d='up'))

Результат:

      Original: 2022-11-16 10:02:30.424242
Standard: 2022-11-16 10:05:00
Down:     2022-11-16 10:00:00
Up:       2022-11-16 10:05:00

Простой подход:

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

Общая функция для округления минут в меньшую сторону:

      from datetime import datetime
def round_minute(date: datetime = None, round_to: int = 1):
    """
    round datetime object to minutes
    """
    if not date:
        date = datetime.now()
    date = date.replace(second=0, microsecond=0)
    delta = date.minute % round_to
    return date.replace(minute=date.minute - delta)

Да , если ваши данные принадлежат столбцу DateTime в серии pandas, вы можете округлить их, используя встроенную функцию 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]

Я придумал эту очень простую функцию, работающую с любым значением timedelta, если оно кратно или делится на 60 секунд. Он также совместим с датами с указанием часового пояса.

      #!/usr/env python3
from datetime import datetime, timedelta

def round_dt_to_delta(dt, delta=timedelta(minutes=30)):
    ref = datetime.min.replace(tzinfo=dt.tzinfo)
    return ref + round((dt - ref) / delta) * delta

Выход:

      In [1]: round_dt_to_delta(datetime(2012,12,31,23,44,49), timedelta(seconds=15))
Out[1]: datetime.datetime(2012, 12, 31, 23, 44, 45)
In [2]: round_dt_to_delta(datetime(2012,12,31,23,44,49), timedelta(minutes=15))
Out[2]: datetime.datetime(2012, 12, 31, 23, 45)

Это кажется слишком сложным

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

Большинство ответов кажутся слишком сложными для такого простого вопроса.

Предполагая это объект datetime, который у вас есть, следующие раунды (фактически полы) его с желаемым разрешением, определенным в минутах.

      from math import floor

your_time = datetime.datetime.now() 

g = 10  # granularity in minutes
print(
datetime.datetime.fromtimestamp(
floor(your_time.timestamp() / (60*g)) * (60*g)
))
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)

Основано на 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')

Функция ниже с минимальным импортом выполнит эту работу. Вы можете округлить до чего угодно, установив параметры unit, rnd и frm. Поиграйте с этой функцией, и вы увидите, насколько это просто.

      def toNearestTime(ts, unit='sec', rnd=1, frm=None):
    ''' round to nearest Time format
    param ts = time string to round in '%H:%M:%S' or '%H:%M' format :
    param unit = specify unit wich must be rounded 'sec' or 'min' or 'hour', default is seconds :
    param rnd = to which number you will round, the default is 1 :
    param frm = the output (return) format of the time string, as default the function take the unit format'''
    from time import strftime, gmtime

    ts = ts + ':00' if len(ts) == 5 else ts
    if 'se' in unit.lower():
        frm = '%H:%M:%S' if frm is None else frm
    elif 'm' in unit.lower():
        frm = '%H:%M' if frm is None else frm
        rnd = rnd * 60
    elif 'h' in unit.lower():
        frm = '%H' if frm is None else frm
        rnd = rnd * 3600
    secs = sum(int(x) * 60 ** i for i, x in enumerate(reversed(ts.split(':'))))
    rtm = int(round(secs / rnd, 0) * rnd)
    nt = strftime(frm, gmtime(rtm))
    return nt

Функция вызова выглядит следующим образом: округление до ближайших 5 минут с форматом вывода по умолчанию = чч: мм, как показано ниже.

      ts = '02:27:29'
nt = toNearestTime(ts, unit='min', rnd=5)
print(nt)
output: '02:25'

Или округлить до ближайшего часа с выходным форматом чч: мм: сс, как показано ниже.

      ts = '10:30:01'
nt = toNearestTime(ts, unit='hour', rnd=1, frm='%H:%M:%S')
print(nt)
output: '11:00:00'

последняя обновленная версия

Другое решение:

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

Надеюсь это поможет!

Самый короткий путь, который я знаю

min = tm.minute // 10 * 10

Не лучший для скорости, когда ловится исключение, однако это сработает.

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

Двухстрочное интуитивное решение для округления до заданной единицы времени, здесь секунд, для datetime объект t:

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

Если вы хотите округлить в другую единицу, просто измените format_str,

Этот подход не округляет до произвольных значений времени, как описанные выше методы, но представляет собой приятный Pythonic способ округления до заданного часа, минуты или секунды.

Другие вопросы по тегам