from datetime import timedelta, datetime, timezone from .timescales import SCALES def decompose_interval(attrname): scales = [scale[1] for scale in SCALES] scales.reverse() def decorator(cls): scl_name = '{}_scale'.format(attrname) sig_name = '{}_significand'.format(attrname) @property def scale(self): if getattr(self, attrname) == timedelta(0): return timedelta(minutes=1) for m in scales: if getattr(self, attrname) % m == timedelta(0): return m return timedelta(minutes=1) @scale.setter def scale(self, value): if not isinstance(value, timedelta): value = timedelta(seconds=float(value)) setattr(self, attrname, max(1, getattr(self, sig_name)) * value) @property def significand(self): return int(getattr(self, attrname) / getattr(self, scl_name)) @significand.setter def significand(self, value): if isinstance(value, str) and value.strip() == '': value = 0 try: value = int(value) if not value >= 0: raise ValueError(value) except ValueError as e: raise ValueError("Incorrect time interval", e) setattr(self, attrname, value * getattr(self, scl_name)) setattr(cls, scl_name, scale) setattr(cls, sig_name, significand) return cls return decorator def relative(interval): # special cases if interval > timedelta(seconds=-15) and interval < timedelta(0): return "just now" elif interval > timedelta(0) and interval < timedelta(seconds=15): return "in a few seconds" else: output = None for name, scale in reversed(SCALES): if abs(interval) > scale: value = abs(interval) // scale output = '{} {}'.format(value, name) if value == 1: output = output[:-1] break if not output: output = '{} seconds'.format(abs(interval).seconds) if interval > timedelta(0): return 'in {}'.format(output) else: return '{} ago'.format(output) def relnow(time): return relative(time - datetime.now(timezone.utc))