【问题标题】:Compute the number of light- and dark- periods between two dates in Python在 Python 中计算两个日期之间的明暗周期数
【发布时间】:2020-07-05 17:06:10
【问题描述】:

这是我最近在一个程序中遇到的一个计算:

给定 2 个日期和一个明暗周期,计算这些日期之间发生的明暗周期的(分数)数。

def foo(start_date, end_date, lights_on, lights_off):
    #stuff...
    return (daytimes, nighttimes)
  • start_dateend_date 是具有(至少)分钟精度的日期时间对象
  • lights_onlights_offint,分别代表一天中灯打开和关闭的时间。这些定义了“开灯”和“关灯”期间的持续时间。
  • lights_onlights_off 可以不均匀地划分一天(即不一定是 12H 亮:12H 暗),并且 光周期不必匹配 "true " 白天和晚上(例如,可以在早上 7 点关灯,在晚上 7 点打开灯)。但是,假设光照周期是恒定的(即想象房间或建筑物中的灯,而不是日出/日落),并且灯总是会在交替时打开或关闭小时(例如,晚上 8:30 不能改变灯光)。
  • 结果应该是float 表示 2 个日期之间已完成的亮灯时间和熄灯时间的数量
  • 您可以假设日期相隔不到一个月,在 DST 不变的时期内。

例子:

import pandas as pd

date1 = pd.Timestamp(year=2020, month=1, day=1, hour=9, minute=0)
date2 = pd.Timestamp(year=2020, month=1, day=3, hour=13, minute=0)
date3 = pd.Timestamp(year=2020, month=1, day=5, hour=20, minute=47)

foo(date1, date2, 9, 17)
#returns (2.5, 2)

foo(date1, date3, 9, 17)
#returns (5, 4.236...)

foo(date1, date2, 19, 7)
#returns (2, 2.333...)

解释:

  • 光周期为 9:00 到 17:00,有 2.5 个完成光周期(1 月 1 日 9:00 到 17:00,1 月 9:00 到 17:00 2 日,以及 1 月 3 日 9:00 至 13:00)和 2 个在 date1 和 @987654332 之间完成的黑暗期(1 月 1 日的整个晚上和 1 月 2 日的整个晚上) @。
  • 光照周期为 9:00 到 17:00,有 5 个完成的光照周期(1 月 1 日、2 日、3 日、4 日和 5 日的 9:00 到 17:00)和 4.236.. 完成黑暗期(1 月 1 日、2 日、3 日和 4 日的整个晚上,然后从 17:00 到 20:47 再增加 3 小时 47 分钟的黑暗,这是 date1date3 之间的一个暗周期长度的 0.236... 倍。
  • 光照周期19:00 到 7:00,有 2 个完成的光照周期(19:00 到 7: 1 月 1 日和 2 日晚上 00 点)和 2.333.. 完成黑暗期(1 月 1 日从 9:00 到 17:00 的黑暗周期的 10/12 + 黑暗的 12/12 1 月 2 日从 7:00 到 17:00 的周期 + 1 月 3 日从 7:00 到 13:00 的黑暗周期的 6/12 = 28/12 = 2.333 ......)在 date1date2 之间.

对我来说,这种计算很容易概念化,但很难编码。我有一个可行的答案(在下面发布),但我为此苦苦挣扎了很长时间,并希望看到不同的更好的方法。如果有可以使用的现有库,那就太好了,只要输出正确,我不在乎数据的表示方式是否不同。

【问题讨论】:

    标签: python datetime


    【解决方案1】:

    这是一种替代解决方案,它试图通过将开始时间和结束时间偏移lights_onlights_off 的最小值来简化数学运算,这样每天的周期实际上由abs(lights_on - lights_off) 处的一个变化组成。我已经使用 datetime.timedelta 对象完成了所有数学运算。

    from datetime import datetime, time, timedelta
    
    def cycle_times(start_date, end_date, lights_on):
        start_time = timedelta(hours=start_date.hour, minutes=start_date.minute)
        lights_on_time = timedelta(hours=lights_on)
        # generate the cycles for the first day
        off_time = max(lights_on_time - start_time, timedelta(seconds = 0))
        on_time = timedelta(hours=24) - max(lights_on_time, start_time)
        cycle_start = start_date.replace(hour=0, minute=0) + timedelta(days=1)
        # add a complete cycle for each day before end_date
        while cycle_start.date() < end_date.date():
            cycle_start += timedelta(days=1)
            off_time += lights_on_time
            on_time += timedelta(hours=24-lights_on)
        # generate the partial cycles for the last day
        end_time = timedelta(hours=end_date.hour, minutes=end_date.minute)
        off_time += min(end_time, lights_on_time)
        on_time += max(end_time - lights_on_time, timedelta(seconds=0))
        return (off_time.total_seconds() / 3600, on_time.total_seconds() / 3600)
        
    def get_daynight_count(start_time, end_time, lights_on, lights_off):
        if lights_on > lights_off:
            off_period = lights_on - lights_off
            on_period = 24 - off_period
            offset = timedelta(hours=-lights_off)
            off_time, on_time = cycle_times(start_time + offset, end_time + offset, lights_on - lights_off)
        else:
            on_period = lights_off - lights_on
            off_period = 24 - on_period
            offset=timedelta(hours=-lights_on)
            on_time, off_time = cycle_times(start_time + offset, end_time + offset, lights_off - lights_on)
        return (on_time / on_period, off_time / off_period)
        
    date1 = datetime(year=2020, month=1, day=1, hour=9, minute=0)
    date2 = datetime(year=2020, month=1, day=3, hour=13, minute=0)
    date3 = datetime(year=2020, month=1, day=5, hour=20, minute=47)
    
    print(get_daynight_count(date1, date2, 9, 17))
    print(get_daynight_count(date1, date3, 9, 17))
    print(get_daynight_count(date1, date2, 19, 7))
    

    输出:

    (2.5, 2.0)
    (5.0, 4.236458333333333)
    (2.0, 2.3333333333333335)
    

    更新

    cycle_times 代码可以通过仅使用小时/分钟数学 (1 hour = 60 minutes) 来简化:

    def cycle_times(start_date, end_date, lights_on):
        start_time = start_date.hour*60 + start_date.minute
        lights_on_time = lights_on*60
        # generate the cycles for the first day
        off_time = max(lights_on_time - start_time, 0)
        on_time = 24*60 - max(lights_on_time, start_time)
        cycle_start = start_date.replace(hour=0, minute=0) + timedelta(days=1)
        # add a complete cycle for each day before end_date
        while cycle_start.date() < end_date.date():
            cycle_start += timedelta(days=1)
            off_time += lights_on_time
            on_time += (24-lights_on)*60
        # generate the partial cycles for the last day
        end_time = end_date.hour*60 + end_date.minute
        off_time += min(end_time, lights_on_time)
        on_time += max(end_time - lights_on_time, 0)
        return (off_time / 60, on_time / 60)
    

    【讨论】:

    • 把我的头包裹在这个?感谢分享,非常酷!
    【解决方案2】:

    下面是代码中使用的步骤:

    1. 定义一个函数来确定时间是“白天”还是“夜晚”
    2. 创建一个数组cuts,该数组以start_time 开头,以end_time 结尾,中间是每次灯光变化的时间
    3. 计算每个明暗时期的小时数
    4. 对于cuts 中的每个时间(end_date 除外),检查它是在“白天”还是“夜晚”,并计算它与下一次之间的时间差。
    5. 将每个时间增量除以适当的光照或夜间持续时间。
    6. 求和并返回明暗时期的标准化时间增量。
    import pandas as pd
    import datetime as dt
    
    #step 1
    def is_day_or_night(time, period, lights_on=7, lights_off=19):
        lights_on = dt.time(hour=lights_on)
        lights_off = dt.time(hour=lights_off)
        val = False
        #defaults to checking if at night
        if lights_off > lights_on:
            val = time.time() >= lights_off or time.time() < lights_on
        elif lights_off < lights_on:
            val = time.time() >= lights_off and time.time() < lights_on
        #reverses if period='day'
        return val if period=='night' else not val
    
    def get_daynight_count(start_time, end_time, lights_on, lights_off):
        #step 2
        cuts = []
        cuts.append(start_time)
        loop_time = start_time.replace(minute=0,second=0)
        while loop_time < end_time:
            loop_time += pd.Timedelta(hours=1)
            if loop_time.hour == lights_on:
                cuts.append(loop_time)
            elif loop_time.hour == lights_off:
                cuts.append(loop_time)
        cuts.append(end_time)
        days = []
        nights = []
        #step 3
        if lights_off > lights_on:
            day_hours = lights_off - lights_on
            night_hours = 24 - day_hours
        else:
            night_hours = lights_on - lights_off
            day_hours = 24 - night_hours    
        day_hours = pd.Timedelta(hours = day_hours)
        night_hours = pd.Timedelta(hours = night_hours)
        #step 4 and 5
        for i, t in enumerate(cuts[:-1]):
            if is_day_or_night(t, 'day', lights_on, lights_off):
                days.append((cuts[i+1] - t)/day_hours)
            else:
                nights.append((cuts[i+1] - t)/night_hours)
        #step 6
        return (sum(days),sum(nights))
    

    以上例子:

    In[3]: get_daynight_count(date1, date2, 9, 17)
    Out[3]: (2.5, 2.0)
    
    In[4]: get_daynight_count(date1, date3, 9, 17)
    Out[4]: (5.0, 4.236458333333333)
    
    In[5]: get_daynight_count(date1, date2, 19, 7)
    Out[5]: (2.0, 2.3333333333333335)
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2021-10-16
      • 1970-01-01
      • 2011-04-17
      • 2022-12-06
      • 1970-01-01
      • 1970-01-01
      • 2012-12-20
      相关资源
      最近更新 更多