94 lines
3.0 KiB
Python
94 lines
3.0 KiB
Python
#!/usr/bin/env python3
|
||
# utils.py
|
||
|
||
from datetime import datetime, date
|
||
from typing import List, Optional
|
||
from zoneinfo import ZoneInfo
|
||
|
||
from telegram import ReplyKeyboardMarkup, InlineKeyboardMarkup, InlineKeyboardButton
|
||
from config import BTN_ADD, BTN_LIST, BTN_EDIT, BTN_DELETE, BTN_HISTORY, BTN_TZ, BTN_CANCEL, TZ_SUGGESTIONS
|
||
|
||
# ----------------- UI HELPERS -----------------
|
||
def main_menu_kb():
|
||
kb = [
|
||
[BTN_ADD, BTN_LIST],
|
||
[BTN_EDIT, BTN_DELETE],
|
||
[BTN_HISTORY, BTN_TZ]
|
||
]
|
||
return ReplyKeyboardMarkup(kb, resize_keyboard=True, one_time_keyboard=False)
|
||
|
||
def tz_keyboard():
|
||
kb = [[tz] for tz in TZ_SUGGESTIONS]
|
||
kb.append(["Ввести вручную"])
|
||
kb.append([BTN_CANCEL])
|
||
return ReplyKeyboardMarkup(kb, one_time_keyboard=True, resize_keyboard=True)
|
||
|
||
def build_wd_keyboard(wd_sel: set):
|
||
labels = ["Пн", "Вт", "Ср", "Чт", "Пт", "Сб", "Вс"]
|
||
kb = []
|
||
row = []
|
||
for i, lab in enumerate(labels):
|
||
prefix = "✅" if i in wd_sel else "◻️"
|
||
row.append(InlineKeyboardButton(f"{prefix} {lab}", callback_data=f"wd_toggle:{i}"))
|
||
if (i + 1) % 3 == 0:
|
||
kb.append(row)
|
||
row = []
|
||
if row:
|
||
kb.append(row)
|
||
kb.append([
|
||
InlineKeyboardButton("Готово", callback_data="wd_done"),
|
||
InlineKeyboardButton("Отмена", callback_data="wd_cancel")
|
||
])
|
||
return InlineKeyboardMarkup(kb)
|
||
|
||
# ----------------- DATA HELPERS -----------------
|
||
def parse_hhmm(s: str) -> Optional[str]:
|
||
s = s.strip()
|
||
try:
|
||
dt = datetime.strptime(s, "%H:%M")
|
||
return dt.strftime("%H:%M")
|
||
except ValueError:
|
||
return None
|
||
|
||
def weekdays_list_to_csv(lst: List[int]) -> str:
|
||
return ",".join(str(i) for i in sorted(set(lst))) if lst else ""
|
||
|
||
def weekdays_csv_to_list(csv: Optional[str]) -> List[int]:
|
||
if not csv:
|
||
return []
|
||
return [int(x) for x in csv.split(",") if x.isdigit()]
|
||
|
||
def now_utc() -> datetime:
|
||
return datetime.utcnow()
|
||
|
||
def utc_str_min(dt: datetime) -> str:
|
||
return dt.strftime("%Y-%m-%d %H:%M")
|
||
|
||
def utc_str_full(dt: datetime) -> str:
|
||
return dt.strftime("%Y-%m-%d %H:%M:%S")
|
||
|
||
def localized_now_for_tz(tzname: str) -> datetime:
|
||
try:
|
||
tz = ZoneInfo(tzname)
|
||
except Exception:
|
||
tz = ZoneInfo("UTC")
|
||
return datetime.now(tz)
|
||
|
||
def local_time_hhmm_for_user(tzname: str, when_utc: datetime) -> str:
|
||
try:
|
||
tz = ZoneInfo(tzname)
|
||
except Exception:
|
||
tz = ZoneInfo("UTC")
|
||
local = when_utc.astimezone(tz)
|
||
return local.strftime("%H:%M")
|
||
|
||
def user_local_hhmm_to_utc_dt(tzname: str, local_date: date, hhmm: str) -> datetime:
|
||
hour, minute = map(int, hhmm.split(":"))
|
||
local_dt = datetime(local_date.year, local_date.month, local_date.day, hour, minute)
|
||
try:
|
||
tz = ZoneInfo(tzname)
|
||
local_dt = local_dt.replace(tzinfo=tz)
|
||
except Exception:
|
||
tz = ZoneInfo("UTC")
|
||
local_dt = local_dt.replace(tzinfo=tz)
|
||
return local_dt.astimezone(ZoneInfo("UTC")).replace(tzinfo=None) |