Dear community,
Rounding of datetime has been a recurring issue for many (400,000+) users [ 1, 2, 3, 4 ] and the recommended solutions have been anything but pretty.
I would like to propose the following simple method for rounding to be added/merged into the stdlibs (where?).
from datetime import datetime, timedelta, timezone
epoch = datetime(2000,1,1,0,0,0,0,timezone.utc)
epoch_no_tz = datetime(2000,1,1,0,0,0,0)
def round(value, multiple, up=None):
""" a nicer way to round numbers.
:param value: float/integer
:param multiple: base of the rounding.
:param up: None (default) or boolean rounds half, up or down.
round(1.6, 1) rounds to 2.
round(1.4, 1) rounds to 1.
round(1.5, 1, up=True) rounds to 2.
round(1.5, 1, up=False) rounds to 1.
:return: value
Examples:
multiple = 1 is the same as rounding to whole integers.
multiple = 0.001 is the same as rounding to 3 digits precision.
mulitple = 3.1415 is rounding to nearest multiplier of 3.1415
dt = datetime(2022,8,18,11,14,53,440)
td = timedelta(hours=0.5)
round(dt,td) is datetime(2022,8,18,11,0)
"""
epoch = 0
if isinstance(value, (datetime)) and isinstance(multiple, timedelta):
if value.tzinfo is None:
epoch = DataTypes.epoch_no_tz
else:
epoch = DataTypes.epoch
low = ((value - epoch) // multiple) * multiple
high = low + multiple
if up is True:
return high + epoch
elif up is False:
return low + epoch
else:
if abs((high + epoch) - value) < abs(value-(low + epoch)):
return high + epoch
else:
return low + epoch
Here is the test suite:
import datatypes # the module above
import math
def test_round():
xround = datatypes.round # xround is merely used to distinguish from common `round` method.
# round up
assert xround(1.6, 1, True) == 2
assert xround(1.4, 1, True) == 2
# round down
assert xround(1.6, 1, False) == 1
assert xround(1.4, 1, False) == 1
# round half
assert xround(1.6, 1) == 2
assert xround(1.4, 1) == 1
# round half
assert xround(16, 10) == 20
assert xround(14, 10) == 10
# round half
assert xround(-16, 10) == -20
assert xround(-14, 10) == -10
# round to odd multiples
assert xround(6, 3.1415, 1) == 2 * 3.1415
assert xround(1.2345, 0.001, True) == 1.2349999999999999 and math.isclose(1.2349999999999999, 1.235)
assert xround(1.2345, 0.001, False) == 1.234
assert xround(123, 100, False) == 100
assert xround(123, 100, True) == 200
assert xround(123, 5.07, False) == 24 * 5.07
dt = datetime(2022,8,18,11,14,53,440)
td = timedelta(hours=0.5)
assert xround(dt,td, up=False) == datetime(2022,8,18,11,0)
assert xround(dt,td, up=None) == datetime(2022,8,18,11,0)
assert xround(dt,td, up=True) == datetime(2022,8,18,11,30)
td = timedelta(hours=24)
assert xround(dt,td, up=False) == datetime(2022,8,18)
assert xround(dt,td, up=None) == datetime(2022,8,18)
assert xround(dt,td, up=True) == datetime(2022,8,19)
td = timedelta(days=0.5)
assert xround(dt,td, up=False) == datetime(2022,8,18)
assert xround(dt,td, up=None) == datetime(2022,8,18,12)
assert xround(dt,td, up=True) == datetime(2022,8,18,12)
td = timedelta(days=1.5)
assert xround(dt,td, up=False) == datetime(2022,8,18)
assert xround(dt,td, up=None) == datetime(2022,8,18)
assert xround(dt,td, up=True) == datetime(2022,8,19,12)
td = timedelta(seconds=0.5)
assert xround(dt,td, up=False) == datetime(2022,8,18,11,14,53,0)
assert xround(dt,td, up=None) == datetime(2022,8,18,11,14,53,0)
assert xround(dt,td, up=True) == datetime(2022,8,18,11,14,53,500000)
td = timedelta(seconds=40000)
assert xround(dt,td, up=False) == datetime(2022,8,18,6,40)
assert xround(dt,td, up=None) == datetime(2022,8,18,6,40)
assert xround(dt,td, up=True) == datetime(2022,8,18,17,46,40)
Kind regards
Dr. Bjorn Madsen