使用 Python 将一个月添加到给定日期(后一天四舍五入)

Add one month to a given date (rounded day after) with Python

我想在给定的日期上加一个月

import datetime
dt = datetime.datetime(year=2014, month=5, day=2)

所以我应该

datetime.datetime(year=2014, month=6, day=2)

但是

dt = datetime.datetime(year=2015, month=1, day=31)

我应该得到

datetime.datetime(year=2015, month=3, day=1)

因为没有 2015-02-31(而且我希望我的结果在之后

有些月份有 31 天,有些月份有 30 天,有些月份有 29 天,有些月份有 28 天!

所以添加 datetime.timedelta 可能不是一个好的做法(因为我们不知道要添加的天数)

我注意到 Pandas 有一个有趣的概念 DateOffset

http://pandas.pydata.org/pandas-docs/stable/timeseries.html#dateoffset-objects

但我没有找到 Month 偏移量,只有 MonthBeginMonthEnd

我也看到这个post How do I calculate the date six months from the current date using the datetime Python module?

所以我尝试了 dateutil.relativedelta 但是

from dateutil.relativedelta import relativedelta
datetime.datetime(year=2015, month=1, day=31)+relativedelta(months=1)

returns

datetime.datetime(2015, 2, 28, 0, 0)

所以结果在 .

前一天四舍五入

在之后是否有一种(干净的)方式来圆整一天?

编辑: 我给出了一个要添加一个月的示例,但我也希望能够添加例如:2 年零 6 个月(使用 relativedelta(years=2, months=6)

快速解决方案:

import datetime
import calendar
dt = datetime.datetime(year=2014, month=5, day=2)
d = calendar.monthrange(dt.year,dt.month+1)[1] 
print dt+datetime.timedelta(days=d+1)

第一个输入的输出 (year=2014, month=5, day=2):

2014-06-02 00:00:00

第二个输入的输出 (year=2015, month=1, day=31):

2015-03-01 00:00:00

可以使用dateutil.relativedelta.relativedelta and manually check the datetime.day属性,如果原来的日期大于新的日期,则添加一天。

下面的函数接受一个 datetime 对象和 relativedelta 对象。请注意,下面的代码仅适用于数年和数月,我认为如果您使用低于该代码的任何内容(天数、小时数等),它就不会起作用。您可以轻松修改此函数以将 yearsmonths 作为参数,然后在函数本身内部构造 relativedelta

from datetime import datetime
from dateutil.relativedelta import relativedelta

def add_time(d, rd):
    day = relativedelta(days=+1)

    out = d + rd
    if d.day > out.day:
        out = out + day

    return out    

# Check that it "rolls over"
print(add_time(datetime(year=2015, month=1, day=29), relativedelta(years=+4, months=+1))) # 2019-03-01 00:00:00
print(add_time(datetime(year=2015, month=3, day=31), relativedelta(years=+0, months=+2))) # 2015-05-01 00:00:00

# Check that it handles "normal" scenarios
print(add_time(datetime(year=2015, month=6, day=19), relativedelta(months=+1))) # 2015-07-19 00:00:00
print(add_time(datetime(year=2015, month=6, day=30), relativedelta(years=+2, months=+1))) # 2017-07-30 00:00:00

# Check across years
print(add_time(datetime(year=2015, month=12, day=25), relativedelta(months=+1))) # 2016-01-25 00:00:00

# Check leap years
print(add_time(datetime(year=2016, month=1, day=29), relativedelta(years=+4, months=+1))) # 2020-02-29 00:00:00

这似乎有效。挺干净的,就是不漂亮:

def add_month(now):
    try:
        then = (now + relativedelta(months=1)).replace(day=now.day)
    except ValueError:
        then = (now + relativedelta(months=2)).replace(day=1)
    return then

for now in [datetime(2015, 1, 20), datetime(2015, 1, 31), datetime(2015, 2, 28)]:
    print now, add_month(now)

打印:

2015-01-20 00:00:00 2015-02-20 00:00:00
2015-01-31 00:00:00 2015-03-01 00:00:00
2015-02-28 00:00:00 2015-03-28 00:00:00

增加一个月并尝试用原来的日期替换日期。如果成功,那不是特例。如果失败(ValueError),我们必须再增加一个月并转到它的第一天。

使用下面的方法:

def datetime_offset_by_months(datetime_origin, n=1):
    """
    datetime offset by months
    :param datetime_origin: the original datetime
    :param n: count of months
    :return: after offset datetime
    """
    # create a shortcut object for one day
    one_day = datetime.timedelta(days=1)

    # first use div and mod to determine year cycle
    q, r = divmod(datetime_origin.month + n, 12)

    # create a datetime_offset
    # to be the last day of the target month
    datetime_offset = datetime.datetime(
        datetime_origin.year + q, r + 1, 1) - one_day

    '''
       if input date is the last day of this month
       then the output date should also be the last
       day of the target month, although the day
       www.iplaypy.com
       may be different.
       for example:
       datetime_origin = 8.31
       datetime_offset = 9.30
    '''

    if datetime_origin.month != (datetime_origin + one_day).month:
        return datetime_offset

    '''
        if datetime_origin day is bigger than last day of
        target month, then, use datetime_offset
        for example:
        datetime_origin = 10.31
        datetime_offset = 11.30
    '''

    if datetime_origin.day >= datetime_offset.day:
        return datetime_offset

    '''
     then, here, we just replace datetime_offset's day
     with the same of datetime_origin, that's ok.
    '''

    return datetime_offset.replace(day= datetime_origin.day)

它的用法:

import datetime
now_date = datetime.datetime.now()

off_set_datetime = datetime_offset_by_months(now_date, -2)
print(off_set_datetime)