2016-02-26 13 views
10

Chcę uzyskać wszystkie miesiące od teraz do sierpnia 2010, jako lista sformatowane tak:Python: uzyskać wszystkie miesiące w zasięgu?

['2010-08-01', '2010-09-01', .... , '2016-02-01'] 

Teraz to jest to, co mam:

months = [] 
for y in range(2010, 2016): 
    for m in range(1, 13): 
     if (y == 2010) and m < 8: 
      continue 
     if (y == 2016) and m > 2: 
      continue 
     month = '%s-%s-01' % (y, ('0%s' % (m)) if m < 10 else m) 
     months.append(month) 

Co byłoby lepszym sposobem Zrób to?

Odpowiedz

14

dateutil.relativedelta jest przydatny tutaj.

Zostawiłem formatowanie jako ćwiczenie.

from dateutil.relativedelta import relativedelta 
import datetime 

result = [] 

today = datetime.date.today() 
current = datetime.date(2010, 8, 1)  

while current <= today: 
    result.append(current) 
    current += relativedelta(months=1) 
+0

Ani, to jest prawie poprawne, ale musisz sformatować datetime w taki sposób, aby pojawił się w formacie ISO. Można to zrobić, dołączając isoformat. Powinieneś zmienić linię na 'result.append (current.isoformat())' - [EDIT: nie widzisz, że zostawiłeś formatowanie jako ćwiczenie ... ale no cóż.] –

+0

Dzięki, faktycznie zrobiłem ' result.append (datetime.strftime (current, '% Y-% m-01')) ', aby uzyskać żądany format. – Richard

0

Można zmniejszyć liczbę instrukcji if do dwóch linii zamiast do czterech linii, ponieważ posiadanie drugiej instrukcji if, która robi to samo z poprzednią instrukcją if, jest nieco zbędne.

if (y == 2010 and m < 8) or (y == 2016 and m > 2): 
    continue 
4

użycie datetime i timedelta standardowe moduły Pythona - bez instalowania żadnych nowych bibliotek

from datetime import datetime, timedelta 

now = datetime(datetime.now().year, datetime.now().month, 1) 
ctr = datetime(2010, 8, 1) 
list = [ctr.strftime('%Y-%m-%d')] 

while ctr <= now: 
    ctr += timedelta(days=32) 
    list.append(datetime(ctr.year, ctr.month, 1).strftime('%Y-%m-%d')) 

Dodaję 32 dni, aby wprowadzić nowy miesiąc za każdym razem (najdłuższe miesięcy ma 31 dni)

+0

jest to ładne rozwiązanie, ale trzeba dodać 'CTR = ctr.replace (dzień = 1)' 'po CTR = timedelta (dzień = 32)', inaczej będziesz pominąć miesiąc w dłuższym okresie. Spójrz na tę sekwencję: [2018-10-28, 2018-11-29, 2018-12-31, 2019-02-01, 2019-03-05] -> Styczeń został pominięty tutaj. Ustawienie dnia = 1 pozwala uniknąć tego problemu. – jps

0

Nie wiem, czy to lepiej, ale podejście jak na poniższej ilustracji może być uważane za bardziej 'pythonic':

months = [ 
    '{}-{:0>2}-01'.format(year, month) 
     for year in xrange(2010, 2016 + 1) 
     for month in xrange(1, 12 + 1) 
     if not (year <= 2010 and month < 8 or year >= 2016 and month > 2) 
] 

Główne różnice są tu:

  • Ponieważ chcemy, aby iteracja (y) tworzyła listę, należy użyć list comprehension zamiast agregować elementy listy w pętli for.
  • Zamiast jawnie dokonując rozróżnienia między numerami poniżej 10 i liczb 10 i powyżej, należy skorzystać z możliwości tej format specification mini-language dla .format() method of str określić
  • zamiast z range zwraca generator zamiast listy, dzięki czemu wartości iteracji mogą być generowane podczas ich konsumpcji i nie muszą być przechowywane w pamięci. (Nie ma znaczenia w przypadku tak małych zakresów, ale dobrze jest się do tego przyzwyczaić w Pythonie 2.) Uwaga: W Pythonie 3 nie ma funkcji xrange, a funkcja range już zwraca generator zamiast listy.
  • Wyraźnie określ górne ograniczenia dla + 1.Ułatwia to ludzkim czytelnikom kodu rozpoznanie, że chcemy określić włączające powiązanie z metodą (range lub xrange), która traktuje górną granicę jako wyłączną. W przeciwnym razie mogliby się zastanawiać, jaka jest umowa z numerem 13.
+0

Jeszcze więcej pythonic używa oczywiście 'dateutil' i' datetime', aby poradzić sobie z różnicami dat i czasu, jak robi to [Ani's answer] (http://stackoverflow.com/a/35651063/674064). –

3

Zapoznałem się z dokumentacją dateutil. Okazuje się, że to zapewnia jeszcze bardziej wygodny sposób niż za pomocą dateutil.relativedelta: recurrence rules (examples)

dla zadania pod ręką, to jest tak proste, jak

from dateutil.rrule import * 
from datetime import date 

months = map(
    date.isoformat, 
    rrule(MONTHLY, dtstart=date(2010, 8, 1), until=date.today()) 
) 

Drobnym drukiem

Należy pamiętać, że jesteśmy oszukuje trochę, tutaj. Elementy, które produkuje dateutil.rrule.rrule są typu datetime.datetime, nawet jeśli przekazujemy dtstart i until typu datetime.date, tak jak to robimy powyżej. Pozwoliłem map przekazać je do funkcji dateisoformat, która po prostu okazuje się konwertować je na ciągi, jak gdyby były to tylko daty bez informacji o czasie.

Dlatego pozornie równoważne listowego

[day.isoformat() 
    for day in rrule(MONTHLY, dtstart=date(2010, 8, 1), until=date.today())] 

wróci listę jak

['2010-08-01T00:00:00', 
'2010-09-01T00:00:00', 
'2010-10-01T00:00:00', 
'2010-11-01T00:00:00', 
⋮ 
'2015-12-01T00:00:00', 
'2016-01-01T00:00:00', 
'2016-02-01T00:00:00'] 

Zatem, jeśli chcemy użyć wyrażeń listowych zamiast map, musimy coś zrobić jak

[dt.date().isoformat() 
    for dt in rrule(MONTHLY, dtstart=date(2010, 8, 1), until=date.today())] 
+0

Należy zauważyć, że zmienna iteracyjna 'day' w pierwszym zrozumieniu listy jest _deliberate_ misnomer. Jak wyjaśniono, zawiera "datetime", a nie "date". –

0

Mam inny sposób korzystania z datetime, timedelta i kalendarz:

from calendar import monthrange 
from datetime import datetime, timedelta 

def monthdelta(d1, d2): 
    delta = 0 
    while True: 
     mdays = monthrange(d1.year, d1.month)[1] 
     d1 += timedelta(days=mdays) 
     if d1 <= d2: 
      delta += 1 
     else: 
      break 
    return delta 

start_date = datetime(2016, 1, 1) 
end_date = datetime(2016, 12, 1) 

num_months = [i-12 if i>12 else i for i in range(start_date.month, monthdelta(start_date, end_date)+start_date.month+1)] 
monthly_daterange = [datetime(start_date.year,i, start_date.day, start_date.hour) for i in num_months] 
Powiązane problemy