簡體   English   中英

將日期時間限制到下一刻鍾

[英]Ceil a datetime to next quarter of an hour

讓我們想象一下這個datetime

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

但當然,這行不通

這需要考慮微秒!

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 分鍾(沒有閏秒的一刻鍾,我認為 datetime 無法處理......
  • nsecs / 900是已經發生的四分之一小時塊的數量。 取這個的ceil將四分之一小時塊的數量四舍五入。
  • 將四分之一小時塊的數量乘以 900 以計算自“四舍五入”后的一小時開始以來發生的秒數。

@Mark Dickinson 提出了迄今為止最好的公式:

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 "<stdin>", line 1, in <module>
  File "<stdin>", 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)
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)

您只需要計算正確的分鍾數並在將分鍾、秒數設置為零后將它們添加到 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

這是我的代碼適用於任何時期:

def floorDT(dt, secperiod):
    tstmp = dt.timestamp()
    return datetime.datetime.fromtimestamp(
        math.floor(tstmp/secperiod)*secperiod).astimezone().astimezone(datetime.timezone.utc)


def ceilDT(dt, secperiod):
    tstmp = dt.timestamp()
    return datetime.datetime.fromtimestamp(
        math.ceil(tstmp/secperiod)*secperiod).astimezone().astimezone(datetime.timezone.utc)

注意:我們必須使用 astimezone().astimezone() 技巧,否則它在從時間戳轉換期間使用本地時區

@Mark Dickinson這里提出公式效果很好,但我需要一個還可以處理時區和夏令時 (DST) 的解決方案。

使用pytz ,我到達了:

import pytz
from datetime import datetime, timedelta

def datetime_ceiling(dt, delta):
    # Preserve original timezone info
    original_tz = dt.tzinfo
    if original_tz:
        # If the original was timezone aware, translate to UTC.
        # This is necessary because datetime math does not take
        # DST into account, so first we normalize the datetime...
        dt = dt.astimezone(pytz.UTC)
        # ... and then make it timezone naive
        dt = dt.replace(tzinfo=None)
    # We only do math on a timezone naive object, which allows
    # us to pass naive objects directly to the function
    dt = dt + ((datetime.min - dt) % delta)
    if original_tz:
        # If the original was tz aware, we make the result aware...
        dt = pytz.UTC.localize(dt)
        # ... then translate it from UTC back its original tz.
        # This translation applies appropriate DST status.
        dt = dt.astimezone(original_tz)
    return dt

通過更改一行代碼可以實現幾乎相同的floor函數:

def datetime_floor(dt, delta):
    ...
    dt = dt - ((datetime.min - dt) % delta)
    ...

以下日期時間是從 DST 轉換回標准時間 (STD) 之前的三分鍾:

datetime.datetime(2020, 11, 1, 1, 57, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)

假設以上為dt ,我們可以使用我們的 floor 函數向下舍入到最接近的五分鍾增量:

>>> datetime_floor(dt, timedelta(minutes=5))
datetime.datetime(2020, 11, 1, 1, 55, tzinfo=<DstTzInfo 'US/Eastern' EDT-1 day, 20:00:00 DST>)

保留時區和與 DST 的關系。 (對於天花板函數也是如此。)

在這一天,夏令時將在凌晨 2 點結束,此時時間將“回滾”到標准時間凌晨 1 點。 如果我們用我們的吊頂功能全面上漲,從上午01時57分DST,我們應該在凌晨2點DST結束,而是在凌晨1:00 STD,這是我們得到的結果:

>>> datetime_ceiling(dt, timedelta(minutes=5))
datetime.datetime(2020, 11, 1, 1, 0, tzinfo=<DstTzInfo 'US/Eastern' EST-1 day, 19:00:00 STD>)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM