Quel est le moyen le plus simple de soustraire un mois d'une date en Python?

99

Si seulement timedelta avait un argument de mois dans son constructeur. Alors, quelle est la façon la plus simple de faire cela?

EDIT: Je ne pensais pas trop à cela, comme indiqué ci-dessous. Ce que je voulais vraiment, c'était n'importe quel jour du mois dernier parce que finalement je vais saisir l'année et le mois seulement. Donc, étant donné un objet datetime, quel est le moyen le plus simple de renvoyer un objet datetime qui tombe le mois précédent ?

Bialecki
la source

Réponses:

62

Essaye ça:

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)

Modifier Corrigé pour gérer la journée également.

Edit Voir aussi la réponse de la perplexité qui souligne un calcul plus simple pour d:

d = min(date.day, calendar.monthrange(y, m)[1])
Duncan
la source
Si je l'ai bien ((date.month+delta-1) % 12)+1fonctionne et signifie que le if not m: m = 12peut être retiré
Charles L.
@ dan-klasson pouvez-vous élaborer?
Jean-François Fabre
@ Jean-FrançoisFabre Le premier n'est pas simple. En outre, on peut facilement le faire avec datetime et timedelta.
dan-klasson
Oui, après l'édition de l'OP, il devient clair que ce qu'ils veulent peut être fait simplement par dt - timedelta(days=dt.day)Tel que formulé à l'origine, bien que cela sonnait comme s'ils voulaient être en mesure de soustraire un nombre arbitraire de mois et c'est un peu plus difficile.
Duncan le
183

Vous pouvez utiliser le dateutilmodule tiers (entrée PyPI ici ).

import datetime
import dateutil.relativedelta

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

production:

2013-02-28 00:00:00
amoe
la source
Je suis confondu avec le param monthet monthsdans dateutil. Soustraire avec param monthne fonctionne pas mais monthsfonctionne 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: Je pense que l'utilisation monthsfera une différence d'un mois. L'utilisation monthdéfinira le mois sur 1 (par exemple, janvier), quel que soit le mois de la saisie.
Frank Meulenaar
70

Après la modification de la question d'origine sur "n'importe quel objet datetime du mois précédent", vous pouvez le faire assez facilement en soustrayant 1 jour du premier du mois.

from datetime import datetime, timedelta

def a_day_in_previous_month(dt):
   return dt.replace(day=1) - timedelta(days=1)
Cory
la source
4
vous pourriez utiliserdt.replace(day=1) - timedelta(1)
jfs
3
oudt - timedelta(days=dt.day)
SergiyKolesnikov
23

Une variante de la réponse de Duncan (je n'ai pas assez de réputation pour commenter), qui utilise calendar.monthrange pour simplifier considérablement le calcul du dernier jour du mois:

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)

Informations sur la plage mensuelle de Obtenir le dernier jour du mois en Python

perplexité
la source
22

Une solution pandas vectorisée est très simple:

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

Corey Levinson
la source
Je vous remercie! Cela m'a aidé à ajouter / soustraire facilement un mois à / de mon ensemble de dates!
Mateus da Silva Teixeira
C'est bien si vous pouvez utiliser pandas. Merci!
igorkf
17

Si seulement timedelta avait un argument de mois dans son constructeur. Alors, quelle est la façon la plus simple de faire cela?

Que voulez-vous que le résultat soit lorsque vous soustrayez un mois, par exemple, à une date qui est le 30 mars? C'est le problème de l'addition ou de la soustraction de mois: les mois ont des durées différentes! Dans certaines applications, une exception est appropriée dans de tels cas, dans d'autres, le "dernier jour du mois précédent" est correct à utiliser (mais c'est une arithmétique vraiment folle, quand on soustrait un mois, puis en ajoutant un mois, ce n'est pas globalement une opération!) , dans d'autres encore, vous voudrez garder en plus de la date une indication sur le fait, par exemple, "Je dis le 28 février mais je voudrais vraiment le 30 février s'il existait", afin d'ajouter ou de soustraire un autre mois à qui peut redéfinir les choses correctement (et ce dernier nécessite évidemment une classe personnalisée contenant une donnée plus une autre chose).

Il ne peut y avoir de vraie solution tolérable pour toutes les applications, et vous ne nous avez pas dit quels sont les besoins de votre application spécifique pour la sémantique de cette opération misérable, donc nous ne pouvons pas fournir beaucoup plus d'aide ici.

Alex Martelli
la source
Vous avez absolument raison. Je plaisantais juste sur le fait que timedelta avait un argument pour cela. Je me demande si la même logique s'applique pour ne pas avoir d'arguments pendant des heures ou des minutes. Techniquement, une journée n'est pas exactement 24 heures.
Bialecki
PHP gère cela en ajoutant 2 jours au 28 février et au 2 mars. Il y a des conventions pour cela, laissez-moi les trouver très rapidement
NullUserException
Cela devient encore plus intéressant si vous calculez des dates de change: par exemple, vous devrez peut-être trouver une date, disons 3 ou 6 mois dans le futur, mais cela doit aussi être un jour ouvrable dans les deux pays (pas le vendredi dans les pays musulmans) et pas un jour férié dans les deux pays.
Duncan
@Bialecki: les datetimemodules supposent qu'un jour est exactement 24 heures (pas de secondes
intercalaires
8

Je pense que le moyen le plus simple est d'utiliser DateOffset de Pandas comme ceci:

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

Le résultat sera un objet datetime

FranciscoRodríguezCuenca
la source
2
C'est la solution la plus intuitive
Kots le
5

Si tout ce que vous voulez est n'importe quel jour du mois dernier, la chose la plus simple que vous pouvez faire est de soustraire le nombre de jours de la date actuelle, ce qui vous donnera le dernier jour du mois précédent.

Par exemple, à partir de n'importe quelle date:

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

En soustrayant les jours de la date actuelle, nous obtenons:

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

Cela suffit pour votre besoin simplifié de n'importe quel jour du mois dernier.

Mais maintenant que vous l'avez, vous pouvez également obtenir n'importe quel jour du mois, y compris le même jour que vous avez commencé (c'est-à-dire plus ou moins la même chose que de soustraire un mois):

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

Bien sûr, vous devez faire attention avec le 31e sur un mois de 30 jours ou les jours manquants à partir de février (et prendre soin des années bissextiles), mais c'est aussi facile à faire:

>>> 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
la source
1

Je l'utilise pour les exercices gouvernementaux où le quatrième trimestre commence le 1er octobre. Notez que je convertis la date en trimestres et je l'annule également.

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)
Arthur D. Howland
la source
0

Voici un code pour faire exactement cela. Je n'ai pas essayé moi-même ...

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
sajal
la source
0

Étant donné un tuple (année, mois), où le mois va de 1 à 12, essayez ceci:

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

Suppose qu'il y a 12 mois dans chaque année.

PaulMcG
la source
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
la source
0

J'ai utilisé le code suivant pour remonter n mois à partir d'une date spécifique:

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

Par exemple: date d'entrée = 28/12/2015 Calculer la date précédente de 6 mois.

I) CALCULER LE MOIS: Cette étape vous donnera la date de début au 30/06/2015.
Notez qu'après l'étape de calcul du mois, vous obtiendrez le dernier jour du mois requis.

II) CALCULER DAY: Condition if (start_date.day> your_date.day) vérifie si le jour de input_date est présent dans le mois requis. Cela gère la condition où la date d'entrée est 31 (ou 30) et le mois requis a moins de 31 (ou 30 en cas de février) jours. Il gère également le cas des années bissextiles (pour février). Après cette étape, vous obtiendrez le résultat du 28/06/2015

Si cette condition n'est pas remplie, start_date reste la dernière date du mois précédent. Donc, si vous donnez 31/12/2015 comme date d'entrée et que vous voulez une date antérieure de 6 mois, cela vous donnera 30/06/2015

ashay93k
la source
0

Vous pouvez utiliser la fonction ci-dessous pour obtenir la date avant / après X mois.

à partir de la date d'importation datetime

def next_month (date_donnée, mois):
    aaaa = int (((date_donnée.année * 12 + date_donnée.mois) + mois) / 12)
    mm = entier (((date_donnée.année * 12 + date_donnée.mois) + mois)% 12)

    si mm == 0:
        aaaa - = 1
        mm = 12

    return given_date.replace (année = aaaa, mois = mm)


si __name__ == "__main__":
    aujourd'hui = date.aujourd'hui ()
    imprimer (aujourd'hui)

    pour mm in [-12, -1, 0, 1, 2, 12, 20]:
        next_date = next_month (aujourd'hui, mm)
        imprimer (date_suivante)
Nandlal Yadav
la source
0

Je pense que cette réponse est assez lisible:

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
Stylo à billeBen
la source
0

Bon mot ?

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

onekiloparsec
la source
0

La manière la plus simple que j'ai essayée tout à l'heure

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)
Saad Mirza
la source
0

Il y a quelque temps, je suis tombé sur l'algorithme suivant qui fonctionne très bien pour incrémenter et décrémenter des mois sur a dateou datetime.

CAVEAT : Cela échouera s'il dayn'est pas disponible dans le nouveau mois. J'utilise ceci sur les objets de date où day == 1toujours.

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)

Pour Python 2.7, remplacez le //12par juste /12car la division entière est implicite.

J'ai récemment utilisé ceci dans un fichier par défaut lorsqu'un script a commencé à obtenir ces globaux utiles:

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)
Neil C. Obremski
la source
-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)

Solution simple, pas besoin de bibliothèques spéciales.

Cristian Florin Ionescu
la source
2
Ne fonctionnera pas lorsque la date est quelque chose comme le 31 mars.
Firecast du
1
Ou comme le 1er janvier
LeandroHumb
-3

Vous pouvez le faire en deux lignes comme ceci:

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

rappelez-vous les importations

from datetime import datetime
Omri Jacobsz
la source
1
Et si c'était Jan?
Sergei