Unexpected millisecond offsets when doing subtraction with datetime and timedelta objects - python

I have this class implemented in python on which i am performing some unitests
from datetime import datetime, timedelta
class FreeTime:
"""Range in a timeframe where a task can be located"""
def __init__(self, start: datetime, end: datetime) -> None:
self.start = start
""" start of the freetime """
self.end = end
""" end of the freetime """
self.duration = end - start
""" duration of the freetime"""
def set_start(self, start: datetime):
self.start = start
self.duration = self.end - self.start
def set_duration(self, duration: timedelta):
self.duration = duration
self.start = self.end - self.duration
def __repr__(self) -> str:
return f"FreeTime - {self.start} -> {self.end} - {self.duration}"
The test looks like this:
from datetime import datetime, timedelta
def test_FreeTime():
"""test the FreeTime class"""
start = datetime.now()
end = start + timedelta(hours=4.0)
free_time = FreeTime(start=start, end=end)
assert free_time.start == start
assert free_time.end == end
assert free_time.duration == end - start
start_2 = datetime.now() + timedelta(hours=1.0)
free_time.set_start(start_2)
assert free_time.start == start_2
assert free_time.duration == end - start_2
delta = timedelta(hours=3.0)
free_time.set_duration(delta)
assert free_time.duration == timedelta(hours=3.0)
assert free_time.start == end - delta
assert str(free_time) == f"FreeTime - {start_2} -> {end} - {delta}"
Strangely when I run the test I get some variant of this assertion error, (some variant meaning the millisecond difference):
assert str(free_time) == f"FreeTime - {start_2} -> {end} - {delta}"
E AssertionError: assert 'FreeTime - 2...544 - 3:00:00' == 'FreeTime - 2...544 - 3:00:00'
E - FreeTime - 2023-01-04 21:54:27.421567 -> 2023-01-05 00:54:27.421544 - 3:00:00
E ? ^^
E + FreeTime - 2023-01-04 21:54:27.421544 -> 2023-01-05 00:54:27.421544 - 3:00:00
E ? ^^
However I dont get how or where the difference in millisecond comes from, since all previous assertion tests pass wiht out errors. It alwasy failes on this assertion but the offset is different every time.
I allready tried instantiatiating all timedelata objects whith floats instead of int, but this did not have any effect.

I try to untangle your test logic:
start = t1 = now()
end = t1 + 4h
free_time.start = start = t1
free_time.end = end = t1 + 4h
start2 = t2 = now() + 1h = t1 + delta_t + 1h
free_time.start = start2 = t2 = t1 + delta_t + 1h
free_time.duration = end - start2 = t1 + 4h - t2
free_time.duration = 3h
free_time.start = free_time.end - 3h = end - 3h = t1 + 1h
The end result is:
free_time.start = t1 + 1h
free_time.end = t1 + 4h
free_time.duration = 3h
And your assertion is that
free_time.start == start2 == t2 == t1 + delta_t + 1h
free_time.end == end == t1 + 4h
free_time.duration == 3h
The error in your logic is that you assume the two calls to datetime.now() result in the same value. They do not. Python is not infinitely fast and the timestamp resolution is sufficiently small that both result in different values, thus resulting in the time difference which I put in the equations as delta_t

Yes - Based on #homer512 's input, change your test class to be
from datetime import datetime, timedelta
def test_FreeTime():
"""test the FreeTime class"""
start = datetime.now()
end = start + timedelta(hours=4.0)
free_time = FreeTime(start=start, end=end)
assert free_time.start == start
assert free_time.end == end
assert free_time.duration == end - start
start_2 = start + timedelta(hours=1.0)
free_time.set_start(start_2)
assert free_time.start == start_2
assert free_time.duration == end - start_2
delta = timedelta(hours=3.0)
free_time.set_duration(delta)
assert free_time.duration == timedelta(hours=3.0)
assert free_time.start == end - delta
assert str(free_time) == f"FreeTime - {start_2} -> {end} - {delta}"

Related

Change time from 12-h to 24-h format

I want to convert a time from 12-h format to 24-h format
This is my code:
def change_time(time):
import datetime as dt
FMT12 = '%H:%M:%S %p'
FMT24 = '%H:%M:%S'
# time is a string
if time.find('PM') != -1: # if exists
t1 = dt.datetime.strptime(time, FMT12)
t2 = dt.datetime.strptime('12:00:00', FMT24)
time_zero = dt.datetime.strptime('00:00:00', FMT24)
return (t1 - time_zero + t2).time()
else:
return dt.datetime.strptime(time, FMT12).time()
This is the output :
print(change_time('09:52:08 PM')) # -> 21:52:08
So, this code is working, but I want a better version of it.
Here is a much faster working method:
from datetime import datetime
def change_time(time):
in_time = datetime.strptime(time, "%I:%M:%S %p")
new_time = datetime.strftime(in_time, "%H:%M:%S")
print(new_time)
change_time('09:52:08 PM')
Output:
>>> 21:52:08
def t12_to_24(time):
am_or_pm = time[-2] + time[-1]
time_update = ''
if am_or_pm == 'am' or am_or_pm == 'AM':
for i in time[0:-3]:
time_update += i
elif am_or_pm == 'pm' or am_or_pm == 'PM':
change = ''
for i in time[0:2]:
change += i
c = 12 + int(change)
if c >= 24:
c = 24 - c
c = list(str(c))
for i1 in c:
time_update += i1
for i2 in time[2:-3]:
time_update += i2
print(time_update)
time = list(input())
t12_to_24(time)

Problem with showing elapsed time in python

I have a problem with function time.time().
I've written a code, which has 3 different hash functions and then it counts how long does they execute.
start_time = time.time()
arr.add(Book1, 1)
end_time = time.time()
elapsed_time = start_time - end_time
print(elapsed_time)
When I execute this in pycharm/IDLE/Visual it shows 0. When I do this in online compiler (https://www.programiz.com/python-programming/online-compiler/) it shows a good result. Why is that?
Here is the full code if needed.
import time
class Ksiazka:
def __init__(self, nazwa, autor, wydawca, rok, strony):
self.nazwa = nazwa
self.autor = autor
self.wydawca = wydawca
self.rok = rok
self.strony = strony
def hash_1(self):
h = 0
for char in self.nazwa:
h += ord(char)
return h
def hash_2(self):
h = 0
for char in self.autor:
h += ord(char)
return h
def hash_3(self):
h = self.strony + self.rok
return h
class HashTable:
def __init__(self):
self.size = 6
self.arr = [None for i in range(self.size)]
def add(self, key, c):
if c == 1:
h = Ksiazka.hash_1(key) % self.size
print("Hash 1: ", h)
if c == 2:
h = Ksiazka.hash_2(key) % self.size
print("Hash 2: ", h)
if c == 3:
h = Ksiazka.hash_3(key) % self.size
print("Hash 3: ", h)
self.arr[h] = key
arr = HashTable()
Book1 = Ksiazka("Harry Potter", "J.K Rowling", "foo", 1990, 700)
start_time = time.time()
arr.add(Book1, 1)
end_time = time.time()
elapsed_time = end_time - start_time
print(elapsed_time)
start_time = time.time()
arr.add(Book1, 2)
end_time = time.time()
elapsed_time = end_time - start_time
print(elapsed_time)
start_time = time.time()
arr.add(Book1, 3)
end_time = time.time()
elapsed_time = end_time - start_time
print(elapsed_time)
I looks like 0 might just be a return value for successful script execution. You need to add a print statement to show anything. Also you might want to change the order of the subtraction:
start_time = time.time()
arr.add(Book1, 1)
end_time = time.time()
elapsed_time = end_time - start_time
print(elapsed_time)
Edit b/c of updated questions:
If it still shows 0, it might just happen, that your add operation is extremely fast. In that case, try averaging over several runs, i.e. instead of your add operation use a version like this:
start_time = time.time()
for _ in range(10**6):
arr.add(Book1, 1)
end_time = time.time()
elapsed_time = end_time - start_time
print(elapsed_time) # indicates the average microseconds for a single run
The documentation for time.time says:
Note that even though the time is always returned as a floating point number, not all systems provide time with a better precision than 1 second. While this function normally returns non-decreasing values, it can return a lower value than a previous call if the system clock has been set back between the two calls.
So, depending on your OS, anything that is faster than 1 second might be displayed as a difference of 0.
I suggest you use time.perf_counter instead:
Return the value (in fractional seconds) of a performance counter, i.e. a clock with the highest available resolution to measure a short duration.

Time residual function in Python

I have 2 fields in the database: confirmed_at and preparation_time
I would like to get two simple functions that should return:
(a) the time residual
and
(b) the % of time remaining.
This is my logic which I am trying to achieve:
when order is confirmed_at I update the database with datetime.now() and preparation_time is indicated in minutes and stored as integer (for example 5 min is stored as 5)
completed_time is confirmed_at + preparation_time
time_remaining is completed_time - now()
order status is completed_time / now() * 100
These are my functions but I cannot make them work:
def get_remaining_time(self):
start_time = datetime(self.confirmed_at)
end_time = (start_time + datetime.timedelta(0,self.preparation_time*60)).time() # days, seconds.
return end_time - datetime.now()
def get_order_status(self):
end_time = (datetime(self.confirmed_at) + datetime.timedelta(0,self.preparation_time*60)).time()
return end_time / datetime.now() * 100
What kind of type returned from functions? If it's timedelta - just turn it into date.
So here it is the solution that I've managed to work out
def get_remaining_time(self):
start_time = self.confirmed_at
end_time = start_time + timedelta(
0, self.preparation_time * 60
) # days, seconds.
return end_time - timezone.now()
def get_time_left(self):
left = self.get_remaining_time()
sec = int(left.total_seconds())
if sec > 60:
return "{} minutes".format(int(sec / 60))
elif sec < 60 and sec > 0:
return "{} seconds".format(sec)
else:
return 0
def get_order_status(self):
left = int(self.get_remaining_time().total_seconds())
if left < 0:
return 100
else:
return round((1 - left / (self.preparation_time * 60)) * 100)

what does the ```time.perf_counter()``` command do?

I do not understand what the time.perf_counter () command does.
Here is the code which includes the command time.perf_counter()
import random
num_nums = 100
start_time = time.perf_counter()
numbers = str(random.randint(1,100))
for i in range(num_nums):
num = random.randint(1,100)
numbers += ',' + str(num)
end_time = time.perf_counter()
td1 = end_time - start_time
start_time = time.perf_counter()
numbers=[]
for i in range(num_nums):
num = random.randint(1,100)
numbers.append(str(num))
numbers = ', '.join(numbers)
end_time = time.perf_counter()
td2 = end_time - start_time
start_time = time.perf_counter()
numbers = [str(random.randint(1,100)) for i in range(1,num_nums)]
numbers = ', '.join(numbers)
end_time = time.perf_counter()
td3 = end_time - start_time
print('''Number of numbers: {:,}
Time Delta 1: {}
Time Delta 2: {}
Time Delta 3: {}'''.format(num_nums, td1, td2, td3))
An here is the result
Time Delta 1: 0.0003232999999909225
Time Delta 2: 0.00016150000010384247
Time Delta 3: 0.0003734999997959676```
Based on the definition here: https://docs.python.org/3/library/time.html#time.perf_counter
The call is gathering the amount of time that has taken place between 2 consecutive calls of time.perf_counter and will be with extreme precision.

Python returns ints when one of variable inside function is active

The main idea is:
searchindex() - repeat binary search algorithm over the list of random data with looking back and with fix.(variable counter1 should save number of occurences)
occur() - just assumning total number of occurences.
Please help to find a problem.
I always get counter1 = 0 after running a code.
def searchindex(searchlist, secs, x):
ts = calendar.timegm(time.gmtime())
delta_minute_ts = (ts - (ts % 60)) - secs
last_minute = datetime.datetime.fromtimestamp(delta_minute_ts).strftime('%Y-%m-%d %H:%M')
start = 0
end = len(searchlist) - 1
counter1 = 0
while (start <= end):
mid = (start + end) // 2
if (searchlist[mid] == last_minute):
counter1 = int(mid)
if x == 1:
end = mid - 1
else:
start = mid + 1
elif (last_minute < searchlist[mid]):
end = mid - 1
else:
start = mid + 1
return counter1
def occur():
start_result = searchindex(new1, 60, 1)
end_result = searchindex(new1, 60, 2)
if start_result is None:
return 'no results'
else:
end_result - start_result + 1

Categories