duration.py 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. from enum import *
  2. from abc import *
  3. from dataclasses import *
  4. from typing import *
  5. import math
  6. import functools
  7. @dataclass
  8. class _Unit:
  9. notation: str
  10. relative_size: int
  11. larger: Optional[Tuple[Any, int]] = None
  12. # smaller: Optional[Tuple[Any, int]] = None
  13. def __eq__(self, other):
  14. return self is other
  15. FemtoSecond = _Unit("fs", 1)
  16. PicoSecond = _Unit("ps", 1000)
  17. Nanosecond = _Unit("ns", 1000000)
  18. Microsecond = _Unit("µs", 1000000000)
  19. Millisecond = _Unit("ms", 1000000000000)
  20. Second = _Unit("s", 1000000000000000)
  21. Minute = _Unit("m", 60000000000000000)
  22. Hour = _Unit("h", 3600000000000000000)
  23. Day = _Unit("D", 86400000000000000000)
  24. FemtoSecond.larger = (PicoSecond, 1000)
  25. PicoSecond.larger = (Nanosecond, 1000)
  26. Nanosecond.larger = (Microsecond, 1000)
  27. Microsecond.larger = (Millisecond, 1000)
  28. Millisecond.larger = (Second, 1000)
  29. Second.larger = (Minute, 60)
  30. Minute.larger = (Hour, 60)
  31. Hour.larger = (Day, 24)
  32. # Day.smaller = (Hour, 24)
  33. # Hour.smaller = (Minute, 60)
  34. # Minute.smaller = (Second, 60)
  35. # Second.smaller = (Millisecond, 1000)
  36. # Millisecond.smaller = (Microsecond, 1000)
  37. # Microsecond.smaller = (Nanosecond, 1000)
  38. # Nanosecond.smaller = (PicoSecond, 1000)
  39. # PicoSecond.smaller = (FemtoSecond, 1000)
  40. class Duration(ABC):
  41. def __repr__(self):
  42. return "Duration("+self.__str__()+")"
  43. @abstractmethod
  44. def __str__(self):
  45. pass
  46. @abstractmethod
  47. def __eq__(self):
  48. pass
  49. @abstractmethod
  50. def __mul__(self):
  51. pass
  52. def __floordiv__(self, other: 'Duration') -> int:
  53. if other is _zero:
  54. raise ZeroDivisionError("duration floordiv by zero duration")
  55. self_val, other_val, _ = _same_unit(self, other)
  56. return self_val // other_val
  57. def __mod__(self, other):
  58. self_val, other_val, unit = _same_unit(self, other)
  59. new_val = self_val % other_val
  60. if new_val == 0:
  61. return _zero
  62. else:
  63. return _NonZeroDuration(new_val, unit)
  64. def __lt__(self, other):
  65. self_val, other_val, unit = _same_unit(self, other)
  66. return self_val < other_val
  67. class _ZeroDuration(Duration):
  68. def _convert(self, unit: _Unit) -> int:
  69. return 0
  70. def __str__(self):
  71. return '0 d'
  72. def __eq__(self, other):
  73. return self is other
  74. def __mul__(self, other: int) -> Duration:
  75. return self
  76. # Commutativity
  77. __rmul__ = __mul__
  78. _zero = _ZeroDuration() # Singleton. Only place the constructor should be called.
  79. def duration(val: int, unit: Optional[_Unit] = None) -> Duration:
  80. if unit is None:
  81. if val != 0:
  82. raise Exception("Duration: Non-zero value should have unit")
  83. else:
  84. return _zero
  85. else:
  86. if val == 0:
  87. raise Exception("Duration: Zero value should not have unit")
  88. else:
  89. return _NonZeroDuration(val, unit)
  90. # @dataclass
  91. class _NonZeroDuration(Duration):
  92. def __init__(self, val: int, unit: _Unit = None):
  93. self.val = val
  94. self.unit = unit
  95. while self.unit.larger:
  96. next_unit, factor = self.unit.larger
  97. if self.val % factor != 0:
  98. break
  99. self.val //= factor
  100. self.unit = next_unit
  101. # Can only convert to smaller units.
  102. # Returns new Duration.
  103. def _convert(self, unit: _Unit) -> int:
  104. # Precondition
  105. assert self.unit.relative_size >= unit.relative_size
  106. factor = self.unit.relative_size // unit.relative_size
  107. return self.val * factor
  108. def __str__(self):
  109. return str(self.val)+' '+self.unit.notation
  110. def __eq__(self, other):
  111. if isinstance(other, _ZeroDuration):
  112. return False
  113. return self.val == other.val and self.unit is other.unit
  114. def __mul__(self, other: int) -> Duration:
  115. if other == 0:
  116. return _zero
  117. return _NonZeroDuration(self.val * other, self.unit)
  118. # Commutativity
  119. __rmul__ = __mul__
  120. def _same_unit(x: Duration, y: Duration) -> Tuple[int, int, _Unit]:
  121. if x is _zero:
  122. if y is _zero:
  123. return (0, 0, None)
  124. else:
  125. return (0, y.val, y.unit)
  126. if y is _zero:
  127. return (x.val, 0, x.unit)
  128. if x.unit.relative_size >= y.unit.relative_size:
  129. return (x._convert(y.unit), y.val, y.unit)
  130. else:
  131. return (x.val, y._convert(x.unit), x.unit)
  132. return (x_conv, y_conv, unit)
  133. def gcd_pair(x: Duration, y: Duration) -> Duration:
  134. x_conv, y_conv, unit = _same_unit(x, y)
  135. gcd = math.gcd(x_conv, y_conv)
  136. return duration(gcd, unit)
  137. def gcd(*iterable: Iterable[Duration]) -> Duration:
  138. return functools.reduce(gcd_pair, iterable, _zero)