I'm trying to write something that can take a description and a date range and return all the dates that satisfy that description.
Descriptions can be anything from the following...
parsed_tests_dict = [
{"Saturday": [5]},
{"Sunday": [6]},
{"Weekend": [5, 6]},
{"Weekday": [0, 1, 2, 3, 4]},
{"Monday-Saturday": [0, 1, 2, 3, 4, 5]},
{"Jan": "", "Feb": "", "Mar": "", "Nov": "", "Dec": ""},
{"Jan-Feb": "", "Jan-Mar": "", "Jan-Apr":"", "Mar-Oct": "Apr-Oct", "May-Oct": "",
"Jul-Nov": "", "Nov-Feb": "", "Nov-Mar": "", "Nov-Dec": "", "Dec-Jun": ""}
So we could have a situation whereby Monday-Saturday appears, so the utility would bucket all the dates within the date range that fall between Monday-Saturday
and then another bucket for all the dates that are Sunday
.
Now there can also be a situation whereby, we need to bucket Jan-Mar
, Apr-Oct
, Nov-Dec
AND Weekends
- in this scenario, we would go through say 1-year date range, bucket every weekday that is between Jan-Mar
, Apr-Oct
and Nov-Dec
and finally, all the Weekends
in between would be bucketed under Weekends
I want to try to be as efficient and fast as possible and have been trying a method of this structure:
STR2WKDAY = {
'mon': 0, 'tue': 1, 'wed': 2, 'thu': 3, 'fri': 4, 'sat': 5, 'sun': 6,
'wkd': [0, 1, 2, 3, 4], 'wke': [5, 6], 'all': [0, 1, 2, 3, 4, 5, 6],
}
STR2MON = {
'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4, 'may': 5, 'jun': 6,
'jul': 7, 'aug': 8, 'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12,
}
This is what I have so far, but I would be interested to see other solutions to this problem as mine feels a bit 'wrong' -
class DateRange(Database):
parsed_dates = []
def __init__(self, days, weekdays, months):
super().__init__()
self.days = str(days)
self.weekdays = frozenset(DateRange.flatn(weekdays)) # flat set of ints
self.months = frozenset(DateRange.flatn(months)) # flat set of ints
def __repr__(self):
return self.days
def accepts(self, dt):
return any([
dt.weekday() in self.weekdays,
dt.month in self.months,
])
@staticmethod
def get_days(description):
for days in description:
wkdays = list()
months = list()
for item in days:
item = item.strip().lower().replace('ee', '', 1) # weekend,weekday => wke,wkd
if '-' not in item:
if item[:3] in Constants.STR2WKDAY:
wkdays.append(Constants.STR2WKDAY[item[:3]])
elif item[:3] in Constants.STR2MON:
months.append(Constants.STR2MON[item[:3]])
else:
f, t = item.split('-', 2)
f = f.strip()
t = t.strip()
if f[:3] in Constants.STR2WKDAY:
f = Constants.STR2WKDAY[f[:3]]
t = Constants.STR2WKDAY[t[:3]]
if f > t:
wkdays.append([i for i in range(t + 1)])
wkdays.append([i for i in range(f, 7)])
else:
wkdays.append([i for i in range(f, t + 1)])
elif f[:3] in Constants.STR2MON and t[:3] in Constants.STR2MON:
f = Constants.STR2MON[f[:3]]
t = Constants.STR2MON[t[:3]]
if f > t:
months.append([i for i in range(t + 1)])
months.append([i for i in range(f, 13)])
else:
months.append([i for i in range(f, t + 1)])
DateRange.parsed_dates.append(DateRange(days[0], wkdays, months))
@staticmethod
def flatn(array):
if isinstance(array, list):
for element in array:
yield from DateRange.flatn(element)
else:
yield (array)
@staticmethod
def date_range(from_date, to_date):
for n in range((to_date - from_date).days + 1):
yield from_date + timedelta(n)
Then calling this I just pass a Numpy array of descriptions atm to get_days()
- I.E:
[['Saturdays'], ['Sundays'], ['Weekdays']]
This will then bucket every date that falls into each description. - This is long, so thanks to anyone who has a go and I appreciate any advice as I'm fairly new to programming.
Example usage
from datetime import timedelta, datetime
from collections import defaultdict
class Constants:
STR2WKDAY = {
'mon': 0, 'tue': 1, 'wed': 2, 'thu': 3, 'fri': 4, 'sat': 5, 'sun': 6,
'wkd': [0, 1, 2, 3, 4], 'wke': [5, 6], 'all': [0, 1, 2, 3, 4, 5, 6],
}
STR2MON = {
'jan': 1, 'feb': 2, 'mar': 3, 'apr': 4, 'may': 5, 'jun': 6,
'jul': 7, 'aug': 8, 'sep': 9, 'oct': 10, 'nov': 11, 'dec': 12,
}
def usage(start_date, end_date, description):
active_dates_dict = defaultdict(list)
# populate the list in DateRange with dates that match the description for given date range
DateRange.active_days(description)
for date in DateRange.parsed_dates:
# iterate over the dates and organise them into buckets that match the description
for dt in [dt for dt in DateRange.date_range(datetime.strptime(start_date, '%Y-%m-%d').date(),
datetime.strptime(end_date, '%Y-%m-%d').date())
if date.accepts(dt)]:
# add the datetime objects to a list of values with the description as the key
active_dates_dict[str(date)].append(str(dt.isoformat()))
return active_dates_dict
if __name__ == '__main__':
a = usage(start_date="2020-01-01", end_date="2020-01-04", description=[['Monday-Saturday']])
print(a)
'''
Possible descriptions - it's worth noting that you can have more than one of these descriptions at the same time
i.e we could have Nov-Dec AND Weekend so we would need to bucket all dates that are from Nov-Dec that are also no weekends
and then the remaining dates would do in the Weekend bucket
Saturday
Sunday
Weekend
Weekday
Monday-Saturday
Jan
Feb
Mar
Nov
Dec
Jan-Feb
Jan-Mar
Jan-Apr
Mar-Oct
Apr-Oct
May-Oct
Jul-Nov
Nov-Feb
Nov-Mar
Nov-Dec
Dec-Jun
Not supported yet (Not sure how) - nice to have but not a Must have
3rd Nov - Dec
9th Mar - 2nd Nov
Jan-8th Mar
3rd Nov-8th Mar
'''
Any improvements are appreciated
Database
inclass DateRange(Database)
? Is it your custom class too, or provided by an external library? Can I get it from somewhere to run the code locally? \$\endgroup\$