Skip to content

Instantly share code, notes, and snippets.

@python273
Last active April 20, 2021 20:03
Show Gist options
  • Save python273/83bd826b5f47bd31bfdb9689929a1c00 to your computer and use it in GitHub Desktop.
Save python273/83bd826b5f47bd31bfdb9689929a1c00 to your computer and use it in GitHub Desktop.
Python Date Range & number of unique overlapping days with date range
# -*- coding: utf-8 -*-
"""
:authors: python273
:license: Apache License, Version 2.0
:copyright: (c) 2018 python273
"""
from datetime import datetime, timedelta
from functools import reduce
import operator
class DatetimeRange:
def __init__(self, start_date, end_date):
if end_date < start_date:
raise ValueError('{!r} < {!r}'.format(end_date, start_date))
self.start_date = start_date
self.end_date = end_date
def __contains__(self, date):
""" (start_date, end_date] """
return self.start_date <= date < self.end_date
def __eq__(self, other):
return (
(self.start_date == other.start_date) and
(self.end_date == other.end_date)
)
def __and__(self, other):
""" Return overlapping range for dr1 & dr2. """
dates = sorted([
self.start_date,
self.end_date,
other.start_date,
other.end_date
])
if self.end_date < other.start_date or self.start_date > other.end_date:
return
return DatetimeRange(dates[1], dates[2])
def __sub__(self, other):
""" Return list with ranges """
intersection = self & other
if intersection is None:
return [self]
result = []
if intersection.start_date != self.start_date:
result.append(DatetimeRange(self.start_date, intersection.start_date))
if intersection.end_date != self.end_date:
result.append(DatetimeRange(intersection.end_date, self.end_date))
return result
@property
def delta(self):
return self.end_date - self.start_date
def __repr__(self):
return '<DatetimeRange({!r}, {!r}>'.format(
self.start_date, self.end_date
)
def coverage(primary_range, sub_ranges):
left = [primary_range]
for sub_range in sub_ranges:
new_left = []
for left_range in left:
new_left.extend(left_range - sub_range)
left = new_left
if not left:
break
delta_left = reduce(
operator.add,
(i.delta for i in left),
timedelta()
)
return primary_range.delta - delta_left
primary = DatetimeRange(
datetime(2018, 1, 1),
datetime(2018, 2, 1)
)
print(coverage(primary, [
DatetimeRange(
datetime(2018, 1, 10),
datetime(2018, 1, 15)
),
DatetimeRange(
datetime(2018, 1, 10),
datetime(2018, 1, 15)
),
])) # 5 days, 0:00:00
print(coverage(primary, [
DatetimeRange(
datetime(2018, 1, 10),
datetime(2018, 1, 15)
),
DatetimeRange(
datetime(2017, 12, 30),
datetime(2018, 2, 15)
),
])) # 31 days, 0:00:00
print(coverage(primary, [
DatetimeRange(
datetime(2018, 1, 10),
datetime(2018, 1, 15)
),
DatetimeRange(
datetime(2018, 1, 15),
datetime(2018, 1, 25)
),
DatetimeRange(
datetime(2018, 1, 10),
datetime(2018, 1, 25)
),
DatetimeRange(
datetime(2018, 1, 11),
datetime(2018, 1, 24)
),
])) # 15 days, 0:00:00
print(coverage(primary, [
DatetimeRange(
datetime(2018, 1, 10),
datetime(2018, 2, 15)
),
])) # 22 days, 0:00:00
print(coverage(
DatetimeRange(
datetime(2018, 10, 1),
datetime(2018, 11, 1)
), [
DatetimeRange(
datetime(2018, 10, 1),
datetime(2018, 10, 6)
),
DatetimeRange(
datetime(2018, 10, 1),
datetime(2018, 10, 6)
),
DatetimeRange(
datetime(2018, 10, 10),
datetime(2018, 10, 16)
),
DatetimeRange(
datetime(2018, 10, 15),
datetime(2018, 10, 21)
),
DatetimeRange(
datetime(2018, 10, 21),
datetime(2018, 10, 26)
),
]
)) # 21 days, 0:00:00
@python273
Copy link
Author

@rez0n I think it had some bugs, so I'd recheck twice 😃

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment