Template
Fix naive/aware datetime comparison for user expiration.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1855,16 +1855,18 @@ async def periodic_background_tasks():
|
|||||||
if uid not in to_disable_uids:
|
if uid not in to_disable_uids:
|
||||||
to_disable_uids.append(uid)
|
to_disable_uids.append(uid)
|
||||||
|
|
||||||
# Check expiration date
|
# Check expiration date (naive/aware-safe)
|
||||||
exp_str = u.get('expiration_date')
|
if u.get('enabled', True):
|
||||||
if exp_str and u.get('enabled', True):
|
|
||||||
try:
|
try:
|
||||||
exp_date = datetime.fromisoformat(exp_str)
|
from managers.user_expiration import user_is_expired
|
||||||
if now > exp_date:
|
if user_is_expired(u, now=now):
|
||||||
logger.info(f"Subscription expired for user {u['username']} (expired at {exp_str})")
|
logger.info(
|
||||||
|
f"Subscription expired for user {u['username']} "
|
||||||
|
f"(expired at {u.get('expiration_date')})"
|
||||||
|
)
|
||||||
if uid not in to_disable_uids:
|
if uid not in to_disable_uids:
|
||||||
to_disable_uids.append(uid)
|
to_disable_uids.append(uid)
|
||||||
except:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
save_data(curr_data)
|
save_data(curr_data)
|
||||||
|
|
||||||
|
|||||||
@@ -2,20 +2,44 @@
|
|||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta, timezone
|
||||||
from typing import Optional
|
from typing import Optional, Union
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_dt(value: Union[str, datetime, None]) -> Optional[datetime]:
|
||||||
|
if value is None or value == '':
|
||||||
|
return None
|
||||||
|
if isinstance(value, datetime):
|
||||||
|
return value
|
||||||
|
text = str(value).strip()
|
||||||
|
if not text:
|
||||||
|
return None
|
||||||
|
if text.endswith('Z'):
|
||||||
|
text = text[:-1] + '+00:00'
|
||||||
|
try:
|
||||||
|
return datetime.fromisoformat(text)
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _as_utc(dt: datetime) -> datetime:
|
||||||
|
"""Normalize naive/aware datetimes for safe comparison."""
|
||||||
|
if dt.tzinfo is None:
|
||||||
|
# Treat naive wall time as local, then convert to UTC.
|
||||||
|
return dt.astimezone(timezone.utc)
|
||||||
|
return dt.astimezone(timezone.utc)
|
||||||
|
|
||||||
|
|
||||||
def user_is_expired(user: dict, *, now: Optional[datetime] = None) -> bool:
|
def user_is_expired(user: dict, *, now: Optional[datetime] = None) -> bool:
|
||||||
"""True when absolute expiration_date is in the past."""
|
"""True when absolute expiration_date is in the past."""
|
||||||
exp_str = user.get('expiration_date')
|
exp_date = _parse_dt(user.get('expiration_date'))
|
||||||
if not exp_str:
|
if not exp_date:
|
||||||
return False
|
return False
|
||||||
|
stamp = now if now is not None else datetime.now().astimezone()
|
||||||
try:
|
try:
|
||||||
exp_date = datetime.fromisoformat(str(exp_str))
|
return _as_utc(stamp) > _as_utc(exp_date)
|
||||||
except Exception:
|
except Exception:
|
||||||
return False
|
return False
|
||||||
return (now or datetime.now()) > exp_date
|
|
||||||
|
|
||||||
|
|
||||||
def maybe_start_user_expiration(data: dict, user_id: str, *, now: Optional[datetime] = None) -> Optional[str]:
|
def maybe_start_user_expiration(data: dict, user_id: str, *, now: Optional[datetime] = None) -> Optional[str]:
|
||||||
@@ -35,8 +59,11 @@ def maybe_start_user_expiration(data: dict, user_id: str, *, now: Optional[datet
|
|||||||
days = int(user.get('expiration_days') or 0)
|
days = int(user.get('expiration_days') or 0)
|
||||||
if days <= 0:
|
if days <= 0:
|
||||||
return None
|
return None
|
||||||
stamp = now or datetime.now()
|
stamp = now if now is not None else datetime.now()
|
||||||
expires = stamp + timedelta(days=days)
|
expires = stamp + timedelta(days=days)
|
||||||
iso = expires.isoformat()
|
# Store without tz so UI/local comparisons stay consistent with manual dates
|
||||||
|
if expires.tzinfo is not None:
|
||||||
|
expires = expires.replace(tzinfo=None)
|
||||||
|
iso = expires.isoformat(timespec='seconds')
|
||||||
user['expiration_date'] = iso
|
user['expiration_date'] = iso
|
||||||
return iso
|
return iso
|
||||||
|
|||||||
Reference in New Issue
Block a user