Який найпростіший спосіб відняти місяць від дати в Python?


99

Якби тільки timedelta мав аргумент місяця у своєму конструкторі. То який найпростіший спосіб це зробити?

EDIT: Я не надто замислювався над цим, як було зазначено нижче. Дійсно, я хотів будь-якого дня останнього місяця, тому що врешті-решт я збираюся взяти лише рік і місяць. Отже, враховуючи об’єкт datetime, який найпростіший спосіб повернути будь-який об’єкт datetime, який потрапляє у попередній місяць ?

Відповіді:


62

Спробуйте це:

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

Так, після відредагованого ОП стає зрозуміло, що те, що вони хочуть, може зробити лише dt - timedelta(days=dt.day)як спочатку сформульовано, хоча це звучало так, ніби вони хотіли мати можливість відняти довільну кількість місяців, і це трохи складніше.
Дункан

183

Ви можете використовувати сторонній 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

Я плутаюся з param monthі monthsв dateutil. Віднімання з param 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'>)
Simin Jie

1
@SiminJie: Я думаю, що використання monthsдасть різницю в 1 місяць. Використання monthвстановить для місяця значення 1 (наприклад, січень), незалежно від місяця введення.
Френк Меуленар

70

Після редагування вихідного запитання на "будь-який об'єкт дати та часу в попередньому місяці", ви можете зробити це досить легко, віднявши 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



17

Якби тільки timedelta мав аргумент місяця у своєму конструкторі. То який найпростіший спосіб це зробити?

Яким ви хочете отримати результат, коли віднімаєте місяць, скажімо, від дати, яка є 30 березня? Ось проблема з додаванням або відніманням місяців: місяці мають різну тривалість! У деяких додатках виняток підходить у таких випадках, в інших "останній день попереднього місяця" можна використовувати (але це справді божевільна арифметика, коли віднімаючи місяць, тоді додавання місяця не є цілому є відсутністю операції!) , в інших ви все-таки захочете зберегти на додаток до дати деяку вказівку на факт, наприклад, "я кажу 28 лютого, але я дійсно хотів би 30 лютого, якби це існувало", так що додавання або віднімання ще одного місяця до що може знову налагодити ситуацію (і остання, очевидно, вимагає спеціального класу, що містить дані плюс s / річ ще).

Не може бути реального рішення, прийнятного для всіх додатків, і ви не повідомили нам, які потреби вашого конкретного додатка стосуються семантики цієї жалюгідної операції, тому ми не можемо надати тут набагато більше допомоги.


Ви абсолютно праві. Просто жартував про те, що Timedelta мав для цього аргумент. Цікаво, чи застосовується та сама логіка для відсутності аргументів протягом годин чи хвилин. Технічно день - це не зовсім 24 години.
Bialecki,

PHP це обробляє, додавши 2 дні до 28 лютого, і це переходить до 2 березня. Для цього є домовленості, дозвольте мені швидко знайти їх
NullUserException

Це стає ще цікавішим, якщо ви розраховуєте дати для обміну валют: наприклад, можливо, вам доведеться знайти дату, скажімо, 3 або 6 місяців у майбутньому, але це також повинен бути робочий день в обох країнах (а не в п’ятницю в мусульманських країнах) а не святковий день в жодній країні.
Дункан

@Bialecki: datetimeмодулі припускають, що день - це рівно 24 години (без високосних секунд)
jfs

8

Я думаю, що простий спосіб - використовувати DateOffset від Pandas так:

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

Результатом буде об'єкт дати та часу


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)

1

Я використовую це для державних фінансових років, де Q4 починається 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 місяців.


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

Я використав наступний код для повернення 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) РОЗРАХУВАТИ МІСЯЦЬ: Цей крок надасть вам дату_початкового_ 30/06/2015.
Зверніть увагу, що після кроку обчислення місяця ви отримаєте останній день необхідного місяця.

II) РОЗРАХУВАТИ ДЕНЬ: Умова, якщо (start_date.day> your_date.day) перевіряє, чи присутній день із input_date у необхідному місяці. Це обробляє умову, коли датою введення є 31 (або 30), а необхідний місяць має менше 31 (або 30 у випадку лютого) днів. Він також розглядає справу високосного року (за лютий). Після цього кроку ви отримаєте результат 28/06/2015

Якщо ця умова не виконується, дата_початкового_залишається останньою датою попереднього місяця. Отже, якщо ви вказали 31.12.2015 як дату введення і хочете дати 6 місяців попередньої дати, це дасть вам 30.06.2015


0

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

з дати і часу імпорту

def next_month (задана дата, місяць):
    рррр = int (((задана_дата.рік * 12 + дана_дата.місяць) + місяць) / 12)
    mm = int (((дана_дата.рік * 12 + дана_дата.місяць) + місяць)% 12)

    якщо мм == 0:
        рррр - = 1
        мм = 12

    повернення given_date.replace (рік = рррр, місяць = мм)


якщо __name__ == "__main__":
    сьогодні = date.today ()
    друк (сьогодні)

    для мм у [-12, -1, 0, 1, 2, 12, 20]:
        next_date = наступний_місяць (сьогодні, мм)
        друк (дата_ наступного)

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

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

Деякий час тому я натрапив на наступний алгоритм, який дуже добре працює для збільшення та зменшення місяців на а dateабо datetime.

CAVEAT : це не вдасться, якщо 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на just/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 березня.
вогняне каміння

1
Або як 1 січня
LeandroHumb

-3

Ви можете зробити це у два рядки так:

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

пам’ятайте про імпорт

from datetime import datetime

1
Що, якщо це Ян?
Сергій
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.