asyncpg не принимает aware-datetime для CAST AS TIMESTAMP WITHOUT TIME ZONE (400). Даты переводятся в пояс оргединицы и становятся naive, SQL приписывает тот же offset. timezone_from_offset вынесен в src/core/timezones.py, добавлен to_org_local_naive. Aware-дата при NULL utc_offset оргединицы даёт 422. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
41 lines
1.6 KiB
Python
41 lines
1.6 KiB
Python
import datetime
|
||
|
||
from fastapi import HTTPException
|
||
|
||
|
||
def timezone_from_offset(offset: str) -> datetime.timezone | None:
|
||
"""Строка вида '+03' / '-05' (org_unit.utc_offset) -> datetime.timezone."""
|
||
if not offset:
|
||
return None
|
||
|
||
hours = int(offset[1:])
|
||
return datetime.timezone(
|
||
datetime.timedelta(
|
||
hours=hours if offset[0] == '+' else -hours
|
||
)
|
||
)
|
||
|
||
|
||
def to_org_local_naive(
|
||
dt: datetime.datetime, utc_offset: str | None
|
||
) -> datetime.datetime:
|
||
"""Приводит дату к местному naive-времени оргединицы.
|
||
|
||
Naive-значение возвращается без изменений (оно уже местное). Aware-значение
|
||
переводится в пояс оргединицы, после чего tzinfo отбрасывается: SQL-функции
|
||
(v3.add_form_phase и др.) затем приписывают тот же offset, и момент времени
|
||
сохраняется. Если у оргединицы нет utc_offset, а дата aware - 422.
|
||
"""
|
||
if dt.tzinfo is None:
|
||
return dt
|
||
tz = timezone_from_offset(utc_offset) if utc_offset else None
|
||
if tz is None:
|
||
raise HTTPException(
|
||
status_code=422,
|
||
detail=(
|
||
"Дата передана с часовым поясом, но у оргединицы формы не "
|
||
"задан utc_offset. Передайте дату без часового пояса."
|
||
),
|
||
)
|
||
return dt.astimezone(tz).replace(tzinfo=None)
|