mirror of
https://github.com/pocketpy/pocketpy
synced 2025-11-10 05:30:16 +00:00
Add timedelta and test
This commit is contained in:
parent
acc8ab802f
commit
c5592d3742
233
python/datetime.py
Normal file
233
python/datetime.py
Normal file
@ -0,0 +1,233 @@
|
|||||||
|
import math
|
||||||
|
|
||||||
|
|
||||||
|
def float_as_integer_ratio(x):
|
||||||
|
if x == 0:
|
||||||
|
return (0, 1)
|
||||||
|
|
||||||
|
sign = 1 if x > 0 else -1
|
||||||
|
x = abs(x)
|
||||||
|
|
||||||
|
p0, q0 = 0, 1
|
||||||
|
p1, q1 = 1, 0
|
||||||
|
while True:
|
||||||
|
n = int(x)
|
||||||
|
p2 = n * p1 + p0
|
||||||
|
q2 = n * q1 + q0
|
||||||
|
if x == n:
|
||||||
|
break
|
||||||
|
x = 1 / (x - n)
|
||||||
|
p0, q0 = p1, q1
|
||||||
|
p1, q1 = p2, q2
|
||||||
|
gcd_value = math.gcd(p2, q2)
|
||||||
|
return (sign * p2 // gcd_value, q2 // gcd_value)
|
||||||
|
|
||||||
|
|
||||||
|
def _divide_and_round(a, b):
|
||||||
|
"""divide a by b and round result to the nearest integer
|
||||||
|
|
||||||
|
When the ratio is exactly half-way between two integers,
|
||||||
|
the even integer is returned.
|
||||||
|
"""
|
||||||
|
# Based on the reference implementation for divmod_near
|
||||||
|
# in Objects/longobject.c.
|
||||||
|
q, r = divmod(a, b)
|
||||||
|
# round up if either r / b > 0.5, or r / b == 0.5 and q is odd.
|
||||||
|
# The expression r / b > 0.5 is equivalent to 2 * r > b if b is
|
||||||
|
# positive, 2 * r < b if b negative.
|
||||||
|
r *= 2
|
||||||
|
greater_than_half = r > b if b > 0 else r < b
|
||||||
|
if greater_than_half or r == b and q % 2 == 1:
|
||||||
|
q += 1
|
||||||
|
|
||||||
|
return q
|
||||||
|
|
||||||
|
|
||||||
|
class timedelta:
|
||||||
|
def __init__(self, days=0, seconds=0, minutes=0, hours=0, weeks=0):
|
||||||
|
d = 0
|
||||||
|
s = 0
|
||||||
|
|
||||||
|
days += weeks * 7
|
||||||
|
seconds += minutes * 60 + hours * 3600
|
||||||
|
# Get rid of all fractions, and normalize s and us.
|
||||||
|
# Take a deep breath <wink>.
|
||||||
|
if isinstance(days, float):
|
||||||
|
dayfrac, days = math.modf(days)
|
||||||
|
daysecondsfrac, daysecondswhole = math.modf(dayfrac * float(24 * 3600))
|
||||||
|
assert daysecondswhole == int(daysecondswhole) # can't overflow
|
||||||
|
s = int(daysecondswhole)
|
||||||
|
assert days == int(days)
|
||||||
|
d = int(days)
|
||||||
|
else:
|
||||||
|
daysecondsfrac = 0.0
|
||||||
|
d = days
|
||||||
|
|
||||||
|
assert isinstance(daysecondsfrac, float)
|
||||||
|
assert abs(daysecondsfrac) <= 1.0
|
||||||
|
assert isinstance(d, int)
|
||||||
|
assert abs(s) <= 24 * 3600
|
||||||
|
# days isn't referenced again before redefinition
|
||||||
|
|
||||||
|
if isinstance(seconds, float):
|
||||||
|
secondsfrac, seconds = math.modf(seconds)
|
||||||
|
assert seconds == int(seconds)
|
||||||
|
seconds = int(seconds)
|
||||||
|
secondsfrac += daysecondsfrac
|
||||||
|
assert abs(secondsfrac) <= 2.0
|
||||||
|
else:
|
||||||
|
secondsfrac = daysecondsfrac
|
||||||
|
|
||||||
|
# daysecondsfrac isn't referenced again
|
||||||
|
assert isinstance(secondsfrac, float)
|
||||||
|
assert abs(secondsfrac) <= 2.0
|
||||||
|
if round(secondsfrac) == 1.0:
|
||||||
|
s += 1
|
||||||
|
assert isinstance(seconds, int)
|
||||||
|
days, seconds = divmod(seconds, 24 * 3600)
|
||||||
|
|
||||||
|
d += days
|
||||||
|
s += int(seconds) # can't overflow
|
||||||
|
|
||||||
|
while s > 0 and d < 0:
|
||||||
|
s -= 86400
|
||||||
|
d += 1
|
||||||
|
|
||||||
|
while s < 0 and d > 0:
|
||||||
|
s += 86400
|
||||||
|
d -= 1
|
||||||
|
|
||||||
|
assert isinstance(s, int)
|
||||||
|
assert abs(s) <= 2 * 24 * 3600
|
||||||
|
|
||||||
|
# seconds isn't referenced again before redefinition
|
||||||
|
|
||||||
|
if abs(d) > 999999999:
|
||||||
|
raise OverflowError # "timedelta # of days is too large: {d}"
|
||||||
|
self.days = d
|
||||||
|
self.second = s
|
||||||
|
|
||||||
|
def __repr__(self) -> str:
|
||||||
|
args = []
|
||||||
|
if self.days:
|
||||||
|
args.append(f'days={self.days:1d}')
|
||||||
|
if self.second:
|
||||||
|
args.append(f'seconds={self.second:1d}')
|
||||||
|
if not args:
|
||||||
|
args.append('0')
|
||||||
|
args = ", ".join(map(str, args))
|
||||||
|
return f'datetime.timedelta({args})'
|
||||||
|
|
||||||
|
def __cmp__(self, other) -> int:
|
||||||
|
assert isinstance(other, timedelta)
|
||||||
|
return self._cmp(self._getstate(), other._getstate())
|
||||||
|
|
||||||
|
def __eq__(self, other: 'timedelta') -> bool:
|
||||||
|
if isinstance(other, timedelta):
|
||||||
|
return self.__cmp__(other) == 0
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def __lt__(self, other: 'timedelta') -> bool:
|
||||||
|
if isinstance(other, timedelta):
|
||||||
|
return self.__cmp__(other) < 0
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def __le__(self, other: 'timedelta') -> bool:
|
||||||
|
if isinstance(other, timedelta):
|
||||||
|
return self.__cmp__(other) <= 0
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def __gt__(self, other: 'timedelta') -> bool:
|
||||||
|
if isinstance(other, timedelta):
|
||||||
|
return self.__cmp__(other) > 0
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def __ge__(self, other: 'timedelta') -> bool:
|
||||||
|
if isinstance(other, timedelta):
|
||||||
|
return self.__cmp__(other) >= 0
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def __radd__(self, other: 'timedelta') -> 'timedelta':
|
||||||
|
if isinstance(other, timedelta):
|
||||||
|
return timedelta(days=self.days + other.days, seconds=self.second + other.second)
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def __add__(self, other: 'timedelta') -> 'timedelta':
|
||||||
|
if isinstance(other, timedelta):
|
||||||
|
return timedelta(days=self.days + other.days, seconds=self.second + other.second)
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def __rsub__(self, other: 'timedelta'):
|
||||||
|
if isinstance(other, timedelta):
|
||||||
|
return -self + other
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def __sub__(self, other: 'timedelta') -> 'timedelta':
|
||||||
|
if isinstance(other, timedelta):
|
||||||
|
return timedelta(days=self.days - other.days, seconds=self.second - other.second)
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def __mul__(self, other: 'timedelta') -> 'timedelta':
|
||||||
|
if isinstance(other, int):
|
||||||
|
# for CPython compatibility, we cannot use
|
||||||
|
# our __class__ here, but need a real timedelta
|
||||||
|
return timedelta(self.days * other, self.second * other)
|
||||||
|
raise NotImplemented
|
||||||
|
|
||||||
|
def __neg__(self) -> 'timedelta':
|
||||||
|
return timedelta(days=-self.days, seconds=-self.second)
|
||||||
|
|
||||||
|
def __pos__(self):
|
||||||
|
return self
|
||||||
|
|
||||||
|
def __abs__(self) -> 'timedelta':
|
||||||
|
if self.days < 0:
|
||||||
|
return -self
|
||||||
|
else:
|
||||||
|
return self
|
||||||
|
|
||||||
|
def _to_seconds(self) -> int:
|
||||||
|
return self.days * (24 * 3600) + self.second
|
||||||
|
|
||||||
|
def __floordiv__(self, other: 'timedelta') -> 'timedelta':
|
||||||
|
if not (isinstance(other, int) or isinstance(other, timedelta)):
|
||||||
|
raise NotImplemented
|
||||||
|
usec = self._to_seconds()
|
||||||
|
if isinstance(other, timedelta):
|
||||||
|
return usec // other._to_seconds()
|
||||||
|
if isinstance(other, int):
|
||||||
|
return timedelta(0, usec // other)
|
||||||
|
|
||||||
|
def __truediv__(self, other: 'timedelta') -> 'timedelta':
|
||||||
|
if not (isinstance(other, int) or isinstance(other, float) or isinstance(other, timedelta)):
|
||||||
|
raise NotImplemented
|
||||||
|
usec = self._to_seconds()
|
||||||
|
if isinstance(other, timedelta):
|
||||||
|
return usec / other._to_seconds()
|
||||||
|
if isinstance(other, int):
|
||||||
|
return timedelta(0, _divide_and_round(usec, other))
|
||||||
|
if isinstance(other, float):
|
||||||
|
a, b = float_as_integer_ratio(other)
|
||||||
|
return timedelta(0, _divide_and_round(b * usec, a))
|
||||||
|
|
||||||
|
def _getstate(self):
|
||||||
|
return (self.days, self.second)
|
||||||
|
|
||||||
|
def _cmp(self, a, b) -> int:
|
||||||
|
if a[0] > b[0]:
|
||||||
|
return 1
|
||||||
|
if a[0] < b[0]:
|
||||||
|
return -1
|
||||||
|
if a[1] > b[1]:
|
||||||
|
return 1
|
||||||
|
if a[1] < b[1]:
|
||||||
|
return -1
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def __hash__(self):
|
||||||
|
return hash(self._getstate())
|
||||||
|
|
||||||
|
|
||||||
|
timedelta.min = timedelta(-999999999)
|
||||||
|
timedelta.max = timedelta(days=999999999, seconds=59)
|
||||||
|
timedelta.resolution = timedelta(seconds=1)
|
||||||
88
tests/70_datetime.py
Normal file
88
tests/70_datetime.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
|
assert repr(timedelta(days=50, seconds=27)) == 'datetime.timedelta(days=50, seconds=27)'
|
||||||
|
assert repr(timedelta(days=1.0 / (60 * 60 * 24), seconds=0)) == 'datetime.timedelta(seconds=1)'
|
||||||
|
assert repr(timedelta(days=0, seconds=1.0)) == 'datetime.timedelta(seconds=1)'
|
||||||
|
assert repr(timedelta(days=1.0 / (60 * 60 * 24), seconds=0)) == 'datetime.timedelta(seconds=1)'
|
||||||
|
assert repr(timedelta(42)) == 'datetime.timedelta(days=42)'
|
||||||
|
|
||||||
|
|
||||||
|
def eq(a, b):
|
||||||
|
assert a == b
|
||||||
|
|
||||||
|
|
||||||
|
# test_constructor:
|
||||||
|
# Check keyword args to constructor
|
||||||
|
eq(timedelta(), timedelta(days=0, seconds=0, minutes=0, hours=0, weeks=0))
|
||||||
|
eq(timedelta(1), timedelta(days=1))
|
||||||
|
eq(timedelta(0, 1), timedelta(seconds=1))
|
||||||
|
|
||||||
|
# Check float args to constructor
|
||||||
|
eq(timedelta(days=1.0), timedelta(seconds=60 * 60 * 24))
|
||||||
|
eq(timedelta(days=1.0 / 24), timedelta(hours=1))
|
||||||
|
eq(timedelta(hours=1.0 / 60), timedelta(minutes=1))
|
||||||
|
eq(timedelta(minutes=1.0 / 60), timedelta(seconds=1))
|
||||||
|
|
||||||
|
# test_hash_equality:
|
||||||
|
t1 = timedelta(days=100, seconds=-8640000)
|
||||||
|
t2 = timedelta()
|
||||||
|
eq(hash(t1), hash(t2))
|
||||||
|
|
||||||
|
a = timedelta(days=7) # One week
|
||||||
|
b = timedelta(0, 60) # One minute
|
||||||
|
eq(a + b, timedelta(7, 60))
|
||||||
|
eq(a - b, timedelta(6, 24 * 3600 - 60))
|
||||||
|
eq(b.__rsub__(a), timedelta(6, 24 * 3600 - 60))
|
||||||
|
eq(-a, timedelta(-7))
|
||||||
|
eq(a, timedelta(7))
|
||||||
|
eq(timedelta(-1, 24 * 3600 - 60), -b)
|
||||||
|
|
||||||
|
eq(timedelta(6, 24 * 3600), a)
|
||||||
|
# TODO __rmul__
|
||||||
|
# eq(a * 10, timedelta(70))
|
||||||
|
# eq(a * 10, 10 * a)
|
||||||
|
# eq(a * 10, 10 * a)
|
||||||
|
# eq(b * 10, timedelta(0, 600))
|
||||||
|
# eq(10 * b, timedelta(0, 600))
|
||||||
|
eq(b * 10, timedelta(0, 600))
|
||||||
|
eq(a * -1, -a)
|
||||||
|
eq(b * -2, -b - b)
|
||||||
|
eq(b * (60 * 24), (b * 60) * 24)
|
||||||
|
eq(a // 7, timedelta(1))
|
||||||
|
eq(b // 10, timedelta(0, 6))
|
||||||
|
eq(a // 10, timedelta(0, 7 * 24 * 360))
|
||||||
|
eq(a / 0.5, timedelta(14))
|
||||||
|
eq(b / 0.5, timedelta(0, 120))
|
||||||
|
eq(a / 7, timedelta(1))
|
||||||
|
eq(b / 10, timedelta(0, 6))
|
||||||
|
eq(a / 10, timedelta(0, 7 * 24 * 360))
|
||||||
|
|
||||||
|
# test_compare:
|
||||||
|
t1 = timedelta(2, 4)
|
||||||
|
t2 = timedelta(2, 4)
|
||||||
|
eq(t1, t2)
|
||||||
|
|
||||||
|
assert t1 <= t2
|
||||||
|
assert t1 >= t2
|
||||||
|
assert not t1 < t2
|
||||||
|
assert not t1 > t2
|
||||||
|
|
||||||
|
# test_repr:
|
||||||
|
eq(repr(timedelta(1)), "datetime.timedelta(days=1)")
|
||||||
|
eq(repr(timedelta(10, 2)), "datetime.timedelta(days=10, seconds=2)")
|
||||||
|
eq(repr(timedelta(seconds=60)), "datetime.timedelta(seconds=60)")
|
||||||
|
eq(repr(timedelta()), "datetime.timedelta(0)")
|
||||||
|
|
||||||
|
# test_resolution_info:
|
||||||
|
assert isinstance(timedelta.min, timedelta)
|
||||||
|
assert isinstance(timedelta.max, timedelta)
|
||||||
|
assert isinstance(timedelta.resolution, timedelta)
|
||||||
|
assert timedelta.max > timedelta.min
|
||||||
|
eq(timedelta.min, timedelta(-999999999))
|
||||||
|
eq(timedelta.max, timedelta(999999999, 59))
|
||||||
|
eq(timedelta.resolution, timedelta(0, 1))
|
||||||
|
|
||||||
|
# test_bool:
|
||||||
|
assert timedelta(1)
|
||||||
|
assert timedelta(0, 1)
|
||||||
|
assert timedelta(0)
|
||||||
Loading…
x
Reference in New Issue
Block a user