1. What is UTC?

지역마다 '현재 시각'은 다릅니다.
그로 인해 '시차'라는 것이 있습니다.

예를 들어,
현재 대한민국의 시각이
2019년 1월 1일 03:00(AM)이면
현재 영국의 시각은
2018년 12월 31일 18:00(PM)입니다.

이는 대한민국의 입장에서 영국은 9시간이 느린 것이고, 영국의 입장에서 대한민국은 9시간이 빠른 것입니다.

그렇기 때문에 국제적으로 기준점이 필요했고 그 기준점을 영국의 그리니치 천문대로 삼았습니다.
그 기준점의 시간대를 UTC 혹은 GMT라고 합니다.

기술적인 표기로는 UTC가 사용됩니다.

UTC를 기준으로 각 나라별 시간대를 표현하는 방법은 다음과 같습니다.

시간대 나라 코드
UTC-5 미국(동부) EST
UTC 영국 GMT
UTC+8 대만 TW
UTC+9 대한민국 KST
UTC+9 일본 JST

미국(동부)의 경우,
영국 런던보다 5시간 느리고
대한민국과 일본의 경우,
영국 런던보다 9시간이 빠릅니다.

2. naive & aware

Python의 datetime객체는 naive와 aware로 나뉩니다.

naive는 시간대를 명시하지 않은 것,
aware는 시간대를 명시한 것 입니다.

예를 들어,
naive는 시간을 '2019년 01월 01일 00시 00분 00초'라고 알려주는 반면에
aware는 'UTC를 기준으로 2019년 01월 01일 00시 00분 00초'라고 알려주는 것입니다.

위 시간은 대한민국의 입장에서
naive의 경우, 2019.1.1 00:00:00이지만
aware의 경우, 2019.1.1 09:00:00입니다.

기준점이 확실한 aware를 사용하는 것이 좋습니다.

3. datetime

Python의 datetime이라는 모듈에는 날짜와 시간을 다룰 수 있는 여러 클래스들이 있습니다.

datetime
    ├── date
    ├── datetime
    ├── time
    └── timedelta

위에 명시된 클래스 말고도 더 있으니 Python Doumentation의 datetime파트를 참고하면 됩니다.

여기서 다루고자 하는 클래스는 datetime과 timezone입니다.

보통 현재 시간 객체를 생성할 때,

current_time = datetime.datetime.now()

위와 같이 생성하곤 하는데 이렇게 생성하게 되면 naive한 객체가 생성됩니다.

current_time = datetime.datetime.now(datetime.timezone.utc)

위와 같이 정확히 시간대를 명시해 aware 객체로 생성하는 것이 좋습니다.

기준이 정해지지 않은 datetime 객체를 DB에 저장하는 것은 위험합니다.

4. timedelta

시간관련 연산을 다루는 경우가 있습니다. 예를 들어, 쇼핑몰에서 물건을 구매하고 1주일이 지나면 리뷰를 쓸 수 없는 기능을 구현할 때, '주문한 시간' 부터 '지금'까지 7일 이상 차이가 나는지 연산해야 합니다.

Python에서는 시간의 차이를 쉽게 계산할 수 있습니다.

from datetime import datetime, timezone, timedelta
>>> may_first  = datetime(2019, 5, 1, 00, 00, tzinfo = timezone.utc)
>>> may_second = datetime(2019, 5, 2, 00, 00, tzinfo = timezone.utc)
>>> may_second - may_first
datetime.timedelta(days=1)

위와 같이, 두 datetime 객체의 차이를 아주 쉽게 구할 수 있습니다.

하지만, 여기서 주의해야 할 사항이 있습니다. naive객체와 aware객체를 함께 연산하면 안된다는 것입니다.

>>> may_third_naive = datetime(2019, 5, 3, 00, 00)
>>> may_third_naive - may_second
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: can't subtract offset-naive and offset-aware datetimes

naive객체와 aware객체를 연산하면
위와 같이 TypeError가 납니다.

5. UTC to KST

DB에 저장하는 datetime 객체는 UTC로 저장하는 것이 좋습니다.

그렇다면 tzinfo가 UTC인 datetime객체를 KST로 바꿔주는 작업도 필요합니다.

이를 위해 필요한 클래스들을 import합니다.

from datetime import datetime, timezone, timedelta
>>> jul_first_utc=datetime(2019, 7, 1, 0, 0, tzinfo=timezone.utc)
>>> print(jul_first_utc)
2019-07-01 00:00:00+00:00

UTC를 기준으로 생성한 aware datetime 객체를 다뤄보겠습니다.

timezone클래스는 인자로 timedelta를 받습니다. 즉, 각 시간대를 다음과 같이 생성할 수 있습니다.

EST = timezone(timedelta(hours=-5))
TW  = timzzone(timedelta(hours=8))
KST = timezone(timedelta(hours=9))
JST = timezone(timedelta(hours=9))

datetime 객체의 각 요소들은 replace를 통해 값을 바꿀수 있습니다.
시간대도 마찬가지로 바꿀 수 있습니다.
immutable method이므로 다른 변수에 저장시켜줍니다.

>>> jul_first_KST=jul_first_utc.replace(tzinfo=KST)
>>> print(jul_first_utc)
2019-07-01 00:00:00+00:00
>>> print(jul_first_KST)
2019-07-01 00:00:00+09:00

둘의 시간차이를 확인해보겠습니다.

>>> print(jul_first_utc - jul_first_KST)
9:00:00

차이가 있습니다.
둘은 '다른 시각'입니다.

영국의 00시 00분은
한국의 09시 00분입니다.

현재 위에서 한 연산은
영국의 00시 00분(한국의 09시 00분)과
한국의 00시 00분을 연산한 것이기 때문에 시차만큼의 timedelta 값을 return한 것입니다.

왜냐하면 replace() 메서드는 시간대만 바꿔줄 뿐이고 정확하게 시간정보를 바꿔주지 않습니다.

그러므로 직접 시차만큼 연산을 한번 더 해줘야합니다.

>>> jul_first_KST=jul_first_KST+timedelta(hours=9)
>>> print(jul_first_KST)
2019-07-01 09:00:00+09:00

이제 UTC 기준의 시간과 KST 기준의 시간을 연산해보겠습니다.

>>> print(jul_first_KST - jul_first_utc)
0:00:00

이제 둘은 같은 시각을 표현하고 있습니다.

>>> print(jul_first_utc)
2019-07-01 00:00:00+00:00
>>> print(jul_first_KST)
2019-07-01 00:09:00+09:00

Summary

  1. 시간대를 바꾼다.
    KST = timezone(timedelta(hours=9))
    jul_first_KST = jul_first_utc.replace(tzinfo=KST)
  2. 시차 만큼 연산한다.
    jul_first_KST = jul_first_KST + timedelta(hours=9)