web-dev-qa-db-fra.com

Générer une liste de mois entre intervalles en python

Je souhaite générer une liste python contenant tous les mois survenant entre deux dates, avec les entrées et les sorties formatées comme suit:

date1 = "2014-10-10"  # input start date
date2 = "2016-01-07"  # input end date
month_list = ['Oct-14', 'Nov-14', 'Dec-14', 'Jan-15', 'Feb-15', 'Mar-15', 'Apr-15', 'May-15', 'Jun-15', 'Jul-15', 'Aug-15', 'Sep-15', 'Oct-15', 'Nov-15', 'Dec-15', 'Jan-16']  # output
20
Birlla
>>> from datetime import datetime, timedelta
>>> from collections import OrderedDict
>>> dates = ["2014-10-10", "2016-01-07"]
>>> start, end = [datetime.strptime(_, "%Y-%m-%d") for _ in dates]
>>> OrderedDict(((start + timedelta(_)).strftime(r"%b-%y"), None) for _ in xrange((end - start).days)).keys()
['Oct-14', 'Nov-14', 'Dec-14', 'Jan-15', 'Feb-15', 'Mar-15', 'Apr-15', 'May-15', 'Jun-15', 'Jul-15', 'Aug-15', 'Sep-15', 'Oct-15', 'Nov-15', 'Dec-15', 'Jan-16']

Mise à jour: un peu d'explication, comme demandé dans un commentaire. Il y a trois problèmes ici: analyser les dates dans des structures de données appropriées (strptime); obtenir la plage de dates compte tenu des deux extrêmes et du pas (un mois); formater les dates de sortie (strftime). Le type datetime surcharge l'opérateur de soustraction, de sorte que end - start a du sens. Le résultat est un objet timedelta qui représente la différence entre les deux dates et l'attribut .days obtient cette différence exprimée en jours. Il n'y a pas d'attribut .months, nous itérons donc un jour à la fois et convertissons les dates au format de sortie souhaité. Cela génère beaucoup de doublons, que la OrderedDict supprime tout en maintenant les éléments dans le bon ordre.

Maintenant, c’est simple et concis, car cela permet au module datetime de faire tout le travail, mais c’est aussi terriblement inefficace. Nous appelons beaucoup de méthodes pour chaque jour alors que nous n'avons besoin que de produire des mois. Si les performances ne sont pas un problème, le code ci-dessus ira très bien. Sinon, nous devrons travailler un peu plus. Comparons la mise en œuvre ci-dessus avec une autre plus efficace:

from datetime import datetime, timedelta
from collections import OrderedDict

dates = ["2014-10-10", "2016-01-07"]

def monthlist_short(dates):
    start, end = [datetime.strptime(_, "%Y-%m-%d") for _ in dates]
    return OrderedDict(((start + timedelta(_)).strftime(r"%b-%y"), None) for _ in xrange((end - start).days)).keys()

def monthlist_fast(dates):
    start, end = [datetime.strptime(_, "%Y-%m-%d") for _ in dates]
    total_months = lambda dt: dt.month + 12 * dt.year
    mlist = []
    for tot_m in xrange(total_months(start)-1, total_months(end)):
        y, m = divmod(tot_m, 12)
        mlist.append(datetime(y, m+1, 1).strftime("%b-%y"))
    return mlist

assert monthlist_fast(dates) == monthlist_short(dates)

if __== "__main__":
    from timeit import Timer
    for func in "monthlist_short", "monthlist_fast":
        print func, Timer("%s(dates)" % func, "from __main__ import dates, %s" % func).timeit(1000)

Sur mon ordinateur portable, je reçois la sortie suivante:

monthlist_short 2.3209939003
monthlist_fast 0.0774540901184

L'implémentation concise est environ 30 fois plus lente, donc je ne le recommanderais pas dans les applications à délai critique :)

27
simleo

J'ai trouvé une façon très succincte de faire cela avec les pandas, à partager au cas où cela aiderait quelqu'un


UPDATE: Je l'ai réduit à une ligne avec l'aide de ce post :) 

pd.date_range('2014-10-10','2016-01-07', 
              freq='MS').strftime("%Y-%b").tolist()

ANCIENNE RÉPONSE:

daterange = pd.date_range('2014-10-10','2016-01-07' , freq='1M') 
daterange = daterange.union([daterange[-1] + 1])  
daterange = [d.strftime('%y-%b') for d in daterange]

La deuxième ligne empêche la dernière date d'être coupée de la liste.

28
atkat12

Vous devez utiliser Calendrier et Date/heure

import calendar
from datetime import *
date1 = datetime.strptime("2014-10-10", "%Y-%m-%d")
date2 = datetime.strptime("2016-01-07", "%Y-%m-%d")
months_str = calendar.month_name
months = []
while date1 < date2:
    month = date1.month
    year  = date1.year
    month_str = months_str[month][0:3]
    months.append("{0}-{1}".format(month_str,str(year)[-2:]))
    next_month = month+1 if month != 12 else 1
    next_year = year + 1 if next_month == 1 else year
    date1 = date1.replace( month = next_month, year= next_year)

print months

Ce code retourne 

['Oct-14', 'Nov-14', 'Dec-14', 'Jan-14', 'Feb-15', 'Mar-15', 'Apr-15', 'May-15', 'Jun-15', 'Jul-15', 'Aug-15', 'Sep-15', 'Oct-15', 'Nov-15', 'Dec-15', 'Jan-15']
5
Luis González

Avec les pandas, vous pouvez avoir un one liner comme ceci:

import pandas as pd

date1 = "2014-10-10"  # input start date
date2 = "2016-01-07"  # input end date

month_list = [i.strftime("%b-%y") for i in pd.date_range(start=date1, end=date2, freq='MS')]
4
tmsss

Trouvez ci-dessous mon approche de ce problème en utilisant split et simple modulo - based iterations sans importer de module spécial.

date1 = "2014-10-10"
date2 = "2016-01-07"

y0 = int( date1.split('-')[0] ) # 2014
y1 = int( date2.split('-')[0] ) # 2016

m0 = int( date1.split('-')[1] ) - 1 # 10-1 --> 9 because will be used for indexing
m1 = int( date2.split('-')[1] ) - 1 # 01-1 --> 0 because will be used for indexing

months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
result = []
start = m0
for y in range(y0, y1+1):
    for m in range(start,12):
        result.append( str( months[m  % 12])+'-'+str(y) )
        if y == y1 and (m % 12) == m1:
            break
    start = 0

print result

$ python dates.py  

['Oct-2014', 'Nov-2014', 'Dec-2014', 'Jan-2015', 'Feb-2015', 'Mar-2015', 'Apr-2015', 'May-2015', 'Jun-2015', 'Jul-2015', 'Aug-2015', 'Sep-2015', 'Oct-2015', 'Nov-2015', 'Dec-2015', 'Jan-2016']
1
funk

Ayant déjà fait des choses similaires, j'ai tenté de résoudre ce problème. L'utilisation de composants distincts à cette fin est plus flexible et vous permet de les combiner pour différents cas d'utilisation. Ils peuvent également être testés plus facilement de cette façon, comme le montrent les doctests dans iterate_months.

Aussi, je suggère d'utiliser des objets datetime.date pour votre entrée car vous pouvez simplement en faire plus avec ceux-ci. Pour ce faire, vous devez d'abord analyser votre chaîne d'entrée, mais cela se fait très facilement.

Analyser les chaînes de date

def datify(date):
    if isinstance(date, datetime.date):
        return date
    Elif isinstance(date, datetime.datetime):
        return date.date()
    else:
        # taken from simleo's answer
        return datetime.strptime(date, "%Y-%m-%d")

Tout d'abord, nous parcourons les mois

import datetime


def iterate_months(start_date, end_date):
    """Iterate monthly between two given dates.

    Emitted will be the first day of each month.

    >>> list(iterate_months(datetime.date(1999, 11, 1),
    ...                     datetime.date(2000, 2, 1)))
    [datetime.date(1999, 11, 1), datetime.date(1999, 12, 1),\
 datetime.date(2000, 1, 1), datetime.date(2000, 2, 1)]

    """
    assert isinstance(start_date, datetime.date)
    assert isinstance(end_date, datetime.date)
    assert start_date < end_date

    year = start_date.year
    month = start_date.month
    while True:
        current = datetime.date(year, month, 1)
        yield current
        if current.month == end_date.month and current.year == end_date.year:
            break
        else:
            month = ((month + 1) % 12) or 12
            if month == 1:
                year += 1


if __== '__main__':
    import doctest
    doctest.testmod()

Pour formater vos dates, utilisez quelque chose comme ceci

def format_month(date):
    return date.strftime(r"%b-%y")

Mettre tous ensemble

start = datify("2014-10-10")
end = datify("2016-01-07")

for entry in iterate_months(start, end):
    print format_month(entry)

Ou enregistrez-le sous forme de liste:

result = list(iterate_months(start, end))
1
pi.

Voici ma solution avec une compréhension de liste simple qui utilise range pour savoir où les mois doivent commencer et se terminer

from datetime import datetime as dt
sd = dt.strptime('2014-10-10', "%Y-%m-%d") 
ed = dt.strptime('2016-01-07', "%Y-%m-%d") 

lst = [dt.strptime('%2.2d-%2.2d' % (y, m), '%Y-%m').strftime('%b-%y') \
       for y in xrange(sd.year, ed.year+1) \
       for m in xrange(sd.month if y==sd.year else 1, ed.month+1 if y == ed.year else 13)]

print lst

produit

['Oct-14', 'Nov-14', 'Dec-14', 'Jan-15', 'Feb-15', 'Mar-15', 'Apr-15', 'May-15', 'Jun-15', 'Jul-15', 'Aug-15', 'Sep-15', 'Oct-15', 'Nov-15', 'Dec-15', 'Jan-16']
0
Pynchia