"""
Unit Management Module
===========================
This module provides a class, `UnitManager`, for managing time units. It allows users to
add, retrieve, and sort time units by their values (in seconds), as well as print the units
and generate conversion tables for easy comparison.
Classes:
--------
- `UnitManager`: Handles the addition, management, and retrieval of time units.
Dependencies:
-------------
- `decimal.Decimal`: For precise arithmetic with time unit values.
- `utms.utils.format_value`: A utility function for formatting values.
Features:
---------
1. Add new time units with a full name, abbreviation, and value (in seconds).
2. Retrieve time unit values or details by their abbreviation.
3. Retrieve all stored time units in a sorted dictionary.
4. Print time units and their details.
5. Generate and print a conversion table centered around a specific time unit.
Usage:
------
```python
from decimal import Decimal
from time_unit_manager import UnitManager
# Initialize the manager
manager = UnitManager()
# Add time units
manager.add_unit("Second", "s", Decimal("1"))
manager.add_unit("Minute", "m", Decimal("60"))
manager.add_unit("Hour", "h", Decimal("3600"))
# Print all units
manager.print_units()
# Print a conversion table centered around seconds
manager.print_conversion_table("s", num_columns=2, num_rows=5)
"""
import argparse
from decimal import Decimal
from typing import Dict, Iterator, Optional, Union
from colorama import Fore, Style
from utms.utils import seconds_to_hplt, seconds_to_pplt
[docs]
class Unit:
"""Represents a time unit with a full name, abbreviation, and value in
seconds. Provides methods for comparisons, conversions, and formatting.
Attributes:
name (str): The full name of the time unit.
abbreviation (str): The abbreviation of the time unit.
value (Decimal): The value of the time unit in seconds.
"""
def __init__(self, name: str, abbreviation: str, value: Decimal) -> None:
"""Initializes a Unit instance.
Args:
name (str): The full name of the time unit.
abbreviation (str): The abbreviation of the time unit.
value (Decimal): The value of the time unit in seconds.
"""
self.name = name
self.abbreviation = abbreviation
self.value = value
[docs]
def __repr__(self) -> str:
"""Provides a string representation of the Unit instance.
Returns:
str: A string representation of the unit.
"""
return f"Unit(name={self.name}, abbreviation={self.abbreviation}, value={self.value})"
[docs]
def __eq__(self, other: object) -> bool:
"""Compares two Unit instances for equality based on value.
Args:
other (object): The other object to compare to.
Returns:
bool: True if both units are equal, False otherwise.
"""
if isinstance(other, Unit):
return self.value == other.value
return False
[docs]
def __lt__(self, other: object) -> bool:
"""Compares two Unit instances for less-than based on value.
Args:
other (object): The other object to compare to.
Returns:
bool: True if this unit is less than the other unit, False otherwise.
"""
if isinstance(other, Unit):
return self.value < other.value
return False
[docs]
def convert_to(self, other: "Unit", value: Decimal) -> Decimal:
"""Converts a value from this unit to another unit.
Args:
other (Unit): The unit to convert to.
value (Decimal): The value to convert.
Returns:
Decimal: The converted value in the new unit.
"""
# Conversion formula: value_in_new_unit = value_in_current_unit * (self.value / other.value)
return value * (self.value / other.value)
[docs]
def __str__(self) -> str:
"""Provides a human-readable string for the unit.
Returns:
str: A string representation of the unit.
"""
return f"{self.name} ({self.abbreviation}): {self.value} seconds"
[docs]
class UnitManager:
"""A class to manage time units, allowing adding new units, sorting by
value, and accessing them by abbreviation."""
def __init__(self) -> None:
self._units: Dict[str, Unit] = {}
[docs]
def add_unit(self, name: str, abbreviation: str, value: Decimal) -> None:
"""Adds a new time unit to the manager and ensures the units are sorted
by value.
Args:
name (str): The full name of the time unit.
abbreviation (str): The abbreviation of the time unit.
value (Decimal): The value of the unit in seconds.
"""
new_unit = Unit(name, abbreviation, value)
self._units[abbreviation] = new_unit
self._sort_units()
def _sort_units(self) -> None:
"""Sort the units by their value (in seconds)."""
self._units = dict(sorted(self._units.items(), key=lambda item: item[1].value))
[docs]
def get_value(self, abbreviation: str) -> Decimal:
"""Get a unit value by its abbreviation.
Args:
abbreviation (str): The abbreviation of the time unit.
Returns:
value: A Decimal value of the unit
"""
unit = self.get_unit(abbreviation)
if unit:
return unit.value
raise ValueError(f"No unit with abbreviation {unit} defined")
[docs]
def get_unit(self, abbreviation: str) -> Optional[Unit]:
"""Get a time unit by its abbreviation.
Args:
abbreviation (str): The abbreviation of the time unit.
Returns:
dict: A dictionary containing 'name' and 'value' of the time unit.
"""
return self._units.get(abbreviation)
[docs]
def get_all_units(self) -> Dict[str, Unit]:
"""Get all the time units stored in the manager.
Returns:
dict: A dictionary with abbreviations as keys and unit information as values.
"""
return self._units
[docs]
def __iter__(self) -> Iterator[str]:
"""Returns an iterator over the abbreviations of all time units.
:return: An iterator of unit abbreviations.
"""
return iter(self._units)
[docs]
def __getitem__(self, index: Union[int, str]) -> Unit:
"""Makes the class subscriptable by allowing access via abbreviation or
index.
Args:
index (int or str): The abbreviation or index of the unit.
Returns:
dict: A dictionary containing 'name' and 'value' of the time unit.
Raises:
KeyError: If the abbreviation does not exist.
IndexError: If the index is out of range.
"""
if isinstance(index, int): # Index-based access
try:
return list(self._units.values())[index]
except IndexError as exc:
raise IndexError(f"Index {index} is out of range.") from exc
else:
if index in self._units:
return self._units[index]
raise KeyError(f"Unit with abbreviation '{index}' not found.")
[docs]
def __len__(self) -> int:
"""Returns the number of time units in the manager.
:return: The number of units.
"""
return len(self._units)
[docs]
def print(self, args: argparse.Namespace) -> None:
"""Prints all time units sorted by their value in seconds."""
plt = bool(getattr(args, "plt", False))
if plt:
print(
"Unit name (abbr):".ljust(25)
+ "Value".ljust(20)
+ "hPLT".ljust(20)
+ "pPLT".ljust(20)
)
else:
print("Unit name (abbr):".ljust(25) + "Value".ljust(20))
for unit in self._units.values():
if plt:
print(
f"{unit.name} ({unit.abbreviation}):".ljust(25)
+ f"{format_value(unit.value)}"
+ f"{seconds_to_hplt(unit.value):.5f}".ljust(20)
+ f"{seconds_to_pplt(unit.value):.5f}".ljust(20)
)
else:
print(
f"{unit.name} ({unit.abbreviation}):".ljust(25) + f"{format_value(unit.value)}"
)
[docs]
def print_conversion_table(
self, center_unit: str, num_columns: int = 5, num_rows: int = 100
) -> None:
"""Prints a table of time unit conversions centered around a specific
unit.
Args:
center_unit (str): The abbreviation of the unit around
which the table will be centered.
num_units (int): The number of units to display on either
side of the center unit. Defaults to 5.
Example:
manager.print_conversion_table("s", 3)
This will print a table centered around "s", showing 3 units to the left and right.
"""
# Get all unit abbreviations
unit_keys = list(self._units.keys())
if center_unit not in unit_keys:
raise ValueError(f"Center unit '{center_unit}' not found in time units.")
# Find the index of the center unit
center_index = unit_keys.index(center_unit)
# Determine the range of units to display for columns (header)
col_start_index = max(0, center_index - num_columns)
col_end_index = min(len(unit_keys), center_index + num_columns + 1)
displayed_columns = unit_keys[col_start_index:col_end_index]
# Determine the range of units to display for rows
row_start_index = max(0, center_index - num_rows)
row_end_index = min(len(unit_keys), center_index + num_rows + 1)
displayed_rows = unit_keys[row_start_index:row_end_index]
# Print header
print(
"Time Unit".ljust(25)
+ "".join(f"{self._units[key].name} ({key})".ljust(20) for key in displayed_columns)
)
print("-" * (25 + len(displayed_columns) * 20))
# Print conversion table
for row_abbrev in displayed_rows:
conversions = {
col_abbrev: self._units[row_abbrev].convert_to(
self._units[col_abbrev], Decimal("1")
)
for col_abbrev in displayed_columns
}
print(
f"{self._units[row_abbrev].name} ({row_abbrev})".ljust(25)
+ "".join(
f"{format_value(conversions[col_abbrev])}" for col_abbrev in displayed_columns
)
)
[docs]
def convert_units(self, args: argparse.Namespace) -> None:
"""Convert a given value from one unit to all other units and print the
results.
Args:
value (Decimal): The value to be converted.
input_unit (str): The abbreviation of the unit of the input value.
Raises:
ValueError: If the input unit is not found in the manager.
"""
decimal_value = Decimal(args.value)
if args.source_unit not in self._units:
raise ValueError(f"Input unit '{args.source_unit}' not found in time units.")
input_unit_instance = self._units[args.source_unit]
if not args.raw:
print(f"Converting {args.value} {args.source_unit}:")
print("-" * 50)
precision = args.precision if args.precision is not None else 5
if not args.target_unit:
for abbrev, unit_instance in self._units.items():
converted_value = input_unit_instance.convert_to(unit_instance, decimal_value)
if args.raw:
print(f"{converted_value:.{precision}f}")
else:
print(
f"{unit_instance.name} ({abbrev}):".ljust(25)
+ f"{format_value(converted_value)}"
)
else:
converted_value = input_unit_instance.convert_to(
self._units[args.target_unit], decimal_value
)
if args.raw:
print(f"{converted_value:.{precision}f}")
else:
print(
f"{self._units[args.target_unit].name} ({args.target_unit}):".ljust(25)
+ f"{format_value(converted_value)}"
)