Tworzenie zakresu dat w Pythonie

373

Chcę utworzyć listę dat, zaczynając od dzisiaj i cofając dowolną liczbę dni, powiedzmy w moim przykładzie 100 dni. Czy jest na to lepszy sposób?

import datetime

a = datetime.datetime.today()
numdays = 100
dateList = []
for x in range (0, numdays):
    dateList.append(a - datetime.timedelta(days = x))
print dateList
Thomas Browne
źródło

Odpowiedzi:

458

Lepiej marginalnie ...

base = datetime.datetime.today()
date_list = [base - datetime.timedelta(days=x) for x in range(numdays)]
S.Lott
źródło
6
To nie działa, jeśli używasz danych czasowych świadomych strefy czasowej i następuje zmiana DST !!! Mam na myśli, że kiedy użyjesz daty początkowej i końcowej i wypełnisz ją w ten sposób ...
gabn88
@ s-lott nie mógłbyś po prostu użyć range(0, numdays)w tym przypadku?
Lukas Juhrich
3
Zastosowałem tę metodę w nieco inny sposób, aby wygenerować dziesięć, 5-minutowe interwały, +/- pewną wartość (w moim przypadku 30 sekund). Właśnie pomyślałem, że podzielę się: datetimes = [start + timedelta(seconds=x*60+randint(-30, 30)) for x in range (0, range_end*5, 5)]gdzie zmiennarange_end = 10
MikeyE
246

Pandas jest świetny do szeregów czasowych i ma bezpośrednie wsparcie dla zakresów dat.

Na przykład pd.date_range():

import pandas as pd
from datetime import datetime

datelist = pd.date_range(datetime.today(), periods=100).tolist()

Ma również wiele opcji ułatwiających życie. Na przykład, jeśli chcesz tylko dni robocze, po prostu zamienisz się bdate_range.

Zobacz dokumentację zakresu dat

Ponadto w pełni obsługuje strefy czasowe pytza i może płynnie obejmować wiosenne / jesienne zmiany czasu letniego.

EDYCJA przez OP:

Jeśli potrzebujesz rzeczywistych czasów danych w Pythonie, w przeciwieństwie do znaczników czasu Pandas:

import pandas as pd
from datetime import datetime

pd.date_range(end = datetime.today(), periods = 100).to_pydatetime().tolist()

#OR

pd.date_range(start="2018-09-09",end="2020-02-02")

Używa parametru „koniec”, aby dopasować oryginalne pytanie, ale jeśli chcesz mieć daty malejące:

pd.date_range(datetime.today(), periods=100).to_pydatetime().tolist()
fantastyczny
źródło
22
Zdecyduj najlepszą odpowiedź, jeśli pandy są już używane. Dodam tylko, że użyjesz parametru end =, jeśli daty będą się cofać jak w oryginalnym poście. pd.date_range (end = pd.datetime.today (), period = 100) .tolist ()
Thomas Browne
5
Jednym z problemów związanych z tymi metodami jest to, że potrzebujesz typu Python Datetime ... Użycie Pand lub metod zakresu dat numpy spowoduje utworzenie typów datownika i Datetime64 ... Musisz list(map(pd.Timestamp.to_pydatetime, datelist))odzyskać typ datetime ...
Malik Koné
pandas.datetimeKlasa jest przestarzała i zostanie usunięty z pand w wersji przyszłości. datetimeZamiast tego importuj z modułu.
Trenton McKinney
82

Uzyskaj zakres dat między określoną datą początkową i końcową (zoptymalizowany pod kątem złożoności czasu i przestrzeni):

import datetime

start = datetime.datetime.strptime("21-06-2014", "%d-%m-%Y")
end = datetime.datetime.strptime("07-07-2014", "%d-%m-%Y")
date_generated = [start + datetime.timedelta(days=x) for x in range(0, (end-start).days)]

for date in date_generated:
    print date.strftime("%d-%m-%Y")
Sandeep
źródło
6
Początkowa sugestia to użycie () zamiast [], aby uzyskać date_generator. Wydajne w tym sensie, że nie będzie potrzeby przechowywania całego zakresu dat i generowania tylko jednego, gdy zajdzie taka potrzeba.
Sandeep,
2
Uwaga: data_końcowa nie jest generowana.
Thorbjørn Ravn Andersen
8
użyj (koniec-początek + 1), aby uzyskać datę zakończenia.
Sandeep
6
Nie odpowiada na pytanie OP, ale tego właśnie szukałem :)
Thierry J.
2
(end-start+1)nie działa, nie można dodać timedelta i int. Możesz jednak użyć (end-start).days + 1.
Stephen Paulger
44

Możesz napisać funkcję generatora, która zwraca obiekty daty od dziś:

import datetime

def date_generator():
  from_date = datetime.datetime.today()
  while True:
    yield from_date
    from_date = from_date - datetime.timedelta(days=1)

Ten generator zwraca daty zaczynające się od dzisiaj i cofające się o jeden dzień na raz. Oto jak wziąć pierwsze 3 daty:

>>> import itertools
>>> dates = itertools.islice(date_generator(), 3)
>>> list(dates)
[datetime.datetime(2009, 6, 14, 19, 12, 21, 703890), datetime.datetime(2009, 6, 13, 19, 12, 21, 703890), datetime.datetime(2009, 6, 12, 19, 12, 21, 703890)]

Zaletą tego podejścia w porównaniu ze zrozumieniem pętli lub listy jest to, że możesz cofać się tyle razy, ile chcesz.

Edytować

Bardziej kompaktowa wersja wykorzystująca wyrażenie generatorowe zamiast funkcji:

date_generator = (datetime.datetime.today() - datetime.timedelta(days=i) for i in itertools.count())

Stosowanie:

>>> dates = itertools.islice(date_generator, 3)
>>> list(dates)
[datetime.datetime(2009, 6, 15, 1, 32, 37, 286765), datetime.datetime(2009, 6, 14, 1, 32, 37, 286836), datetime.datetime(2009, 6, 13, 1, 32, 37, 286859)]
Ayman Hourieh
źródło
1
Generatory to idealny sposób na zrobienie tego, jeśli liczba dni jest dowolna.
xssChauhan
32

tak, wynajdź koło ... po prostu przeszukaj forum, a otrzymasz coś takiego:

from dateutil import rrule
from datetime import datetime

list(rrule.rrule(rrule.DAILY,count=100,dtstart=datetime.now()))
BOFH
źródło
22
Wymaga labix.org/python-dateutil . Wygląda ładnie, ale nie jest wart zewnętrznej zależności tylko po to, aby zapisać jedną linię.
Beni Cherniavsky-Paskin
1
Nie mogę uwierzyć nikomu, że inne odpowiedzi są bardzo pytoniczne. Chociaż reguła jest okropną nazwą, ta jest przynajmniej łatwa dla oczu.
Boatcoder
7
@ BeniCherniavsky-Paskin: bardzo łatwo wprowadzić subtelne błędy podczas implementacji arytmetyki okresu (kalendarza). dateutil.rruleimplementuje iCalendar RFC - łatwiej jest korzystać z funkcji o dobrze zdefiniowanym zachowaniu zamiast wielu implementacji o prawie takiej samej funkcjonalności, które są jeszcze nieco inne. dateutil.rrulepozwala ograniczyć naprawianie błędów w jednym miejscu.
jfs
3
@ Mark0978: rrulenazwa nie jest dowolna; pochodzi z odpowiedniego rfc
jfs
1
Tak, nie obwiniałem dateutil za niefortunny wybór nazwisk :-)
Boatcoder
32

Aby uprościć tę czynność, możesz także użyć dziennej porządkowej:

def date_range(start_date, end_date):
    for ordinal in range(start_date.toordinal(), end_date.toordinal()):
        yield datetime.date.fromordinal(ordinal)

Lub zgodnie z sugestiami w komentarzach możesz utworzyć taką listę:

date_range = [
    datetime.date.fromordinal(ordinal) 
    for ordinal in range(
        start_date.toordinal(),
        end_date.toordinal(),
    )
]
Hosane
źródło
18

Z tytułu tego pytania spodziewałem się znaleźć coś takiego range(), co pozwoliłoby mi określić dwie daty i stworzyć listę ze wszystkimi datami pomiędzy nimi. W ten sposób nie trzeba obliczać liczby dni między tymi dwiema datami, jeśli nie znamy ich wcześniej.

Więc z ryzykiem nieco nie na temat, ten liniowiec wykonuje swoją pracę:

import datetime
start_date = datetime.date(2011, 01, 01)
end_date   = datetime.date(2014, 01, 01)

dates_2011_2013 = [ start_date + datetime.timedelta(n) for n in range(int ((end_date - start_date).days))]

Wszystkie kredyty dla tej odpowiedzi !

snake_charmer
źródło
1
To nie jest jedna linijka, tak jak wiele innych odpowiedzi na pytanie.
Thomas Browne 16.04.16
4
Nigdy nie udawałem, że było to bardziej jednopłaszczyznowe niż odpowiedzi innych, ani że nie było to lepsze rozwiązanie. Pokazałem fragment kodu, który robi coś nieco innego niż to, o co prosiłeś, ale chętnie go tu znajdę, biorąc pod uwagę tytuł pytania.
snake_charmer 16.04.16
11

Oto nieco inna odpowiedź oparta na odpowiedzi S.Lott, która podaje listę dat między dwiema datami starti end. W poniższym przykładzie od początku 2017 r. Do dziś.

start = datetime.datetime(2017,1,1)
end = datetime.datetime.today()
daterange = [start + datetime.timedelta(days=x) for x in range(0, (end-start).days)]
Derek Powell
źródło
7

Wiem trochę spóźnioną odpowiedź, ale właśnie miałem ten sam problem i zdecydowałem, że wewnętrznej funkcji zakresu Pythona trochę brakuje pod tym względem, więc przesłoniłem go w moim module użytkownika.

from __builtin__ import range as _range
from datetime import datetime, timedelta

def range(*args):
    if len(args) != 3:
        return _range(*args)
    start, stop, step = args
    if start < stop:
        cmp = lambda a, b: a < b
        inc = lambda a: a + step
    else:
        cmp = lambda a, b: a > b
        inc = lambda a: a - step
    output = [start]
    while cmp(start, stop):
        start = inc(start)
        output.append(start)

    return output

print range(datetime(2011, 5, 1), datetime(2011, 10, 1), timedelta(days=30))
Rob Young
źródło
1
Myślę, że chcesz output = []i zamienić linie w while cmp(...)pętli. Porównaj range(0,10,1)i _range(0,10,1).
sigfpe
3
Myślę, że ta odpowiedź byłaby lepsza, gdybyś nazwał funkcję date_range.
Brandon Bradley,
7

Na podstawie odpowiedzi napisałem dla siebie:

import datetime;
print [(datetime.date.today() - datetime.timedelta(days=x)).strftime('%Y-%m-%d') for x in range(-5, 0)]

Wynik:

['2017-12-11', '2017-12-10', '2017-12-09', '2017-12-08', '2017-12-07']

Różnica polega na tym, że dostaję dateprzedmiot „nie datetime.datetime”.

wmlynarski
źródło
7

Jeśli są dwie daty i potrzebujesz zakresu, spróbuj

from dateutil import rrule, parser
date1 = '1995-01-01'
date2 = '1995-02-28'
datesx = list(rrule.rrule(rrule.DAILY, dtstart=parser.parse(date1), until=parser.parse(date2)))
Nelu
źródło
5

Oto, co stworzyłem, z własnego kodu, może to pomóc. (Wiem, że pytanie jest za stare, ale inni mogą z niego skorzystać)

https://gist.github.com/2287345

(to samo poniżej)

import datetime
from time import mktime

def convert_date_to_datetime(date_object):
    date_tuple = date_object.timetuple()
    date_timestamp = mktime(date_tuple)
    return datetime.datetime.fromtimestamp(date_timestamp)

def date_range(how_many=7):
    for x in range(0, how_many):
        some_date = datetime.datetime.today() - datetime.timedelta(days=x)
        some_datetime = convert_date_to_datetime(some_date.date())
        yield some_datetime

def pick_two_dates(how_many=7):
    a = b = convert_date_to_datetime(datetime.datetime.now().date())
    for each_date in date_range(how_many):
        b = a
        a = each_date
        if a == b:
            continue
        yield b, a
roopesh
źródło
4

Oto jeden linijka dla skryptów bash, aby uzyskać listę dni powszednich, to jest python 3. Łatwo modyfikowany do cokolwiek, int na końcu to liczba dni w przeszłości, którą chcesz.

python -c "import sys,datetime; print('\n'.join([(datetime.datetime.today() - datetime.timedelta(days=x)).strftime(\"%Y/%m/%d\") for x in range(0,int(sys.argv[1])) if (datetime.datetime.today() - datetime.timedelta(days=x)).isoweekday()<6]))" 10

Oto wariant zapewniający datę początkową (a raczej końcową)

python -c "import sys,datetime; print('\n'.join([(datetime.datetime.strptime(sys.argv[1],\"%Y/%m/%d\") - datetime.timedelta(days=x)).strftime(\"%Y/%m/%d \") for x in range(0,int(sys.argv[2])) if (datetime.datetime.today() - datetime.timedelta(days=x)).isoweekday()<6]))" 2015/12/30 10

Oto wariant dla dowolnych dat rozpoczęcia i zakończenia. nie że nie jest to zbyt wydajne, ale jest dobre do umieszczenia pętli for w skrypcie bash:

python -c "import sys,datetime; print('\n'.join([(datetime.datetime.strptime(sys.argv[1],\"%Y/%m/%d\") + datetime.timedelta(days=x)).strftime(\"%Y/%m/%d\") for x in range(0,int((datetime.datetime.strptime(sys.argv[2], \"%Y/%m/%d\") - datetime.datetime.strptime(sys.argv[1], \"%Y/%m/%d\")).days)) if (datetime.datetime.strptime(sys.argv[1], \"%Y/%m/%d\") + datetime.timedelta(days=x)).isoweekday()<6]))" 2015/12/15 2015/12/30
Tim P.
źródło
Może chcesz zaktualizować swój post z odpowiedzią za pomocą kodu w komentarzu. Python zostaje zniekształcony w komentarzach i trochę potrzebuje formatowania.
Jake Bathman
3

Związane z Matplotlib

from matplotlib.dates import drange
import datetime

base = datetime.date.today()
end  = base + datetime.timedelta(days=100)
delta = datetime.timedelta(days=1)
l = drange(base, end, delta)
milton
źródło
3

Wiem, że udzielono na nie odpowiedzi, ale odłożę moją odpowiedź do celów historycznych i ponieważ uważam, że jest to prosta sprawa.

import numpy as np
import datetime as dt
listOfDates=[date for date in np.arange(firstDate,lastDate,dt.timedelta(days=x))]

Jasne, że nie wygra czegoś takiego jak golf, ale myślę, że jest elegancki.

BarocliniCplusplus
źródło
Z arangekrokami jest całkiem fajny, ale listOfDatesskłada się z numpy datetime64 zamiast natywnych czasów danych w Pythonie.
F.Raab
2
Można jednak użyć np.arange(…).astype(dt.datetime)do arangeprzywrócenia natywnego zapytania datetime zamiast numpy datetime64.
F.Raab
2

Kolejny przykład, który liczy się do przodu lub do tyłu, zaczynając od odpowiedzi Sandeepa.

from datetime import date, datetime, timedelta
from typing import Sequence
def range_of_dates(start_of_range: date, end_of_range: date) -> Sequence[date]:

    if start_of_range <= end_of_range:
        return [
            start_of_range + timedelta(days=x)
            for x in range(0, (end_of_range - start_of_range).days + 1)
        ]
    return [
        start_of_range - timedelta(days=x)
        for x in range(0, (start_of_range - end_of_range).days + 1)
    ]

start_of_range = datetime.today().date()
end_of_range = start_of_range + timedelta(days=3)
date_range = range_of_dates(start_of_range, end_of_range)
print(date_range)

daje

[datetime.date(2019, 12, 20), datetime.date(2019, 12, 21), datetime.date(2019, 12, 22), datetime.date(2019, 12, 23)]

i

start_of_range = datetime.today().date()
end_of_range = start_of_range - timedelta(days=3)
date_range = range_of_dates(start_of_range, end_of_range)
print(date_range)

daje

[datetime.date(2019, 12, 20), datetime.date(2019, 12, 19), datetime.date(2019, 12, 18), datetime.date(2019, 12, 17)]

Pamiętaj, że data początkowa jest podana w zwrocie, więc jeśli chcesz mieć cztery daty całkowite, użyj timedelta(days=3)

Chad Lowe
źródło
1
from datetime import datetime, timedelta
from dateutil import parser
def getDateRange(begin, end):
    """  """
    beginDate = parser.parse(begin)
    endDate =  parser.parse(end)
    delta = endDate-beginDate
    numdays = delta.days + 1
    dayList = [datetime.strftime(beginDate + timedelta(days=x), '%Y%m%d') for x in range(0, numdays)]
    return dayList
Xmduhan
źródło
1

Generator miesięcznych zakresów dat z datetimei dateutil. Prosty i łatwy do zrozumienia:

import datetime as dt
from dateutil.relativedelta import relativedelta

def month_range(start_date, n_months):
        for m in range(n_months):
            yield start_date + relativedelta(months=+m)
aysa
źródło
0
import datetime    
def date_generator():
    cur = base = datetime.date.today()
    end  = base + datetime.timedelta(days=100)
    delta = datetime.timedelta(days=1)
    while(end>base):
        base = base+delta
        print base

date_generator()
Sasmita
źródło
1
Chciał wrócić, nie iść naprzód. Tak endpowinno być base - datetime.timedelta. Co więcej ... Dlaczego to rozwiązanie jest lepsze od oryginalnego?
frarugi87
0

Z powyższych odpowiedzi stworzyłem ten przykład dla generatora daty

import datetime
date = datetime.datetime.now()
time = date.time()
def date_generator(date, delta):
  counter =0
  date = date - datetime.timedelta(days=delta)
  while counter <= delta:
    yield date
    date = date + datetime.timedelta(days=1)
    counter +=1

for date in date_generator(date, 30):
   if date.date() != datetime.datetime.now().date():
     start_date = datetime.datetime.combine(date, datetime.time())
     end_date = datetime.datetime.combine(date, datetime.time.max)
   else:
     start_date = datetime.datetime.combine(date, datetime.time())
     end_date = datetime.datetime.combine(date, time)
   print('start_date---->',start_date,'end_date---->',end_date)
Dimitris Kougioumtzis
źródło