Generate date ranges broken by month for a given period
Question: I'm struggling with writing a pythonic, clean generator method that, given a
date period, like `['2014-01-15', '2015-02-03]`, will give me this:

    
    
    ['2014-01-15', '2014-01-31']
    ['2014-02-01', '2014-02-28']
    ...
    ['2015-02-01', '2015-02-03']
    

This is what I came up with:

    
    
    from datetime import datetime
    import calendar
    
    def genDatePeriods(startDate, endDate, format='%Y-%m-%d'):
        dt1 = datetime.strptime(startDate, format)
        dt2 = datetime.strptime(endDate, format)
    
        for year in range(dt1.year, dt2.year + 1):
            for month in range(1, 13):
                day0 = dt1.day if month == dt1.month and year == dt1.year else 1
                day1 = dt2.day if month == dt2.month and year == dt2.year else calendar.monthrange(year, month)[1]
                if (year == dt1.year and month < dt1.month) or (year == dt2.year and month > dt2.month):
                    continue
                else:
                    d0 = (year, month, day0)
                    d1 = (year, month, day1)
                    yield [datetime(*d).strftime(format) for d in [d0, d1]]
    

It works, however I feel like there is a more pythonic/tidy/efficient way to
do this. Any ideas?


Answer: The following is much more concise, using `datetime.date()` objects to find
the first day of the next month each time, until you reach the end date:

    
    
    from datetime import datetime, timedelta
    
    def genDatePeriods(startDate, endDate, format='%Y-%m-%d'):
        curr = datetime.strptime(startDate, format).date()
        end = datetime.strptime(endDate, format).date()
    
        while curr <= end:
            # first day of the next month, using modular arithmetic
            next_month = curr.replace(
                month=curr.month % 12 + 1, year=curr.year + curr.month // 12,
                day=1)
            curr_formatted = curr.strftime(format)
            # end date is next month's first day, minus one day,
            # or the given endDate, whichever comes first
            end_formatted = min(next_month - timedelta(days=1), end).strftime(format)
            yield [curr_formatted, end_formatted]
            curr = next_month
    

Demo:

    
    
    >>> for res in genDatePeriods('2014-01-15', '2015-02-03'):
    ...     print res
    ... 
    ['2014-01-15', '2014-01-31']
    ['2014-02-01', '2014-02-28']
    ['2014-03-01', '2014-03-31']
    ['2014-04-01', '2014-04-30']
    ['2014-05-01', '2014-05-31']
    ['2014-06-01', '2014-06-30']
    ['2014-07-01', '2014-07-31']
    ['2014-08-01', '2014-08-31']
    ['2014-09-01', '2014-09-30']
    ['2014-10-01', '2014-10-31']
    ['2014-11-01', '2014-11-30']
    ['2014-12-01', '2014-12-31']
    ['2015-01-01', '2015-01-31']
    ['2015-02-01', '2015-02-03']
    

