Najlepszy sposób na znalezienie miesięcy między dwiema datami


92

Muszę być w stanie dokładnie znaleźć miesiące między dwiema datami w Pythonie. Mam rozwiązanie, które działa, ale nie jest zbyt dobre (jak eleganckie) lub szybkie.

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

Aby to wyjaśnić, robię tutaj dwie daty i konwertuję je z formatu iso na obiekty datetime w Pythonie. Następnie przechodzę w pętlę przez dodanie tygodnia do obiektu początkowej daty i godziny i sprawdzam, czy wartość liczbowa miesiąca jest większa (chyba że miesiącem jest grudzień, wtedy sprawdza, czy data jest mniejsza), jeśli wartość jest większa, dołączam ją do listy miesięcy i powtarzaj, aż dojdę do daty zakończenia.

Działa doskonale, ale nie wydaje się dobrym sposobem na zrobienie tego ...


Czy pytasz o LICZBĘ miesięcy między dwiema datami, czy też o rzeczywiste miesiące?
Charles Hooper

w moim rozwiązaniu: nie zwiększam się o „liczbę sekund odpowiadającą miesiącowi”. Po prostu zwiększam liczbę od 1 do 2, a później z 2 do 3.
nonopolarity

Chciałem tylko, żebyś wiedział, że chociaż nie podobała Ci się moja odpowiedź, ponieważ „miała pętlę”, wybrałeś odpowiedź, która ma DWIE pętle. Listy składane nadal są pętlami.
Charles Hooper

Odpowiedzi:


0

Aktualizacja 2018-04-20: wygląda na to, że OP @Joshkunz pytał, które miesiące są między dwiema datami, zamiast „ile miesięcy” jest między dwiema datami. Więc nie jestem pewien, dlaczego @JohnLaRooy jest głosowany za ponad 100 razy. @Joshkunz wskazał w komentarzu pod pierwotnym pytaniem, że chciał poznać rzeczywiste daty [lub miesiące], zamiast znaleźć całkowitą liczbę miesięcy .

Wydawało się więc, że było to pożądane pytanie, od dwóch dat 2018-04-11do2018-06-01

Apr 2018, May 2018, June 2018 

A co, jeśli jest między 2014-04-11do 2018-06-01? Wtedy odpowiedź byłaby

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

Dlatego wiele lat temu miałem następujący pseudokod. Po prostu zasugerował użycie dwóch miesięcy jako punktów końcowych i zapętlenie ich, zwiększając o jeden miesiąc na raz. @Joshkunz wspomniał, że chciałby mieć „miesiące”, a także wspomniał, że chciałby mieć „daty”, nie wiedząc dokładnie. Trudno było napisać dokładny kod, ale chodzi o to, aby użyć jednej prostej pętli do zapętlenia punktów końcowych i narastająco o jeden miesiąc.

Odpowiedź 8 lat temu w 2010 roku:

Jeśli dodasz o tydzień, będzie działać około 4,35 razy więcej niż potrzeba. Dlaczego nie tylko:

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)

na razie tylko kod pseduo, nie testowałem, ale myślę, że pomysł z tej samej linii zadziała.


1
Ok, widzę problemy z miesiącami takimi jak luty, jeśli przyrost jest wykonywany według miesiąca, a nie tygodni.
Joshkunz

Nie jestem zwiększany o „liczbę sekund odpowiadającą miesiącowi”. Po prostu zwiększam liczbę 1do 2, a potem od 2do 3później.
nonopolarity

198

Zacznij od zdefiniowania kilku przypadków testowych, a zobaczysz, że funkcja jest bardzo prosta i nie wymaga pętli

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

Powinieneś dodać kilka przypadków testowych do swojego pytania, ponieważ istnieje wiele potencjalnych przypadków narożnych do omówienia - istnieje więcej niż jeden sposób określenia liczby miesięcy między dwiema datami.


2
daje zły wynik. Wynika z tego 1 miesiąc między „2015-04-30” a „2015-05-01”, czyli tak naprawdę tylko jeden dzień.
Rao

21
@Rao. Dlatego powiedziałem: „istnieje więcej niż jeden sposób określenia liczby miesięcy między dwiema datami”. Pytanie wciąż nie ma formalnej definicji. Dlatego też sugeruję dołączenie do definicji przypadków testowych.
John La Rooy

2
I zaleca się dodatek (abs) wokół substractions aby umożliwić D1 jest mniejsza niż D2: abs powrotne (d1.year - d2.year) * 12 + ABS (d1.month - d2.month)
lszrh

Czy na pewno @LukasSchulze? Jeśli d1 jest mniejsze niż d2, i tak musiałbyś odjąć tę liczbę od pierwszej, prawda?
Lilith-Elina

41

Jedna linijka, aby znaleźć listę dat dat, zwiększoną o miesiąc, między dwiema datami.

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

To! Dostosowuje się to do wielu wymyślnych sytuacji specjalnych dzięki zastosowaniu reguły. Zwróć uwagę, że wartości wyjściowe są datami, więc możesz je przekonwertować na cokolwiek chcesz (w tym ciągi znaków), aby dopasować je do tego, co pokazują inni.
Ezekiel Kruglick,

Nie udaje się to, gdy strt_dt ma wartość 2001-1-29, ponieważ w tym roku nie ma dnia przestępnego.
CS

2
OP poprosił o listę miesięcy pomiędzy datą rozpoczęcia i zakończenia. Brakuje lutego, używając twojej metody na moim przykładzie. Oczywiście rozwiązanie nadal można uratować, na przykład dostosowując datę początkową do pierwszego dnia miesiąca.
CS

2
To fajne rozwiązanie i oszczędza mi dużo czasu. Możemy to poprawić, podając datę rozpoczęcia[dt for dt in rrule(MONTHLY, bymonthday=10,dtstart=strt_dt, until=end_dt)]
Pengju Zhao

1
Bądź tego świadomy. Właściwie mają w dokumentacji notatkę mówiącą, że reguła może mieć „zaskakujące zachowanie, gdy na przykład data rozpoczęcia przypada na koniec miesiąca” (zobacz Uwaga w dateutil.readthedocs.io/en/stable/rrule.html ). Jednym ze sposobów uniknięcia tego jest zastąpienie dat pierwszym dniem miesiąca: start_date_first = start_date.replace(day=1), end_date_first = end_date.replace(day=1)wtedy reguła prawidłowo zlicza miesiące.
Alla Sorokina

37

To zadziałało dla mnie -

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)

usuń niepotrzebne str()wokół literałów łańcuchowych.
jfs

7
Nic nie warte, że jeśli delta r.monthsbędzie
dłuższa

2
zwykle robię r. miesiące * (r. lata + 1), ponieważ to dostosowuje się do tego, o czym mówił
@jbkkd

9
@triunenature wygląda to źle, na pewno powinno to być coś w rodzaju r.months + (r. lata * 12)
Moataz Elmasry

2
Tak, to nie działa, jeśli daty są oddalone od siebie o więcej niż rok.
HansG600

11

Możesz to łatwo obliczyć używając rrule from dateutil module:

from dateutil import rrule
from datetime import date

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

da tobie:

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

10

Pobierz miesiąc końcowy (w stosunku do roku i miesiąca miesiąca początkowego, np .: styczeń 2011 = 13, jeśli data początkowa zaczyna się w październiku 2010 r.), A następnie wygeneruj daty rozpoczynające się od miesiąca początkowego i kończącego w następujący sposób:

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

jeśli obie daty przypadają w tym samym roku, można to również zapisać jako:

dates=[datetime.datetime(year=dt1.year, month=mn, day=1) for mn in range(dt1.month, dt2.month + 1)]

8

Ten post jest świetny! Użyj 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

1
@edouard Jak widać w podanym przeze mnie przykładzie, daty są w różnych latach. Niemniej jednak str()obsada, którą zrobiłem, jest zupełnie niepotrzebna.
srodriguex

6
to nie działa, jeśli daty obejmują więcej niż rok, należy dodaćrelativedelta.relativedelta(date2, date1).years * 12
mion

delta.years*12 + delta.months
user2682863

7

Moje proste rozwiązanie:

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

niedocenione rozwiązanie
reabow

6
from dateutil import relativedelta

relativedelta.relativedelta(date1, date2)

months_difference = (r.years * 12) + r.months

4

Zdefiniuj „miesiąc” jako 1 / 12 rok, to zrobić:

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

Możesz spróbować zdefiniować miesiąc jako „okres 29, 28, 30 lub 31 dni (w zależności od roku)”. Ale robisz to, masz dodatkowy problem do rozwiązania.

Choć jest to zazwyczaj jasne, że 15 czerwca th + 1 miesiąc powinien być 15 lipca th , to nie nie jest zazwyczaj jasne, czy 30 stycznia th + 1 miesiąc to w lutym lub marcu. W tym ostatnim przypadku może być zmuszany do obliczyć datę w lutym 30 th , to „poprawne” go do 2 marca nd . Ale gdy to zrobisz, przekonasz się, że 2 marca nd - 1 miesiąc to wyraźnie 02 lutego nd . Ergo, reductio ad absurdum (ta operacja nie jest dobrze zdefiniowana).


4

Istnieje proste rozwiązanie oparte na 360-dniowych latach, gdzie wszystkie miesiące mają 30 dni. Pasuje do większości przypadków użycia, w których biorąc pod uwagę dwie daty, należy obliczyć liczbę pełnych miesięcy plus pozostałe dni.

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

4

Nieco upiększone rozwiązanie autorstwa @ 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)

4

Możesz także skorzystać z biblioteki strzałek . Oto prosty przykład:

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

Spowoduje to wydrukowanie:

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

Mam nadzieję że to pomoże!


3

Spróbuj czegoś takiego. Obecnie obejmuje miesiąc, jeśli obie daty przypadają w tym samym miesiącu.

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

Wynik wygląda następująco:

>>> start = datetime.now() - timedelta(days=120)
>>> end = datetime.now()
>>> months_between(start,end)
[6, 7, 8, 9, 10]

To wciąż jednak działa w ten sam sposób, więc niekoniecznie widzę korzyści ...
Joshkunz

1
Nie rozumiem, jaki to problem. Pętle nie są twoim wrogiem.
Charles Hooper

Cóż, trzeba to robić za każdym razem, gdy jest to zapytanie AJAX. Wiem, że pętle nie są wrogiem, ale wygląda na to, że są powolnym rozwiązaniem problemu, który należy rozwiązać w znacznie łatwiejszy sposób.
Joshkunz


3

Oto jak to zrobić z 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')

Zauważ, że zaczyna się od miesiąca następującego po podanej dacie rozpoczęcia.


2

Można to zrobić za pomocą datetime.timedelta, gdzie liczbę dni do przeskoczenia do następnego miesiąca można uzyskać za pomocą kalendarza.monthrange. Monthrange zwraca dzień tygodnia (0-6 ~ Pon-Nd) i liczbę dni (28-31) dla danego roku i miesiąca.
Na przykład: monthrange (2017, 1) zwraca (6,31).

Oto skrypt używający tej logiki do iteracji między dwoma miesiącami.

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

`


Czy mógłbyś wyjaśnić, jak to rozwiązuje problem? Zachęcamy ludzi do dodawania kontekstu do swoich odpowiedzi; dzięki.
Gi0rgi0s

1
Dodano wyjaśnienie
pankaj kumar

2

Wiele osób już udzieliło Ci dobrych odpowiedzi, aby rozwiązać ten problem, ale nie czytałem żadnego przy użyciu rozumienia list, więc daję ci to, czego użyłem w podobnym przypadku użycia:


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']


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)

1

podobnie jak rangefunkcja, gdy miesiąc jest 13 , przejdź do następnego roku

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

przykład w powłoce Pythona

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

1

Zwykle 90 dni NIE jest dosłownie 3 miesiącami, tylko odniesieniem.

Na koniec musisz sprawdzić, czy dni są większe niż 15, aby dodać +1 do licznika miesięcy. lub lepiej, dodaj kolejny elif z licznikiem pół miesiąca.

Z tej innej odpowiedzi na stosy w końcu skończyłem na tym:

#/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 ")"

Wynik:

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

Zauważyłem, że to nie działa, jeśli dodasz więcej dni niż pozostało w bieżącym roku i jest to nieoczekiwane.


1

wydaje się, że odpowiedzi są niezadowalające i od tego czasu używam własnego kodu, który jest łatwiejszy do zrozumienia

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

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

1

Oto moje rozwiązanie:

def calc_age_months(from_date, to_date):
    from_date = time.strptime(from_date, "%Y-%m-%d")
    to_date = time.strptime(to_date, "%Y-%m-%d")

    age_in_months = (to_date.tm_year - from_date.tm_year)*12 + (to_date.tm_mon - from_date.tm_mon)

    if to_date.tm_mday < from_date.tm_mday:
        return age_in_months -1
    else
        return age_in_months

Będzie to dotyczyło również niektórych przypadków skrajnych, w których różnica w miesiącach między 31 grudnia 2018 r. A 1 stycznia 2019 r. Będzie wynosić zero (ponieważ różnica jest tylko jednym dniem).


0

Zakładając, że upperDate jest zawsze późniejsza niż lowerDate i oba są obiektami 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 ) )

Nie testowałem tego dokładnie, ale wygląda na to, że powinno to załatwić sprawę.


0

Oto metoda:

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

To jest pierwsze obliczenie całkowitej liczby miesięcy między dwiema datami włącznie. Następnie tworzy listę, używając pierwszej daty jako podstawy i wykonuje arytmetykę modulową, aby utworzyć obiekty daty.


0

Właściwie musiałem teraz zrobić coś podobnego

Skończyło się na napisaniu funkcji, która zwraca listę krotek wskazujących startiend każdego miesiąca między dwoma zestawami dat, więc mogłem napisać kilka zapytań SQL z tyłu dla miesięcznych sum sprzedaży itp.

Jestem pewien, że może to poprawić ktoś, kto wie, co robi, ale mam nadzieję, że to pomoże ...

Zwracana wartość wygląda następująco (generowanie na dziś - 365 dni do dzisiaj jako przykład)

[   (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))]

Kod w następujący sposób (zawiera pewne elementy debugowania, które można usunąć):

#! /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)

0

Zakładając, że chciałeś znać „ułamek” miesiąca, w którym były daty, a ja to zrobiłem, musisz wykonać trochę więcej pracy.

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)

zawiera przykład, który wylicza liczbę miesięcy między dwiema datami włącznie, w tym ułamek każdego miesiąca, w którym data się znajduje. Oznacza to, że można obliczyć, ile miesięcy przypada między 20.01.2015 a 14.02.2015 , gdzie ułamek dnia w miesiącu styczniu zależy od liczby dni w styczniu; lub też biorąc pod uwagę, że liczba dni w lutym może zmieniać się z roku na rok.

Dla mojego odniesienia ten kod jest również na github - https://gist.github.com/andrewyager/6b9284a4f1cdb1779b10


0

Spróbuj tego:

 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

Nie powinno mieć znaczenia, w jakiej kolejności wprowadzasz daty, i uwzględnia różnicę w długościach miesięcy.


Pamiętaj, że to nie oznacza, że ​​Twoje daty są takie same. Najłatwiej byłoby, gdyby delta_time.days = 0: months = 0 else reszta procedury.
om_henners

0

To działa...

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))
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.