2010-08-06 6 views
40

Если только timedelta имеет аргумент месяца в его конструкторе. Так какой самый простой способ сделать это?Какой самый простой способ вычесть месяц из даты в Python?

EDIT: Я не думал об этом слишком сильно, как было указано ниже. На самом деле, что я хотел, был в любой день в последний месяц, потому что в конечном итоге я собираюсь захватить только год и месяц. Так что, учитывая объект datetime, какой самый простой способ вернуть любой объект datetime, который выпадает в предыдущем месяце?

ответ

29

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

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) 

Edit Исправленная для обработки на следующий день, а также.

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

d = min(date.day, calendar.monthrange(y, m)[1]) 
+0

Отличная работа! Это может мне очень помочь. –

12

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

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

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

+0

Вы абсолютно правы.Просто шутил о timedelta, аргументируя это. Интересно, применяется ли та же самая логика для отсутствия аргументов в течение нескольких часов или минут. Технически день не ровно 24 часа. – Bialecki

+0

PHP [обрабатывает это] (http://codepad.org/hLeHWRgz), добавив 2 дня до 28 февраля, и он отправится на 2 марта. Для этого есть соглашения, позвольте мне найти их очень быстро – NullUserException

+0

Это становится еще более интересным, если вы рассчитываете даты для обмена валюты: например. вам может потребоваться найти дату, скажем, 3 или 6 месяцев в будущем, но она также должна быть рабочим днем ​​в обеих странах (а не в пятницу в мусульманских странах), а не в банковском каникуле в любой стране. – Duncan

0

Вот некоторые code сделать это. Не пробовал это сам ...

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 месяцев каждый год.

27

После редактирования исходного вопроса на «любой объект 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

101

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

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 
+0

Я путаюсь с параметром 'month' и' months' in dateutil. Вычитание с параметром 'month' не работает, но' месяцев' работает 'В [23]: created_datetime__lt - relativedelta (months = 1) Out [23]: datetime.datetime (2016, 11, 29, 0, 0, tzinfo = ) В [24]: created_datetime__lt - relativedelta (месяц = ​​1) Out [24]: datetime.datetime (2016, 1, 29, 0, 0, tzinfo = < StaticTzInfo 'Etc/GMT-8'>) ' –

+0

@SiminJie: Я думаю, что использование' месяцев' будет иметь значение 1 месяц. Использование 'month' будет устанавливать месяц на 1 (например, январь), независимо от месяца ввода. –

14

Разновидность Duncan's answer (у меня нет достаточной репутации комментировать), которая использует 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) 

Информация о monthrange от Get Last Day of the Month in Python

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) 
0

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

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) CALCULATE ДЕНЬ: Состояние если (start_date.day> your_date.day) проверяет, является ли день от input_date присутствует в требуемом месяце. Это обрабатывает условие, когда дата ввода составляет 31 (или 30), а требуемый месяц имеет менее 31 (или 30 дней). Он также обрабатывает случай високосного года (для февраля). После этого шага вы получите результат как 28/06/2015

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

2

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

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

>>> 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) 
0

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

 
from datetime import date 

def next_month(given_date, month): 
    yyyy = int(((given_date.year * 12 + given_date.month) + month)/12) 
    mm = int(((given_date.year * 12 + given_date.month) + month)%12) 

    if mm == 0: 
     yyyy -= 1 
     mm = 12 

    return given_date.replace(year=yyyy, month=mm) 


if __name__ == "__main__": 
    today = date.today() 
    print(today) 

    for mm in [-12, -1, 0, 1, 2, 12, 20 ]: 
     next_date = next_month(today, mm) 
     print(next_date)