熊猫:计算时间戳和当前时间之间经过的时间,但只有营业时间和时区

Posted

技术标签:

【中文标题】熊猫:计算时间戳和当前时间之间经过的时间,但只有营业时间和时区【英文标题】:Pandas: calculate time elapsed between timestamp and current time, but only business hours and with timezone 【发布时间】:2021-08-16 21:59:59 【问题描述】:

我正在尝试使用 Pandas 来计算经过的业务秒数。我在 Pandas 数据框中有一列在纽约时区有一堆时间戳。这是我到目前为止的代码:

import pandas as pd
import datetime

times = pd.DataFrame([datetime.datetime.now(timezone('America/New_York')),datetime.datetime.now(timezone('America/New_York'))],columns=['timestamp'])
time.sleep(2)
times['difference'] = (datetime.datetime.now(timezone('America/New_York')) - times)
times['difference'] = times['difference'].dt.seconds

这按预期工作,并在“差异”列中给出 2 的答案。但现在我只想包括营业时间(比如上午 9 点到下午 5 点)。所以昨天下午 5 点到今天早上 9 点之间的输出为零。我已阅读有关时间偏移的 Pandas 文档并寻找类似的问题,但没有找到任何有效的示例。

【问题讨论】:

【参考方案1】:

您可以通过首先使用 Pandas BusinessHour class 检查给定时间戳是否在营业时间内(感谢 this thread)然后计算时差或在时间戳超出营业时间时分配零来实现此目的。

我创建了一个虚拟数据集来测试代码,如下所示:

import pandas as pd
import time

# Sets the timezone
timezone = "America/New_York"

# Gets business hours from native Pandas class
biz_hours = pd.offsets.BusinessHour()

# Creates array with timestamps to test code
times_array = pd.date_range(start='2021-05-18 16:59:00', end='2021-05-18 17:01:00',
                            tz=timezone, freq='S')

# Creates DataFrame with timestamps
times = pd.DataFrame(times_array,columns=['timestamp'])

# Checks if a timestamp falls within business hours                           
times['is_biz_hour'] = times['timestamp'].apply(pd.Timestamp).apply(biz_hours.onOffset)

time.sleep(2)

# Calculates the time delta or assign zero, as per business hour condition
times['difference'] = (times.apply(lambda x: (pd.Timestamp.now(tz=timezone) - x['timestamp']).seconds
                                   if x['is_biz_hour'] else 0,
                       axis=1))

目前的输出并不完美,因为它从现在的时间中减去了时间戳,因此相差很大:

    timestamp                   is_biz_hour  difference
57  2021-05-18 16:59:57-04:00   True         71238
58  2021-05-18 16:59:58-04:00   True         71237
59  2021-05-18 16:59:59-04:00   True         71236
60  2021-05-18 17:00:00-04:00   True         71235
61  2021-05-18 17:00:01-04:00   False        0
62  2021-05-18 17:00:02-04:00   False        0
63  2021-05-18 17:00:03-04:00   False        0
64  2021-05-18 17:00:04-04:00   False        0

但是,您可以看到下午 5 点之后的时间戳有 0 的差异,而其他时间戳有一个有效的差异。

【讨论】:

【参考方案2】:

这是一种数学方法。

一步一步

让我们调查一下时间戳some_time 的工作秒数。 我们将在午夜需要some_time 的时间戳。我们就叫它d_day

d_day = some_time.replace(hour=0, minute=0, second=0, microsecond=0)

今天

首先,让我们定义我们的工作日。它从9 AM 开始,一直持续到5 PM。在几秒钟内,它会给出:

start_time = 9*3600
max_work_time = (17-9) * 3600

现在,让我们获取现在的时间戳和今天午夜的时间戳,以秒为单位。

now = datetime.now()
today = datetime.today().replace(hour=0, minute=0, second=0, microsecond=0)
seconds_today = (now - today).seconds

要获得今天的工作秒数,我们必须减去 start_time 然后保持最大值 max_work_time

worked_seconds_today = min(seconds_today - start_time, max_work_time)

当然,我们只想在 todayd-day 是不同的日子时才保留它,否则我们要计算自 some_time 以来的工作秒数:

secs_today = min(seconds_today - start_time, max_work_time) \
    if today > d_day \
    else min(seconds_today - start_time, max_work_time) - min((some_time - today).seconds - start_time, max_work_time)

从 d 日到今天之间的天数

我们只想在这里工作一整天。所以让我们简单地做:

 inbetween_days = max((datetime.today() - d_day).days - 1, 0)

现在我们可以简单地计算一整天的工作秒数:

secs_inbetween_days = inbetween_days * max_work_time

D 日

最后,我们还想要从some_time 开始的 d_day 的工作秒数。我们可以应用与今天相同的逻辑。如果今天和some_time 是同一天,我们只需输入零,就像我们今天已经计算的那样。

def worked_secs(x, since):
    return min((x - since).seconds - start_time, max_work_time)
secs_day_d = max_work_time - worked_secs(some_time, d_day) if today != d_day else 0

总计

总和是前面三个分量的总和:

total = secs_day_d + secs_inbetween_days + secs_today

最终功能

def busy_seconds(some_time):

    # Outside the function is OK also
    start_time = 9*3600
    max_work_time = (17-9)*3600

    # We must calculate all times with the same timezone
    tz = some_time.tz

    now = datetime.now(tz=tz) # now
    today = now.replace(hour=0, minute=0, second=0, microsecond=0) # today at midnight
    d_day = some_time.replace(hour=0, minute=0, second=0, microsecond=0) # d-day at midnight

    def worked_secs(x, since): # a function is more convenient
        return min((x - since).seconds - start_time, max_work_time)

    n_complete_days = max((today - d_day).days - 1, 0)

    secs_day_d = max_work_time - worked_secs(some_time, d_day) if today != d_day else 0
    secs_inbetween_days =  max_work_time * n_complete_days
    secs_today = worked_secs(now, today) \
        if d_day < today \
        else worked_secs(now, today) - worked_secs(some_time, today)

    return  secs_day_d + secs_inbetween_days + secs_today

终于

我们可以将此函数应用于列:

times.timestamp.apply(busy_seconds)
# > (Example)
# 0    67420800
# 1    57340800
# 2       28800
# Name: timestamp, dtype: int64

【讨论】:

以上是关于熊猫:计算时间戳和当前时间之间经过的时间,但只有营业时间和时区的主要内容,如果未能解决你的问题,请参考以下文章

如何查找当前时间戳和登录日期之间的天数 [重复]

Python time和datetime时间戳和时间字符串相互转换

DB2,在尝试计算提供的时间戳和存储的时间戳之间的差异时,出现错误“函数的调用不明确”

如何找到纪元时间戳和 std::chrono::system_clock::now 之间的时间差(以毫秒为单位)

如何计算熊猫事件之间的时间

熊猫:计算df列之间的时间差[重复]