Python - Ceil дата-время до следующей четверти часа

Позволять'представьте себе эту дату и время

>>> import datetime
>>> dt = datetime.datetime(2012, 10, 25, 17, 32, 16)

хотел бы поставить его на следующую четверть часа, чтобы получить

datetime.datetime(2012, 10, 25, 17, 45)

Я представляю что-то вроде

>>> quarter = datetime.timedelta(minutes=15)
>>> import math
>>> ceiled_dt = math.ceil(dt / quarter) * quarter

Но, конечно, это не работает

Ответы на вопрос(4)

@ Марк Дикинсон предложил лучшая формула на данный момент:

def ceil_dt(dt, delta):
    return dt + (datetime.min - dt) % delta

В Python 3 для произвольной дельты времени (не только 15 минут):

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

def ceil_dt(dt, delta):
    return datetime.min + math.ceil((dt - datetime.min) / delta) * delta

print(ceil_dt(datetime(2012, 10, 25, 17, 32, 16), timedelta(minutes=15)))
# -> 2012-10-25 17:45:00

Чтобы избежать промежуточных поплавков,divmod() может быть использован:

def ceil_dt(dt, delta):
    q, r = divmod(dt - datetime.min, delta)
    return (datetime.min + (q + 1)*delta) if r else dt

Пример:

>>> ceil_dt(datetime(2012, 10, 25, 17, 32, 16), timedelta(minutes=15))
datetime.datetime(2012, 10, 25, 17, 45)
>>> ceil_dt(datetime.min, datetime.resolution) 
datetime.datetime(1, 1, 1, 0, 0)
>>> ceil_dt(datetime.min, 2*datetime.resolution)
datetime.datetime(1, 1, 1, 0, 0)
>>> ceil_dt(datetime.max, datetime.resolution)
datetime.datetime(9999, 12, 31, 23, 59, 59, 999999)
>>> ceil_dt(datetime.max, 2*datetime.resolution)
Traceback (most recent call last):
  File "", line 1, in 
  File "", line 3, in ceil_dt
OverflowError: date value out of range
>>> ceil_dt(datetime.min+datetime.resolution, datetime.resolution)
datetime.datetime(1, 1, 1, 0, 0, 0, 1)
>>> ceil_dt(datetime.min+datetime.resolution, 2*datetime.resolution)
datetime.datetime(1, 1, 1, 0, 0, 0, 2)
>>> ceil_dt(datetime.max-datetime.resolution, datetime.resolution)
datetime.datetime(9999, 12, 31, 23, 59, 59, 999998)
>>> ceil_dt(datetime.max-datetime.resolution, 2*datetime.resolution)
datetime.datetime(9999, 12, 31, 23, 59, 59, 999998)
>>> ceil_dt(datetime.max-2*datetime.resolution, datetime.resolution)
datetime.datetime(9999, 12, 31, 23, 59, 59, 999997)
>>> ceil_dt(datetime.max-2*datetime.resolution, 2*datetime.resolution)
datetime.datetime(9999, 12, 31, 23, 59, 59, 999998)
>>> ceil_dt(datetime.max-timedelta(1), datetime.resolution)
datetime.datetime(9999, 12, 30, 23, 59, 59, 999999)
>>> ceil_dt(datetime.max-timedelta(1), 2*datetime.resolution)
datetime.datetime(9999, 12, 31, 0, 0)
>>> ceil_dt(datetime.min, datetime.max-datetime.min)
datetime.datetime(1, 1, 1, 0, 0)
>>> ceil_dt(datetime.max, datetime.max-datetime.min)
datetime.datetime(9999, 12, 31, 23, 59, 59, 999999)
 jfs19 сент. 2015 г., 22:39
@MarkDickinson: да. Это работает на CPython (для всех примеров в ответе). Вы должны опубликовать:def ceil_dt(dt, delta): return dt + (datetime.min - dt) % delta как ответ.
 Mark Dickinson19 сент. 2015 г., 21:51
Если ты'ограничиваясь Python 3, тогда более простое выражениеdt + (datetime.min - dt) % delta тоже работает.

Вам просто нужно рассчитать правильные минуты и добавить их в объект datetime после установки минут, секунд до нуля

import datetime

def quarter_datetime(dt):
    minute = (dt.minute//15+1)*15
    return dt.replace(minute=0, second=0)+datetime.timedelta(minutes=minute)

for minute in [12, 22, 35, 52]:
    print quarter_datetime(datetime.datetime(2012, 10, 25, 17, minute, 16))

Работает на все случаи:

2012-10-25 17:15:00
2012-10-25 17:30:00
2012-10-25 17:45:00
2012-10-25 18:00:00
 mgilson25 окт. 2012 г., 17:12
Дон»забыть о микросекундах ...
 Gareth Latty25 окт. 2012 г., 17:11
Отлично, хотя ябуду отмечать использование/ над// все еще неоптимальный. Кроме того, ТАК уведомляет вас, когда кто-то другой публикует ответ,Стоит посмотреть, прежде чем опубликовать свой, чтобы убедиться, что вы не дублируете ответ.
 Anurag Uniyal25 окт. 2012 г., 17:12
Я худой, у нас была разница в 15 секунд, и я не видел никаких других сообщений
 Anurag Uniyal25 окт. 2012 г., 17:09
@ Lattyware Я думаю, что мы разместили его одновременно + теперь это не то же самое, что я исправил ошибку, и его решение имеет дефект
 Gareth Latty25 окт. 2012 г., 17:07
Это дублированный Пьер GM 's ответ, за исключением того, что ваш будет работать только как ожидалось в Python 2.x из-за использования/ над .//
def ceil(dt):
    if dt.minute % 15 or dt.second:
        return dt + datetime.timedelta(minutes = 15 - dt.minute % 15,
                                       seconds = -(dt.second % 60))
    else:
        return dt

Это дает вам:

>>> ceil(datetime.datetime(2012,10,25, 17,45))
datetime.datetime(2012, 10, 25, 17, 45)
>>> ceil(datetime.datetime(2012,10,25, 17,45,1))
datetime.datetime(2012, 10, 25, 18, 0)
>>> ceil(datetime.datetime(2012,12,31,23,59,0))
datetime.datetime(2013,1,1,0,0)
 mgilson25 окт. 2012 г., 17:29
Да уж. Я'я доволен этим теперь, когда вы исправили API-интерфейс (+1)
 mgilson25 окт. 2012 г., 17:38
Мне тоже нравится, что ты сказалвы'верноконечно", Хотя у меня есть неприятная привычка полагать, что яЯ всегда прав, я неНе думаю, что другие люди обычно соглашаются O: ^)
 mgilson25 окт. 2012 г., 17:17
Это немного забавно, потому что вы оперируетеdt на месте, но потом возвращаешь. с точки зрения API, кажется, что вы должны либо сделать одно или другое ... (Кроме того, это невзятьmicroseconds в учетную запись)
 Tim Pietzcker25 окт. 2012 г., 17:24
@mgilson: спасибо за комментарий, выправильно конечно. Я игнорировал микросекунды, потому что ОП строил свои даты без них. Но, конечно, это может быть проблемой, если этоЭто не его реальный вариант использования.
Решение Вопроса

Это учитывает микросекунды!

import math

def ceil_dt(dt):
    # how many secs have passed this hour
    nsecs = dt.minute*60 + dt.second + dt.microsecond*1e-6  
    # number of seconds to next quarter hour mark
    # Non-analytic (brute force is fun) way:  
    #   delta = next(x for x in xrange(0,3601,900) if x>=nsecs) - nsecs
    # analytic way:
    delta = math.ceil(nsecs / 900) * 900 - nsecs
    #time + number of seconds to quarter hour mark.
    return dt + datetime.timedelta(seconds=delta)

t1 = datetime.datetime(2017, 3, 6, 7, 0)
assert ceil_dt(t1) == t1

t2 = datetime.datetime(2017, 3, 6, 7, 1)
assert ceil_dt(t2) == datetime.datetime(2017, 3, 6, 7, 15)

t3 = datetime.datetime(2017, 3, 6, 7, 15)
assert ceil_dt(t3) == t3

t4 = datetime.datetime(2017, 3, 6, 7, 16)
assert ceil_dt(t4) == datetime.datetime(2017, 3, 6, 7, 30)

t5 = datetime.datetime(2017, 3, 6, 7, 30)
assert ceil_dt(t5) == t5

t6 = datetime.datetime(2017, 3, 6, 7, 31)
assert ceil_dt(t6) == datetime.datetime(2017, 3, 6, 7, 45)

t7 = datetime.datetime(2017, 3, 6, 7, 45)
assert ceil_dt(t7) == t7

t8 = datetime.datetime(2017, 3, 6, 7, 46)
assert ceil_dt(t8) == datetime.datetime(2017, 3, 6, 8, 0)

Объяснение:delta

900 секунд - это 15 минут (четверть часабез високосных секунд, которые я неЯ думаю, что дата и время обрабатываются ...)nsecs / 900 количество четвертьчасовых кусков, которые выяснились. Принимаяceil это округляет количество порций четверти часа.Умножьте количество порций четверти часа на 900, чтобы выяснить, сколько секунд прошло с начала часа после »округление».
 Anuj17 мая 2016 г., 11:19
Я попробовал это с 3600 вместо 900, и этоне работает. Это'добавив 3600, даже если время уже округлено.
 mgilson17 мая 2016 г., 17:55
Я думаю, что я должен был использоватьdelta = math.ceil(nsecs / 900.) * 900.
 Pierre de LESPINAY25 окт. 2012 г., 17:53
Работает нормально. Кажется, самый простой. Спасибо
 junebob06 мар. 2017 г., 12:19
не должен»t быть: delta = math.ceil (nsecs / 900) * 900 - nsecs
 mgilson06 мар. 2017 г., 16:08
@junebob - Да, должно. В предыдущих ревизиях он был, и где-то по пути мне почему-то удалось его отбросить. Я полагаю, что в какой-то момент мне стоит написать несколько юнит-тестов для этого ...

Ваш ответ на вопрос