Какой самый простой способ вычесть месяц из даты в Python?

101

Если бы только timedelta имел аргумент месяца в конструкторе. Итак, как это сделать проще всего?

РЕДАКТИРОВАТЬ: Я не слишком много думал об этом, как было указано ниже. На самом деле я хотел, чтобы в последний месяц был любой день, потому что в конце концов я собираюсь взять только год и месяц. Итак, учитывая объект datetime, каков самый простой способ вернуть любой объект datetime, который выпал на предыдущий месяц ?

Бялецкий
источник

Ответы:

63

Попробуй это:

def monthdelta(date, delta):
    m, y = (date.month+delta) % 12, date.year + ((date.month)+delta-1) // 12
    if not m: m = 12
    d = min(date.day, [31,
        29 if y%4==0 and not y%400==0 else 28,31,30,31,30,31,31,30,31,30,31][m-1])
    return date.replace(day=d,month=m, year=y)

>>> for m in range(-12, 12):
    print(monthdelta(datetime.now(), m))


2009-08-06 16:12:27.823000
2009-09-06 16:12:27.855000
2009-10-06 16:12:27.870000
2009-11-06 16:12:27.870000
2009-12-06 16:12:27.870000
2010-01-06 16:12:27.870000
2010-02-06 16:12:27.870000
2010-03-06 16:12:27.886000
2010-04-06 16:12:27.886000
2010-05-06 16:12:27.886000
2010-06-06 16:12:27.886000
2010-07-06 16:12:27.886000
2010-08-06 16:12:27.901000
2010-09-06 16:12:27.901000
2010-10-06 16:12:27.901000
2010-11-06 16:12:27.901000
2010-12-06 16:12:27.901000
2011-01-06 16:12:27.917000
2011-02-06 16:12:27.917000
2011-03-06 16:12:27.917000
2011-04-06 16:12:27.917000
2011-05-06 16:12:27.917000
2011-06-06 16:12:27.933000
2011-07-06 16:12:27.933000
>>> monthdelta(datetime(2010,3,30), -1)
datetime.datetime(2010, 2, 28, 0, 0)
>>> monthdelta(datetime(2008,3,30), -1)
datetime.datetime(2008, 2, 29, 0, 0)

Править. Исправлено также и для дня.

Изменить См. Также ответ из недоумения, который указывает на более простой расчет для d:

d = min(date.day, calendar.monthrange(y, m)[1])
Дункан
источник
Если у меня все правильно, ((date.month+delta-1) % 12)+1работает и означает, что if not m: m = 12банка будет удалена
Чарльз Л.
@ dan-klasson ты можешь уточнить?
Жан-Франсуа Фабр
@ Jean-FrançoisFabre Первое непросто. Кроме того, это легко сделать с помощью datetime и timedelta.
dan-klasson
Да, после редактирования OP становится ясно, что то, что они хотят, может быть сделано просто в соответствии с dt - timedelta(days=dt.day)первоначальной формулировкой, хотя это звучало так, как будто они хотели вычесть произвольное количество месяцев, а это немного сложнее.
Дункан
Считаю, что расчет високосных лет неверен - так и должно быть 29 if y%4==0 and (not y%100==0 or y%400 == 0) else 28. например, 1600 - високосный год, а 1700 - нет
Шон Лоуэн
185

Вы можете использовать сторонний dateutilмодуль ( здесь запись PyPI ).

import datetime
import dateutil.relativedelta

d = datetime.datetime.strptime("2013-03-31", "%Y-%m-%d")
d2 = d - dateutil.relativedelta.relativedelta(months=1)
print d2

вывод:

2013-02-28 00:00:00
амое
источник
Меня смущает параметр monthи monthsdateutil. Вычитание с параметром monthне работает, но monthsработает In [23]: created_datetime__lt - relativedelta(months=1) Out[23]: datetime.datetime(2016, 11, 29, 0, 0, tzinfo=<StaticTzInfo 'Etc/GMT-8'>) In [24]: created_datetime__lt - relativedelta(month=1) Out[24]: datetime.datetime(2016, 1, 29, 0, 0, tzinfo=<StaticTzInfo 'Etc/GMT-8'>)
Симин Джи
1
@SiminJie: Я думаю, что использование monthsбудет иметь значение 1 месяц. Использование monthустановит месяц на 1 (например, январь), независимо от месяца ввода.
Frank Meulenaar
70

После изменения исходного вопроса на «любой объект datetime в предыдущем месяце» вы можете сделать это довольно легко, вычтя 1 день из первого числа месяца.

from datetime import datetime, timedelta

def a_day_in_previous_month(dt):
   return dt.replace(day=1) - timedelta(days=1)
Кори
источник
4
Вы могли бы использоватьdt.replace(day=1) - timedelta(1)
jfs
3
илиdt - timedelta(days=dt.day)
Сергей Колесников
23

Вариант ответа Дункана (у меня недостаточно репутации для комментариев), в котором используется calendar.monthrange, чтобы значительно упростить вычисление последнего дня месяца:

import calendar
def monthdelta(date, delta):
    m, y = (date.month+delta) % 12, date.year + ((date.month)+delta-1) // 12
    if not m: m = 12
    d = min(date.day, calendar.monthrange(y, m)[1])
    return date.replace(day=d,month=m, year=y)

Информация о месячном диапазоне от Получить последний день месяца в Python

недоумение
источник
23

Векторизованное решение pandas очень простое:

df['date'] - pd.DateOffset(months=1)

Кори Левинсон
источник
Спасибо! Это помогло мне легко добавить / вычесть месяц из набора дат!
Матеуш да Силва Тейшейра
Это хорошо, если можно использовать pandas. Спасибо!
igorkf
17

Если бы только timedelta имел аргумент месяца в конструкторе. Итак, как это сделать проще всего?

Каким должен быть результат, если вычесть месяц, скажем, из даты 30 марта? В этом проблема добавления или вычитания месяцев: месяцы имеют разную длину! В некоторых приложениях в таких случаях уместно исключение, в других можно использовать «последний день предыдущего месяца» (но это действительно сумасшедшая арифметика, когда вычитание месяца и добавление месяца в целом не является бездействием!) , в других случаях вы захотите сохранить в дополнение к дате некоторое указание на этот факт, например, «Я говорю 28 февраля, но мне действительно нужно 30 февраля, если он существует», чтобы добавить или вычесть еще один месяц к который может снова все исправить (а последнее, очевидно, требует специального класса, содержащего данные плюс что-то еще).

Не может быть реального решения, приемлемого для всех приложений, и вы не сказали нам, какие потребности вашего конкретного приложения необходимы для семантики этой жалкой операции, так что мы не можем предоставить больше помощи здесь.

Алекс Мартелли
источник
Ты совершенно прав. Просто пошутил о том, что timedelta аргументирует это. Интересно, применима ли такая же логика к отсутствию аргументов в течение часов или минут. Технически день - это не 24 часа.
Bialecki
PHP обрабатывает это , добавляя 2 дня к 28 февраля, и это переходит ко 2 марта. Для этого есть условные обозначения, позвольте мне быстро их найти
NullUserException
Это становится еще интереснее, если вы рассчитываете даты для обмена валюты: например, вам может потребоваться найти дату, скажем, через 3 или 6 месяцев в будущем, но это также должен быть рабочий день в обеих странах (не пятница в мусульманских странах) и не праздничный день в любой стране.
Дункан
@Bialecki: datetimemodules предполагает, что день составляет ровно 24 часа (без дополнительных секунд)
jfs
9

Я думаю, что простой способ - использовать DateOffset из Pandas следующим образом:

import pandas as pd
date_1 = pd.to_datetime("2013-03-31", format="%Y-%m-%d") - pd.DateOffset(months=1)

Результатом будет объект datetime

FranciscoRodríguezCuenca
источник
2
Это наиболее интуитивно понятное решение
Коц
5

Если вам нужен любой день последнего месяца, самое простое, что вы можете сделать, - это вычесть количество дней из текущей даты, что даст вам последний день предыдущего месяца.

Например, начиная с любой даты:

>>> import datetime                                                                                                                                                                 
>>> today = datetime.date.today()                                                                                                                                                   
>>> today
datetime.date(2016, 5, 24)

Вычитая дни текущей даты, получаем:

>>> last_day_previous_month = today - datetime.timedelta(days=today.day)
>>> last_day_previous_month
datetime.date(2016, 4, 30)

Этого достаточно для вашей упрощенной потребности в любой день последнего месяца.

Но теперь, когда он у вас есть, вы также можете получить любой день месяца, включая тот же день, с которого вы начали (т.е. более или менее то же самое, что и вычитание месяца):

>>> same_day_last_month = last_day_previous_month.replace(day=today.day)
>>> same_day_last_month
datetime.date(2016, 4, 24)

Конечно, вам нужно быть осторожным с 31-м числом 30-дневного месяца или с днями, пропущенными с февраля (и позаботиться о високосных годах), но это также легко сделать:

>>> a_date = datetime.date(2016, 3, 31)                                                                                                                                             
>>> last_day_previous_month = a_date - datetime.timedelta(days=a_date.day)
>>> a_date_minus_month = (
...     last_day_previous_month.replace(day=a_date.day)
...     if a_date.day < last_day_previous_month.day
...     else last_day_previous_month
... )
>>> a_date_minus_month
datetime.date(2016, 2, 29)
LeoRochael
источник
1

Я использую это для государственных финансовых лет, когда четвертый квартал начинается 1 октября. Обратите внимание, я конвертирую дату в четверти и также отменяю ее.

import pandas as pd

df['Date'] = '1/1/2020'
df['Date'] = pd.to_datetime(df['Date'])              #returns 2020-01-01
df['NewDate'] = df.Date - pd.DateOffset(months=3)    #returns 2019-10-01 <---- answer

# For fun, change it to FY Quarter '2019Q4'
df['NewDate'] = df['NewDate'].dt.year.astype(str) + 'Q' + df['NewDate'].dt.quarter.astype(str)

# Convert '2019Q4' back to 2019-10-01
df['NewDate'] = pd.to_datetime(df.NewDate)
Артур Д. Хауленд
источник
0

Вот код для этого. Сам не пробовал ...

def add_one_month(t):
    """Return a `datetime.date` or `datetime.datetime` (as given) that is
    one month earlier.

    Note that the resultant day of the month might change if the following
    month has fewer days:

        >>> add_one_month(datetime.date(2010, 1, 31))
        datetime.date(2010, 2, 28)
    """
    import datetime
    one_day = datetime.timedelta(days=1)
    one_month_later = t + one_day
    while one_month_later.month == t.month:  # advance to start of next month
        one_month_later += one_day
    target_month = one_month_later.month
    while one_month_later.day < t.day:  # advance to appropriate day
        one_month_later += one_day
        if one_month_later.month != target_month:  # gone too far
            one_month_later -= one_day
            break
    return one_month_later

def subtract_one_month(t):
    """Return a `datetime.date` or `datetime.datetime` (as given) that is
    one month later.

    Note that the resultant day of the month might change if the following
    month has fewer days:

        >>> subtract_one_month(datetime.date(2010, 3, 31))
        datetime.date(2010, 2, 28)
    """
    import datetime
    one_day = datetime.timedelta(days=1)
    one_month_earlier = t - one_day
    while one_month_earlier.month == t.month or one_month_earlier.day > t.day:
        one_month_earlier -= one_day
    return one_month_earlier
Саджал
источник
0

Учитывая кортеж (год, месяц), где месяц идет от 1 до 12, попробуйте следующее:

>>> from datetime import datetime
>>> today = datetime.today()
>>> today
datetime.datetime(2010, 8, 6, 10, 15, 21, 310000)
>>> thismonth = today.year, today.month
>>> thismonth
(2010, 8)
>>> lastmonth = lambda (yr,mo): [(y,m+1) for y,m in (divmod((yr*12+mo-2), 12),)][0]
>>> lastmonth(thismonth)
(2010, 7)
>>> lastmonth( (2010,1) )
(2009, 12)

Предполагается, что в году 12 месяцев.

PaulMcG
источник
0
def month_sub(year, month, sub_month):
    result_month = 0
    result_year = 0
    if month > (sub_month % 12):
        result_month = month - (sub_month % 12)
        result_year = year - (sub_month / 12)
    else:
        result_month = 12 - (sub_month % 12) + month
        result_year = year - (sub_month / 12 + 1)
    return (result_year, result_month)

>>> month_sub(2015, 7, 1)    
(2015, 6)
>>> month_sub(2015, 7, -1)
(2015, 8)
>>> month_sub(2015, 7, 13)
(2014, 6)
>>> month_sub(2015, 7, -14)
(2016, 9)
damn_c
источник
0

Я использовал следующий код, чтобы вернуться на n месяцев с определенной даты:

your_date =  datetime.strptime(input_date, "%Y-%m-%d")  #to convert date(2016-01-01) to timestamp
start_date=your_date    #start from current date

#Calculate Month
for i in range(0,n):    #n = number of months you need to go back
    start_date=start_date.replace(day=1)    #1st day of current month
    start_date=start_date-timedelta(days=1) #last day of previous month

#Calculate Day
if(start_date.day>your_date.day):   
    start_date=start_date.replace(day=your_date.day)            

print start_date

Например: дата ввода = 28/12/2015 Вычислить дату за 6 месяцев до даты.

I) РАССЧИТАТЬ МЕСЯЦ: На этом шаге вы получите start_date как 30.06.2015.
Обратите внимание, что после шага вычисления месяца вы получите последний день требуемого месяца.

II) РАСЧЕТ ДНЯ: Условие if (start_date.day> your_date.day) проверяет, присутствует ли день из input_date в требуемом месяце. Это обрабатывает условие, когда дата ввода 31 (или 30), а в требуемом месяце меньше 31 (или 30 в случае февраля) дня. Он также обрабатывает случай високосного года (за февраль). После этого шага вы получите результат 28.06.2015.

Если это условие не выполняется, start_date остается последней датой предыдущего месяца. Итак, если вы укажете 31/12/2015 в качестве даты ввода и хотите, чтобы дата была 6 месяцев назад, это даст вам 30/06/2015

ashay93k
источник
0

Вы можете использовать приведенную ниже функцию, чтобы получить дату до / после X месяца.

от даты импорта времени

def next_month (данная_дата, месяц):
    гггг = int (((заданная_дата.год * 12 + заданная_дата.месяц) + месяц) / 12)
    мм = int (((заданная_дата.год * 12 + заданная_дата.месяц) + месяц)% 12)

    если мм == 0:
        гггг - = 1
        мм = 12

    вернуть given_date.replace (год = гггг, месяц = ​​мм)


если __name__ == "__main__":
    сегодня = date.today ()
    печать (сегодня)

    для мм в [-12, -1, 0, 1, 2, 12, 20]:
        next_date = next_month (сегодня, мм)
        печать (следующая_дата)
Нандлал Ядав
источник
0

Думаю, ответ вполне читаем:

def month_delta(dt, delta):
    year_delta, month = divmod(dt.month + delta, 12)

    if month == 0:
        # convert a 0 to december
        month = 12
        if delta < 0:
            # if moving backwards, then it's december of last year
            year_delta -= 1

    year = dt.year + year_delta

    return dt.replace(month=month, year=year)

for delta in range(-20, 21):
    print(delta, "->", month_delta(datetime(2011, 1, 1), delta))

-20 -> 2009-05-01 00:00:00
-19 -> 2009-06-01 00:00:00
-18 -> 2009-07-01 00:00:00
-17 -> 2009-08-01 00:00:00
-16 -> 2009-09-01 00:00:00
-15 -> 2009-10-01 00:00:00
-14 -> 2009-11-01 00:00:00
-13 -> 2009-12-01 00:00:00
-12 -> 2010-01-01 00:00:00
-11 -> 2010-02-01 00:00:00
-10 -> 2010-03-01 00:00:00
-9 -> 2010-04-01 00:00:00
-8 -> 2010-05-01 00:00:00
-7 -> 2010-06-01 00:00:00
-6 -> 2010-07-01 00:00:00
-5 -> 2010-08-01 00:00:00
-4 -> 2010-09-01 00:00:00
-3 -> 2010-10-01 00:00:00
-2 -> 2010-11-01 00:00:00
-1 -> 2010-12-01 00:00:00
0 -> 2011-01-01 00:00:00
1 -> 2011-02-01 00:00:00
2 -> 2011-03-01 00:00:00
3 -> 2011-04-01 00:00:00
4 -> 2011-05-01 00:00:00
5 -> 2011-06-01 00:00:00
6 -> 2011-07-01 00:00:00
7 -> 2011-08-01 00:00:00
8 -> 2011-09-01 00:00:00
9 -> 2011-10-01 00:00:00
10 -> 2011-11-01 00:00:00
11 -> 2012-12-01 00:00:00
12 -> 2012-01-01 00:00:00
13 -> 2012-02-01 00:00:00
14 -> 2012-03-01 00:00:00
15 -> 2012-04-01 00:00:00
16 -> 2012-05-01 00:00:00
17 -> 2012-06-01 00:00:00
18 -> 2012-07-01 00:00:00
19 -> 2012-08-01 00:00:00
20 -> 2012-09-01 00:00:00
BallpointBen
источник
0

Один лайнер ?

previous_month_date = (current_date - datetime.timedelta(days=current_date.day+1)).replace(day=current_date.day)

одинкилопарсек
источник
0

Самый простой способ, который я пробовал только что

from datetime import datetime
from django.utils import timezone





current = timezone.now()
if current.month == 1:
     month = 12
else:
     month = current.month - 1
current = datetime(current.year, month, current.day)
Саад Мирза
источник
0

Некоторое время назад я наткнулся на следующий алгоритм, который очень хорошо работает для увеличения и уменьшения месяцев на a dateили datetime.

ПРЕДОСТЕРЕЖЕНИЕ : это не удастся, если dayне будет доступно в новом месяце. Я использую это на объектах даты где day == 1всегда.

Python 3.x:

def increment_month(d, add=1):
    return date(d.year+(d.month+add-1)//12, (d.month+add-1) % 12+1, 1)

Для Python 2.7 измените значение //12на только /12поскольку подразумевается целочисленное деление.

Я недавно использовал это в файле значений по умолчанию, когда скрипт начал получать эти полезные глобальные переменные:

MONTH_THIS = datetime.date.today()
MONTH_THIS = datetime.date(MONTH_THIS.year, MONTH_THIS.month, 1)

MONTH_1AGO = datetime.date(MONTH_THIS.year+(MONTH_THIS.month-2)//12,
                           (MONTH_THIS.month-2) % 12+1, 1)

MONTH_2AGO = datetime.date(MONTH_THIS.year+(MONTH_THIS.month-3)//12,
                           (MONTH_THIS.month-3) % 12+1, 1)
Нил С. Обремски
источник
-2
import datetime
date_str = '08/01/2018'
format_str = '%d/%m/%Y'
datetime_obj = datetime.datetime.strptime(date_str, format_str)   
datetime_obj.replace(month=datetime_obj.month-1)

Простое решение, никаких специальных библиотек не требуется.

Кристиан Флорин Ионеску
источник
2
Не сработает, когда дата 31 марта.
firecast
1
Или как 1 января
LeandroHumb
-3

Вы можете сделать это двумя строчками:

now = datetime.now()
last_month = datetime(now.year, now.month - 1, now.day)

помните импорт

from datetime import datetime
Омри Якобс
источник
1
Что, если это Ян?
Сергей