From d2da07a2f6782f065f23337cb0eb42eb7fad6800 Mon Sep 17 00:00:00 2001 From: Erik Stein Date: Fri, 3 Feb 2017 17:49:03 +0100 Subject: [PATCH] Added date related calculation helpers. --- requirements.txt | 2 ++ utils/dates.py | 84 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 86 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..bb38adb --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +Django<2 +python-dateutil diff --git a/utils/dates.py b/utils/dates.py index 9fb760e..3b5c5a3 100644 --- a/utils/dates.py +++ b/utils/dates.py @@ -4,8 +4,92 @@ from __future__ import unicode_literals import calendar +from collections import namedtuple +from dateutil import rrule +from datetime import date, datetime + + +def force_date(d): + if type(d) == datetime: + return d.date() + else: + return d + + +def first_of_month(d): + return d.replace(day=1) def get_last_of_month(d): + """ + >>> get_last_of_month(date(2000, 2, 1)) + datetime.date(2000, 2, 29) + >>> get_last_of_month(date(2004, 2, 1)) + datetime.date(2004, 2, 29) + >>> get_last_of_month(date(2004, 12, 1)) + datetime.date(2004, 12, 31) + """ day = calendar.monthrange(d.year, d.month)[1] return d.replace(day=day) + + +def as_month(d): + """ + >>> as_month(datetime(2007, 1, 13, 14, 0)) + datetime.date(2007, 1, 1) + """ + return force_date(first_of_month(d)) + + +# +# Date Range Calculations + + +DateRange = namedtuple('Range', ['start', 'end']) + + +def months_for_daterange(daterange): + """ + Returns all months for a given date range. + A month is represented by a datetime.date object of the first day of the month + + >>> daterange = DateRange(date(2015, 3, 5), date(2015, 8, 25)) + >>> months_for_daterange(daterange) + [datetime.date(2015, 3, 1), datetime.date(2015, 4, 1), datetime.date(2015, 5, 1), datetime.date(2015, 6, 1), datetime.date(2015, 7, 1), datetime.date(2015, 8, 1)] + + """ + return map( + as_month, + rrule.rrule(rrule.MONTHLY, dtstart=daterange.start, until=daterange.end) + ) + + +def months_for_daterange_list(daterange_list): + """ + >>> range_list = [DateRange(date(2015, 2, 25), date(2015, 2, 25)), DateRange(date(2015, 3, 13), date(2015, 3, 13)), DateRange(date(2015, 5, 3), date(2015, 5, 3)), DateRange(date(2015, 5, 7), date(2015, 7, 7)), DateRange(date(2015, 6, 28), date(2015, 6, 28)), DateRange(date(2015, 7, 5), date(2015, 7, 5)), DateRange(date(2015, 10, 14), date(2015, 10, 14)), DateRange(date(2015, 11, 11), date(2015, 12, 11))] + >>> months_for_daterange_list(range_list) + [datetime.date(2015, 2, 1), datetime.date(2015, 3, 1), datetime.date(2015, 5, 1), datetime.date(2015, 6, 1), datetime.date(2015, 7, 1), datetime.date(2015, 10, 1), datetime.date(2015, 11, 1), datetime.date(2015, 12, 1)] + """ + all_months = set() + for dr in daterange_list: + all_months.update(months_for_daterange(dr)) + return sorted(list(all_months)) + + +""" +# >>> from datetime import datetime +# >>> from collections import namedtuple +# >>> Range = namedtuple('Range', ['start', 'end']) +# >>> r1 = Range(start=datetime(2012, 1, 15), end=datetime(2012, 5, 10)) +# >>> r2 = Range(start=datetime(2012, 3, 20), end=datetime(2012, 9, 15)) +# >>> latest_start = max(r1.start, r2.start) +# >>> earliest_end = min(r1.end, r2.end) +# >>> overlap = (earliest_end - latest_start).days + 1 +# >>> overlap +52 +""" + + +if __name__ == "__main__": + import doctest + doctest.testmod()