nutools/lib/nulib/python/nulib/times.py

222 lines
7.9 KiB
Python

# -*- coding: utf-8 mode: python -*- vim:sw=4:sts=4:et:ai:si:sta:fenc=utf-8
"""Des fonction pour gérer les heures.
"""
__all__ = ('pytime',
'NUM_TIMEF', 'FR_TIMEF', 'HM_TIMEF',
'Time', 'istime', 'isanytime',
'parse_time', 'ensure_time',
)
import re
from time import time as timedbl
from time import localtime
from datetime import time as pytime, datetime as pydatetime, timedelta
from .base import isstr, isnum
from .uio import _s, _u
NUM_TIMEF = '%H%M%S'
FR_TIMEF = '%H:%M:%S'
HM_TIMEF = '%H:%M'
def _fix_second(minute, second):
while second > 59:
second -= 60
minute += 1
while second < 0:
second += 60
minute -= 1
return minute, second
def _fix_minute(hour, minute):
while minute > 59:
minute -= 60
hour += 1
while minute < 0:
minute += 60
hour -= 1
return hour, minute
def _fix_hour(hour):
return hour % 24
def _fix_time(hour, minute, second):
minute, second = _fix_second(minute, second)
hour, minute = _fix_minute(hour, minute)
hour = _fix_hour(hour)
return hour, minute, second
class Time(object):
"""Un wrapper pour 'datetime.time'.
Attention! Cet objet est mutable, il ne faut donc pas l'utiliser comme clé
dans un dictionnaire.
"""
_t = None
time = property(lambda self: self._t)
hour = property(lambda self: self._t.hour)
minute = property(lambda self: self._t.minute)
second = property(lambda self: self._t.second)
def __init__(self, hour=None, minute=None, second=None, t=None, s=None):
"""Initialiser l'objet
Dans l'ordre, les champs considérés sont:
- s le nombre de secondes depuis minuit.
- hour, minute, second la spécification de l'heure.
- t le nombre de secondes depuis l'epoch, comme retourné par
time.time().
- sinon prendre l'heure courante.
"""
if s is not None:
hour, s = s / 3600, s % 3600
minute, second = s / 60, s % 60
elif hour is None and minute is None and second is None:
if t is None: t = timedbl()
hour, minute, second = localtime(t)[3:6]
hour, minute, second = _fix_time(hour or 0, minute or 0, second or 0)
self._t = pytime(hour, minute, second)
FORMAT_MAP = {'%Y': '%(y)04i', '%m': '%(m)02i', '%d': '%(d)02i',
'%H': '%(H)02i', '%M': '%(M)02i', '%S': '%(S)02i',
}
def format(self, format=None):
"""Formater l'heure pour affichage.
Les champs valides sont %Y, %m, %d qui valent toujours 0, et %H, %M, %S
qui correspondent à l'heure de cet objet.
"""
if format is None: format = FR_TIMEF
y, m, d, H, M, S = 0, 0, 0, self.hour, self.minute, self.second
for fr, to in self.FORMAT_MAP.items():
format = format.replace(fr, to)
return format % locals()
def nbsecs(self):
"""Retourner le nombre de secondes depuis minuit.
"""
return self.hour * 3600 + self.minute * 60 + self.second
nbsecs = property(nbsecs)
def __repr__(self):
if self.second == 0: seconds = ''
else: seconds = ', %i' % self.second
return '%s(%i, %i%s)' % (self.__class__.__name__, self.hour, self.minute, seconds)
def __str__(self):
if self.second == 0: seconds = ''
else: seconds = ':%02i' % self.second
return '%02i:%02i%s' % (self.hour, self.minute, seconds)
def __unicode__(self):
if self.second == 0: seconds = ''
else: seconds = u':%02i' % self.second
return u'%02i:%02i%s' % (self.hour, self.minute, seconds)
def __eq__(self, other): return self._t == self._time(other, False)
def __ne__(self, other): return self._t != self._time(other, False)
def __lt__(self, other):
if other is None: return False
else: return self._t < self._time(other)
def __le__(self, other):
if other is None: return False
else: return self._t <= self._time(other)
def __gt__(self, other):
if other is None: return True
else: return self._t > self._time(other)
def __ge__(self, other):
if other is None: return True
else: return self._t >= self._time(other)
def __cmp__(self, other):
if other is None: return 1
else: return cmp(self._t, self._time(other))
def __hash__(self): return hash(self._t)
def _time(self, t, required=True):
"""Retourner l'instance de datetime.time correspondant à l'objet t.
"""
if isinstance(t, pytime): return t
elif isinstance(t, pydatetime): return t.timetz()
elif isinstance(t, Time): return t._t
elif required: raise ValueError("Expected datetime.*time or Time instance, got %s" % repr(t))
else: return None
def _s(self, t=None):
"""Obtenir le nombre de secondes depuis minuit de t.
"""
if t is None: t = self
if isinstance(t, Time): t = t._t
if isinstance(t, pytime):
return t.hour * 3600 + t.minute * 60 + t.second
elif isinstance(t, pydatetime):
return t.hour * 3600 + t.minute * 60 + t.second
elif isinstance(t, timedelta): return t.seconds
elif isnum(t): return t
else: raise ValueError("Expected number, datetime.*time, datetime.timedelta or Time instance, got %s" % repr(t))
def _new(cls, t=None, s=None):
"""Constructeur.
* t est une instance de Time, datetime.time ou datetime.datetime.
* s est un nombre de secondes depuis minuit.
"""
if t is not None:
if isinstance(t, pytime): return cls(t.hour, t.minute, t.second)
elif isinstance(t, pydatetime): return cls(t.hour, t.minute, t.second)
elif isinstance(t, Time): return cls(t.hour, t.minute, t.second)
else: raise ValueError("Expected datetime.*time or Time instance, got %s" % repr(t))
elif s is not None: return cls(s=s)
else: return cls()
_new = classmethod(_new)
def replace(self, hour=None, minute=None, second=None):
"""Retourner une nouvelle instance avec les champs spécifiés modifiés.
"""
kw = {}
for name, value in [('hour', hour), ('minute', minute), ('second', second)]:
if value is not None: kw[name] = value
return self._new(self._t.replace(**kw))
def __add__(self, other): return self._new(s=self._s() + self._s(other))
__radd__ = __add__
def add(self, seconds=1): return self + seconds
def __sub__(self, other): return self._new(s=self._s() - self._s(other))
__rsub__ = __sub__
def sub(self, seconds=1): return self - seconds
def diff(self, other): return self._s() - self._s(other)
def istime(t):
"""Tester si t est une instance de Time
"""
return isinstance(t, Time)
def isanytime(t):
"""Tester si t est une instance de Time, datetime.time ou datetime.datetime
"""
return isinstance(t, Time) or isinstance(t, pytime) or isinstance(t, pydatetime)
RE_TIME = re.compile(r'(\d+)(?:[.:](\d+)(?:[.:](\d+))?)?$')
def parse_time(s):
"""Parser une chaine et retourner une instance de Time.
"""
mo = RE_TIME.match(s)
if mo is None: raise ValueError("Invalid time format: %s" % _s(s))
hour = mo.group(1)
minute = mo.group(2)
second = mo.group(3)
if hour is not None: hour = int(hour)
if minute is not None: minute = int(minute)
if second is not None: second = int(second)
return Time(hour, minute, second)
def ensure_time(t):
"""Retourner une instance de Time, ou None si t==None.
t peut être une instance de Time, datetime.time, datetime.datetime ou une
chaine.
"""
if t is None: return None
elif isinstance(t, Time): return t
elif isinstance(t, pytime): return Time._new(t)
elif isinstance(t, pydatetime): return Time._new(t)
if not isstr(t): t = _s(t)
return parse_time(t)