Fix Django "received a naive datetime" Warnings
Fix the Django RuntimeWarning DateTimeField received a naive datetime while time zone support is active: use timezone.now, make parsed and imported datetimes aware, and set USE_TZ, TIME_ZONE, and database time zones correctly.
Problem statement
Production logs fill with:
RuntimeWarning: DateTimeField Order.created_at received a naive datetime
(2026-10-02 14:30:00) while time zone support is active.
Worse, reports show events an hour or two off, "today" filters miss evening orders, and scheduled emails go out at the wrong time. With USE_TZ = True, Django stores every datetime in UTC and expects aware datetimes that carry their timezone. A naive datetime has no timezone, so Django has to guess, and it guesses the default time zone. The warning marks every place where that guess happens.
- Replace
datetime.now()anddatetime.utcnow()withdjango.utils.timezone.now(). - Make parsed datetimes aware with
timezone.make_aware(dt, ZoneInfo("Europe/Berlin"))or parse ISO strings that include an offset. - Turn the warning into an error in tests so new cases fail CI.
- Keep
USE_TZ = True, and setTIME_ZONEto the zone your users mostly expect for display. - Use
timezone.localdate()andtimezone.localtime()for "today" and display logic.
Step-by-step solution
1. Find the source of each warning
The warning names the model field, not the line of code. Make it raise so you get a traceback:
python -W error::RuntimeWarning manage.py shell -c "import myapp.jobs; myapp.jobs.run()"
Or in test settings:
import warnings
warnings.filterwarnings(
"error", r"DateTimeField .* received a naive datetime",
RuntimeWarning, r"django\.db\.models\.fields",
)
Run the test suite and every naive datetime becomes a failing test with a stack trace.
2. Use timezone.now()
from datetime import datetime
from django.utils import timezone
order.paid_at = datetime.now() # naive, server local time
order.paid_at = datetime.utcnow() # naive, and deprecated since Python 3.12
order.paid_at = timezone.now() # aware, UTC
timezone.now() returns an aware UTC datetime when USE_TZ = True. Search the codebase for datetime.now(, datetime.utcnow(, datetime.today(, and date.today( used to build datetimes.
3. Make external data aware
CSV imports, third-party APIs, and form fields often give strings without offsets. Decide which timezone they are in and attach it:
from zoneinfo import ZoneInfo
from django.utils import timezone
from django.utils.dateparse import parse_datetime
raw = parse_datetime("2026-10-02 14:30:00") # naive
aware = timezone.make_aware(raw, ZoneInfo("America/New_York"))
If an API returns ISO 8601 with Z or +00:00, parse_datetime produces an aware value already. Forms with DateTimeField use the current timezone (activated per request with timezone.activate) to interpret user input, so activate the user's zone in middleware if your users span regions.
4. Fix date boundaries and display
A frequent bug: "orders today" computed in UTC misses everything after midnight UTC in the evening for American users.
from datetime import datetime, time, timedelta
from django.utils import timezone
today = timezone.localdate() # date in the current timezone
start = timezone.make_aware(datetime.combine(today, time.min))
end = start + timedelta(days=1)
Order.objects.filter(created_at__gte=start, created_at__lt=end)
Templates convert aware datetimes to the current timezone automatically with {{ order.created_at }}; in Python, use timezone.localtime(dt) before formatting.
5. Check settings and the database
USE_TZ = True
TIME_ZONE = "Europe/Berlin" # default for display and naive interpretation
Since Django 5.0, USE_TZ defaults to True. With PostgreSQL, Django uses timestamp with time zone columns and sets the connection to UTC, so the database server's own timezone does not matter. With MySQL, columns are datetime without zone information and Django stores UTC by convention, so other programs writing to the same tables must also write UTC.
Verification check: run the test suite with the warning turned into an error and grep production logs for naive datetime a day after deploying. Compare a known order's created_at in the admin with the time it was placed.
Explanation
Without timezone information, a datetime is just numbers on a clock face and could mean any of two dozen instants. Django's time zone support solves this by normalising everything to UTC for storage and converting to a chosen zone only when showing values to people. When naive values sneak in, Django converts them using the default zone and warns, because it cannot know whether 14:30 meant Berlin, New York, or UTC. The warning is therefore a data-correctness signal, not noise to silence.
Edge cases / notes
- DST gaps.
make_awareon a time that does not exist (02:30 on the spring-forward night) or exists twice raises errors or picks one; handle imports around DST explicitly withfold. - Celery. Set
CELERY_TIMEZONEand keepCELERY_ENABLE_UTC = True; ETA values must be aware. - Date fields.
DateFieldhas no time and no timezone; the warning only concernsDateTimeField. - Existing bad data. Rows saved from naive server-local values may need a one-off correction migration once you know their original zone.
Next steps
- Review time settings with the Django Production Settings Checklist.
- Scheduled tasks at the wrong hour are covered in Fix Celery Beat Not Sending Scheduled Tasks.
FAQ
Should I set USE_TZ = False to stop the warning?
No. That stores local times without zones and breaks comparisons across DST and regions. Fix the naive values instead.
What is the difference between TIME_ZONE and the database time zone?
TIME_ZONE is Django's default for display and interpreting naive values. With PostgreSQL, storage is always UTC regardless.
Is datetime.utcnow() safe?
It returns a naive datetime and is deprecated. Use timezone.now() or datetime.now(tz=UTC).
How do I show times in each user's timezone?
Store a zone per user and call timezone.activate() in middleware for each request.