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
>>> 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 :)
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.
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']
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')]
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']
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")
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()
def format_month(date):
return date.strftime(r"%b-%y")
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))
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']