Source code for bikram_sambat.bs_timedelta

"""Defines the BSTimedelta class for representing time durations.

This module provides a timedelta object that is a subclass of the standard
`datetime.timedelta`, extending it with custom string formatting capabilities,
including support for Nepali numerals.
"""

import datetime as _dt

from .exceptions import InvalidTypeError
from .constants import STANDARD_DIGITS


[docs] class BSTimedelta(_dt.timedelta): """Represents a duration, the difference between two dates or times. `BSTimedelta` is a subclass of the standard `datetime.timedelta` and supports all the same operations and arguments. Its main enhancement is a more descriptive `__str__` method that can optionally display durations using Nepali numerals. All arithmetic operations will correctly return a new `BSTimedelta` instance. Args: days (float): Number of days. Defaults to 0. seconds (float): Number of seconds. Defaults to 0. microseconds (float): Number of microseconds. Defaults to 0. milliseconds (float): Number of milliseconds. Defaults to 0. minutes (float): Number of minutes. Defaults to 0. hours (float): Number of hours. Defaults to 0. weeks (float): Number of weeks. Defaults to 0. Example: >>> delta = BSTimedelta(days=5, hours=10) >>> print(delta) 5 days, 10:00:00 >>> print(delta.__str__(use_nepali_digits=True)) ५ दिन, १०:००:०० """ def __new__( cls, days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0, ): for name, value in [ ("days", days), ("seconds", seconds), ("microseconds", microseconds), ("milliseconds", milliseconds), ("minutes", minutes), ("hours", hours), ("weeks", weeks), ]: if value != 0 and not isinstance(value, (int, float)): raise InvalidTypeError( f"unsupported type for timedelta {name} component: {type(value).__name__}" ) return super().__new__( cls, days, seconds, microseconds, milliseconds, minutes, hours, weeks ) def __repr__(self) -> str: """Returns the official, unambiguous string representation of the BSTimedelta.""" args = [] if self.days: args.append(f"days={self.days}") if self.seconds: args.append(f"seconds={self.seconds}") if self.microseconds: args.append(f"microseconds={self.microseconds}") return ( f"{self.__class__.__module__}.{self.__class__.__name__}({', '.join(args)})" ) def __str__(self, use_nepali_digits: bool = False) -> str: """Returns a user-friendly string representation of the timedelta. Formats the duration into a "X day(s), H:MM:SS.f" style. It can also render all numerical components in Nepali Unicode characters. Args: use_nepali_digits (bool): If True, all numbers in the output string will be represented using Nepali numerals. Defaults to False. Returns: str: The formatted string representation of the duration. """ if self.total_seconds() == 0: return "0:00:00" is_negative = self < _dt.timedelta(0) # For calculations, work with the absolute value of the timedelta delta = -self if is_negative else self days = delta.days hours, remainder = divmod(delta.seconds, 3600) minutes, seconds = divmod(remainder, 60) parts = [] if days: days_str = str(days) if use_nepali_digits: nepali_days = "".join(STANDARD_DIGITS[c] for c in days_str) parts.append(f"{nepali_days} दिन{'हरू' if days != 1 else ''}") else: parts.append(f"{days_str} day{'s' if days != 1 else ''}") time_values = { "hours": str(hours), "minutes": str(minutes).zfill(2), "seconds": str(seconds).zfill(2), } if use_nepali_digits: for key, value in time_values.items(): time_values[key] = "".join(STANDARD_DIGITS[c] for c in value) time_str = f"{time_values['hours']}:{time_values['minutes']}:{time_values['seconds']}" if delta.microseconds: micro_str = str(delta.microseconds).zfill(6) if use_nepali_digits: micro_str = "".join(STANDARD_DIGITS[c] for c in micro_str) time_str += f".{micro_str}" parts.append(time_str) result = ", ".join(parts) return f"-{result}" if is_negative else result # def __str__(self, use_nepali_digits: bool = False) -> str: # """Returns a user-friendly string representation of the timedelta. # Formats the duration into a "X day(s), H:MM:SS.f" style. It can also # render all numerical components in Nepali Unicode characters. # Args: # use_nepali_digits (bool): If True, all numbers in the output string # will be represented using Nepali numerals. Defaults to False. # Returns: # str: The formatted string representation of the duration. # """ # if not self: # return "0:00:00" # days = abs(self.days) # hours, remainder = divmod(abs(self.seconds), 3600) # minutes, seconds = divmod(remainder, 60) # parts = [] # if days: # if use_nepali_digits: # days_str = "".join(STANDARD_DIGITS[c] for c in str(days)) # parts.append(f"{days_str} दिन{'हरू' if days != 1 else ''}") # else: # parts.append(f"{days} day{'s' if days != 1 else ''}") # hours_str = str(hours).lstrip("0") or "0" # if use_nepali_digits: # hours_str = "".join(STANDARD_DIGITS[c] for c in hours_str) # minutes_str = "".join(STANDARD_DIGITS[c] for c in str(minutes).zfill(2)) # seconds_str = "".join(STANDARD_DIGITS[c] for c in str(seconds).zfill(2)) # else: # minutes_str = str(minutes).zfill(2) # seconds_str = str(seconds).zfill(2) # time_str = f"{hours_str}:{minutes_str}:{seconds_str}" # if self.microseconds: # micro_str = str(abs(self.microseconds)).zfill(6) # if use_nepali_digits: # micro_str = "".join(STANDARD_DIGITS[c] for c in micro_str) # time_str += f".{micro_str}" # parts.append(time_str) # result = ", ".join(parts) # return f"-{result}" if self < BSTimedelta() else result def __add__(self, other): """Adds another timedelta, returning a new BSTimedelta instance.""" result = super().__add__(other) return self.__class__( days=result.days, seconds=result.seconds, microseconds=result.microseconds ) def __sub__(self, other): """Subtracts another timedelta, returning a new BSTimedelta instance.""" result = super().__sub__(other) return self.__class__( days=result.days, seconds=result.seconds, microseconds=result.microseconds ) def __mul__(self, other): """Multiplies by a float or int, returning a new BSTimedelta instance.""" result = super().__mul__(other) return self.__class__( days=result.days, seconds=result.seconds, microseconds=result.microseconds ) def __truediv__(self, other): # type: ignore """Divides by another timedelta or number, returning a BSTimedelta if applicable.""" if isinstance(other, (int, float)): result = super().__truediv__(other) return self.__class__( days=result.days, seconds=result.seconds, microseconds=result.microseconds, ) return super().__truediv__(other) def __reduce__(self): """Supports pickling of the BSTimedelta object for serialization.""" return (self.__class__, (self.days, self.seconds, self.microseconds))