Scorrendo una serie di date in Python


369

Ho il seguente codice per farlo, ma come posso farlo meglio? In questo momento penso che sia meglio dei loop nidificati, ma inizia a ottenere Perl-one-linerish quando hai un generatore in una lista di comprensione.

day_count = (end_date - start_date).days + 1
for single_date in [d for d in (start_date + timedelta(n) for n in range(day_count)) if d <= end_date]:
    print strftime("%Y-%m-%d", single_date.timetuple())

Appunti

  • In realtà non lo sto usando per stampare. Questo è solo a scopo dimostrativo.
  • Le variabili start_datee end_datesono datetime.dateoggetti perché non ho bisogno dei timestamp. (Verranno utilizzati per generare un rapporto).

Uscita campione

Per una data di inizio 2009-05-30e una data di fine di 2009-06-09:

2009-05-30
2009-05-31
2009-06-01
2009-06-02
2009-06-03
2009-06-04
2009-06-05
2009-06-06
2009-06-07
2009-06-08
2009-06-09

3
Solo per sottolineare: non credo che ci sia alcuna differenza tra 'time.strftime ("% Y-% m-% d", single_date.timetuple ())' e il più breve 'single_date.strftime ("% Y-% m-% d ")'. La maggior parte delle risposte sembra copiare lo stile più lungo.
Mu Mind,

8
Caspita, queste risposte sono troppo complicate. Prova questo: stackoverflow.com/questions/7274267/…
Gringo Suave,

@GringoSuave: cosa c'è di complicato nella risposta di Sean Cavanagh ?
jfs


1
Duplicato o no, otterrai una risposta più semplice sull'altra pagina.
Gringo Suave,

Risposte:


553

Perché ci sono due iterazioni nidificate? Per me produce lo stesso elenco di dati con una sola iterazione:

for single_date in (start_date + timedelta(n) for n in range(day_count)):
    print ...

E nessun elenco viene memorizzato, viene ripetuto solo un generatore. Anche il "if" nel generatore sembra non essere necessario.

Dopotutto, una sequenza lineare dovrebbe richiedere solo un iteratore, non due.

Aggiornamento dopo discussione con John Machin:

Forse la soluzione più elegante sta usando una funzione di generatore per nascondere / astrarre completamente l'iterazione nell'intervallo di date:

from datetime import timedelta, date

def daterange(start_date, end_date):
    for n in range(int ((end_date - start_date).days)):
        yield start_date + timedelta(n)

start_date = date(2013, 1, 1)
end_date = date(2015, 6, 2)
for single_date in daterange(start_date, end_date):
    print(single_date.strftime("%Y-%m-%d"))

NB: per coerenza con la range()funzione integrata, questa iterazione si interrompe prima di raggiungere il end_date. Quindi per l'iterazione inclusiva usa il giorno successivo, come faresti con range().


4
-1 ... avere un calcolo preliminare di day_count e usare range non è fantastico quando sarà sufficiente un semplice ciclo while.
John Machin,

7
@ John Machin: Ok. Tuttavia, preferisco eseguire un'iterazione durante i cicli con un incremento esplicito di alcuni contatori o valori. Il modello di interazione è più pitonico (almeno secondo il mio punto di vista personale) e anche più generale, in quanto consente di esprimere un'iterazione nascondendo i dettagli di come viene eseguita l'iterazione.
Ber,

10
@Ber: non mi piace per niente; è doppiamente male. Hai già avuto un'iterazione! Avvolgendo i costrutti lamentati in un generatore, hai aggiunto un ulteriore sovraccarico di esecuzione oltre a dirottare l'attenzione dell'utente da qualche altra parte per leggere il codice e / o i documenti del tuo 3-liner. -2
John Machin,

8
@ John Machin: non sono d'accordo. Il punto non è di ridurre il numero di righe al minimo assoluto. Dopotutto, non stiamo parlando di Perl qui. Inoltre, il mio codice esegue una sola iterazione (è così che funziona il generatore, ma credo che tu lo sappia). *** Il mio punto riguarda l'astrazione di concetti per il riutilizzo e il codice autoesplicativo. Ritengo che questo sia molto più utile che avere il codice più breve possibile.
Ber,

9
Se stai cercando la terseness puoi usare un'espressione del generatore:(start_date + datetime.timedelta(n) for n in range((end_date - start_date).days))
Mark Ransom,

219

Questo potrebbe essere più chiaro:

from datetime import date, timedelta

start_date = date(2019, 1, 1)
end_date = date(2020, 1, 1)
delta = timedelta(days=1)
while start_date <= end_date:
    print (start_date.strftime("%Y-%m-%d"))
    start_date += delta

3
Molto chiaro e breve, ma non funziona bene se si desidera utilizzare continue
rslite

funziona
benissimo

169

Usa la dateutillibreria:

from datetime import date
from dateutil.rrule import rrule, DAILY

a = date(2009, 5, 30)
b = date(2009, 6, 9)

for dt in rrule(DAILY, dtstart=a, until=b):
    print dt.strftime("%Y-%m-%d")

Questa libreria Python ha molte altre funzionalità avanzate, alcune molto utili, come relative deltas, ed è implementata come un singolo file (modulo) che è facilmente incluso in un progetto.


3
Si noti che la data limite nel ciclo for qui è comprensivo di untilmentre la data finale del daterangemetodo nella risposta di Ber è esclusiva di end_date.
Ninjakannon,


77

Panda è ottimo per le serie storiche in generale e ha un supporto diretto per gli intervalli di date.

import pandas as pd
daterange = pd.date_range(start_date, end_date)

È quindi possibile scorrere il daterange per stampare la data:

for single_date in daterange:
    print (single_date.strftime("%Y-%m-%d"))

Ha anche molte opzioni per semplificare la vita. Ad esempio, se volevi solo giorni feriali, devi semplicemente scambiare in bdate_range. Vedi http://pandas.pydata.org/pandas-docs/stable/timeseries.html#generating-ranges-of-timestamps

Il potere di Panda è proprio i suoi frame di dati, che supportano operazioni vettoriali (molto simili a numpy) che rendono le operazioni su grandi quantità di dati molto veloci e facili.

EDIT: puoi anche saltare completamente il ciclo for e stamparlo direttamente, il che è più facile ed efficiente:

print(daterange)

"molto simile al numpy" - Pandas è costruito sul numpy: P
Zach Saucier,

15
import datetime

def daterange(start, stop, step=datetime.timedelta(days=1), inclusive=False):
  # inclusive=False to behave like range by default
  if step.days > 0:
    while start < stop:
      yield start
      start = start + step
      # not +=! don't modify object passed in if it's mutable
      # since this function is not restricted to
      # only types from datetime module
  elif step.days < 0:
    while start > stop:
      yield start
      start = start + step
  if inclusive and start == stop:
    yield start

# ...

for date in daterange(start_date, end_date, inclusive=True):
  print strftime("%Y-%m-%d", date.timetuple())

Questa funzione svolge più del necessario, supportando il passaggio negativo, ecc. Fintanto che si tiene conto della logica dell'intervallo, non è necessario il separato day_counte, soprattutto, il codice diventa più facile da leggere quando si chiama la funzione da più posti.


Grazie, rinominato per abbinare più da vicino i parametri della gamma, ho dimenticato di cambiare nel corpo.

+1 ... ma poiché stai permettendo al passaggio di essere un timedelta, dovresti (a) chiamarlo dateTIMErange () e fare in modo che i passaggi di timedelta (ore = 12) e timedelta (ore = 36) funzionino correttamente o ( b) intercettare passaggi che non sono un numero intero di giorni o (c) salvare il chiamante la seccatura ed esprimere il passaggio come numero di giorni anziché come un timedelta.
John Machin,

Qualsiasi timedelta dovrebbe già funzionare, ma ho aggiunto datetime_range e date_range alla mia raccolta di rottami personali dopo averlo scritto, a causa di (a). Non sono sicuro che valga la pena un'altra funzione per (c), il caso più comune di giorni = 1 è già curato e dover passare un tempo esplicito evita confusione. Forse caricarlo da qualche parte è il migliore: bitbucket.org/kniht/scraps/src/tip/python/gen_range.py

per far funzionare questo con incrementi diversi dai giorni, è necessario verificare con step.total_seconds () e non con step.days
amohr,

12

Questa è la soluzione più leggibile dall'uomo che mi viene in mente.

import datetime

def daterange(start, end, step=datetime.timedelta(1)):
    curr = start
    while curr < end:
        yield curr
        curr += step

11

Perché non provare:

import datetime as dt

start_date = dt.datetime(2012, 12,1)
end_date = dt.datetime(2012, 12,5)

total_days = (end_date - start_date).days + 1 #inclusive 5 days

for day_number in range(total_days):
    current_date = (start_date + dt.timedelta(days = day_number)).date()
    print current_date

7

La arangefunzione di Numpy può essere applicata alle date:

import numpy as np
from datetime import datetime, timedelta
d0 = datetime(2009, 1,1)
d1 = datetime(2010, 1,1)
dt = timedelta(days = 1)
dates = np.arange(d0, d1, dt).astype(datetime)

L'uso di astypeè di convertire da numpy.datetime64in una matrice di datetime.datetimeoggetti.


Costruzione super magra! L'ultima riga funziona con medates = np.arange(d0, d1, dt).astype(datetime.datetime)
pyano il

+1 per la pubblicazione di una soluzione generica con una sola riga che consente qualsiasi timedelta, anziché un passaggio arrotondato fisso come orario / minuzioso /….
F.Raab,

7

Mostra gli ultimi n giorni da oggi:

import datetime
for i in range(0, 100):
    print((datetime.date.today() + datetime.timedelta(i)).isoformat())

Produzione:

2016-06-29
2016-06-30
2016-07-01
2016-07-02
2016-07-03
2016-07-04

Aggiungi parentesi tonde, comeprint((datetime.date.today() + datetime.timedelta(i)).isoformat())
TitanFighter

@TitanFighter non esitate a fare modifiche, le accetterò.
user1767754

2
Provai. La modifica richiede un minimo di 6 caratteri, ma in questo caso è necessario aggiungere solo 2 caratteri, "(" e ")"
TitanFighter

print((datetime.date.today() + datetime.timedelta(i)))senza .isoformat () fornisce esattamente lo stesso output. Ho bisogno della mia sceneggiatura per stampare YYMMDD. Qualcuno sa come farlo?
mr.zog,

d = datetime.date.today() + datetime.timedelta(i); d.strftime("%Y%m%d")
Fallo

5
import datetime

def daterange(start, stop, step_days=1):
    current = start
    step = datetime.timedelta(step_days)
    if step_days > 0:
        while current < stop:
            yield current
            current += step
    elif step_days < 0:
        while current > stop:
            yield current
            current += step
    else:
        raise ValueError("daterange() step_days argument must not be zero")

if __name__ == "__main__":
    from pprint import pprint as pp
    lo = datetime.date(2008, 12, 27)
    hi = datetime.date(2009, 1, 5)
    pp(list(daterange(lo, hi)))
    pp(list(daterange(hi, lo, -1)))
    pp(list(daterange(lo, hi, 7)))
    pp(list(daterange(hi, lo, -7))) 
    assert not list(daterange(lo, hi, -1))
    assert not list(daterange(hi, lo))
    assert not list(daterange(lo, hi, -7))
    assert not list(daterange(hi, lo, 7)) 

4
for i in range(16):
    print datetime.date.today() + datetime.timedelta(days=i)

4

Per completezza, Panda ha anche una period_rangefunzione per i timestamp che sono fuori limite:

import pandas as pd

pd.period_range(start='1/1/1626', end='1/08/1627', freq='D')

3

Ho un problema simile, ma devo iterare mensilmente anziché giornalmente.

Questa è la mia soluzione

import calendar
from datetime import datetime, timedelta

def days_in_month(dt):
    return calendar.monthrange(dt.year, dt.month)[1]

def monthly_range(dt_start, dt_end):
    forward = dt_end >= dt_start
    finish = False
    dt = dt_start

    while not finish:
        yield dt.date()
        if forward:
            days = days_in_month(dt)
            dt = dt + timedelta(days=days)            
            finish = dt > dt_end
        else:
            _tmp_dt = dt.replace(day=1) - timedelta(days=1)
            dt = (_tmp_dt.replace(day=dt.day))
            finish = dt < dt_end

Esempio 1

date_start = datetime(2016, 6, 1)
date_end = datetime(2017, 1, 1)

for p in monthly_range(date_start, date_end):
    print(p)

Produzione

2016-06-01
2016-07-01
2016-08-01
2016-09-01
2016-10-01
2016-11-01
2016-12-01
2017-01-01

Esempio n. 2

date_start = datetime(2017, 1, 1)
date_end = datetime(2016, 6, 1)

for p in monthly_range(date_start, date_end):
    print(p)

Produzione

2017-01-01
2016-12-01
2016-11-01
2016-10-01
2016-09-01
2016-08-01
2016-07-01
2016-06-01

3

Può 't * credere a questa domanda esiste da 9 anni senza che nessuno che suggerisce una semplice funzione ricorsiva:

from datetime import datetime, timedelta

def walk_days(start_date, end_date):
    if start_date <= end_date:
        print(start_date.strftime("%Y-%m-%d"))
        next_date = start_date + timedelta(days=1)
        walk_days(next_date, end_date)

#demo
start_date = datetime(2009, 5, 30)
end_date   = datetime(2009, 6, 9)

walk_days(start_date, end_date)

Produzione:

2009-05-30
2009-05-31
2009-06-01
2009-06-02
2009-06-03
2009-06-04
2009-06-05
2009-06-06
2009-06-07
2009-06-08
2009-06-09

Modifica: * Ora posso crederci - vedi Python ottimizza la ricorsione della coda? . Grazie Tim .


3
Perché dovresti sostituire un semplice loop con la ricorsione? Ciò si interrompe per intervalli più lunghi di circa due anni e mezzo.
Tim-Erwin,

@ Tim-Erwin Onestamente non avevo idea che CPython non ottimizzasse la ricorsione della coda, quindi il tuo commento è prezioso.
Tasche e

2

Puoi generare una serie di date tra due date usando la libreria panda in modo semplice e affidabile

import pandas as pd

print pd.date_range(start='1/1/2010', end='1/08/2018', freq='M')

Puoi modificare la frequenza di generazione delle date impostando freq come D, M, Q, Y (giornaliero, mensile, trimestrale, annuale)


Ho

2
> pip install DateTimeRange

from datetimerange import DateTimeRange

def dateRange(start, end, step):
        rangeList = []
        time_range = DateTimeRange(start, end)
        for value in time_range.range(datetime.timedelta(days=step)):
            rangeList.append(value.strftime('%m/%d/%Y'))
        return rangeList

    dateRange("2018-09-07", "2018-12-25", 7)  

    Out[92]: 
    ['09/07/2018',
     '09/14/2018',
     '09/21/2018',
     '09/28/2018',
     '10/05/2018',
     '10/12/2018',
     '10/19/2018',
     '10/26/2018',
     '11/02/2018',
     '11/09/2018',
     '11/16/2018',
     '11/23/2018',
     '11/30/2018',
     '12/07/2018',
     '12/14/2018',
     '12/21/2018']

1

Questa funzione ha alcune funzionalità extra:

  • può passare una stringa corrispondente a DATE_FORMAT per l'inizio o la fine e viene convertita in un oggetto data
  • può passare un oggetto data per l'inizio o la fine
  • controllo degli errori nel caso in cui la fine sia più vecchia dell'inizio

    import datetime
    from datetime import timedelta
    
    
    DATE_FORMAT = '%Y/%m/%d'
    
    def daterange(start, end):
          def convert(date):
                try:
                      date = datetime.datetime.strptime(date, DATE_FORMAT)
                      return date.date()
                except TypeError:
                      return date
    
          def get_date(n):
                return datetime.datetime.strftime(convert(start) + timedelta(days=n), DATE_FORMAT)
    
          days = (convert(end) - convert(start)).days
          if days <= 0:
                raise ValueError('The start date must be before the end date.')
          for n in range(0, days):
                yield get_date(n)
    
    
    start = '2014/12/1'
    end = '2014/12/31'
    print list(daterange(start, end))
    
    start_ = datetime.date.today()
    end = '2015/12/1'
    print list(daterange(start, end))

1

Ecco il codice per una funzione di intervallo di date generale, simile alla risposta di Ber, ma più flessibile:

def count_timedelta(delta, step, seconds_in_interval):
    """Helper function for iterate.  Finds the number of intervals in the timedelta."""
    return int(delta.total_seconds() / (seconds_in_interval * step))


def range_dt(start, end, step=1, interval='day'):
    """Iterate over datetimes or dates, similar to builtin range."""
    intervals = functools.partial(count_timedelta, (end - start), step)

    if interval == 'week':
        for i in range(intervals(3600 * 24 * 7)):
            yield start + datetime.timedelta(weeks=i) * step

    elif interval == 'day':
        for i in range(intervals(3600 * 24)):
            yield start + datetime.timedelta(days=i) * step

    elif interval == 'hour':
        for i in range(intervals(3600)):
            yield start + datetime.timedelta(hours=i) * step

    elif interval == 'minute':
        for i in range(intervals(60)):
            yield start + datetime.timedelta(minutes=i) * step

    elif interval == 'second':
        for i in range(intervals(1)):
            yield start + datetime.timedelta(seconds=i) * step

    elif interval == 'millisecond':
        for i in range(intervals(1 / 1000)):
            yield start + datetime.timedelta(milliseconds=i) * step

    elif interval == 'microsecond':
        for i in range(intervals(1e-6)):
            yield start + datetime.timedelta(microseconds=i) * step

    else:
        raise AttributeError("Interval must be 'week', 'day', 'hour' 'second', \
            'microsecond' or 'millisecond'.")

0

Che dire di quanto segue per fare un intervallo incrementato di giorni:

for d in map( lambda x: startDate+datetime.timedelta(days=x), xrange( (stopDate-startDate).days ) ):
  # Do stuff here
  • startDate e stopDate sono oggetti datetime.date

Per una versione generica:

for d in map( lambda x: startTime+x*stepTime, xrange( (stopTime-startTime).total_seconds() / stepTime.total_seconds() ) ):
  # Do stuff here
  • startTime e stopTime sono oggetti datetime.date o datetime.datetime (entrambi devono essere dello stesso tipo)
  • stepTime è un oggetto timedelta

Nota che .total_seconds () è supportato solo dopo Python 2.7 Se sei bloccato con una versione precedente puoi scrivere la tua funzione:

def total_seconds( td ):
  return float(td.microseconds + (td.seconds + td.days * 24 * 3600) * 10**6) / 10**6

0

Approccio leggermente diverso ai passaggi reversibili memorizzando rangeargs in una tupla.

def date_range(start, stop, step=1, inclusive=False):
    day_count = (stop - start).days
    if inclusive:
        day_count += 1

    if step > 0:
        range_args = (0, day_count, step)
    elif step < 0:
        range_args = (day_count - 1, -1, step)
    else:
        raise ValueError("date_range(): step arg must be non-zero")

    for i in range(*range_args):
        yield start + timedelta(days=i)

0
import datetime
from dateutil.rrule import DAILY,rrule

date=datetime.datetime(2019,1,10)

date1=datetime.datetime(2019,2,2)

for i in rrule(DAILY , dtstart=date,until=date1):
     print(i.strftime('%Y%b%d'),sep='\n')

PRODUZIONE:

2019Jan10
2019Jan11
2019Jan12
2019Jan13
2019Jan14
2019Jan15
2019Jan16
2019Jan17
2019Jan18
2019Jan19
2019Jan20
2019Jan21
2019Jan22
2019Jan23
2019Jan24
2019Jan25
2019Jan26
2019Jan27
2019Jan28
2019Jan29
2019Jan30
2019Jan31
2019Feb01
2019Feb02

Benvenuto in Stack Overflow! Mentre questo codice può risolvere la domanda, inclusa una spiegazione di come e perché questo risolve il problema, specialmente su domande con troppe risposte valide, aiuterebbe davvero a migliorare la qualità del tuo post e probabilmente comporterebbe più voti. Ricorda che stai rispondendo alla domanda per i lettori in futuro, non solo per la persona che chiede ora. Si prega di modificare la risposta per aggiungere spiegazioni e dare un'indicazione di ciò si applicano le limitazioni e le assunzioni. Dalla recensione
bip doppio
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.