__date 필터가 로컬에서만 0건 나오는 이유DB 에 저장은 됐는데 조회해보면 로컬은 0건. 코드 버그처럼 보였지만 범인은 로컬 MySQL의 타임존 테이블이었다. 그 과정을 디버깅하며 정리한 타임존 개념 노트.
CONVERT_TZ는 설정이 아니라 시각 변환 계산기다. DB에 저장된 값을 바꾸지 않고, 읽을 때 임시로 계산만 한다.USE_TZ=True면 Django는 서버 시간과 무관하게 무조건 UTC로 저장한다.__date 등) = DB의 CONVERT_TZ.CONVERT_TZ('...','UTC','Asia/Seoul')처럼 타임존 이름을 쓰면 MySQL에 타임존 테이블(이름표 사전)이 적재돼 있어야 한다. 없으면 NULL 반환 → 모든 비교가 거짓 → 0건.방송 목록 API에 날짜 필터를 걸었다.
/api/v2/broadcasting/?start_date=2026-04-26&end_date=2026-06-07
같은 코드인데 결과가 달랐다.
네 가지 경로로 같은 데이터를 조회해봤다.
| 방법 | 결과 | 의미 |
|---|---|---|
| API 호출 | 0건 | 증상 |
ScheduleFilter (ORM) | 0건 | 증상 재현 |
start_time__date 범위 (CONVERT_TZ 경로) | 0건 | 여기가 깨짐 |
start_time raw __gte/__lte (CONVERT_TZ 안 씀) | 199건 | 데이터는 멀쩡 |
마지막 줄이 결정적이었다. __date를 거치지 않는 raw 비교는 199건이 정상으로 나온다.
→ 데이터/저장 문제가 아니라, 그 필터가 의존하는 DB 기능(CONVERT_TZ)이 고장난 것.
CONVERT_TZ는 설정이 아니라 계산기다CONVERT_TZ(시각, '이 타임존에서', '저 타임존으로')
시각 하나를 받아 다른 타임존 기준으로 바꿔 돌려주는 함수일 뿐이다. 서버나 DB에 무언가를 설정하지 않는다.
CONVERT_TZ('2026-06-08 05:00:00', 'UTC', 'Asia/Seoul')
-- 결과: '2026-06-08 14:00:00' (UTC + 9시간)
변환의 주체가 방향에 따라 다르다. 이게 가장 헷갈리는 지점.
Django(한국시간 14:00) → Django가 직접 UTC 05:00으로 변환 → DB에 05:00 저장
USE_TZ=True면 서버 시간(UTC든 KST든)과 무관하게 무조건 UTC로 저장.CONVERT_TZ가 쓰이지 않는다.주의: 흔히 "서버가 UTC라서 UTC로 저장된다"고 생각하지만, 정확히는 "Django 설정이 UTC로 저장한다". 서버 시간은 보지 않는다.
DB에서 05:00(UTC) 꺼냄 → 파이썬이 14:00(KST)으로 표시
CONVERT_TZ 안 씀.__date, __year, __month)DB가 WHERE 단계에서 05:00(UTC) → 14:00(KST) 변환 후 날짜 비교
CONVERT_TZ를 쓴다.왜 필터만 DB에서 변환하나?
"6월 7일 방송만 줘"라는 조건은 DB가 행을 고르는 단계(WHERE) 에서 날짜를 잘라야 한다. 파이썬이 데이터를 꺼낸 뒤에 거를 수 없다. DB 안에 값은 UTC로 있으니, DB가 KST로 바꿔서 날짜를 잘라야 정확하다.
핵심 구분:
CONVERT_TZ)-- start_time__date=2026-06-07 는 내부적으로 이렇게 바뀐다
WHERE DATE(CONVERT_TZ(start_time, 'UTC', 'Asia/Seoul')) = '2026-06-07'
CONVERT_TZ가 'Asia/Seoul'이 UTC+9라는 걸 알려면, MySQL의 mysql.time_zone* 시스템 테이블(이름표 사전)이 적재돼 있어야 한다.
-- 로컬 (사전 없음)
CONVERT_TZ('2026-06-08 05:00:00', 'UTC', 'Asia/Seoul') → NULL ❌
-- 프로덕션 (사전 있음)
CONVERT_TZ('2026-06-08 05:00:00', 'UTC', 'Asia/Seoul') → '2026-06-08 14:00:00' ✅
NULL = '2026-06-07' 비교는 항상 거짓이므로 → 전부 0건.
참고:
CONVERT_TZ('...', '+00:00', '+09:00')처럼 숫자 오프셋으로 쓰면 사전이 필요 없다. Django가 자동으로 이름('Asia/Seoul')을 쓰기 때문에 사전이 필요했던 것.
[저장] — 문제 없음 ✅
Django(KST 14:00) → Django가 UTC 05:00으로 변환 → DB에 05:00 저장
(서버 시간 무관, CONVERT_TZ 안 씀)
[보여주기] — 문제 없음 ✅
DB에서 05:00(UTC) 꺼냄 → 파이썬이 14:00(KST)으로 표시
(CONVERT_TZ 안 씀)
[날짜 필터] — 여기가 깨짐 ❌
DB가 WHERE에서 05:00(UTC) → 14:00(KST) 변환 시도 → 이름표 사전 없음 → NULL → 0건
로컬 MySQL에 타임존 테이블을 한 번만 적재하면 된다.
# Docker MySQL (호스트에서):
mysql_tzinfo_to_sql /usr/share/zoneinfo | docker exec -i <컨테이너이름> mysql -u root -p mysql
# 컨테이너 안에서:
mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p mysql
적재 후 확인:
SELECT CONVERT_TZ('2026-06-08 05:00:00','UTC','Asia/Seoul');
-- NULL이 아니라 '2026-06-08 14:00:00' 이면 성공
mysql 인자는 적재 대상 DB 이름이다 (앱 DB가 아니라 시스템 스키마 mysql).Windows + Docker라 호스트에
/usr/share/zoneinfo가 없다면, 컨테이너 안에서 실행한다:docker exec -i <컨테이너이름> sh -c "mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root -p'<루트비번>' mysql"
__date/__year/__month같은 날짜 부분 필터는USE_TZ=True인 Django에서CONVERT_TZ(컬럼,'UTC','Asia/Seoul')로 변환된다. 로컬 MySQL에 타임존 이름표 사전이 비어 있으면 그 계산이NULL이 되어 모든 결과가 0건이 된다. 코드 버그가 아니라 로컬 MySQL 환경 문제이고, 타임존 테이블만 적재하면 해결된다.