UTC 시간과 파이썬 : 주의사항UTC Time and Python: Pitfalls
UTC 시간을 다룰 때, 주의해야 할 사항들Things to watch out for when working with UTC time
datetime 출력 결과 확인
앞서 본 문자열을 다시 한번 확인 해보겠습니다. 앞서 코드로 timezone 까지 입력하면서 제대로 UTC 포맷을 지켰다고 생각 할 수 있습니다. 하지만 실제 출력결과는 ISO 표준과 차이가 있습니다.
ISO 포맷을 지키는 것은 매우 중요합니다. 일반적인 웹 프론트-백 연동 뿐 아니라, 다른 서비스와 데이터를 주고 받는 통신에서 표준을 지키지 않는다면 서로 다른 시간값을 의미 할 수 있습니다.
# 코드에서 만든 시간 문자열
2023-12-24 15:00:00.000000+00:00
2023-12-25 00:00:00.000000+09:00
ISO 표준 다시 확인
다시 한번 ISO 표준 표기법에 대해서 상기해 보겠습니다. 크게 두 가지를 기억해야 합니다.
- 날짜와 시간을 구분하는 구분자 T
- 시간대 오프셋을 표현하는 Z 또는 +00:00
2023-12-24T15:00:00Z
2023-12-24T15:00:00+00:00
실제 표준 문자열과 비교해보면, 코드 결과에서는 구분자 T 가 빠진 것을 확인 할 수 있습니다. 그럼 어떻게 해야 datetime 객체를 ISO 표준 문자열로 표현 할 수 있는 지 알아 봅시다.
-
strftime(): 포맷 문자열 사용하여 변환 -
isoformat(): ISO 표준 문자열로 변환
strftime() : datetime 객체 → 문자열
가장 기본적인 방법으로는 strftime 함수를 사용하는 방법입니다. 정해진 포맷 문자열을 사용하여 datetime 객체를 문자열로 변환 할 수 있습니다. 아래와 같이 ISO 포맷에 알맞은 문자열을 구성하여 표준을 지키는 문자열과 사용자가 원하는 형태의 문자열로 만들 수 있습니다.
from datetime import datetime, timezone
utc_time = datetime.now(timezone.utc)
custom_str_time = utc_time.strftime("%Y/%m/%d") # 커스텀 시간 문자열
iso_str_time = utc_time.strftime("%Y-%m-%dT%H:%M:%SZ") # ISO 표준을 따른 포맷
print(utc_time) # 2023-12-24 15:00+00:00
print(custom_str_time) # 2023/12/24
print(iso_str_time) # 2023-12-24T15:00+00:00
시간 포맷 문자열
아래의 기본적인 포맷 이외에도 많은 포맷이 있습니다. 공식 문서를 참고 하시길 바랍니다.
| 포맷 | 의미 |
|---|---|
| %Y | 연도 (네 자리 숫자) |
| %m | 월 (두 자리 숫자) |
| %d | 일 (두 자리 숫자) |
| %H | 시간을 24시간 형식으로 (두 자리 숫자) |
| %M | 분 (두 자리 숫자) |
| %S | 초 (두 자리 숫자) |
| %f | 마이크로초를 (숫자) |
시간대 표현 문제점
strftime를 사용해도 제대로 잘 작동하는것 같습니다. 하지만 여기서 문제는 여러 시간대를 표현해야 하는 경우 입니다. 이 경우는 각 시간대에 알맞은 시간대 문자열을 각각 직접 지정해줘야 합니다.
아래의 예제 같은 경우 UTC가 아닌 시간대를 UTC 로 표기하게 되면서 9시간이라 시차가 발생하게 됐습니다.
from datetime import datetime
from pytz import timezone
tz = timezone("Asia/Seoul")
now = datetime.now(tz)
iso_str_time = now.strftime("%Y-%m-%dT%H:%M:%SZ") # UTC - ISO 표준을 따른 포맷
print(iso_str_time) # 2023-12-25T00:00+00:00 = 잘못된 UTC 값으로 표현
isoformat() : ISO 표기에 따른 변환
위에서 사용한 strftime 함수에서는 사용자가 직접 ISO 표준에 맞는 문자열을 구성해야 했습니다. 뿐만 아니라 시간대에 따라서 각각의 문자열을 지정해야 하는 문제가 있습니다.
이를 간단하게 isoformat 함수를 사용해서 일일히 작성 할 필요없이 표준을 준수하는 문자열을 만들 수 있습니다.
from datetime import datetime
from pytz import timezone
tz = timezone("Asia/Seoul")
now = datetime.now(tz)
iso_str_time = now.isoformat()
iso_seconds_time = now.isoformat(sep="T", timespec="seconds")
print(now) # 2023-12-25 00:00:00.0000+09:00
print(iso_str_time) # 2023-12-25T00:00:00.000000+09:00 = 알맞은 시간대까지 표현
print(iso_seconds_time) # 2023-12-25T00:00:00+09:00 = 밀리초는 제거된것 확인
isoformat 함수 호출 시, 선택적으로 두 가지 인자값을 넣어 줄 수 있습니다.
| 구분자 | 기능 |
|---|---|
| sep | 날짜와 시간 사이의 구분자를 지정하는데, 기본값은 ‘T’입니다. |
| timespec | 시간 중 어디까지 포함해서 문자열을 구성 할 것인지, 시간 단위를 지정합니다. 유효한 옵션은 ‘auto’, ‘hours’, ‘minutes’, ‘seconds’, ‘milliseconds’, ‘microseconds’입니다. |
문자열에서 변환
이제까지는 datetime 객체에서 문자열로 변환하는 것을 알아보았습니다.
이번에는 문자열에서 datetime 객체를 만드는 방법에 대해서 알아보겠습니다.
-
strptime(): 지정한 포맷을 따르는 문자열에서 변환 -
fromisoformat(): ISO 표준 문자열에서 변환
strptime() : 문자열 → datetime 객체
가장 기본적인 문자열에서 시간을 만들 때 쓰는 함수입니다. 먼저 문자열로 들어오는 시간 형식에 알맞은 포맷 문자열을 만들어 줍니다. 해당하는 포맷에 알맞지 않는 경우, ValueError 예외가 발생합니다.
이 함수도 시간대에 따른 각 시간대를 표기 해줘야 하는 문제가 존재합니다. 서울 시간대를 예를 들면 정확히 +09:00 라는 문자열을 포맷에 지정 해줘야 에러가 발생하지 않습니다.
from datetime import datetime
date_string = "2023-12-25T00:00:00+09:00"
date_format = "%Y-%m-%dT%H:%M:%S+09:00"
# datetime 객체로 변환
datetime_obj = datetime.strptime(date_string, date_format)
fromisoformat() : ISO 포맷 문자열 → datetime 객체
문자열이 ISO 포맷에 일치하는 경우 시간대를 알아서 반영해서 datetime 객체를 생성해줍니다. 시간 구분자가 T가 아니라 공백으로 되어있거나 다른 문자로 되어있더라도 해당 형식에 맞게 파싱을 시도합니다. 공백 하나, T 가 아닌 다른 문자 하나인 경우는 자동으로 처리 합니다. 다만, 이외의 형식과 다르다면 파싱에 실패할 수 있습니다.
from datetime import datetime
date_string = "2023-12-25 00:00:00+09:00"
date_format = "%Y-%m-%dT%H:%M:%S+09:00"
# datetime 객체로 변환
datetime_obj = datetime.fromisoformat(date_string)
print(datetime_obj.tzinfo) # UTC+09:00
다른 시간대로 변환
이번에는 UTC를 다른 시간대로 변환하거나 다른 시간대를 UTC 로 변환하는 방법에 대해서 알아 보겠습니다.
-
timedelta객체로 계산하기 -
astimezone(): 서로 다른 시간대로 변경
아래와 같이 UTC 시간을 한국 시간인 UTC+9 인 시간대로 변환한다고 가정하겠습니다. (밀리초는 무시하고 설명 하겠습니다.)
2023-12-24T15:00:00Z
2023-12-25T00:00:00+09:00
datedelta 객체 이용
UTC 시간에서 한국 시간대는 9시간이 빠르므로 9시간을 더해주면 간단하게 시간을 계산 할 수 있습니다. 이때 시간 계산에는 datedelta 객체를 이용하여 9 시간을 더 해줍니다.
하지만 더하고 결과를 확인해보면 문제가 있습니다. 시간대가 그대로 UTC를 의미하는 +00:00 인 상태 그대로 입니다. 의도와 달리 본래 UTC 시간보다 9시간 더 빠른 UTC 시간을 의미하게 됐습니다.
from datetime import datetime, timezone, timedelta
utc_time = datetime.now(timezone.utc)
kor_time = utc_time + timedelta(hours=9)
print(utc_time) # 2023-12-24 15:00:00+00:00
print(kor_time) # 2023-12-25 00:00:00+00:00
print(kor_time.tzinfo) # UTC
astimezone() : UTC ↔ 다른 시간대
astimezone 함수를 서로 다른 시간대를 간단하게 변경 할 수 있습니다. 시간대 정보까지 고려해서 다른 시간대로 변경해줍니다.
변환하고자 하는 목표 시간대를 정확히 지정해야 합니다. 예를 들어, ‘EST’나 ‘CST’와 같은 약어 대신 ‘America/New_York’, ‘Asia/Seoul’ 등의 정확한 시간대 이름을 사용하는 것이 좋습니다. pytz 라이브러리를 사용해서 정확한 시간대를 설정합니다.
from datetime import datetime, UTC
from pytz import timezone
utc_time = datetime.now(UTC)
target_timezone = timezone("Asia/Seoul")
kor_time = utc_time.astimezone(target_timezone)
print(utc_time) # 2023-12-24 15:00:00+00:00
print(kor_time) # 2023-12-25 00:00:00+09:00
print(kor_time.tzinfo) # Asia/Seoul
마무리
UTC 시간을 사용하여 데이터 통신을 하는 경우 반드시 표준을 준수하는 형태의 시간대 문자열을 사용 해야 혼동이 없습니다. 또한 시간을 계산할 때는 해당하는 시간의 시간대를 확인하고 그에 따른 알맞은 함수를 써서 시간대에 따른 오차가 없도록 해야 합니다.
-
strftime()보다는isoformat()를 사용 -
timedelta객체로 직접 계산보다astimezone()를 사용
이상으로 UTC를 파이썬에서 다루는 기본적인 내용을 알아보았습니다. 감사합니다. 😊
Checking the datetime output
Let’s look at the strings from before one more time. Since we went as far as passing a timezone in the code, you might think we followed the UTC format correctly. But the actual output differs from the ISO standard.
Sticking to the ISO format is very important. Not just in ordinary web front-end/back-end integration, but in any communication where you exchange data with other services, ignoring the standard can mean the two sides read different time values.
# Time strings produced by the code
2023-12-24 15:00:00.000000+00:00
2023-12-25 00:00:00.000000+09:00
Revisiting the ISO standard
Let’s recall the ISO standard notation once more. There are two main things to remember:
- the separator T between the date and the time
- Z or +00:00 to express the time zone offset
2023-12-24T15:00:00Z
2023-12-24T15:00:00+00:00
Compared with the standard string, the code output is missing the separator T. So let’s see how to express a datetime object as an ISO-standard string.
-
strftime(): convert using a format string -
isoformat(): convert to an ISO-standard string
strftime(): datetime object → string
The most basic approach is the strftime function. It converts a datetime object to a string using a given format string. As shown below, you can build a string that matches the ISO format to follow the standard, or build a string in whatever shape you like.
from datetime import datetime, timezone
utc_time = datetime.now(timezone.utc)
custom_str_time = utc_time.strftime("%Y/%m/%d") # custom time string
iso_str_time = utc_time.strftime("%Y-%m-%dT%H:%M:%SZ") # format following the ISO standard
print(utc_time) # 2023-12-24 15:00+00:00
print(custom_str_time) # 2023/12/24
print(iso_str_time) # 2023-12-24T15:00+00:00
Time format strings
There are many more formats beyond the basic ones below. See the official documentation.
| Format | Meaning |
|---|---|
| %Y | Year (four digits) |
| %m | Month (two digits) |
| %d | Day (two digits) |
| %H | Hour in 24-hour format (two digits) |
| %M | Minute (two digits) |
| %S | Second (two digits) |
| %f | Microseconds (number) |
The problem with expressing time zones
strftime seems to work just fine. The problem is when you have to express multiple time zones. In that case, you have to specify the right time zone string for each time zone yourself.
In the example below, a non-UTC time is labeled as UTC, which introduces a 9-hour discrepancy.
from datetime import datetime
from pytz import timezone
tz = timezone("Asia/Seoul")
now = datetime.now(tz)
iso_str_time = now.strftime("%Y-%m-%dT%H:%M:%SZ") # UTC - format following the ISO standard
print(iso_str_time) # 2023-12-25T00:00+00:00 = expressed as a wrong UTC value
isoformat(): converting to ISO notation
With the strftime function above, you had to build the ISO-compliant string yourself. On top of that, you had to specify a separate string for each time zone.
The isoformat function makes this simple: it produces a standard-compliant string without you having to write it out by hand.
from datetime import datetime
from pytz import timezone
tz = timezone("Asia/Seoul")
now = datetime.now(tz)
iso_str_time = now.isoformat()
iso_seconds_time = now.isoformat(sep="T", timespec="seconds")
print(now) # 2023-12-25 00:00:00.0000+09:00
print(iso_str_time) # 2023-12-25T00:00:00.000000+09:00 = includes the correct time zone
print(iso_seconds_time) # 2023-12-25T00:00:00+09:00 = milliseconds removed
When calling isoformat, you can optionally pass two arguments.
| Argument | What it does |
|---|---|
| sep | Sets the separator between the date and the time. The default is ‘T’. |
| timespec | Sets the time unit, i.e. how much of the time to include in the string. Valid options are ‘auto’, ‘hours’, ‘minutes’, ‘seconds’, ‘milliseconds’, and ‘microseconds’. |
Converting from strings
So far we’ve looked at converting a datetime object to a string.
Now let’s look at how to create a datetime object from a string.
-
strptime(): convert from a string that follows a given format -
fromisoformat(): convert from an ISO-standard string
strptime(): string → datetime object
This is the most basic function for creating a time from a string. First, you build a format string that matches the format of the incoming time string. If the string doesn’t match the format, a ValueError is raised.
This function also has the problem of having to spell out each time zone. Taking the Seoul time zone as an example, you have to put exactly +09:00 in the format string to avoid an error.
from datetime import datetime
date_string = "2023-12-25T00:00:00+09:00"
date_format = "%Y-%m-%dT%H:%M:%S+09:00"
# convert to a datetime object
datetime_obj = datetime.strptime(date_string, date_format)
fromisoformat(): ISO format string → datetime object
If the string matches the ISO format, it creates a datetime object with the time zone applied for you. Even if the time separator is a space or some other character instead of T, it tries to parse the string accordingly: a single space or a single character other than T is handled automatically. Beyond that, however, parsing may fail if the format differs.
from datetime import datetime
date_string = "2023-12-25 00:00:00+09:00"
date_format = "%Y-%m-%dT%H:%M:%S+09:00"
# convert to a datetime object
datetime_obj = datetime.fromisoformat(date_string)
print(datetime_obj.tzinfo) # UTC+09:00
Converting to another time zone
This time, let’s see how to convert UTC to another time zone, or another time zone to UTC.
- Calculating with a
timedeltaobject -
astimezone(): switching between time zones
Let’s say we want to convert a UTC time to Korean time, UTC+9, as below. (I’ll ignore milliseconds in this explanation.)
2023-12-24T15:00:00Z
2023-12-25T00:00:00+09:00
Using a datedelta object
Korean time is 9 hours ahead of UTC, so you can simply add 9 hours. Here we use a datedelta object to add the 9 hours.
But when you check the result, there’s a problem. The time zone is still +00:00, meaning UTC. Contrary to what we intended, it now represents a UTC time 9 hours ahead of the original.
from datetime import datetime, timezone, timedelta
utc_time = datetime.now(timezone.utc)
kor_time = utc_time + timedelta(hours=9)
print(utc_time) # 2023-12-24 15:00:00+00:00
print(kor_time) # 2023-12-25 00:00:00+00:00
print(kor_time.tzinfo) # UTC
astimezone(): UTC ↔ other time zones
The astimezone function makes it easy to switch between time zones. It takes the time zone information into account when converting.
You need to specify the target time zone precisely. For example, it’s better to use exact time zone names such as ‘America/New_York’ or ‘Asia/Seoul’ rather than abbreviations like ‘EST’ or ‘CST’. Use the pytz library to set the exact time zone.
from datetime import datetime, UTC
from pytz import timezone
utc_time = datetime.now(UTC)
target_timezone = timezone("Asia/Seoul")
kor_time = utc_time.astimezone(target_timezone)
print(utc_time) # 2023-12-24 15:00:00+00:00
print(kor_time) # 2023-12-25 00:00:00+09:00
print(kor_time.tzinfo) # Asia/Seoul
Wrapping up
When exchanging data using UTC time, you must use time zone strings that follow the standard to avoid confusion. And when calculating times, check the time zone of each time and use the right function for it so that there are no errors caused by time zones.
- Use
isoformat()rather thanstrftime() - Use
astimezone()rather than calculating directly with atimedeltaobject
That covers the basics of handling UTC in Python. Thanks for reading! 😊