Claude refactor

This commit is contained in:
Russell Ballestrini 2025-03-08 18:40:39 +00:00
parent c5712e5f34
commit 1090d4ae50
2 changed files with 361 additions and 159 deletions

View file

@ -1,125 +1,215 @@
What are human readable timedeltas? What are human readable timedeltas?
=============================================== ===============================================
ago.py makes customizable human readable timedeltas, for example: The ``ago.py`` module makes customizable human readable timedeltas. For example:
Testing past tense:: Testing past tense::
Russell commented 1 year, 127 days, 16 hours ago Russell commented 1 year, 127 days, 16 hours ago
You replied 1 year, 127 days ago You replied 1 year, 127 days ago
Testing future tense:: Testing future tense::
Program will shutdown in 2 days, 3 hours, 27 minutes Program will shutdown in 2 days, 3 hours, 27 minutes
Job will run 2 days, 3 hours from now Job will run 2 days, 3 hours from now
How to install Installation
=================== ============
There are a number of ways to install this package. There are a number of ways to install this package.
You could run this ad hoc command:: You could run this ad hoc command::
pip install ago pip install ago
or specify *ago* under the *setup_requires* list within your or specify *ago* under the *setup_requires* list within your
*setuptools*-compatible project's *setup.py* file. `setuptools <https://setuptools.readthedocs.io>`_-compatible project's ``setup.py`` file.
How to use How to Use
================== ==========
The ago module comes with three functions: The ``ago`` module comes with the following functions:
#. human 1. ``human``: Convert a datetime or timedelta to a human-readable string
#. delta2dict 2. ``delta2dict``: Convert a timedelta to a dictionary of units
#. get_delta_from_subject 3. ``extract_components``: Extract time components from a timedelta (builds on delta2dict)
4. ``format_components``: Format time components into a readable string
5. ``get_delta_from_subject``: Convert various input types to a timedelta
You really only need to worry about *human*. Basic Usage
-----------
Here are all the available arguments and defaults:: The primary function you'll use is ``human``:
human(subject, precision=2, past_tense='{} ago', future_tense='in {}', abbreviate=False): .. code-block:: python
subject from ago import human
a datetime, timedelta, or timestamp (integer/float) object to become human readable from datetime import datetime, timedelta
precision (default 2): # With a datetime object
the desired amount of unit precision db_date = datetime(year=2010, month=5, day=4, hour=6, minute=54, second=33)
print('Created ' + human(db_date)) # "Created X years, Y months ago"
past_tense (default '{} ago'): # With a timedelta object
the format string used for a past timedelta delta = timedelta(days=5, hours=3, minutes=45)
print('Due in ' + human(delta)) # "Due in 5 days, 3 hours"
future_tense (default 'in {}'):
the format string used for a future timedelta
abbreviate (default False):
boolean to abbreviate units
Here is an example on how to use *human*::
from ago import human
from ago import delta2dict
from datetime import datetime
from datetime import timedelta
# pretend this was stored in database
db_date = datetime(year=2010, month=5, day=4, hour=6, minute=54, second=33, microsecond=4000)
# to find out how long ago, use the human function
print 'Created ' + human( db_date )
# optionally pass a precision
print 'Created ' + human( db_date, 3 )
print 'Created ' + human( db_date, 6 )
We also support future dates and times::
PRESENT = datetime.now()
PAST = PRESENT - timedelta( 492, 58711, 45 ) # days, secs, ms
FUTURE = PRESENT + timedelta( 2, 12447, 963 ) # days, secs, ms
print human( FUTURE )
Example past_tense and future_tense keyword arguments::
output1 = human( PAST,
past_tense = 'titanic sunk {0} ago',
future_tense = 'titanic will sink in {0} from now'
)
output2 = human( FUTURE,
past_tense = 'titanic sunk {0} ago',
future_tense = 'titanic will sink in {0} from now'
)
print output1
# titanic sunk 1 year, 127 days ago
print output2
# titanic will sink in 2 days, 3 hours from now
Need more examples? Function Arguments
========================== ------------------
You should look at test_ago.py The ``human`` function accepts the following arguments:
.. code-block:: python
human(subject, precision=2, past_tense='{} ago', future_tense='in {}', abbreviate=False)
**subject**
A datetime, timedelta, or timestamp (integer/float) object to be converted to a human-readable string.
**precision** (default: 2)
The desired amount of unit precision.
**past_tense** (default: ``'{} ago'``)
The format string used for a past timedelta.
**future_tense** (default: ``'in {}'``)
The format string used for a future timedelta.
**abbreviate** (default: False)
Boolean flag to abbreviate units.
How do I thank you? Examples
========================== --------
You should follow me on twitter http://twitter.com/russellbal Basic usage with different precisions:
.. code-block:: python
from ago import human
from datetime import datetime
# Pretend this was stored in a database
db_date = datetime(year=2010, month=5, day=4, hour=6, minute=54, second=33)
# To find out how long ago, use the human function
print('Created ' + human(db_date)) # "Created X years, Y months ago"
# Optionally pass a precision
print('Created ' + human(db_date, 3)) # Shows 3 units (e.g., years, months, days)
print('Created ' + human(db_date, 6)) # Shows up to 6 units
Future dates and times:
.. code-block:: python
from ago import human
from datetime import datetime, timedelta
PRESENT = datetime.now()
FUTURE = PRESENT + timedelta(days=2, seconds=12447, microseconds=963)
print(human(FUTURE)) # "in 2 days, 3 hours"
Custom format strings:
.. code-block:: python
from ago import human
from datetime import datetime, timedelta
PRESENT = datetime.now()
PAST = PRESENT - timedelta(days=492, seconds=58711, microseconds=45)
FUTURE = PRESENT + timedelta(days=2, seconds=12447, microseconds=963)
output1 = human(
PAST,
past_tense='titanic sunk {} ago',
future_tense='titanic will sink in {} from now'
)
# "titanic sunk 1 year, 127 days ago"
output2 = human(
FUTURE,
past_tense='titanic sunk {} ago',
future_tense='titanic will sink in {} from now'
)
# "titanic will sink in 2 days, 3 hours from now"
Using abbreviations:
.. code-block:: python
from ago import human
from datetime import timedelta
print(human(timedelta(days=5, hours=3, minutes=45), abbreviate=True))
# "5d, 3h ago"
Advanced Usage
--------------
For more advanced use cases, you can utilize the other functions.
Getting a dictionary of time units:
.. code-block:: python
from ago import delta2dict
from datetime import timedelta
delta = timedelta(days=400, hours=5, minutes=30)
time_dict = delta2dict(delta)
# Returns {"year": 1, "day": 35, "hour": 5, "minute": 30, "second": 0, ...}
Extracting non-zero time components:
.. code-block:: python
from ago import extract_components
from datetime import timedelta
delta = timedelta(days=400, hours=5, minutes=30)
components = extract_components(delta)
# Returns a list of components:
# [{"unit": "year", "abbr": "y", "value": 1},
# {"unit": "day", "abbr": "d", "value": 35}, ...]
Formatting time components:
.. code-block:: python
from ago import extract_components, format_components
from datetime import timedelta
delta = timedelta(days=400, hours=5, minutes=30)
components = extract_components(delta)
formatted = format_components(components, precision=3, abbreviate=True)
# "1y, 35d, 5h"
More Examples
-------------
For additional examples, please refer to the file ``test_ago.py``.
Acknowledgements
----------------
**How do I thank you?**
Follow me on Twitter: `@russellbal <http://twitter.com/russellbal>`_.
License License
========================= -------
* Public Domain This project is in the Public Domain.
Revision Control
----------------
Public Revision Control The public revision control repository is available at: `https://git.unturf.com/python/ago <https://git.unturf.com/python/ago>`_.
==============================
* https://git.unturf.com/python/ago

View file

@ -1,82 +1,194 @@
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Dict, Union, List, Tuple, Any, Callable
units = ("year", "day", "hour", "minute", "second", "millisecond", "microsecond") # Define time units as a data structure
# Each unit contains:
# - name: full name of the unit (e.g., "year")
def get_delta_from_subject(subject): # - abbr: abbreviation (e.g., "y")
"""Convert the subject to a timedelta and return it.""" # - seconds: number of seconds in this unit (for reference)
if isinstance(subject, timedelta): # - extract: function to extract this unit's value from a timedelta
return subject TIME_UNITS = [
{
if isinstance(subject, datetime): "name": "year",
dt = subject "abbr": "y",
else: "seconds": 31536000,
# if it's not a datetime, assume it's a unix timestamp "extract": lambda td: int(td.days / 365)
try: },
subject = float(subject) {
except ValueError: "name": "day",
# some unknown type that couldn't be converted to a float "abbr": "d",
raise TypeError("Unsupported subject type") "seconds": 86400,
"extract": lambda td: int(td.days % 365)
dt = datetime.fromtimestamp(subject) },
{
# return a timedelta from the datetime, using its timezone (if it has one) "name": "hour",
return datetime.now(tz=dt.tzinfo) - dt "abbr": "h",
"seconds": 3600,
"extract": lambda td: int(td.seconds / 3600)
def delta2dict(delta): },
"""Accepts a delta, returns a dictionary of units""" {
delta = abs(delta) "name": "minute",
return { "abbr": "m",
"year": int(delta.days / 365), "seconds": 60,
"day": int(delta.days % 365), "extract": lambda td: int(td.seconds / 60) % 60
"hour": int(delta.seconds / 3600), },
"minute": int(delta.seconds / 60) % 60, {
"second": int(delta.seconds % 60), "name": "second",
"millisecond": int(delta.microseconds / 1000), "abbr": "s",
"microsecond": int(delta.microseconds % 1000), "seconds": 1,
"extract": lambda td: int(td.seconds % 60)
},
{
"name": "millisecond",
"abbr": "ms",
"seconds": 0.001,
"extract": lambda td: int(td.microseconds / 1000)
},
{
"name": "microsecond",
"abbr": "μs",
"seconds": 0.000001,
"extract": lambda td: int(td.microseconds % 1000)
} }
]
def get_delta_from_subject(subject: Union[datetime, timedelta, int, float]) -> Tuple[timedelta, bool]:
"""
Convert various input types to a timedelta and determine if it's in the past.
Args:
subject: A datetime, timedelta, or timestamp (int/float)
Returns:
tuple: (timedelta object, is_past boolean)
"""
if isinstance(subject, timedelta):
return subject, subject >= timedelta(0)
if isinstance(subject, datetime):
delta = datetime.now(tz=subject.tzinfo) - subject
return delta, delta >= timedelta(0)
# Assume it's a timestamp
try:
dt = datetime.fromtimestamp(float(subject))
delta = datetime.now() - dt
return delta, delta >= timedelta(0)
except (ValueError, TypeError, OverflowError):
raise TypeError(f"Cannot convert {type(subject)} to a time delta")
def delta2dict(delta: timedelta) -> Dict[str, int]:
"""
Accepts a delta, returns a dictionary of units.
Args:
delta: A timedelta object
Returns:
Dictionary with unit names as keys and their values
"""
delta = abs(delta)
result = {}
for unit in TIME_UNITS:
result[unit["name"]] = unit["extract"](delta)
return result
def extract_components(delta: timedelta) -> List[Dict[str, Union[str, int]]]:
"""
Extract time components from a timedelta, filtering out zero values.
Args:
delta: A timedelta object
Returns:
List of components with values > 0
"""
# Use delta2dict to get all time values
time_dict = delta2dict(delta)
components = []
# For each time unit, create a component if value > 0
for unit in TIME_UNITS:
unit_name = unit["name"]
value = time_dict[unit_name]
if value > 0:
components.append({
"unit": unit_name,
"abbr": unit["abbr"],
"value": value
})
return components
def format_components(
components: List[Dict[str, Union[str, int]]],
precision: int = 2,
abbreviate: bool = False
) -> str:
"""
Format the time components into a human-readable string.
Args:
components: List of time components with values
precision: Number of units to include
abbreviate: Whether to use abbreviations
Returns:
Formatted string (e.g., "2 years, 1 day" or "2y, 1d")
"""
result = []
# Only include up to 'precision' number of components
for component in components[:precision]:
if abbreviate:
result.append(f"{component['value']}{component['abbr']}")
else:
unit_name = component["unit"]
if component["value"] != 1:
# Handle plurals
unit_name += "s"
result.append(f"{component['value']} {unit_name}")
return ", ".join(result)
def human( def human(
subject, precision=2, past_tense="{} ago", future_tense="in {}", abbreviate=False subject: Union[datetime, timedelta, int, float],
): precision: int = 2,
past_tense: str = "{} ago",
future_tense: str = "in {}",
abbreviate: bool = False
) -> str:
""" """
Accept a subject, return a human readable timedelta string. Accept a subject, return a human readable timedelta string.
:param subject: a datetime, timedelta, or timestamp (integer / float) object Args:
:param precision: the desired amount of unit precision (default: 2) subject: A datetime, timedelta, or timestamp (int/float)
:param past_tense: the format string used for a past timedelta (default: '{} ago') precision: The desired amount of unit precision (default: 2)
:param future_tense: the format string used for a future timedelta (default: 'in {}') past_tense: The format string used for past timedeltas (default: '{} ago')
:param abbreviate: boolean to abbreviate units (default: False) future_tense: The format string used for future timedeltas (default: 'in {}')
abbreviate: Boolean to abbreviate units (default: False)
:returns: Human readable timedelta string (Str)
Returns:
Human readable timedelta string
""" """
delta = get_delta_from_subject(subject) # Convert input to timedelta and determine if it's in the past
the_tense = future_tense if delta < timedelta(0) else past_tense delta, is_past = get_delta_from_subject(subject)
d = delta2dict(delta) # Extract time components (e.g., years, days, hours)
components = extract_components(delta)
hlist = []
count = 0 # Handle edge case: no components (very small time difference)
if not components:
# start building up the output in the hlist. return "just now"
for unit in units:
if count >= precision: # Format the components into a readable string
break # met precision formatted = format_components(components, precision, abbreviate)
if d[unit] == 0:
continue # skip 0's # Apply the appropriate tense
if abbreviate: if is_past:
if unit == "millisecond": return past_tense.format(formatted)
abr = "ms" else:
elif unit == "microsecond": return future_tense.format(formatted)
abr = "um"
else:
abr = unit[0]
hlist.append("{}{}".format(d[unit], abr))
else:
s = "" if d[unit] == 1 else "s" # handle plurals
hlist.append("{} {}{}".format(d[unit], unit, s))
count += 1
return the_tense.format(", ".join(hlist))