一种解决方案,让您可以根据需要定义任意数量的时段,并使用它们各自的权重。
首先,一个帮助函数分割我们的日期时间之间的间隔:
from datetime import date, time, datetime, timedelta
def slice_datetimes_interval(start, end):
"""
Slices the interval between the datetimes start and end.
If start and end are on different days:
start time -> midnight | number of full days | midnight -> end time
---------------------- ------------------- --------------------
^ ^ ^
day_part_1 full_days day_part_2
If start and end are on the same day:
start time -> end time
----------------------
^
day_part_1 full_days = 0
Returns full_days and the list of day_parts (as tuples of time objects).
"""
if start > end:
raise ValueError("Start time must be before end time")
# Number of full days between the end of start day and the beginning of end day
# If start and end are on the same day, it will be -1
full_days = (datetime.combine(end, time.min) -
datetime.combine(start, time.max)).days
if full_days >= 0:
day_parts = [(start.time(), time.max),
(time.min, end.time())]
else:
full_days = 0
day_parts = [(start.time(), end.time())]
return full_days, day_parts
计算给定周期和权重列表的加权持续时间的类:
class WeightedDuration:
def __init__(self, periods):
"""
periods is a list of tuples (start_time, end_time, weight)
where start_time and end_time are datetime.time objects.
For a period including midnight, like 22:00 -> 6:30,
we create two periods:
- midnight (start of day) -> 6:30,
- 22:00 -> midnight(end of day)
so periods will be:
[(time.min, time(6, 30), 0.5),
(time(22, 0), time.max, 0.5)]
"""
self.periods = periods
# We store the weighted duration of a whole day for later reuse
self.day_duration = self.time_interval_duration(time.min, time.max)
def time_interval_duration(self, start_time, end_time):
"""
Returns the weighted duration, in seconds, between the datetime.time objects
start_time and end_time - so, two times on the *same* day.
"""
dummy_date = date(2000, 1, 1)
# First, we calculate the total duration, *without weight*.
# time objects can't be substracted, so
# we turn them into datetimes on dummy_date
duration = (datetime.combine(dummy_date, end_time) -
datetime.combine(dummy_date, start_time)).total_seconds()
# Then, we calculate the reductions during all periods
# intersecting our interval
reductions = 0
for period in self.periods:
period_start, period_end, weight = period
if period_end < start_time or period_start > end_time:
# the period and our interval don't intersect
continue
# Intersection of the period and our interval
start = max(start_time, period_start)
end = min (end_time, period_end)
reductions += ((datetime.combine(dummy_date, end) -
datetime.combine(dummy_date, start)).total_seconds()
* (1 - weight))
# as time.max is midnight minus a µs, we round the result
return round(duration - reductions)
def duration(self, start, end):
"""
Returns the weighted duration, in seconds, between the datetime.datetime
objects start and end.
"""
full_days, day_parts = slice_datetimes_interval(start, end)
dur = full_days * self.day_duration
for day_part in day_parts:
dur += self.time_interval_duration(*day_part)
return dur
我们创建一个 WeightedDuration 实例,定义我们的周期及其权重。
我们可以有任意多个周期,权重小于或大于 1。
wd = WeightedDuration([(time.min, time(7, 0), 0.5), # from midnight to 7, 50%
(time(12, 0), time(13, 0), 0.75), # from 12 to 13, 75%
(time(21, 0), time.max, 0.5)]) # from 21 to midnight, 50%
让我们计算日期时间之间的加权持续时间:
# 1 hour at 50%, 1 at 100%: that should be 3600 + 1800 = 5400 s
print(wd.duration(datetime(2017, 1, 3, 6, 0), datetime(2017, 1, 3, 8)))
# 5400
# a few tests
intervals = [
(datetime(2017, 1, 3, 9, 0), datetime(2017, 1, 3, 10)), # 1 hour with weight 1
(datetime(2017, 1, 3, 23, 0), datetime(2017, 1, 4, 1)), # 2 hours, weight 0.5
(datetime(2017, 1, 3, 5, 0), datetime(2017, 1, 4, 5)), # 1 full day
(datetime(2017, 1, 3, 5, 0), datetime(2017, 1, 3, 23)), # same day
(datetime(2017, 1, 3, 5, 0), datetime(2017, 1, 4, 23)), # next day
(datetime(2017, 1, 3, 5, 0), datetime(2017, 1, 5, 23)), # 1 full day in between
]
for interval in intervals:
print(interval)
print(wd.duration(*interval))
# (datetime.datetime(2017, 1, 3, 9, 0), datetime.datetime(2017, 1, 3, 10, 0))
# 3600
# (datetime.datetime(2017, 1, 3, 23, 0), datetime.datetime(2017, 1, 4, 1, 0))
# 3600
# (datetime.datetime(2017, 1, 3, 5, 0), datetime.datetime(2017, 1, 4, 5, 0))
# 67500
# (datetime.datetime(2017, 1, 3, 5, 0), datetime.datetime(2017, 1, 3, 23, 0))
# 56700
# (datetime.datetime(2017, 1, 3, 5, 0), datetime.datetime(2017, 1, 4, 23, 0))
# 124200
# (datetime.datetime(2017, 1, 3, 5, 0), datetime.datetime(2017, 1, 5, 23, 0))
# 191700