Last active
December 22, 2023 17:45
-
-
Save nimaid/f8893767beb70f9cb5b9c7c62c969174 to your computer and use it in GitHub Desktop.
General purpose python classes for computing ETAs and formatting time strings.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import datetime | |
class TimeString: | |
@staticmethod | |
def split_seconds(seconds_in): | |
days, remainder = divmod(seconds_in, (60 ** 2) * 24) | |
hours, remainder = divmod(remainder, 60 ** 2) | |
minutes, seconds = divmod(remainder, 60) | |
return { | |
"d": days, | |
"h": hours, | |
"m": minutes, | |
"s": seconds | |
} | |
class TimeDelta: | |
@staticmethod | |
def short(time_in): | |
if time_in < datetime.timedelta(0): | |
time_in = datetime.timedelta(0) | |
time_parts = TimeString.split_seconds(round(time_in.total_seconds())) | |
time_string = f"{time_parts['h']}H:{time_parts['m']:02}M:{time_parts['s']:02}S" | |
if time_parts["d"] > 0: | |
time_string = f"{time_parts['d']}D:{time_string}" | |
return time_string | |
@staticmethod | |
def long(time_in): | |
if time_in < datetime.timedelta(0): | |
time_in = datetime.timedelta(0) | |
time_parts = TimeString.split_seconds(round(time_in.total_seconds())) | |
time_strings = [] | |
if time_parts["d"] > 0: | |
time_strings.append(f"{time_parts['d']} day") | |
if time_parts["d"] != 1: | |
time_strings[-1] += "s" | |
if time_parts["h"] > 0: | |
time_strings.append(f"{time_parts['h']} hour") | |
if time_parts["h"] != 1: | |
time_strings[-1] += "s" | |
if time_parts["m"] > 0: | |
time_strings.append(f"{time_parts['m']} minute") | |
if time_parts["m"] != 1: | |
time_strings[-1] += "s" | |
if len(time_strings) == 0 or time_parts["s"] > 0: | |
time_strings.append(f"{time_parts['s']} second") | |
if time_parts["s"] != 1: | |
time_strings[-1] += "s" | |
if len(time_strings) == 1: | |
time_string = time_strings[0] | |
elif len(time_strings) == 2: | |
time_string = f"{time_strings[0]} and {time_strings[1]}" | |
else: | |
time_string = ", ".join(time_strings[:-1]) | |
time_string += f", and {time_strings[-1]}" | |
return time_string | |
class DateTime: | |
@staticmethod | |
def short(time_in): | |
now = datetime.datetime.now() | |
if time_in.day != now.day or time_in.year != now.year: | |
return time_in.strftime("%Y/%m/%d @ %#I:%M:%S %p").strip() | |
else: | |
return time_in.strftime("%#I:%M:%S %p").strip() | |
@staticmethod | |
def long(time_in): | |
now = datetime.datetime.now() | |
if time_in.day != now.day or time_in.year != now.year: | |
return time_in.strftime("%A, %B %#d, %Y @ %#I:%M:%S %p %Z").strip() | |
else: | |
return time_in.strftime("%#I:%M:%S %p %Z").strip() | |
class Eta: | |
def __init__(self, total_items, start_time=None, verbose=False): | |
if start_time is None: | |
self.start_time = datetime.datetime.now() | |
else: | |
self.start_time = start_time | |
self.total_items = total_items | |
self.verbose = verbose | |
def set_verbose(self, verbose): | |
self.verbose = verbose | |
def set_total_items(self, total_items): | |
self.total_items = total_items | |
def get_time_taken(self, current_time=None): | |
if current_time is None: | |
current_time = datetime.datetime.now() | |
return current_time - self.start_time | |
def get_eta(self, current_item_index): | |
if current_item_index < 1: | |
raise ValueError("Unable to compute ETA for the first item (infinite time)") | |
if current_item_index > self.total_items: | |
raise IndexError("Item index is larger than the total items") | |
current_time = datetime.datetime.now() | |
time_taken = self.get_time_taken(current_time) | |
percent_done = current_item_index / (self.total_items - 1) | |
progress_scale = (1 - percent_done) / percent_done | |
eta_diff = time_taken * progress_scale | |
eta = current_time + eta_diff | |
return { | |
"eta": eta, | |
"difference": eta_diff | |
} | |
def get_start_time(self): | |
return self.start_time | |
def get_time_taken_string(self, current_time=None): | |
if current_time is None: | |
current_time = datetime.datetime.now() | |
time_taken = self.get_time_taken(current_time) | |
if self.verbose: | |
return TimeString.TimeDelta.long(time_taken) | |
else: | |
return TimeString.TimeDelta.short(time_taken) | |
def get_start_time_string(self): | |
if self.verbose: | |
return TimeString.DateTime.long(self.start_time) | |
else: | |
return TimeString.DateTime.short(self.start_time) | |
def get_time_remaining_string(self, current_item_index): | |
eta = self.get_eta(current_item_index) | |
if self.verbose: | |
difference_string = TimeString.TimeDelta.long(eta["difference"]) | |
eta_string = TimeString.DateTime.long(eta["eta"]) | |
else: | |
difference_string = TimeString.TimeDelta.short(eta["difference"]) | |
eta_string = TimeString.DateTime.short(eta["eta"]) | |
return f"Time remaining: {difference_string} | ETA: {eta_string}" | |
def get_eta_string(self, current_item_index): | |
eta = self.get_eta(current_item_index) | |
if self.verbose: | |
return TimeString.DateTime.long(eta["eta"]) | |
else: | |
return TimeString.DateTime.short(eta["eta"]) | |
def get_difference_string(self, current_item_index): | |
eta = self.get_eta(current_item_index) | |
if self.verbose: | |
return TimeString.TimeDelta.long(eta["difference"]) | |
else: | |
return TimeString.TimeDelta.short(eta["difference"]) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment