# 파이썬 표준 라이브러리로 시간 다루기: `datetime` 완전 정복 > **시리즈 03 – 날짜/시간 연산, 타임존, 포맷 변환까지 한 번에** 시간은 단순히 “문자열”이 아닙니다. 날짜가 하루씩 넘어가고, 월이 바뀌고, 서머타임이 끼어들고, 지역마다 기준이 달라집니다. 그래서 날짜/시간을 다룰 때는 “표시용 문자열”과 “계산 가능한 시간 값”을 구분하고, 타임존까지 포함해 다루는 습관이 중요합니다. ![시계작업자의 마법같은 작업실](/media/editor_temp/6/5843a4cb-f3a6-4d8d-9a13-89c1cbb0ef6c.png) 이 글에서는 `datetime` 모듈을 중심으로 다음을 정리합니다. * 현재 시각과 날짜 만들기 * 기간 계산(`timedelta`) * 문자열 포맷/파싱(`strftime`, `strptime`) * 타임존 처리(`timezone`, `zoneinfo`) * 자주 마주치는 **주의점**과 안정적인 패턴 --- ## 1. `datetime`은 무엇을 제공하나? {#sec-5f3cbdfb3d40} `datetime`에는 비슷해 보이지만 역할이 다른 타입들이 있습니다. * `date` : 연-월-일만 필요할 때 * `time` : 시:분:초만 필요할 때 * `datetime` : 날짜+시간(가장 많이 씀) * `timedelta` : 시간의 “차이”(기간) * `timezone` : 고정 오프셋 타임존(예: UTC+9처럼 오프셋이 변하지 않는 경우) 그리고 파이썬 3.9+에서는 표준 라이브러리로 **`zoneinfo`**가 들어오면서, 지역 타임존(예: Asia/Tokyo)을 다루기 쉬워졌습니다. --- ## 2. “지금”을 얻는 방법: naive vs aware부터 정리 {#sec-80466bd4f739} ### 2.1 naive / aware란? {#sec-893b0793d467} `datetime` 객체는 크게 두 부류가 있습니다. * **naive datetime**: 타임존 정보가 없음 * **aware datetime**: 타임존 정보(`tzinfo`)가 있음 둘을 섞어 연산/비교하면 에러가 나기도 하고, 더 조심해야 할 건 “에러 없이 돌아가는데 결과가 의도와 다를 수 있다”는 점입니다. ### 2.2 추천되는 기본값: UTC aware로 시작하기 {#sec-f876fdec8f2b} 저장/계산 기준을 UTC로 통일하면 깔끔해지는 경우가 많습니다. ```python from datetime import datetime, timezone utc_now = datetime.now(timezone.utc) # aware (UTC) print(utc_now) ``` 로컬 시각이 필요하면 “표시 단계”에서 변환합니다. ```python from datetime import datetime, timezone from zoneinfo import ZoneInfo utc_now = datetime.now(timezone.utc) tokyo_now = utc_now.astimezone(ZoneInfo("Asia/Tokyo")) print(utc_now) print(tokyo_now) ``` --- ## 3. 날짜/시간 계산: `timedelta`가 중심 {#sec-5497d843fd3e} ### 3.1 더하기/빼기 {#sec-0288780aa297} ```python from datetime import datetime, timedelta, timezone now = datetime.now(timezone.utc) print(now + timedelta(days=3)) print(now - timedelta(hours=2)) ``` ### 3.2 두 시각의 차이 {#sec-c781f333910b} ```python from datetime import datetime, timezone a = datetime(2026, 1, 1, tzinfo=timezone.utc) b = datetime(2026, 1, 10, tzinfo=timezone.utc) delta = b - a print(delta.days) # 9 print(delta.total_seconds()) # 777600.0 ``` ### 3.3 `timedelta`로 “기간”을 표현할 때의 감각 {#sec-c81173d2a4f5} `timedelta`는 **일/초/마이크로초** 단위로 동작합니다. “한 달 뒤”처럼 달 길이가 달라지는 표현은 `timedelta(days=30)`로 대충 맞출 수는 있어도, 달력 기준의 “다음 달 같은 날짜”는 다른 접근이 필요합니다(이 부분은 표준 라이브러리 범위 밖에서 `dateutil` 같은 도구가 많이 쓰입니다). --- ## 4. 포맷과 파싱: `strftime` / `strptime` {#sec-a17a8e259a48} ### 4.1 datetime → 문자열(`strftime`) {#sec-8d6f34025b2e} ```python from datetime import datetime, timezone dt = datetime(2026, 1, 30, 14, 5, 0, tzinfo=timezone.utc) print(dt.strftime("%Y-%m-%d %H:%M:%S %z")) ``` 자주 쓰는 포맷 패턴: * `%Y-%m-%d` : 2026-01-30 * `%H:%M:%S` : 14:05:00 * `%z` : +0000 (UTC 오프셋) ### 4.2 문자열 → datetime(`strptime`) {#sec-f2bbc1672485} ```python from datetime import datetime s = "2026-01-30 14:05:00" dt = datetime.strptime(s, "%Y-%m-%d %H:%M:%S") print(dt) ``` 여기서 생성된 `dt`는 **naive**입니다. 타임존을 명확히 하고 싶다면 `tzinfo`를 붙여줘야 합니다. ```python from datetime import datetime, timezone s = "2026-01-30 14:05:00" dt = datetime.strptime(s, "%Y-%m-%d %H:%M:%S").replace(tzinfo=timezone.utc) print(dt) ``` > `replace(tzinfo=...)`는 “시간 값을 변환”하는 게 아니라 **그 시각에 타임존 라벨을 붙이는 동작**입니다. > 변환이 필요하면 `astimezone()`을 사용합니다(다음 섹션 참고). --- ## 5. 타임존: `timezone`과 `zoneinfo`를 구분해서 쓰기 {#sec-05b1d0f7ef82} ### 5.1 고정 오프셋이면 `timezone` {#sec-67fec65074ab} 예: UTC, UTC+9처럼 오프셋이 고정인 경우 ```python from datetime import datetime, timezone, timedelta kst_fixed = timezone(timedelta(hours=9)) dt = datetime(2026, 1, 30, 12, 0, tzinfo=kst_fixed) print(dt) ``` ### 5.2 지역 타임존이면 `zoneinfo` {#sec-834b663bce83} 서머타임처럼 오프셋이 변할 수 있는 지역은 `zoneinfo`가 적합합니다. ```python from datetime import datetime, timezone from zoneinfo import ZoneInfo utc_now = datetime.now(timezone.utc) ny_now = utc_now.astimezone(ZoneInfo("America/New_York")) print(ny_now) ``` ### 5.3 `replace(tzinfo=...)` vs `astimezone(...)` 차이 {#sec-35bf0fbaa9df} * `replace(tzinfo=...)`: **시각 값은 그대로**, 타임존만 “라벨링” * `astimezone(...)`: **같은 순간**을 다른 타임존의 시각으로 “변환” 이 차이를 알고 있으면 타임존 관련 버그를 많이 줄일 수 있습니다. --- ## 6. 자주 마주치는 주의점 정리 {#sec-552ca3933648} ### 6.1 naive/aware 혼용 {#sec-21a4c96b9022} * 내부 계산용 시각은 **aware(UTC)** 로 맞추는 편이 안정적입니다. * 외부 입력은 들어오는 순간 타임존을 결정하고 통일하는 것이 좋습니다. ### 6.2 “로컬 시간”은 환경에 따라 달라질 수 있음 {#sec-e2bd08b413c3} `datetime.now()`는 실행 환경의 로컬 설정을 따릅니다. 컨테이너/서버 환경에서는 로컬이 UTC일 수도, 아닐 수도 있습니다. 환경 차이를 줄이려면 `datetime.now(timezone.utc)` 같은 방식이 낫습니다. ### 6.3 문자열 파싱의 포맷 불일치 {#sec-e52b753858ac} `strptime`은 포맷이 1글자라도 다르면 실패합니다. 입력 포맷이 여러 가지면 “전처리” 또는 포맷 후보를 단계적으로 시도하는 코드가 필요합니다(가능하면 입력 포맷을 하나로 제한하는 편이 더 좋습니다). --- ## 7. 많이 쓰는 패턴 3가지 {#sec-b244af674a6a} ### 7.1 ISO 8601 형태로 저장하기 {#sec-4b1979b29466} 표준적인 문자열 표현이 필요하다면 `isoformat()`이 편합니다. ```python from datetime import datetime, timezone dt = datetime.now(timezone.utc) print(dt.isoformat()) # 예: 2026-01-30T05:12:34.567890+00:00 ``` ### 7.2 “오늘 날짜”로 파일명 만들기 {#sec-ae13a5dac423} ```python from datetime import datetime stamp = datetime.now().strftime("%Y%m%d") filename = f"report_{stamp}.json" print(filename) ``` ### 7.3 특정 시각까지 남은 시간 계산하기 {#sec-b509d1188ff7} ```python from datetime import datetime, timezone target = datetime(2026, 2, 1, 0, 0, tzinfo=timezone.utc) now = datetime.now(timezone.utc) remaining = target - now print(remaining) print(remaining.total_seconds()) ``` --- ## 8. 마무리 {#sec-9fa53305f95a} `datetime`은 “날짜 문자열 만들기”를 넘어, 시간을 **연산 가능한 값**으로 다루게 해주는 핵심 도구입니다. 여기에 타임존(`timezone`, `zoneinfo`)까지 포함하면, 실행 환경이 달라져도 결과가 흔들리지 않는 코드를 만들기 쉬워집니다. 다음 편에서는 `random`에 대하여 난수 생성/샘플링/셔플/시드 그리고 보안용 난수(`secrets`)까지 묶어서 정리해보겠습니다. --- **관련글 :** - [Django에서 datetime과 timezone 제대로 쓰기](/ko/whitedec/2025/11/10/django-datetime-timezone/) - [Django의 시간관리 마법 - 'django.utils.timezone' 완벽가이드](/ko/whitedec/2025/11/14/django-utils-timezone-guide/) --- **이전 시리즈 보기** - [[파이썬 표준라이브러리 - 0] 파이썬 표준 라이브러리란? 초심자를 위한 가이드](/ko/whitedec/2026/1/29/python-standard-library/) - [[파이썬 표준라이브러리 -1] 파일 시스템 & OS 환경 마스터: pathlib vs os](/ko/whitedec/2026/1/29/file-system-os-environment-master-pathlib-vs-os/) - [[파이썬 표준라이브러리 - 2] 데이터 저장 & 직렬화: json, pickle, csv](/ko/whitedec/2026/1/30/python-json-pickle-csv/)