無駄と文化

実用的ブログ

Python で日時計算 ~ 月初とか月末とか N ヵ月前とか

Python で日付計算と言えば datetime モジュールですね。まぁまぁ便利なんですが、標準では 月初月末 を求めることができません。
なのでちょっとしたユーティリティコードとして書いておきました。


N ヶ月後・N ヶ月前

add_month()N ヶ月後の同日同時間 を求める関数です。同様に sub_month()N ヶ月前の同日同時間 を求めます。
2月31日 のように計算結果が存在しない日付になってしまう場合は、同月の末日を返します。

本当は datetime.timedelta を使えれば良かったんですが、datetime.timedelta では N ヶ月差を表現することはできないようです。


月初・月末

現在持っている日付の 月初月末 を求める関数も書きました。
start_of_month() は月初を、end_of_month() は月末を返します。


コード

# -*- coding: utf-8 -*-

from datetime import datetime
from calendar import monthrange


def add_month(_date, month_delta):
    """{month_delta} ヵ月後の同日の日時を返す

    >>> add_month(datetime.datetime(2017, 1, 30, 5, 20, 15), month_delta=1)
    datetime.datetime(2017, 2, 28, 23, 59, 59)

    >>> add_month(datetime.datetime(2017, 1, 15, 5, 20, 15), month_delta=1)
    datetime.datetime(2017, 2, 15, 5, 20, 15)

    >>> add_month(datetime.datetime(2017, 1, 30, 5, 20, 15), month_delta=13)
    datetime.datetime(2018, 2, 28, 23, 59, 59)
    """

    year = _date.year
    month = _date.month
    day = _date.day
    hour = _date.hour
    minute = _date.minute
    second = _date.second
    microsecond = _date.microsecond

    alt_year = year
    alt_month = month + month_delta

    if alt_month > 12:
        month_overflow = alt_month

        alt_year += month_overflow // 12
        alt_month = month_overflow % 12
    elif alt_month <= 0:
        month_overflow = alt_month

        alt_year += (month_overflow - 1) // 12
        alt_month = (month_overflow - 1) % 12 + 1

    try:
        return datetime(alt_year, alt_month, day, hour, minute, second, microsecond)
    except ValueError:
        return end_of_month(datetime(alt_year, alt_month, 1, hour, minute, second, microsecond))

def sub_month(_date, month_delta):
    """{month_delta} ヵ月前の同日の日時を返す

    >>> sub_month(datetime.datetime(2017, 3, 30, 5, 20, 15), month_delta=1)
    datetime.datetime(2017, 2, 28, 23, 59, 59)

    >>> sub_month(datetime.datetime(2017, 3, 15, 5, 20, 15), month_delta=1)
    datetime.datetime(2017, 2, 15, 5, 20, 15)

    >>> sub_month(datetime.datetime(2017, 3, 20, 5, 20, 15), month_delta=5)
    datetime.datetime(2016, 10, 20, 5, 20, 15)
    """

    return add_month(_date, -month_delta)

def start_of_month(_date):
    """その月の月初の時を返す

    >>> start_of_month(datetime.datetime(2017, 3, 15))
    datetime.datetime(2017, 3, 1, 0, 0)

    >>> start_of_month(datetime.datetime(2017, 2, 28, 10, 10, 10))
    datetime.datetime(2017, 2, 1, 0, 0)
    """

    year = _date.year
    month = _date.month

    return datetime(year, month, 1, 0, 0, 0)

def end_of_month(_date):
    """その月の月末の日時を返す

    >>> end_of_month(datetime.datetime(2017, 3, 15))
    datetime.datetime(2017, 3, 31, 23, 59, 59)

    >>> end_of_month(datetime.datetime(2017, 2, 28, 10, 10, 10))
    datetime.datetime(2017, 2, 28, 23, 59, 59)
    """

    year = _date.year
    month = _date.month
    end_of_month = monthrange(year, month)[1]

    return datetime(year, month, end_of_month, 23, 59, 59)


ちなみに、これらの関数名は PHP の良く出来た日付計算ライブラリ Carbon を大いに参考にしています。

一応、Gist があります。

Python で日時計算 ~ 月初とか月末とか N ヵ月前とか · GitHub


私からは以上です。