Personal tools
Pyinter: come gestire intervalli di tempo con Python

Peace on Earth

Apr 30, 2014

Pyinter: come gestire intervalli di tempo con Python

Evitare overbooking? Trovare momenti liberi o individuare eventi che ricadono tra due istanti di tempo? Inutile spaccarsi la testa o reinventare la ruota

Perché Pynter

La libreria gestisce oggetti rappresentabili sotto forma di un intervallo matematico: in soldoni e senza essere troppo rigorosi, un intervallo è qualcosa che ha un inizio e una fine. Vien da sé che per descrivere oggetti come appuntamenti, prenotazioni ed eventi è perfetta!

Di librerie simili ce ne sono molte su PyPI, ad esempio:

Una tale abbondanza vi fa capire che non c'è la "libreria fine di mondo" per fare queste cose e che la mia scelta è stata personale e fondata su motivi per me importanti:
  • copriva in modo semplice parte dei miei casi d'uso
  • la documentazione è chiara
  • il codice (ben scritto) è disponibile su GitHub e appariva mantenuto e recente.

Piuttosto che rimanere paralizzato dalla troppa libertà di scelta, ho smesso di preoccuparmi e iniziato ad amare la libreria.

Imparare Pyinter in 2 minuti (circa)

Attenzione!
Stai per leggere un tutorial destinato a portatori sani di virtualenv. L'elevata esposizione determina il contagio.
Installiamo la libreria in un virtualenv chiamato test-pyinter per indagarne il funzionamento di base:
$ virtualenv test-pyinter
New python executable in test-pyinter/bin/python2.7
...
Installing pip................done.
$ cd test-pyinter/
$ . bin/activate
$ pip install pyinter
Il vostro ambiente è ora pronto per il resto del tutorial. Lanciamo una shell e importiamo il modulo.
$ python
Python 2.7...
>>>  import pyinter
La prima cosa che faremo è creare un intervallo. In questo tutorial non distingueremo tra intervalli chiusi e aperti, lascio a voi approfondire la questione.
Utilizziamo la funzione closed:
>>> pyinter.closed(1, 10)
[1, 10]

Possiamo trovare eventuali intersezioni con un altro intervallo (se presenti):

>>> pyinter.closed(0,10).intersect(pyinter.closed(3,7))
[3, 7]
>>> pyinter.closed(0,10).intersect(pyinter.closed(10,30)) # intervallo "nullo"
[10, 10]
>>> pyinter.closed(0,10).intersect(pyinter.closed(20,30)) is None # nessuna intersezione
True
Possiamo unire intervalli. Per fare questo dobbiamo lavorare con i "set" di intervalli:
>>> pyinter.IntervalSet
<class 'pyinter.interval_set.IntervalSet'>
>>> pyinter.IntervalSet([pyinter.closed(0,10)]).union(pyinter.IntervalSet([pyinter.closed(5,15)]))
IntervalSet([0, 15],)
Come vedete l'utilizzo è semplice e intuitivo, ma per un utilizzo completo e maggiori dettagli vi invito ancora una volta a leggere la documentazione.

Usare Pyinter per gestire intervalli di tempo

Gli intervalli sono delimitati da numeri reali (anche se con Pyinter potete usare altri tipi di oggetti, purché siano ordinabili).
A questo punto dobbiamo decidere una convenzione per trasformare i nostri orari in numeri.
Per farla semplice lavoreremo usando unità di un'ora. Quindi un giorno intero sarà rappresentato dall'intervallo [0, 24]:
>>> giorno = pyinter.closed(0, 24)
>>> giorno
[0, 24]
Supponiamo di gestire una giornata lavorativa divisa in due parti, dalle 9 alle 13 e dalle 14 alle 18.
>>> mattina = pyinter.closed(9, 13)
>>> pomeriggio = pyinter.closed(14, 18)
>>> orario_di_lavoro = pyinter.IntervalSet((mattina, pomeriggio))
>>> orario_di_lavoro
IntervalSet([9, 13], [14, 18])
Definiamo un appuntamento e verifichiamo che possiamo svolgerlo in mattinata e non nel pomeriggio:
>>> appuntamento1 = pyinter.closed(10, 12)
>>> appuntamento1 in mattina
True
>>> appuntamento1 in pomeriggio
False
Notare che il controllo funziona anche sull'unione dei due intervalli:
>>> appuntamento1 in orario_di_lavoro
True
Se l'appuntamento fosse durato oltre le 13 non avremmo potuto gestirlo negli orari di lavoro.
>>> appuntamento2 = pyinter.closed(11, 14)
>>> appuntamento2 in orario_di_lavoro
False
Un eventuale terzo appuntamento dalle 9 alle 11 non sarebbe gestibile perché andrebbe in conflitto con l'appuntamento1:
>>> appuntamento3 = pyinter.closed(9, 11)
>>> appuntamento3.overlaps(appuntamento1)
True
Questi esempi già permettono di evidenziare conflitti e trovare eventuali sovrapposizioni senza aver scritto una riga di codice.

Cosa manca?

La libreria in questione non consente di effettuare con semplicità la sottrazione da un intervallo di tempo di altri intervalli temporali.
Questa funzionalità permette, per esempio, di trovare in un intervallo di tempo eventuali "buchi liberi".
Per fortuna è possibile estendere la libreria per fare questo.

Il codice lo trovate su questo gist. Siccome sono buono, vi evito di scaricarlo e ve lo faccio provare con un trick Pythonico di cui dubito foste a conoscenza.

E per stupirvi meglio ve lo presento nel sottopost!

Il sottopost: come importare un gist al volo

Questa è una chicca che meriterebbe un post a parte, ma potete importare il codice del gist direttamente nel vostro interprete Python con queste due semplici righe:
>>> from urllib import urlopen
>>> exec urlopen('http://goo.gl/DnCNSP').read() in globals()
>>> TimeSlot
<class '__main__.TimeSlot'>

Le sottoconclusioni

Usare exec è una cosa brutta: non fatelo vedere ai bambini e ai programmatori autolesionisti, quelli che appena vedono una cosa nuova devono usarla nel contesto sbagliato per rovinarsi i weekend.

 

Sottrazione di intervalli

Usando la classe TimeSlot importata dal gist, potete finalmente effettuare il calcolo che vi serve:
>>> TimeSlot(0, 24) -TimeSlot(9, 13)
[[0, 9], [13, 24]]
>>> TimeSlot(0, 24) - [TimeSlot(9, 13), TimeSlot(14, 18)]
[[0, 9], [13, 14], [18, 24]]

Note al post e al sottopost

So benissimo di aver usato un linguaggio improprio da un punto di vista matematico.
Molto probabilmente sarebbe stato più opportuno gestire intervalli semichiusi, per cui una giornata sarebbe stata più propriamente rappresentata dall'intervallo [0, 24] ma non volevo scendere troppo nei dettagli, visto che il mio scopo è semplicemente quello di illustrare come è possibile usare una libreria matematica per gestire in modo efficiente e senza scrivere troppo codice (e bug!) per gestire oggetti già ben noti in campo matematico e informatico.
Per chi volesse approfondire, lascio il link alla pagina di wikipedia sugli intervalli.

Credits

Le immagini del testo sono prese da wikimedia.

Le immagini in testata e su facebook sono una rielaborazione di questa.

Filed under: ,
comments powered by Disqus