55400
pythonで特定の期間（start, end）が与えられた際に、月毎にグループ分けを行いたいです。  
具体的には、1/15 - 3/3という期間が与えられた場合には、  
1/15 - 1/31, 2/1 - 2/28, 3/1 - 3/3という形でグループ分けしたいです。

とりあえず下記のコードで実現はできましたが、下記の2点から良い方法、シンプルな方法があるのではないかと考えております。

  1. グループ分けを行える関数がありそう（itertools.groupbyをうまくつかう？）
  2. 前月の最終日を求める所が冗長

改善点等ありましたら教えてください。

```

    from datetime import datetime, timedelta, date
    from dateutil.relativedelta import relativedelta
    def hoge(start, end):
        def _hoge(l, s, e):
            one_month_after = s + relativedelta(months=1)
            next_month = date(one_month_after.year, one_month_after.month, 1)
            month_last_day = next_month - timedelta(days=1)
            if e <= month_last_day:
                l.append((s, e))
                return l
            l.append((s, month_last_day))
            return _hoge(l, next_month, e)
        return _hoge([], start, end)
    
    hoge(date(2019, 1, 15), date(2019, 3, 3))
    
```

出力

```

    # [(datetime.date(2019, 1, 15), datetime.date(2019, 1, 31)),
    #  (datetime.date(2019, 2, 1), datetime.date(2019, 2, 28)),
    #  (datetime.date(2019, 3, 1), datetime.date(2019, 3, 3))]
    
```
CC BY-SA 4.0
2019-05-31T05:47:45.713
55399
2019-07-05T18:24:03.383
13022
question
python python3
特定の期間が与えられた際に、月毎にグループ分けを行いたい
`itertools.groupby()` を使うとすれば以下の様になります。

```

    from datetime import date, timedelta
    from itertools import groupby
    
    def hoge(start, end):
      return [(d[0], d[-1]) for d in [list(v) for _, v in groupby(
        [start + timedelta(days=x) for x in range(0, (end-start).days+1)],
        key = lambda x: (x.year, x.month))]]
    
    hoge(date(2019, 1, 15), date(2019, 3, 3))
    
```

pandas を使うと簡潔になるかもしれません。

ところで、

```

    hoge(date(2019, 1, 31), date(2019, 3, 1))
    
```

とすると、結果は以下の様になるのですが、これはこれでよろしいのでしょうか？

```

    [(datetime.date(2019, 1, 31), datetime.date(2019, 1, 31)),
     (datetime.date(2019, 2, 1), datetime.date(2019, 2, 28)),
     (datetime.date(2019, 3, 1), datetime.date(2019, 3, 1))]
    
```
CC BY-SA 4.0
2019-05-31T07:53:57.440
55400
2019-05-31T07:53:57.440
55399
answer
愚直な方法ですが、開始日から終了日まで datetime オブジェクトを１日ごとに作成、groupby
で月単位でグループにわけ、そのグループの最小値と最大値を求めることで所望の結果が得られます。行っていることは @metropolis
さんのものとほぼ同じで、インデックスの代わりに min, max 関数を使用している分遅いのですが、可読性が上がったのかは微妙なところです。

```

    from datetime import datetime, timedelta
    from itertools import groupby
    
    def hoge(start, end):
        # datetime(start) から datetime(end) までの１日ごとに datetime オブジェクトのリストを作成
        d = start
        d_lst = []
        while d <= end:
            d_lst.append(d)
            d += timedelta(1)
        # 月単位でグループにわけ、そのグループ内の要素の最小と最大を求める
        out = []
        for k, g in groupby(d_lst, lambda d: d.month):
            g = list(g)
            s = min(g)
            e = max(g)
            out.append([s, e])
        return out
    
    start = datetime(2019,1,15)
    end = datetime(2019,3,1)
    out = hoge(start, end)
    print(out)
    
    # Out: [[datetime.datetime(2019, 1, 15, 0, 0), datetime.datetime(2019, 1, 31, 0, 0)],
    #       [datetime.datetime(2019, 2, 1, 0, 0), datetime.datetime(2019, 2, 28, 0, 0)],
    #       [datetime.datetime(2019, 3, 1, 0, 0), datetime.datetime(2019, 3, 1, 0, 0)]]
    
```

pandas は勉強中のため詳しくないのですが、上記の処理を pandas で実装してみました。参考になれば幸いです。また、pandas
は勉強中ですので、改善点等ご指摘いただけたら幸いです。

```

    from datetime import datetime
    import pandas as pd
    
    start = datetime(2019,1,15)
    end = datetime(2019,3,1)
    
    ds = pd.date_range(start, end).to_series()
    out = [i for i in zip(ds.groupby(ds.dt.month).min(), ds.groupby(ds.dt.month).max())]
    print(out)
    # Out: [(Timestamp('2019-01-15 00:00:00'), Timestamp('2019-01-31 00:00:00')),
    #       (Timestamp('2019-02-01 00:00:00'), Timestamp('2019-02-28 00:00:00')),
    #       (Timestamp('2019-03-01 00:00:00'), Timestamp('2019-03-01 00:00:00'))]
    
```
CC BY-SA 4.0
2019-07-05T18:24:03.383
56430
2019-07-05T18:24:03.383
45
55399
answer