Meilleur moyen de trouver les mois entre deux dates

91

J'ai le besoin de pouvoir trouver avec précision les mois entre deux dates en python. J'ai une solution qui fonctionne mais qui n'est pas très bonne (comme en élégant) ou rapide.

dateRange = [datetime.strptime(dateRanges[0], "%Y-%m-%d"), datetime.strptime(dateRanges[1], "%Y-%m-%d")]
months = [] 

tmpTime = dateRange[0]
oneWeek = timedelta(weeks=1)
tmpTime = tmpTime.replace(day=1)
dateRange[0] = tmpTime
dateRange[1] = dateRange[1].replace(day=1)
lastMonth = tmpTime.month
months.append(tmpTime)
while tmpTime < dateRange[1]:
    if lastMonth != 12:
        while tmpTime.month <= lastMonth:
            tmpTime += oneWeek
        tmpTime = tmpTime.replace(day=1)
        months.append(tmpTime)
        lastMonth = tmpTime.month

    else:
        while tmpTime.month >= lastMonth:
            tmpTime += oneWeek
        tmpTime = tmpTime.replace(day=1)
        months.append(tmpTime)
        lastMonth = tmpTime.month

Donc, juste pour expliquer, ce que je fais ici, c'est de prendre les deux dates et de les convertir du format iso en objets datetime python. Ensuite, je boucle en ajoutant une semaine à l'objet datetime de début et je vérifie si la valeur numérique du mois est supérieure (à moins que le mois ne soit décembre, alors il vérifie si la date est inférieure), si la valeur est supérieure, je l'ajoute à la liste des mois et continuez jusqu'à ce que j'arrive à ma date de fin.

Cela fonctionne parfaitement, cela ne semble pas être un bon moyen de le faire ...

Joshkunz
la source
Demandez-vous le NOMBRE de mois entre deux dates ou quels sont les mois réels?
Charles Hooper
dans ma solution: je ne suis pas incrémenté de "l'équivalent d'un mois en nombre de secondes". J'augmente simplement le nombre de 1 à 2, puis de 2 à 3 plus tard.
nonopolarité
Je voulais juste que vous sachiez que même si vous n'aimiez pas ma réponse parce qu'elle "avait une boucle", vous avez sélectionné une réponse qui a DEUX boucles. Les compréhensions de listes sont toujours des boucles.
Charles Hooper

Réponses:

0

Mise à jour 2018-04-20: il semble que OP @Joshkunz demandait de trouver quels mois sont entre deux dates, au lieu de "combien de mois" sont entre deux dates. Donc, je ne sais pas pourquoi @JohnLaRooy est voté plus de 100 fois. @Joshkunz a indiqué dans le commentaire sous la question initiale qu'il voulait les dates réelles [ou les mois], au lieu de trouver le nombre total de mois .

Il est donc apparu que la question était voulue, entre deux dates 2018-04-11 à2018-06-01

Apr 2018, May 2018, June 2018 

Et si c'était entre 2014-04-11à2018-06-01 ? Alors la réponse serait

Apr 2014, May 2014, ..., Dec 2014, Jan 2015, ..., Jan 2018, ..., June 2018

C'est pourquoi j'ai eu le pseudo code suivant il y a de nombreuses années. Il a simplement suggéré d'utiliser les deux mois comme points finaux et de les parcourir en boucle, en incrémentant d'un mois à la fois. @Joshkunz a mentionné qu'il voulait les «mois» et il a également mentionné qu'il voulait les «dates», sans savoir exactement, il était difficile d'écrire le code exact, mais l'idée est d'utiliser une simple boucle pour parcourir les points finaux, et incrémentation d'un mois à la fois.

La réponse il y a 8 ans en 2010:

Si vous ajoutez une semaine, cela fera environ 4,35 fois le travail nécessaire. Pourquoi pas simplement:

1. get start date in array of integer, set it to i: [2008, 3, 12], 
       and change it to [2008, 3, 1]
2. get end date in array: [2010, 10, 26]
3. add the date to your result by parsing i
       increment the month in i
       if month is >= 13, then set it to 1, and increment the year by 1
   until either the year in i is > year in end_date, 
           or (year in i == year in end_date and month in i > month in end_date)

juste du code pseduo pour l'instant, je n'ai pas testé, mais je pense que l'idée dans le même sens fonctionnera.

nonopolarité
la source
1
Ok, je vois des problèmes avec des mois comme février si l'incrémentation se fait par mois plutôt que par semaines.
Joshkunz
Je ne suis pas incrémenté de "l'équivalent d'un mois en secondes". J'incrémente simplement le nombre 1à 2, puis de 2à 3plus tard.
nonopolarité
194

Commencez par définir quelques cas de test, puis vous verrez que la fonction est très simple et ne nécessite aucune boucle

from datetime import datetime

def diff_month(d1, d2):
    return (d1.year - d2.year) * 12 + d1.month - d2.month

assert diff_month(datetime(2010,10,1), datetime(2010,9,1)) == 1
assert diff_month(datetime(2010,10,1), datetime(2009,10,1)) == 12
assert diff_month(datetime(2010,10,1), datetime(2009,11,1)) == 11
assert diff_month(datetime(2010,10,1), datetime(2009,8,1)) == 14

Vous devriez ajouter quelques cas de test à votre question, car il y a beaucoup de cas de coin potentiels à couvrir - il existe plusieurs façons de définir le nombre de mois entre deux dates.

John La Rooy
la source
2
cela donne un mauvais résultat. il en résulte 1 mois entre "2015-04-30" et "2015-05-01" qui est en fait juste 1 jour.
Rao
21
@Rao. c'est pourquoi j'ai dit "il y a plus d'une façon de définir le nombre de mois entre deux dates". La question manque encore de définition formelle. C'est aussi pourquoi je suggère que les cas de test soient fournis avec la définition.
John La Rooy
2
Je recommande d'ajouter abs () autour des soustractions pour permettre à d1 d'être plus petit que d2: return abs (d1.year - d2.year) * 12 + abs (d1.month - d2.month)
Lukas Schulze
Êtes-vous sûr, @LukasSchulze? Si d1 est plus petit que d2, vous devrez de toute façon soustraire ce nombre du premier, non?
Lilith-Elina
Mais dans mon cas, peu importe si d1 <= d2 ou d2 <= d1. Vous n'êtes intéressé que par le diff, quelle que soit la date la plus petite / la plus grande.
Lukas Schulze
40

Une ligne pour trouver une liste de datetimes, incrémentée par mois, entre deux dates.

import datetime
from dateutil.rrule import rrule, MONTHLY

strt_dt = datetime.date(2001,1,1)
end_dt = datetime.date(2005,6,1)

dates = [dt for dt in rrule(MONTHLY, dtstart=strt_dt, until=end_dt)]
N1B4
la source
Ce! Cela s'adapte à de nombreuses situations spéciales fantaisistes grâce à l'utilisation de rrule. Notez que les valeurs de sortie sont des datetimes afin que vous puissiez les convertir en ce que vous voulez (y compris les chaînes) pour correspondre à ce que les autres affichent.
Ezekiel Kruglick
Cela échoue lorsque strt_dt est 2001-1-29, en raison de l'absence de jour bissextile cette année-là.
CS du
2
OP a demandé une liste des mois entre les dates de début et de fin. Février est manqué en utilisant votre méthode sur mon exemple. Bien entendu, votre solution peut toujours être récupérée en ajustant la date de début au premier du mois, par exemple.
CS
2
C'est une bonne solution et cela me fait gagner beaucoup de temps. Nous pouvons l'améliorer en donnant la date de début[dt for dt in rrule(MONTHLY, bymonthday=10,dtstart=strt_dt, until=end_dt)]
Pengju Zhao
1
Soyez conscient de cela. Ils ont en fait une note dans la documentation disant que rrule pourrait avoir "un comportement surprenant lorsque, par exemple, la date de début survient à la fin du mois" (voir Note dans dateutil.readthedocs.io/en/stable/rrule.html ). Une façon d'éviter cela est de remplacer les dates par le premier jour du mois: start_date_first = start_date.replace(day=1), end_date_first = end_date.replace(day=1)alors la règle compte les mois correctement.
Alla Sorokina
36

Cela a fonctionné pour moi -

from datetime import datetime
from dateutil import relativedelta
date1 = datetime.strptime('2011-08-15 12:00:00', '%Y-%m-%d %H:%M:%S')
date2 = datetime.strptime('2012-02-15', '%Y-%m-%d')
r = relativedelta.relativedelta(date2, date1)
r.months + (12*r.years)
aamir23
la source
supprimer les str()littéraux de chaîne inutiles .
jfs
7
Cela ne vaut rien que si le delta est supérieur à 1 an, r.monthsil commencera à 0
jbkkd
2
Habituellement, je fais des r.months * (r.years + 1), car cela s'ajuste à ce dont @jbkkd parlait
triunenature
9
@triunenature cela semble faux, ça devrait sûrement être quelque chose comme r.months + (r.years * 12)
Moataz Elmasry
2
Ouais, cela ne fonctionne pas si les dates sont espacées de plus d'un an.
HansG600
11

Vous pouvez facilement calculer cela en utilisant rrule du module dateutil :

from dateutil import rrule
from datetime import date

print(list(rrule.rrule(rrule.MONTHLY, dtstart=date(2013, 11, 1), until=date(2014, 2, 1))))

te donnera:

 [datetime.datetime(2013, 11, 1, 0, 0),
 datetime.datetime(2013, 12, 1, 0, 0),
 datetime.datetime(2014, 1, 1, 0, 0),
 datetime.datetime(2014, 2, 1, 0, 0)]
Nazarii Gudzovatyi
la source
10

Obtenez le mois de fin (par rapport à l'année et au mois du mois de début, ex: janvier 2011 = 13 si votre date de début commence le octobre 2010), puis générez les dates et heures commençant le mois de début et ce mois de fin comme ceci:

dt1, dt2 = dateRange
start_month=dt1.month
end_months=(dt2.year-dt1.year)*12 + dt2.month+1
dates=[datetime.datetime(year=yr, month=mn, day=1) for (yr, mn) in (
          ((m - 1) / 12 + dt1.year, (m - 1) % 12 + 1) for m in range(start_month, end_months)
      )]

si les deux dates sont de la même année, cela pourrait aussi être simplement écrit comme suit:

dates=[datetime.datetime(year=dt1.year, month=mn, day=1) for mn in range(dt1.month, dt2.month + 1)]
Vin-G
la source
8

Cet article le cloue! Utilisez dateutil.relativedelta.

from datetime import datetime
from dateutil import relativedelta
date1 = datetime.strptime(str('2011-08-15 12:00:00'), '%Y-%m-%d %H:%M:%S')
date2 = datetime.strptime(str('2012-02-15'), '%Y-%m-%d')
r = relativedelta.relativedelta(date2, date1)
r.months
srodriguex
la source
1
@edouard Comme vous pouvez le voir dans l'exemple que j'ai donné, les dates sont dans des années différentes. Néanmoins, le str()casting que j'ai fait est complètement inutile.
srodriguex
5
cela ne fonctionne pas si les dates couvrent plus d'un an, vous devez ajouterrelativedelta.relativedelta(date2, date1).years * 12
muon
delta.years*12 + delta.months
user2682863
7

Ma solution simple:

import datetime

def months(d1, d2):
    return d1.month - d2.month + 12*(d1.year - d2.year)

d1 = datetime.datetime(2009, 9, 26)  
d2 = datetime.datetime(2019, 9, 26) 

print(months(d1, d2))
Hottabov
la source
solution sous-estimée
réarranger
4

Définir un « mois » , comme 1 / 12 année, puis procédez comme suit:

def month_diff(d1, d2): 
    """Return the number of months between d1 and d2, 
    such that d2 + month_diff(d1, d2) == d1
    """
    diff = (12 * d1.year + d1.month) - (12 * d2.year + d2.month)
    return diff

Vous pouvez essayer de définir un mois comme «une période de 29, 28, 30 ou 31 jours (selon l'année)». Mais vous faites cela, vous avez un problème supplémentaire à résoudre.

Bien qu'il soit généralement clair que 15 pour Juin e + 1 mois devrait être Juillet 15 e , il n'est généralement pas clair si Janvier 30 e + 1 mois est en Février ou Mars. Dans ce dernier cas, vous pouvez être obligé de calculer la date 30 Février e , puis « corriger » ce 2 Mars e . Mais quand vous faites cela, vous trouverez que le 2 Mars e - 1 mois est clairement 2 Février e . Ergo, reductio ad absurdum (cette opération n'est pas bien définie).

Seth
la source
4

Il existe une solution simple basée sur des années de 360 ​​jours, où tous les mois ont 30 jours. Il convient à la plupart des cas d'utilisation où, étant donné deux dates, vous devez calculer le nombre de mois complets plus les jours restants.

from datetime import datetime, timedelta

def months_between(start_date, end_date):
    #Add 1 day to end date to solve different last days of month 
    s1, e1 = start_date , end_date  + timedelta(days=1)
    #Convert to 360 days
    s360 = (s1.year * 12 + s1.month) * 30 + s1.day
    e360 = (e1.year * 12 + e1.month) * 30 + e1.day
    #Count days between the two 360 dates and return tuple (months, days)
    return divmod(e360 - s360, 30)

print "Counting full and half months"
print months_between( datetime(2012, 01, 1), datetime(2012, 03, 31)) #3m
print months_between( datetime(2012, 01, 1), datetime(2012, 03, 15)) #2m 15d
print months_between( datetime(2012, 01, 16), datetime(2012, 03, 31)) #2m 15d
print months_between( datetime(2012, 01, 16), datetime(2012, 03, 15)) #2m
print "Adding +1d and -1d to 31 day month"
print months_between( datetime(2011, 12, 01), datetime(2011, 12, 31)) #1m 0d
print months_between( datetime(2011, 12, 02), datetime(2011, 12, 31)) #-1d => 29d
print months_between( datetime(2011, 12, 01), datetime(2011, 12, 30)) #30d => 1m
print "Adding +1d and -1d to 29 day month"
print months_between( datetime(2012, 02, 01), datetime(2012, 02, 29)) #1m 0d
print months_between( datetime(2012, 02, 02), datetime(2012, 02, 29)) #-1d => 29d
print months_between( datetime(2012, 02, 01), datetime(2012, 02, 28)) #28d
print "Every month has 30 days - 26/M to 5/M+1 always counts 10 days"
print months_between( datetime(2011, 02, 26), datetime(2011, 03, 05))
print months_between( datetime(2012, 02, 26), datetime(2012, 03, 05))
print months_between( datetime(2012, 03, 26), datetime(2012, 04, 05))
Daniel Reis
la source
4

Solution un peu jolie de @ Vin-G.

import datetime

def monthrange(start, finish):
  months = (finish.year - start.year) * 12 + finish.month + 1 
  for i in xrange(start.month, months):
    year  = (i - 1) / 12 + start.year 
    month = (i - 1) % 12 + 1
    yield datetime.date(year, month, 1)
saaj
la source
4

Vous pouvez également utiliser la bibliothèque de flèches . Voici un exemple simple:

from datetime import datetime
import arrow

start = datetime(2014, 1, 17)
end = datetime(2014, 6, 20)

for d in arrow.Arrow.range('month', start, end):
    print d.month, d.format('MMMM')

Cela imprimera:

1 January
2 February
3 March
4 April
5 May
6 June

J'espère que cela t'aides!

Luis Alberto Santana
la source
4
from dateutil import relativedelta

relativedelta.relativedelta(date1, date2)

months_difference = (r.years * 12) + r.months
yatabani
la source
3

Essayez quelque chose comme ça. Il comprend actuellement le mois si les deux dates se trouvent dans le même mois.

from datetime import datetime,timedelta

def months_between(start,end):
    months = []
    cursor = start

    while cursor <= end:
        if cursor.month not in months:
            months.append(cursor.month)
        cursor += timedelta(weeks=1)

    return months

La sortie ressemble à:

>>> start = datetime.now() - timedelta(days=120)
>>> end = datetime.now()
>>> months_between(start,end)
[6, 7, 8, 9, 10]
Charles Hooper
la source
Cela adopte toujours la même approche en boucle, donc je ne vois pas nécessairement l'avantage ...
Joshkunz
1
Je ne vois pas en quoi c'est un problème. Les boucles ne sont pas votre ennemi.
Charles Hooper
Eh bien, cela doit être fait à chaque fois qu'il s'agit d'une requête ajax. Je sais que les boucles ne sont pas l'ennemi, mais il semble que ce soit une solution lente à un problème qui devrait être résolu d'une manière beaucoup plus simple.
Joshkunz
3

Voici comment procéder avec Pandas FWIW:

import pandas as pd
pd.date_range("1990/04/03", "2014/12/31", freq="MS")

DatetimeIndex(['1990-05-01', '1990-06-01', '1990-07-01', '1990-08-01',
               '1990-09-01', '1990-10-01', '1990-11-01', '1990-12-01',
               '1991-01-01', '1991-02-01',
               ...
               '2014-03-01', '2014-04-01', '2014-05-01', '2014-06-01',
               '2014-07-01', '2014-08-01', '2014-09-01', '2014-10-01',
               '2014-11-01', '2014-12-01'],
              dtype='datetime64[ns]', length=296, freq='MS')

Notez qu'il commence avec le mois suivant la date de début donnée.

conner.xyz
la source
2

Cela peut être fait en utilisant datetime.timedelta, où le nombre de jours pour passer au mois suivant peut être obtenu par calender.monthrange. monthrange renvoie le jour de la semaine (0-6 ~ lun-dim) et le nombre de jours (28-31) pour une année et un mois donnés.
Par exemple: monthrange (2017, 1) renvoie (6,31).

Voici le script utilisant cette logique pour itérer entre deux mois.

from datetime import timedelta
import datetime as dt
from calendar import monthrange

def month_iterator(start_month, end_month):
    start_month = dt.datetime.strptime(start_month,
                                   '%Y-%m-%d').date().replace(day=1)
    end_month = dt.datetime.strptime(end_month,
                                 '%Y-%m-%d').date().replace(day=1)
    while start_month <= end_month:
        yield start_month
        start_month = start_month + timedelta(days=monthrange(start_month.year, 
                                                         start_month.month)[1])

»

Pankaj Kumar
la source
Pouvez-vous expliquer comment cela résout le problème? Nous encourageons les gens à ajouter du contexte à leurs réponses; Merci.
Gi0rgi0s
1
Ajouté une explication
pankaj kumar
2

Beaucoup de gens vous ont déjà donné de bonnes réponses pour résoudre ce problème, mais je n'ai lu aucune compréhension de liste en utilisant donc je vous donne ce que j'ai utilisé pour un cas d'utilisation similaire:


def compute_months(first_date, second_date):
    year1, month1, year2, month2 = map(
        int, 
        (first_date[:4], first_date[5:7], second_date[:4], second_date[5:7])
    )

    return [
        '{:0>4}-{:0>2}'.format(year, month)
        for year in range(year1, year2 + 1)
        for month in range(month1 if year == year1 else 1, month2 + 1 if year == year2 else 13)
    ]

>>> first_date = "2016-05"
>>> second_date = "2017-11"
>>> compute_months(first_date, second_date)
['2016-05',
 '2016-06',
 '2016-07',
 '2016-08',
 '2016-09',
 '2016-10',
 '2016-11',
 '2016-12',
 '2017-01',
 '2017-02',
 '2017-03',
 '2017-04',
 '2017-05',
 '2017-06',
 '2017-07',
 '2017-08',
 '2017-09',
 '2017-10',
 '2017-11']
H4kim
la source
1
#This definition gives an array of months between two dates.
import datetime
def MonthsBetweenDates(BeginDate, EndDate):
    firstyearmonths = [mn for mn in range(BeginDate.month, 13)]<p>
    lastyearmonths = [mn for mn in range(1, EndDate.month+1)]<p>
    months = [mn for mn in range(1, 13)]<p>
    numberofyearsbetween = EndDate.year - BeginDate.year - 1<p>
    return firstyearmonths + months * numberofyearsbetween + lastyearmonths<p>

#example
BD = datetime.datetime.strptime("2000-35", '%Y-%j')
ED = datetime.datetime.strptime("2004-200", '%Y-%j')
MonthsBetweenDates(BD, ED)
Mehmet Ercan
la source
1

tout comme la rangefonction, quand le mois est 13 , passer à l'année prochaine

def year_month_range(start_date, end_date):
    '''
    start_date: datetime.date(2015, 9, 1) or datetime.datetime
    end_date: datetime.date(2016, 3, 1) or datetime.datetime
    return: datetime.date list of 201509, 201510, 201511, 201512, 201601, 201602
    '''
    start, end = start_date.strftime('%Y%m'), end_date.strftime('%Y%m')
    assert len(start) == 6 and len(end) == 6
    start, end = int(start), int(end)

    year_month_list = []
    while start < end:
        year, month = divmod(start, 100)
        if month == 13:
            start += 88  # 201513 + 88 = 201601
            continue
        year_month_list.append(datetime.date(year, month, 1))

        start += 1
    return year_month_list

exemple en shell python

>>> import datetime
>>> s = datetime.date(2015,9,1)
>>> e = datetime.date(2016, 3, 1)
>>> year_month_set_range(s, e)
[datetime.date(2015, 11, 1), datetime.date(2015, 9, 1), datetime.date(2016, 1, 1), datetime.date(2016, 2, 1),
 datetime.date(2015, 12, 1), datetime.date(2015, 10, 1)]
WeizhongTu
la source
1

Habituellement, 90 jours ne sont PAS 3 mois littéralement, juste une référence.

Donc, enfin, vous devez vérifier si les jours sont supérieurs à 15 pour ajouter +1 au compteur du mois. ou mieux, ajoutez un autre elif avec un compteur demi-mois.

À partir de cette autre réponse stackoverflow, j'ai finalement terminé avec cela:

#/usr/bin/env python
# -*- coding: utf8 -*-

import datetime
from datetime import timedelta
from dateutil.relativedelta import relativedelta
import calendar

start_date = datetime.date.today()
end_date = start_date + timedelta(days=111)
start_month = calendar.month_abbr[int(start_date.strftime("%m"))]

print str(start_date) + " to " + str(end_date)

months = relativedelta(end_date, start_date).months
days = relativedelta(end_date, start_date).days

print months, "months", days, "days"

if days > 16:
    months += 1

print "around " + str(months) + " months", "(",

for i in range(0, months):
    print calendar.month_abbr[int(start_date.strftime("%m"))],
    start_date = start_date + relativedelta(months=1)

print ")"

Production:

2016-02-29 2016-06-14
3 months 16 days
around 4 months ( Feb Mar Apr May )

J'ai remarqué que cela ne fonctionne pas si vous ajoutez plus de jours restants dans l'année en cours, et c'est inattendu.

m3nda
la source
1

il semble que les réponses ne soient pas satisfaisantes et j'ai depuis utilisé mon propre code qui est plus facile à comprendre

from datetime import datetime
from dateutil import relativedelta

date1 = datetime.strptime(str('2017-01-01'), '%Y-%m-%d')
date2 = datetime.strptime(str('2019-03-19'), '%Y-%m-%d')

difference = relativedelta.relativedelta(date2, date1)
months = difference.months
years = difference.years
# add in the number of months (12) for difference in years
months += 12 * difference.years
months
grandia
la source
1
from datetime import datetime
from dateutil import relativedelta

def get_months(d1, d2):
    date1 = datetime.strptime(str(d1), '%Y-%m-%d')
    date2 = datetime.strptime(str(d2), '%Y-%m-%d')
    print (date2, date1)
    r = relativedelta.relativedelta(date2, date1)
    months = r.months +  12 * r.years
    if r.days > 0:
        months += 1
    print (months)
    return  months


assert  get_months('2018-08-13','2019-06-19') == 11
assert  get_months('2018-01-01','2019-06-19') == 18
assert  get_months('2018-07-20','2019-06-19') == 11
assert  get_months('2018-07-18','2019-06-19') == 12
assert  get_months('2019-03-01','2019-06-19') == 4
assert  get_months('2019-03-20','2019-06-19') == 3
assert  get_months('2019-01-01','2019-06-19') == 6
assert  get_months('2018-09-09','2019-06-19') == 10
Yogesh Kushwaha
la source
0

En supposant que upperDate est toujours postérieur à lowerDate et que les deux sont des objets datetime.date:

if lowerDate.year == upperDate.year:
    monthsInBetween = range( lowerDate.month + 1, upperDate.month )
elif upperDate.year > lowerDate.year:
    monthsInBetween = range( lowerDate.month + 1, 12 )
    for year in range( lowerDate.year + 1, upperDate.year ):
        monthsInBetween.extend( range(1,13) )
    monthsInBetween.extend( range( 1, upperDate.month ) )

Je n'ai pas testé cela à fond, mais il semble que cela devrait faire l'affaire.

Scott
la source
0

Voici une méthode:

def months_between(start_dt, stop_dt):
    month_list = []
    total_months = 12*(stop_dt.year-start_dt.year)+(stop_dt.month-start_d.month)+1
    if total_months > 0:
        month_list=[ datetime.date(start_dt.year+int((start_dt+i-1)/12), 
                                   ((start_dt-1+i)%12)+1,
                                   1) for i in xrange(0,total_months) ]
    return month_list

Il s'agit d'abord de calculer le nombre total de mois entre les deux dates, inclusivement. Ensuite, il crée une liste en utilisant la première date comme base et exécute l'arithmétique des modules pour créer les objets de date.

Dan
la source
0

J'avais en fait besoin de faire quelque chose d'assez similaire maintenant

J'ai fini par écrire une fonction qui renvoie une liste de tuples indiquant le startetend de chaque mois entre deux ensembles de dates afin que je puisse écrire des requêtes SQL à l'arrière de celle-ci pour les totaux mensuels des ventes, etc.

Je suis sûr que cela peut être amélioré par quelqu'un qui sait ce qu'il fait mais j'espère que cela aidera ...

La valeur renvoyée se présente comme suit (génération pour aujourd'hui - 365 jours jusqu'à aujourd'hui à titre d'exemple)

[   (datetime.date(2013, 5, 1), datetime.date(2013, 5, 31)),
    (datetime.date(2013, 6, 1), datetime.date(2013, 6, 30)),
    (datetime.date(2013, 7, 1), datetime.date(2013, 7, 31)),
    (datetime.date(2013, 8, 1), datetime.date(2013, 8, 31)),
    (datetime.date(2013, 9, 1), datetime.date(2013, 9, 30)),
    (datetime.date(2013, 10, 1), datetime.date(2013, 10, 31)),
    (datetime.date(2013, 11, 1), datetime.date(2013, 11, 30)),
    (datetime.date(2013, 12, 1), datetime.date(2013, 12, 31)),
    (datetime.date(2014, 1, 1), datetime.date(2014, 1, 31)),
    (datetime.date(2014, 2, 1), datetime.date(2014, 2, 28)),
    (datetime.date(2014, 3, 1), datetime.date(2014, 3, 31)),
    (datetime.date(2014, 4, 1), datetime.date(2014, 4, 30)),
    (datetime.date(2014, 5, 1), datetime.date(2014, 5, 31))]

Code comme suit (contient des éléments de débogage qui peuvent être supprimés):

#! /usr/env/python
import datetime

def gen_month_ranges(start_date=None, end_date=None, debug=False):
    today = datetime.date.today()
    if not start_date: start_date = datetime.datetime.strptime(
        "{0}/01/01".format(today.year),"%Y/%m/%d").date()  # start of this year
    if not end_date: end_date = today
    if debug: print("Start: {0} | End {1}".format(start_date, end_date))

    # sense-check
    if end_date < start_date:
        print("Error. Start Date of {0} is greater than End Date of {1}?!".format(start_date, end_date))
        return None

    date_ranges = []  # list of tuples (month_start, month_end)

    current_year = start_date.year
    current_month = start_date.month

    while current_year <= end_date.year:
        next_month = current_month + 1
        next_year = current_year
        if next_month > 12:
            next_month = 1
            next_year = current_year + 1

        month_start = datetime.datetime.strptime(
            "{0}/{1}/01".format(current_year,
                                current_month),"%Y/%m/%d").date()  # start of month
        month_end = datetime.datetime.strptime(
            "{0}/{1}/01".format(next_year,
                                next_month),"%Y/%m/%d").date()  # start of next month
        month_end  = month_end+datetime.timedelta(days=-1)  # start of next month less one day

        range_tuple = (month_start, month_end)
        if debug: print("Month runs from {0} --> {1}".format(
            range_tuple[0], range_tuple[1]))
        date_ranges.append(range_tuple)

        if current_month == 12:
            current_month = 1
            current_year += 1
            if debug: print("End of year encountered, resetting months")
        else:
            current_month += 1
            if debug: print("Next iteration for {0}-{1}".format(
                current_year, current_month))

        if current_year == end_date.year and current_month > end_date.month:
            if debug: print("Final month encountered. Terminating loop")
            break

    return date_ranges


if __name__ == '__main__':
    print("Running in standalone mode. Debug set to True")
    from pprint import pprint
    pprint(gen_month_ranges(debug=True), indent=4)
    pprint(gen_month_ranges(start_date=datetime.date.today()+datetime.timedelta(days=-365),
                            debug=True), indent=4)
James
la source
0

En supposant que vous vouliez connaître la «fraction» du mois où se trouvaient les dates, ce que j'ai fait, alors vous devez faire un peu plus de travail.

from datetime import datetime, date
import calendar

def monthdiff(start_period, end_period, decimal_places = 2):
    if start_period > end_period:
        raise Exception('Start is after end')
    if start_period.year == end_period.year and start_period.month == end_period.month:
        days_in_month = calendar.monthrange(start_period.year, start_period.month)[1]
        days_to_charge = end_period.day - start_period.day+1
        diff = round(float(days_to_charge)/float(days_in_month), decimal_places)
        return diff
    months = 0
    # we have a start date within one month and not at the start, and an end date that is not
    # in the same month as the start date
    if start_period.day > 1:
        last_day_in_start_month = calendar.monthrange(start_period.year, start_period.month)[1]
        days_to_charge = last_day_in_start_month - start_period.day +1
        months = months + round(float(days_to_charge)/float(last_day_in_start_month), decimal_places)
        start_period = datetime(start_period.year, start_period.month+1, 1)

    last_day_in_last_month = calendar.monthrange(end_period.year, end_period.month)[1]
    if end_period.day != last_day_in_last_month:
        # we have lest days in the last month
        months = months + round(float(end_period.day) / float(last_day_in_last_month), decimal_places)
        last_day_in_previous_month = calendar.monthrange(end_period.year, end_period.month - 1)[1]
        end_period = datetime(end_period.year, end_period.month - 1, last_day_in_previous_month)

    #whatever happens, we now have a period of whole months to calculate the difference between

    if start_period != end_period:
        months = months + (end_period.year - start_period.year) * 12 + (end_period.month - start_period.month) + 1

    # just counter for any final decimal place manipulation
    diff = round(months, decimal_places)
    return diff

assert monthdiff(datetime(2015,1,1), datetime(2015,1,31)) == 1
assert monthdiff(datetime(2015,1,1), datetime(2015,02,01)) == 1.04
assert monthdiff(datetime(2014,1,1), datetime(2014,12,31)) == 12
assert monthdiff(datetime(2014,7,1), datetime(2015,06,30)) == 12
assert monthdiff(datetime(2015,1,10), datetime(2015,01,20)) == 0.35
assert monthdiff(datetime(2015,1,10), datetime(2015,02,20)) == 0.71 + 0.71
assert monthdiff(datetime(2015,1,31), datetime(2015,02,01)) == round(1.0/31.0,2) + round(1.0/28.0,2)
assert monthdiff(datetime(2013,1,31), datetime(2015,02,01)) == 12*2 + round(1.0/31.0,2) + round(1.0/28.0,2)

fournit un exemple qui calcule le nombre de mois entre deux dates inclusivement, y compris la fraction de chaque mois dans laquelle se trouve la date. Cela signifie que vous pouvez calculer combien de mois se situe entre le 20/01/2015 et le 14/02/2015 , où la fraction de la date du mois de janvier est déterminée par le nombre de jours de janvier; ou également en tenant compte du fait que le nombre de jours en février peut changer d'année en année.

Pour ma référence, ce code est également sur github - https://gist.github.com/andrewyager/6b9284a4f1cdb1779b10

Andrew Yager
la source
0

Essaye ça:

 dateRange = [datetime.strptime(dateRanges[0], "%Y-%m-%d"),
             datetime.strptime(dateRanges[1], "%Y-%m-%d")]
delta_time = max(dateRange) - min(dateRange)
#Need to use min(dateRange).month to account for different length month
#Note that timedelta returns a number of days
delta_datetime = (datetime(1, min(dateRange).month, 1) + delta_time -
                           timedelta(days=1)) #min y/m/d are 1
months = ((delta_datetime.year - 1) * 12 + delta_datetime.month -
          min(dateRange).month)
print months

L'ordre dans lequel vous saisissez les dates ne devrait pas avoir d'importance, et cela prend en compte la différence de longueur des mois.

om_henners
la source
Notez que cela ne tient pas compte du fait que vos dates sont identiques. Le moyen le plus simple serait avec si delta_time.days = 0: mois = 0 sinon reste de la routine.
om_henners
0

Cela marche...

from datetime import datetime as dt
from dateutil.relativedelta import relativedelta
def number_of_months(d1, d2):
    months = 0
    r = relativedelta(d1,d2)
    if r.years==0:
        months = r.months
    if r.years>=1:
        months = 12*r.years+r.months
    return months
#example 
number_of_months(dt(2017,9,1),dt(2016,8,1))
George Pamfilis
la source
0
from datetime import datetime

def diff_month(start_date,end_date):
    qty_month = ((end_date.year - start_date.year) * 12) + (end_date.month - start_date.month)

    d_days = end_date.day - start_date.day

    if d_days >= 0:
        adjust = 0
    else:
        adjust = -1
    qty_month += adjust

    return qty_month

diff_month(datetime.date.today(),datetime(2019,08,24))


#Examples:
#diff_month(datetime(2018,02,12),datetime(2019,08,24)) = 18
#diff_month(datetime(2018,02,12),datetime(2018,08,10)) = 5
Max Sandoval
la source