Django에서 날짜와 시간을 다룰 때 생기는 timezone 문제와 그 해결법을 정리한 글이다. USE_TZ와 TIME_ZONE을 켠 한국향 서비스에서 naive와 aware datetime이 섞이며 생기는 증상을 세 갈래로 나눠 짚는다. Python 3.5, Django 1.9, PostgreSQL 기준이다.
핵심 포인트- settings에 USE_TZ를 True로, TIME_ZONE을 Asia/Seoul로 두는 것이 출발점이다.
- datetime.now()는 설정에 따라 +9 기준 값을 주지만 timezone 정보가 없는 naive 객체다.
- 그 값을 저장하면 DB에는 UTC 기준으로 들어가고 모델로 읽으면 UTC 기준의 aware 객체가 나온다.
- 읽은 값에 date()를 호출하면 UTC 기준 날짜라 한국 시간으로 오전 9시 이전이면 하루 전이 된다.
- naive와 aware 객체를 비교하면 예외가 난다.
- DateTimeField와 달리 DateField는 UTC가 아니라 한국 날짜로 저장되고 읽힌다.
상세 정리- 전제 환경: Python 3.5, Django 1.9, PostgreSQL 조합을 기준으로 설명한다.
- 설정: 한국 시간을 쓰려면 settings에서 USE_TZ를 켜고 TIME_ZONE에 Asia/Seoul을 지정한다.
- 영향 1: datetime.now()나 today()로 현재 시각을 얻으면 TIME_ZONE 설정에 따라 +9 기준 값이 나오지만 타임존 정보가 붙지 않은 naive 객체다.
- 영향 2: 그 값을 그대로 DB에 저장하면 UTC 기준 시간값으로 저장된다.
- 영향 3: 모델을 통해 읽은 DateTimeField 값은 타임존이 UTC인 aware 객체다.
- 문제 1 날짜 어긋남: 읽은 값에 date()를 붙이면 UTC 기준 날짜가 나와 한국 시간 기준 날짜와 맞지 않는다. 한국 시간으로 오전 9시 이전이면 UTC 기준으로는 전날이 된다.
- 문제 2 비교 예외: strptime으로 문자열을 파싱하거나 datetime.now()로 얻은 naive 객체를 aware 객체와 비교하면 예외가 발생한다.
- 문제 3 필드별 차이: DateField는 UTC 기준이 아니라 한국 날짜로 저장되고 읽혀 DateTimeField와 동작이 갈린다.
- 해결 1: datetime.now() 대신 django.utils.timezone의 now를 써서 처음부터 aware 객체를 다룬다.
- 해결 2: 한국 시간 표현이 필요하면 timezone의 localtime으로 변환한다.
- 해결 3: naive 객체를 쓸 수밖에 없다면 make_aware로 변환한 뒤 비교한다. 상환 예정 시각 문자열을 파싱해 투자 생성 시각과 비교하는 예제를 든다.
- 마무리: 전 세계 기준 시간대가 한국이 아닌 이상 한국향 서비스는 이 문제를 피할 수 없으므로 처음부터 aware 객체로 다루는 편이 낫다는 취지로 맺는다.
왜 읽나Django 서비스에서 날짜가 하루씩 어긋나거나 datetime 비교에서 예외가 난 적이 있다면 원인 세 갈래와 대응 함수가 바로 정리된다.