파이썬으로 코딩을 하다 보면 날짜, 시간에 관련 처리를 할 일이 많다. 이때 사용하게 되는것이 datetime 모듈이다. 시간을 처리하는 다른 모듈은 time 모듈이 있는데, time 모듈은 좀 더 근본적으로 시간에 관한 여러 가지 연산을 지원하고, datetime 은 기본적인 연산들은 지원하지만 주로 시간이나 날짜를 사용자에게 보여지는 형태로 포매팅하는데 중점을 둔 모듈이라고 한다.
Datetime 모듈의 하위 클래스는
datetime.date
datetime.time
datetime.datetime
datetime.timedelta
datetime.tzinfo
datetime.timezone
이 있다. 시간대 적용은 pytz 모듈로 하는것이 편하므로 이 중 tzinfo 와 timezone 은 생략하고 나머지 모듈에 대해서 정리해 본다. 더 자세한 내용은 공식 문서 참조.
#1. datetime.date
이 클래스의 객체는 년월일 정보를 담고 있는 객체이다.
특정한 날짜의 객체를 만들려면 년, 월, 일을 전달해주면 되고, 오늘 날짜를 얻으려면 today() 를 이용한다.
>>> from datetime import date
>>> date(2019, 1, 31)
datetime.date(2019, 1, 31)
>>> date.today()
datetime.date(2020, 4, 3)
today() 에서는 현재 작업중인 시스템의 로컬 날짜가 반환되게 된다.
date 객체에서 년, 월, 일 을 추출하려면 객체의 year, month, day 속성을 가져오면 된다.
>>> from datetime import date
>>> today = date.today()
>>> today
datetime.date(2020, 4, 3)
>>> today.year
2020
>>> today.month
4
>>> today.day
3
와 같이 한다. 이때 반환되는 값은 int 타입이다.
date.weekday() 는 요일 정보를 숫자로 반환해준다. 월요일이 0이고 순서대로 올라가 일요일이 6이다.
>>> from datetime import date
>>> date1 = date(2020, 1, 1)
>>> date1.weekday()
2 # 수요일
date.isoweekday() 도 같은 기능인데, 차이점은 월요일이 1이고 일요일이 7이다.
년월일 정보가 YYYY-MM-DD 식으로 포매팅된 것을 ISO 포맷이라고 하는데 이런 string을 date 객체로 변환하려면
>>> date.fromisoformat('2020-01-31')
datetime.date(2020, 1, 31)
과 같이 하면 된다. 이때 월/일 숫자가 한자리수인 경우 앞에 0 이 붙어있지 않으면 제대로 처리가 안된다. (즉 2020-01-01 은 되지만 2020-1-1 은 처리가 안됨)
반대로 날짜 객체를 ISO 포맷의 문자열로 반환하려면
>>> d1 = date(2020,1,1)
>>> date.isoformat(d1)
'2020-01-01'
와 같이 할 수 있다.
date.replace()를 이용하면 객체의 년월일 정보를 바꿀수 있다.
>>> from datetime import date
>>> date1 = date(2020, 1, 1)
>>> date1.replace(day=31)
datetime.date(2020, 1, 31)
date.strftime() 메서드를 이용해 객체를 다양한 방법으로 문자열로 변환할 수 있다.
>>> from datetime import date
>>> d = date(2020,1,31)
>>> d.strftime("%d/%m/%y")
'31/01/20'
>>> d.strftime("%A %d. %B %Y")
'Friday 31. January 2020'
strftime() 포매팅 관련 옵션은 포스트 하단 참조.
#2. datetime.time
이 클래스의 객체는 특정 날짜와 무관한 시간 정보를 담고 있는 객체이다.
특정 시간의 객체를 생성하려면 인자를 직접 전달해주면 된다.
>>> from datetime import time
>>> time(hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0)
인자를 따로 전달하지 않으면 디폴트 값이 전달된다. hour는 0~23, minute은 0~59, second는 0~59, microsecond는 0~999,999 까지의 정수 값이 허용된다. fold는 서머타임 등 물리적으로 같은 시간에 대해 2가지 다른 시간이 표시될때 조정해주는 값으로 0 또는 1이 사용되는데, 보통은 기본값 0으로 두면 되는듯하다.
tzinfo는 시간대 정보를 전달해 준다. 시간대는 별도의 pytz 모듈을 이용해서 지정해주면 편하다. pytz는 따로 설치를 해주어야 한다.
$ pip install pytz
>>> from datetime import time
>>> import pytz
>>> tz = pytz.timezone('UTC')
>>> time(12, 10, 30, tzinfo=tz)
datetime.time(12, 10, 30, tzinfo=<UTC>)
pytz에서 지원하는 모든 시간대 정보는
>>> pytz.all_timezones
로 확인할 수 있다.
파이썬 날짜/시간 관련 개체는 크게 naive와 aware 객체로 구분할수 있다. 간단히 말하면 시간대 정보를 담고 있으면 aware, 시간대 정보가 담겨있지 않으면 naive 객체이다. 그런데 naive 객체는 시간대 정보가 없어 혼란을 초래할 수 있으므로 가능하면 모든 객체를 시간대를 지정해 aware 객체로 만들어 주는것이 좋다. 그리고 특별한 경우가 아니면 모든 시간대는 UTC 로 통일하고, 특정 지역 사용자에게 보여질때 해당 시간대로 변환해서 보여주는것이 좋다고 한다.
어떤 time 객체의 시, 분, 초, 마이크로초 및 시간대 정보는 각 속성을 입력하면 확인할 수 있다.
>>> from datetime import time
>>> import pytz
>>> tz = pytz.timezone('UTC')
>>> t1 = time(12, 10, 30, tzinfo=tz)
>>> t1.hour
12
>>> t1.minute
10
>>> t1.second
30
>>> t1.microsecond
0
>>> t1.tzinfo
<UTC>
여기서 나머지는 int type으로 숫자를 반환하고, tzinfo는 pytz.timezone 객체를 반환한다. 만약 시간대의 이름을 문자열로 얻고싶다면 tzname() 메서드를 호출하면 된다.
>>> t1.tzname()
'UTC'
date 객체에서와 마찬가지로 time.replace()를 이용하면 time 객체의 시간 정보를 바꿀 수 있다.
>>> t1.replace(hour=11)
datetime.time(11, 10, 30, tzinfo=<UTC>)
time.isoformat() 메서드는 시간 정보를 ISO 포맷의 문자열로 출력해준다. 다만 날짜 데이터와 달리 시간 데이터의 ISO 포맷은 읽기에 불편해 실제 사용할 일이 많지 않을것같다.
>>> t1.isoformat()
'12:10:30+00:00'
time.strftime() 은 date에서와 마찬가지로 다양한 옵션으로 사용자에게 읽기 편한 문자열 출력을 제공한다. 옵션은 역시 포스트 하단의 표 참조.
>>> t1.strftime("%H:%M:%S %Z")
'12:10:30 UTC'
#3. datetime.datetime
이 클래스의 객체는 날짜와 시간 정보를 모두 담고 있는 객체이다.
객체를 생성하려면
>>> from datetime import datetime
>>> dt = datetime(year, month, day, hour=0, minute=0, second=0, microsecond=0, tzinfo=None, *, fold=0)
으로 생성하면 된다. 생성된 객체의 년월일, 시분초 등 정보는 위에서와 같은 방법으로 확인할 수 있다.
UTC 기준으로 현재 시간을 담은 객체를 생성하려면 아래와 같이 한다.
>>> from datetime import datetime
>>> import pytz
>>> tz = pytz.timezone('UTC')
>>> dt = datetime.now(tz)
>>> dt
datetime.datetime(2020, 4, 3, 11, 21, 35, 832922, tzinfo=<UTC>)
현재 시각을 생성하는 메서드는
datetime.now()
datetime.today()
datetime.utcnow()
가 있는데, 아래의 2가지는 naive datetime 객체를 생성하므로 가능하면 아래 2가지보다 now()를 사용하는것이 권장되고, now()를 쓸 때 시간대 정보를 전달해서 aware 객체를 생성하는 것이 좋다.
date와 time 객체가 따로 존재한다면 datetime.combine() 을 이용해서 결합해줄 수 있다. 이때 별도로 시간대 정보를 전달해줄 수 있다.
>>> from datetime import datetime, date, time
>>> import pytz
>>> tz = pytz.timezone('UTC')
>>> d = date(2020,1,31)
>>> t = time(15,10,25)
>>> dt = datetime.combine(d, t, tz)
>>> dt
datetime.datetime(2020, 1, 31, 15, 10, 25, tzinfo=<UTC>)
또 datetime 객체에서 날짜 정보를 따로 객체로 만들려면 date()을 이용한다.
시간 객체는 time() 혹은 timetz() 이용하면 되는데, 후자는 시간대 정보를 포함하고 있다.
>>> dt.date()
datetime.date(2020, 1, 31)
>>> dt.time()
datetime.time(15, 10, 25)
>>> dt.timetz()
datetime.time(15, 10, 25, tzinfo=<UTC>)
date나 time 객체에서와 동일한 방법으로 datetime.replace() 를 이용하면 날짜와 시간 정보를 수정할 수 있다.
datetime.weekday()와 datetime.isoweekday()는 date 객체에서와 동일하다.
datetime.strptime() 을 이용하면 strftime() 에서 사용하는 포매팅 옵션을 이용해서 datetime 객체를 만들어줄 수 있다.
>>> from datetime import datetime
>>> d = datetime.strptime('2020-01-31', '%Y-%m-%d')
>>> d
datetime.datetime(2020, 1, 31, 0, 0)
date나 time 과 동일하게 datetime.strftime() 을 이용해서 다양한 포맷으로 출력할 수 있다. 옵션은 포스트 하단 참조,
>>> dt
datetime.datetime(2020, 1, 31, 15, 10, 25, tzinfo=<UTC>)
>>> dt.strftime("%A, %d. %B %Y %I:%M%p")
'Friday, 31. January 2020 03:10PM'
#4. datetime.timedelta
이 클래스의 객체는 두 날짜 혹은 시간의 차이를 나타내는 객체이다.
객체를 생성하려면 days, seconds, microseconds, milliseconds, minutes, hours, weeks 인자를 전달해주면 된다. 디폴트값은 0 이다.
>>> from datetime import timedelta
>>> timedelta(days=0, seconds=0, microseconds=0, milliseconds=0, minutes=0, hours=0, weeks=0)
실제 객체에 저장되는것은 days, seconds와 microseconds 이고, 나머지 인자들은 내부적으로 계산이 이루어져 변환된다.
timedelta 개체끼리는 사칙연산과 크기 비교, 절대값 산출 등 기본적인 수학적 처리가 제공된다.
실제로는 timedelta 에 인자를 직접 전달하기보다는 두 datetime 객체의 차이를 구하는데 이용될 것이다. datetime 객체의 차이를 구하면 자동으로 timedelta 객체가 반환된다. datetime 객체끼리 더하는 연산은 지원되지 않아 TypeError 가 발생한다.
>>> from datetime import datetime, timedelta
>>> d1 = datetime.now()
>>> d1
datetime.datetime(2020, 4, 3, 15, 52, 7, 979068)
>>> d2 = datetime(2020, 1, 1, 12, 10 , 25, 0)
>>> d2
datetime.datetime(2020, 1, 1, 12, 10, 25)
>>> d1 - d2
datetime.timedelta(days=93, seconds=13302, microseconds=979068)
>>> d2 - d1
datetime.timedelta(days=-94, seconds=73097, microseconds=20932)
위의 예처럼 과거 날짜에서 현재 날짜를 빼는 연산도 가능하다. 그러나 과거 날짜에서 현재 날짜를 빼는것이 논리적으로 어떤 의미인지 명확하지 않고 계산도 복잡해지므로, 항상 현재에 더 가까운 날짜에서 오래된 날짜를 빼는것이 좋을 것같다.
datetime 객체에 timedelta 객체를 더하거나 빼서 특정 시간 이후나 이전의 시간을 구하는것도 가능하다.
>>> d3 = d1 + timedelta(days=5)
>>> d3
datetime.datetime(2020, 4, 8, 15, 52, 7, 979068)
>>> d4 = d1 - timedelta(days=5)
>>> d4
datetime.datetime(2020, 3, 29, 15, 52, 7, 979068)
이상으로 datetime 모듈 관련 주로 많이 쓰게 되는 내용들을 정리해 보았다.
아래는 strftime() 포매팅 관련 옵션.
옵션 | 의미 | 예시 |
---|---|---|
|
현재 로케일에 따른 요일 이름 약자 |
Sun, Mon, …, Sat (en_US); So, Mo, …, Sa (de_DE) |
|
현재 로케일에 따른 요일 이름 |
Sunday, Monday, …, Saturday (en_US); Sonntag, Montag, …, Samstag (de_DE) |
|
10진수로 표현된 요일 번호 (일요일이 0, 토요일이 6임) |
0, 1, …, 6 |
|
10진수로 표현된 날짜 (한자리수인 경우 0이 앞에 붙음) |
01, 02, …, 31 |
|
현재 로케일에 따른 월 이름 약자 |
Jan, Feb, …, Dec (en_US); Jan, Feb, …, Dez (de_DE) |
|
현재 로케일에 따른 월 이름 |
January, February, …, December (en_US); Januar, Februar, …, Dezember (de_DE) |
|
10진수로 표현된 월 (한자리수인 경우 0이 앞에 붙음) |
01, 02, …, 12 |
|
앞 2자리수룰 제외하고 10진수로 표현된 년도 (한자리수인 경우 0이 앞에 붙음) |
00, 01, …, 99 |
|
10진수로 표현된 년도 |
0001, 0002, …, 2013, 2014, …, 9998, 9999 |
|
10진수로 표현된 시간 (24시간제) |
00, 01, …, 23 |
|
10진수로 표현된 시간 (12시간제) |
01, 02, …, 12 |
|
현재 로케일에 따른 오전, 오후 표시 |
AM, PM (en_US); am, pm (de_DE) |
|
10진수로 표현된 분 (한자리수인 경우 0이 앞에 붙음) |
00, 01, …, 59 |
|
10진수로 표현된 초 (한자리수인 경우 0이 앞에 붙음) |
00, 01, …, 59 |
|
10진수로 표현된 마이크로초 (자리수에 따라 0이 앞에 붙음) |
000000, 000001, …, 999999 |
|
시간대 (시간대 정보가 없는 naive 객체인 경우 None). |
(empty), UTC, EST, CST |
|
10진수로 표현된 년중 날짜 (1월1일이 001 임) |
001, 002, …, 366 |
|
10진수로 표현된 년중 주수 (일요일을 한 주의 첫날로 정함. 처음으로 일요일이 포함된 주가 01주, 일요일이 포함되지 않은 날짜들이 속한 그 전주는 00주임) |
00, 01, …, 53 |
|
10진수로 표현된 년중 주수 (월요일을 한 주의 첫날로 정함. 처음으로 일요일이 포함된 주가 01주, 월요일이 포함되지 않은 날짜들이 속한 그 전주는 00주임) |
00, 01, …, 53 |
|
현재 로케일 설정에 따라 요일과 년월일, 시간을 출력 |
Tue Aug 16 21:30:00 1988 (en_US); Di 16 Aug 21:30:00 1988 (de_DE) |
|
현재 로케일 설정에 따라 년월일을 출력 |
08/16/88 (None); 08/16/1988 (en_US); 16.08.1988 (de_DE) |
|
현재 로케일 설정에 따라 시간을 출력 |
21:30:00 (en_US); 21:30:00 (de_DE) |