#Что происходит и как распознать неисправность
Ошибка TypeError: can't compare offset-naive and offset-aware datetimes возникает при сравнении <, > и других операций упорядочения дат, если одно значение имеет часовой пояс, а другое нет. Простое присваивание UTC к локальному времени может незаметно сдвинуть события.
#Как отличить причины
| Возможная причина | Как узнать | Что делать |
|---|---|---|
Смешаны datetime.now() и UTCdatetime.now() без аргументов создаёт naive, а API возвращает строку с +00:00. | У одного объекта tzinfo равен None. | Сравнивайте aware-значения с timezone.utc. |
| В базе хранится локальное время без зоныСтарое поле типа DATETIME не содержит offset. | Всё работает до сравнения с новым API. | Выясните исходную временную зону записей и корректно интерпретируйте. |
| Строки ISO имеют разный форматОдни содержат Z/+03:00, другие не содержат смещения. | fromisoformat создаёт разные типы datetime. | Нормализуйте формат входа по контракту API. |
#Пошаговая диагностика и исправление
- Откройте место сравнения в tracebackНайдите строку с оператором
>или<в вашем Python-файле, которая вызвала TypeError. Перед ней временно выведитеrepr(left), left.tzinfo, repr(right), right.tzinfo; используйте тестовые данные, если журнал может раскрыть личные сведения. Если одна зона None, ошибка определена. - Определите, что означает время без часового поясаПосмотрите документацию источника записи: UTC ли это, локальное время сервера или локальное время пользователя? Не угадывайте по текущему часовому поясу компьютера. Запросите спецификацию API/схемы базы данных. Пока происхождение неизвестно, не мигрируйте старые даты и не меняйте их массово.
- Используйте aware UTC для текущего моментаЕсли вы сравниваете с текущим временем, замените
datetime.now()наdatetime.now(timezone.utc), предварительно импортировавdatetime, timezone. Новый момент содержит tzinfo UTC. Не применяйте.replace(tzinfo=timezone.utc)к локальному времени: это не переводит часы, а меняет смысл отметки. - Нормализуйте уже aware значенияКогда обе даты имеют реальный tzinfo, переводите в UTC методом
astimezone(timezone.utc)для удобства анализа. Он сохраняет тот же момент времени, меняя представление. Если одна дата naive, сначала по источнику определите её зону и только затем присвойте известную исходную зону. - Учитывайте местные переводы часовЕсли исходные данные записаны по местному времени, используйте
zoneinfo.ZoneInfo("Europe/Tallinn")или фактическую зону источника. В час перевода часов локальное время может повторяться или отсутствовать; уточните по журналам/смещению значениеfold, если событие попадает в неоднозначный интервал. Нельзя механически добавлять фиксированные 2 или 3 часа круглый год. - Измените чтение ISO и проверяйте наличие зоныДля входных строк Python используйте
datetime.fromisoformat(...)и проверяйтеutcoffset()у результата. Если контракт API требует смещение, при его отсутствии сообщайте об ошибке входных данных, а не молча трактуйте UTC. Если API возвращаетZ, проверьте соответствие вашей версии Python или нормализуйте обозначение UTC по документированному формату. - Протестируйте обычную дату и переход на летнее времяСоставьте два теста: дата без перехода часов и дата рядом с переводом времени для нужной зоны. Сравнение двух aware-дат больше не должно вызывать TypeError; результат упорядочения должен соответствовать известным UTC-моментам. Если старые даты интерпретированы неверно, откатите изменение и уточните зону источника до переноса данных.
#Команды и примеры для проверки
from datetime import datetime, timezone
from_api = datetime.fromisoformat("2026-11-02T05:00:00+00:00")
now = datetime.now(timezone.utc)
print(from_api < now) # логическое значение зависит от времени запуска
print(from_api.astimezone(timezone.utc).isoformat())#Как проверить результат и что делать дальше
Проверка: оба операнда имеют ненулевой utcoffset(), сравнение выполняется без ошибки и не меняет хронологический порядок при переводе в UTC. Для хранения новых событий предпочтительна UTC либо строка ISO со смещением.