Files
remnawave-bedolaga-telegram…/app/utils/formatters.py
c0mrade 9a2aea038a chore: add uv package manager and ruff linter configuration
- Add pyproject.toml with uv and ruff configuration
- Pin Python version to 3.13 via .python-version
- Add Makefile commands: lint, format, fix
- Apply ruff formatting to entire codebase
- Remove unused imports (base64 in yookassa/simple_subscription)
- Update .gitignore for new config files
2026-01-24 17:45:27 +03:00

222 lines
6.9 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

from datetime import datetime
def format_datetime(dt: datetime | str, format_str: str = '%d.%m.%Y %H:%M') -> str:
if isinstance(dt, str):
if dt == 'now' or dt == '':
dt = datetime.now()
else:
try:
dt = datetime.fromisoformat(dt.replace('Z', '+00:00'))
except (ValueError, AttributeError):
dt = datetime.now()
return dt.strftime(format_str)
def format_date(dt: datetime | str, format_str: str = '%d.%m.%Y') -> str:
if isinstance(dt, str):
if dt == 'now' or dt == '':
dt = datetime.now()
else:
try:
dt = datetime.fromisoformat(dt.replace('Z', '+00:00'))
except (ValueError, AttributeError):
dt = datetime.now()
return dt.strftime(format_str)
def format_time_ago(dt: datetime | str, language: str = 'ru') -> str:
if isinstance(dt, str):
if dt == 'now' or dt == '':
dt = datetime.now()
else:
try:
dt = datetime.fromisoformat(dt.replace('Z', '+00:00'))
except (ValueError, AttributeError):
dt = datetime.now()
now = datetime.utcnow()
diff = now - dt
language_code = (language or 'ru').split('-')[0].lower()
if diff.days > 0:
if diff.days == 1:
return 'yesterday' if language_code == 'en' else 'вчера'
if diff.days < 7:
value = diff.days
if language_code == 'en':
suffix = 'day' if value == 1 else 'days'
return f'{value} {suffix} ago'
return f'{value} дн. назад'
if diff.days < 30:
value = diff.days // 7
if language_code == 'en':
suffix = 'week' if value == 1 else 'weeks'
return f'{value} {suffix} ago'
return f'{value} нед. назад'
if diff.days < 365:
value = diff.days // 30
if language_code == 'en':
suffix = 'month' if value == 1 else 'months'
return f'{value} {suffix} ago'
return f'{value} мес. назад'
value = diff.days // 365
if language_code == 'en':
suffix = 'year' if value == 1 else 'years'
return f'{value} {suffix} ago'
return f'{value} г. назад'
if diff.seconds > 3600:
value = diff.seconds // 3600
if language_code == 'en':
suffix = 'hour' if value == 1 else 'hours'
return f'{value} {suffix} ago'
return f'{value} ч. назад'
if diff.seconds > 60:
value = diff.seconds // 60
if language_code == 'en':
suffix = 'minute' if value == 1 else 'minutes'
return f'{value} {suffix} ago'
return f'{value} мин. назад'
return 'just now' if language_code == 'en' else 'только что'
def format_days_declension(days: int, language: str = 'ru') -> str:
if language != 'ru':
return f'{days} day{"s" if days != 1 else ""}'
if days % 10 == 1 and days % 100 != 11:
return f'{days} день'
if days % 10 in [2, 3, 4] and days % 100 not in [12, 13, 14]:
return f'{days} дня'
return f'{days} дней'
def format_duration(seconds: int) -> str:
if seconds < 60:
return f'{seconds} сек.'
minutes = seconds // 60
if minutes < 60:
return f'{minutes} мин.'
hours = minutes // 60
if hours < 24:
return f'{hours} ч.'
days = hours // 24
return f'{days} дн.'
def format_bytes(bytes_value: int) -> str:
if bytes_value == 0:
return '0 B'
units = ['B', 'KB', 'MB', 'GB', 'TB']
size = float(bytes_value)
unit_index = 0
while size >= 1024 and unit_index < len(units) - 1:
size /= 1024
unit_index += 1
if size == int(size):
return f'{int(size)} {units[unit_index]}'
return f'{size:.1f} {units[unit_index]}'
def format_percentage(value: float, decimals: int = 1) -> str:
return f'{value:.{decimals}f}%'
def format_number(number: float, separator: str = ' ') -> str:
if isinstance(number, float):
integer_part = int(number)
decimal_part = number - integer_part
formatted_integer = f'{integer_part:,}'.replace(',', separator)
if decimal_part > 0:
return f'{formatted_integer}.{decimal_part:.2f}'.split('.')[0] + f'.{str(decimal_part).split(".")[1][:2]}'
return formatted_integer
return f'{number:,}'.replace(',', separator)
def format_price_range(min_price: int, max_price: int) -> str:
from app.config import settings
min_formatted = settings.format_price(min_price)
max_formatted = settings.format_price(max_price)
if min_price == max_price:
return min_formatted
return f'{min_formatted} - {max_formatted}'
def truncate_text(text: str, max_length: int = 100, suffix: str = '...') -> str:
if len(text) <= max_length:
return text
return text[: max_length - len(suffix)] + suffix
def format_username(username: str | None, user_id: int, full_name: str | None = None) -> str:
if full_name:
return full_name
if username:
return f'@{username}'
return f'ID{user_id}'
def format_subscription_status(is_active: bool, is_trial: bool, end_date: datetime | str, language: str = 'ru') -> str:
if isinstance(end_date, str):
try:
end_date = datetime.fromisoformat(end_date.replace('Z', '+00:00'))
except (ValueError, AttributeError):
end_date = datetime.now()
if not is_active:
return '❌ Неактивна' if language == 'ru' else '❌ Inactive'
if is_trial:
status = '🎁 Тестовая' if language == 'ru' else '🎁 Trial'
else:
status = '✅ Активна' if language == 'ru' else '✅ Active'
now = datetime.utcnow()
if end_date > now:
days_left = (end_date - now).days
if days_left > 0:
status += f' ({days_left} дн.)' if language == 'ru' else f' ({days_left} days)'
else:
hours_left = (end_date - now).seconds // 3600
status += f' ({hours_left} ч.)' if language == 'ru' else f' ({hours_left} hrs)'
else:
status = '⏰ Истекла' if language == 'ru' else '⏰ Expired'
return status
def format_traffic_usage(used_gb: float, limit_gb: int, language: str = 'ru') -> str:
if limit_gb == 0:
if language == 'ru':
return f'{used_gb:.1f} ГБ / ∞'
return f'{used_gb:.1f} GB / ∞'
percentage = (used_gb / limit_gb) * 100 if limit_gb > 0 else 0
if language == 'ru':
return f'{used_gb:.1f} ГБ / {limit_gb} ГБ ({percentage:.1f}%)'
return f'{used_gb:.1f} GB / {limit_gb} GB ({percentage:.1f}%)'
def format_boolean(value: bool, language: str = 'ru') -> str:
if language == 'ru':
return '✅ Да' if value else '❌ Нет'
return '✅ Yes' if value else '❌ No'