“지금부터 1천 시간 후는 몇 년 몇 월 몇 일 몇 시일까?”

우리 인간에게 시간은 매우 익숙한 개념이다. 하지만 시간을 계산하는 것은 그리 간단하지 않다. 큰 범위의 시간과 작은 범위의 시간을 모두 다루기 위해 시간을 재는 척도는 일, 월, 시, 분, 초 등으로 세분화되었다. 시간의 단위마다 사용되는 진법도 다르다는 점이 까다로움을 더한다. 우리는 시간을 다룰 때는 익숙한 십진법 대신 십이진법(시)과 육십진법(분, 초) 등 여러 진법을 섞어 쓴다.

이 절에서는 까다로운 시간을 쉽게 다룰 수 있게 도와주는 모듈을 소개한다. 운영 체제의 시간 관련 기능을 다루는 time 모듈을 간단히 살펴본 뒤, 사람 기준의 시간 표현법을 제공하는 datetime 모듈을 알아볼 것이다.

11.3.1 운영 체제의 시간 관련 기능

time 모듈은 운영 체제가 제공하는 다양한 시간 기능을 다루는 모듈이다. 운영 체제마다 시간을 다루는 방식이 다르기 때문에 이 모듈의 함수들은 어떤 운영 체제에서 실행하느냐에 따라 결과가 다르다. 이 모듈은 다양한 기능을 제공하지만, 그 중 실제로 자주 사용되는 기능은 time.time() 함수 정도다.

time.time() 함수는 컴퓨터의 현재 시각을 구하는 함수이며, 반환 값 기준 시각(운영 체제마다 다름)에서 몇 초가 지났는지를 나타내는 실수다. 예를 들어, 이 함수를 한 번 호출한 뒤 정확히 1 초 후에 다시 호출한다면, 두 반환 값은 1 만큼의 차이가 날 것이다. 다음 예를 보자.

코드 11-37 time.time() 함수로 시각 측정하기

>>> import time    # time 모듈 임포트
>>> time.time()    # 현재 시각
1510647686.5457149

>>> time.time()    # 1 초 후 (반환 값이 약 1 증가했다)
1510647687.551255

보다시피 나름대로 1 초만에 함수를 실행하려 했지만 실제로는 1 초보다 약간 시간이 더 지났다. 시각이 초 단위로 반환되지만 소수점 이하 자리를 통해 1 초 미만의 양도 확인할 수 있다.

time.time() 함수의 반환 값은 언뜻 보기에 무의미한 수로 보인다. 하지만 이 값에서 기준 시각을 빼고 단위 변환을 수행하면 현재 날짜와 시간 등을 구할 수 있다. 이를 수행해주는 함수가 time.gmtime() 함수다.

코드 11-38 time.gmtime() 함수로 시각 해석하기

>>> now = time.gmtime(time.time())        # 현재 시각 측정 후 해석
>>> now.tm_year, now.tm_mon, now.tm_mday  # 년, 월, 일
(2017, 11, 14)

>>> now.tm_hour, now.tm_min, now.tm_sec   # 시, 분, 초
(8, 29, 50)

시간 단위를 다루는 것은 곧이어 알아볼 datetime 모듈을 사용하는 편이 더 편하기 때문에 time.gmtime() 함수에 많은 신경을 쓸 필요는 없다.

이것으로 컴퓨터(와 운영 체제)에서 시간을 다루는 가장 기본적인 방법을 알아보았다. 시각은 어떤 시점을 기준으로 한 변화량이다. 이를 단위 변환하면 사람에게 익숙한 날짜, 시간 단위로 표현할 수 있다. time 모듈을 살펴보는 것은 이 점을 이해하는 것 정도로 충분하다.

11.3.2 날짜와 시각

time 모듈이 제공하는 기능은 복잡한 시간 개념을 다루기에는 너무 단순하다. datetime 모듈은 시간을 날짜와 시각으로 표현하는 방법을 제공해, 시간을 좀더 인간의 관점에 가깝게 다루도록 해준다.

datetime 모듈은 시간을 다루기 위한 여러 가지 클래스를 제공한다. 그 중에서 날짜를 다루는 datetime.date 클래스, 시각을 다루는 datetime.time 클래스, 일시를 다루는 datetime.datetime 클래스를 알아보자.

날짜 표현하기

가장 먼저 살펴볼 datetime.date 클래스는 년, 월, 일을 통해 날짜를 표현한다. 아래와 같이 클래스를 임포트해 두고, 책에서도 편의상 모듈 이름을 생략하고 date라는 클래스 이름으로 부르기로 하자.

코드 11-39 datetime 모듈에서 date 클래스 임포트

>>> from datetime import date

date 객체를 생성하는 방법은 크게 두 가지가 있다. 하나는 나타낼 날짜의 년, 월, 일을 인자로 인스턴스화하는 것이고, 두번째는 현재 날짜에 해당되는 객체를 생성하는 date.today() 메서드를 사용하는 것이다.

  • 특정 날짜 객체 생성: date(년, 월, 일)
  • 현재 날짜 객체 생성: date.today()

대화식 셸에서 date 객체를 생성해 보자.

코드 11-40 date 객체 생성하기

>>> date(1789, 7, 14)                 # 1789년 7월 14일
datetime.date(1789, 7, 14)

>>> date(year=1986, month=3, day=6)   # 1986년 3월 6일
datetime.date(1986, 3, 6)

>>> date.today()                      # 현재 날짜
datetime.date(2017, 11, 14)

date 객체에서 년·월·일 등의 속성을 구하거나 메서드를 호출할 수 있다. 자주 사용되는 속성과 메서드는 다음과 같다.

속성 또는 메서드 값 또는 기능
year
month
day
weekday() 요일 (월요일=0, 일요일=6)
isoformat() ISO 표준 문자열 표현
strftime(format) 문자열 표현 (11.3.3에서 설명)

표 11-10 date 객체에서 자수 사용되는 속성과 메서드

다음은 현재 날짜를 대상으로 표 11-10의 속성과 메서드를 시험해 본 것이다. 참고로 weekday() 메서드는 요일은 동적으로 계산하며, 결과값을 숫자로 반환한다.

코드 11-41 date 객체의 속성과 메서드 사용하기

>>> today = date.today()
>>> today.year, today.month, today.day   # 년, 월, 일
(2017, 11, 14)

>>> today.weekday()                      # 요일
1

>>> '월화수목금토일'[today.weekday()]    # 요일 (문자로)
'화'

>>> today.isoformat()                    # ISO 표준 문자열 표현
'2017-11-14'

isoformat() 메서드는 날짜를 ISO 표준 형식 문자열로 표현하는데, ISO 표준 형식이란 “년-월-일”(예: 1986-03-06)과 같은 형식으로, 한국인이라면 누구나 친숙할 것이다.

시각 표현하기

datetime.time 클래스는 시, 분, 초, 마이크로초(백만 분의 1 초)를 통해 시각을 표현한다. 아래와 같이 클래스를 임포트해 두자.

코드 11-42 datetime 모듈에서 time 클래스 임포트

>>> from datetime import time

원래 time 변수에는 2.1절에서 임포트했던 time 모듈이 대입되어 있었겠지만, 이제 datetime.time 클래스가 새로 대입되었다. 이제부터는 datetime.time 클래스를 time 클래스라고 부를 것이다. time 모듈과 이름이 동일하지만 혼동하지 않도록 주의하자.

time 객체를 생성하려면 date 객체와 유사하게 시각의 시, 분, 초를 인자로 인스턴스화하면 된다.

  • 특정 시각 객체 생성: time(시, 분, 초, 마이크로초)

이 때, 분과 초와 마이크로초는 생략할 수 있다. 이들을 생략하면 각각 0이 된다.

코드 11-43 time 객체 생성하기

>>> time(15)                             # 15시 정각
datetime.time(15, 0)

>>> time(15, 30)                         # 15시 30분
datetime.time(15, 30)

>>> time(15, 30, 45)                     # 15시 30분 45초
datetime.time(15, 30, 45)

>>> time(15, 30, 45, 200000)             # 15:30:45.200000
datetime.time(15, 30, 45, 200000)

>>> time(hour=15, minute=30, second=45, microsecond=200000)
datetime.time(15, 30, 45, 200000)

time 객체에서 자주 사용되는 속성과 메서드는 다음과 같다.

속성 또는 메서드 값 또는 기능
hour
minute
second
microsecond 마이크로초
isoformat() ISO 표준 문자열 표현
strftime(format) 문자열 표현 (11.3.3에서 설명)

표 11-11 time 객체에서 자수 사용되는 속성과 메서드

다음은 표 11-11의 속성과 메서드를 시험해 본 것이다.

코드 11-44 time 객체의 속성과 메서드 사용하기

>>> at = time(15, 30, 45)
>>> at.hour, at.minute, at.second  # 시, 분, 초
(15, 30, 45)

>>> at.microsecond                 # 마이크로초
0

>>> at.isoformat()                 # ISO 표준 문자열 표현
'15:30:45'

isoformat() 메서드는 시간을 ISO 표준 형식 문자열로 표현한다. 이는 “시:분:초”(예: 08:30:00)의 눈에 익은 형식이다.

일시 표현하기

날짜는 시간을 두루뭉술하게 (최대 24 시간의 오차가 존재하는) 표현하며, 시각은 어떤 하루 안에서의 시점만을 가리킨다. 어떤 시점을 더 정확하고 절대적인 기준으로 표현하려면 날짜와 시각을 합한 개념, ‘일시’가 필요하다. 일시는 datetime.datetime 클래스로 나타낼 수 있다. 아래와 같이 클래스를 임포트해 두자.

코드 11-45 datetime 모듈에서 datetime 클래스 임포트

>>> from datetime import datetime

datetime.datetime 클래스는 datetime 모듈과 이름이 같다. 아래에서는 이 클래스를 datetime 클래스라고 부를 텐데, datetime 모듈을 임포트한 것이 아니라 datetime 클래스를 임포트한 것이니 혼동하지 않도록 주의하자.

datetime 객체를 생성하는 방법은 크게 세 가지가 있다. 하나는 나타낼 일시의 년, 월, 일, 시, 분, 초, 마이크로초를 인자로 인스턴스화하는 것이고, 두번째는 현재 일시에 해당되는 객체를 생성하는 datetime.now() 메서드를 사용하는 것이다. 세번째로, date 객체와 time 객체를 합쳐서 datetime 객체를 생성할 수도 있다.

  • 특정 일시 객체 생성: datetime(년, 월, 일, 시, 분, 초, 마이크로초)
  • 현재 일시 객체 생성: datetime.now()
  • 날짜와 시각 결합: datetime.combine(날짜, 시각)

인스턴스화할 때, 시, 분, 초, 마이크로초는 생략할 수 있다. 이들을 생략하면 각각 0이 된다.

코드 11-46 datetime 객체 생성하기

>>> datetime(2017, 11, 14)
datetime.datetime(2017, 11, 14, 0, 0)

>>> datetime(2017, 11, 14, 8, 30)
datetime.datetime(2017, 11, 14, 8, 30)

>>> datetime(2017, 11, 14, 8, 30, 50, 200000)
datetime.datetime(2017, 11, 14, 8, 30, 50, 200000)

>>> datetime.now()  # 현재 일시
datetime.datetime(2017, 11, 14, 19, 4, 7, 950704)

>>> datetime.combine(date.today(), time(15))  # 오늘 3시
datetime.datetime(2017, 11, 14, 15, 0)

datetime 클래스는 date 클래스와 time 클래스를 합친 것이라고 생각하면 쉽다. datetime 객체에는 date 객체와 time 객체의 속성·메서드를 모두 사용할 수 있다. 그리고 date 객체와 time 객체를 추출해낼 수도 있다. 표에서 확인하자.

속성 또는 메서드 값 또는 기능
year
month
day
hour
minute
second
microsecond 마이크로초
weekday() 요일 (월요일=0, 일요일=6)
date() 날짜(date) 객체 추출
time() 시각(time) 객체 추출
isoformat() ISO 표준 문자열 표현
strftime(format) 문자열 표현 (11.3.3에서 설명)

표 11-12 date 객체에서 자수 사용되는 속성과 메서드

다음과 같이 간단히 살펴보자.

코드 11-47 datetime 객체의 속성과 메서드 사용하기

>>> now = datetime.now()
>>> now.year, now.month, now.day, '월화수목금토일'[now.weekday()]
(2017, 11, 14, '화')

>>> now.hour, now.minute, now.second, now.microsecond
(19, 8, 52, 283277)

>>> now.date(), now.time()
(datetime.date(2017, 11, 14), datetime.time(19, 8, 52, 283277))

>>> now.isoformat()
'2017-11-14T19:08:52.283277'

참고로 isoformat() 메서드는 날짜와 시간을 T로 구별한다. 이 문자를 다른 것으로 바꾸려면 sep 매개변수로 지정할 수 있다.

11.3.3 시간 정보를 문자열로 나타내기

사용자에게 시간을 출력하거나, 사건 발생 시간을 로그로 기록할 때 등, 시간 정보를 특정한 양식의 문자열로 나타내야 할 때가 있다. 몇 가지 날짜, 시각, 일시 객체를 준비해두고 시간의 양식화 방법을 생각해 보자.

코드 11-48 실습에 사용할 시간 정보 객체들

>>> from datetime import date, time, datetime
>>> mayday = date(2017, 5, 1)  # 날짜 객체
>>> morning = time(8, 30)      # 시각 객체
>>> now = datetime.now()       # 일시 객체

str() 함수는 객체를 사용자를 위한 형태의 문자열로 변환한다. 이 함수로 시간 정보 객체들을 문자열로 변환하면 ISO 표준 형식의 문자열로 변환된다.

코드 11-49 시간 정보 객체에 str() 함수를 적용했을 때

>>> str(mayday)
'2017-05-01'

>>> str(morning)
'08:30:00'

>>> str(now)
'2017-11-22 17:20:41.274923'

ISO 표준 형식도 좋지만, 아무래도 상황에 따라 양식을 직접 정의하고 싶을 것이다. 11.2.1 텍스트 양식화에서 배운 기법을 활용하여 출력하고 싶은 객체의 속성과 형식을 지정하면 된다.

코드 11-50 format() 메서드, 양식 문자열 리터럴로 시간 정보 양식화하기

>>> '{0.year:04}/{0.month:02}/{0.day:02}'.format(mayday)
'2017/05/01'

>>> '{0.hour:02}:{0.minute:02}'.format(morning)
'08:30'

>>> '{0.month}월 {0.day}일 {0.hour}시 {0.minute}분'.format(now)
'11월 22일 17시 20분'

>>> f'{mayday.year:04}/{mayday.month:02}/{mayday.day:02}'
'2017/05/01'

>>> f'{morning.hour:02}:{morning.minute:02}'
'08:30'

>>> f'{now.month}월 {now.day}일 {now.hour}시 {now.minute}분'
'11월 22일 17시 20분'

이런 방법도 나쁘지 않지만, date, time, datetime 클래스는 모두 시간 정보를 문자열로 양식화하기 위한 전용 메서드를 갖고 있다. strftime() 메서드가 그것이다.

strftime() 메서드

strftime() 메서드는 양식 문자열을 전달받아 시간 정보 객체를 양식화한다. 양식 문자열에는 여러 가지 기호로 시간 정보 출력 방식을 정의할 수 있다. 다음은 그 가운에 일부를 나타낸 표다.

기호 의미 출력 예(2001-02-03 04:05:06 기준)
%Y 년 (네 자리) 2001
%y 년 (두 자리) 01
%m 월 (두 자리) 02
%d 일 (두 자리) 03
%A 요일 Saturday
%H 시 (24시간) 04
%I 시 (12시간) 04
%p 오전, 오후 AM
%M 분 (두 자리) 05
%S 초 (두 자리) 06
%f 마이크로초 000000
%% % 기호 %

표 11-13 strftime 메서드의 시간 출력 양식 기호

strftime() 메서드를 활용하면 시간 정보의 양식화를 좀 더 쉽게 수행할 수 있다. 단, %A%p의 출력 결과는 운영 체제의 언어 설정마다 다르니 주의해야 한다. 다음은 이 메서드를 이용해 시간 정보를 문자열로 변환해 본 예다.

코드 11-51 strftime() 메서드로 시간 정보 양식화하기

>>> mayday.strftime('%Y/%m/%d')
'2017/05/01'

>>> morning.strftime('%H:%M')
'08:30'

>>> now.strftime('%m월 %d일 %H시 %M분')
'11월 22일 17시 20분'

11.3.4 시간의 양

날짜와 시각을 표현하는 방법을 알아보았지만, “지금부터 1천 시간 후는 몇 년 몇 월 몇 일 몇 시일까?”라는 질문에 답하려면 아직 멀었다. 시간에 대한 연산을 하기 위해서는 시간의 양, 다시 말해 기간을 나타내는 방법이 필요하다. datetime 모듈에서 아직 소개하지 않은 datetime.timedelta 클래스가 바로 시간의 양을 표현해 준다.

앞에서와 마찬가지로 datetime.timedelta 클래스를 임포트 해 두자.

코드 11-52 datetime 모듈에서 timedelta 클래스 임포트

>>> from datetime import timedelta

timedelta 클래스는 주(weeks), 일(days), 시(hours), 분(minutes), 초(seconds), 밀리초(milliseconds), 마이크로초(microseconds) 등 다양한 매개변수에 값을 지정해 인스턴스화할 수 있다. 여러 주, 여러 날, 여러 분을 나타내는 것이므로 매개변수 이름이 복수형으로 되어 있다. 예를 들어 보자.

코드 11-53 datetime 모듈에서 timedelta 클래스 임포트

>>> timedelta(days=5)            # 5 일의 기간
datetime.timedelta(5)

>>> timedelta(weeks=3)           # 3 주의 기간
datetime.timedelta(21)

>>> timedelta(minutes=1)         # 1 분의 기간
datetime.timedelta(0, 60)

>>> timedelta(seconds=180)       # 180 초의 기간
datetime.timedelta(0, 180)

>>> timedelta(milliseconds=100)  # 100 밀리초의 기간
datetime.timedelta(0, 0, 100000)

인스턴스화에서는 여러 가지가 단위로 기간을 지정할 수 있지만, 생성된 timedelta 객체 내부에서는 기간을 일, 초, 마이크로초의 세 단위로 환산하여 저장한다. 또한, 인스턴스화 할 때 매개변수를 여럿 지정하면 기간을 서로 더하여 단위를 통일한다.

코드 11-54 여러 단위로 시간을 지정하면 모두 합해진다

>>> timedelta(weeks=3, days=5)  # 3 주 + 5 일의 기간
datetime.timedelta(26)

>>> timedelta(days=2, hours=3, minutes=30)  # 2 일 + 3 시간 + 30 분
datetime.timedelta(2, 12600)

timedelta 객체는 date 객체, time 객체, datetime 객체, 그리고 동일한 timedelta 객체와 연산을 할 수 있다. 이를 통해, “지금부터 1천 시간 후는 몇 년 몇 월 몇 일 몇 시일까?”, “내가 태어난 지 며칠이 지났나?”와 같은 질문의 답을 계산할 수 있다.

코드 11-55 timedelta 객체로 시간 계산하기

>>> now = datetime.now()                 # 현재 일시
>>> after_1000h = timedelta(hours=1000)  # 1천 시간
>>> now + after_1000h                    # 지금부터 1천 시간 후
datetime.datetime(2017, 12, 26, 11, 42, 32, 103816)

>>> birthday = date(1986, 3, 6)          # 생년월일
>>> today = date.toady()                 # 오늘
>>> today - birthday                     # 태어난 뒤 오늘까지의 기간
datetime.timedelta(11576)

연습문제

연습문제 11-6 한국식 나이

한국은 나이를 세는 방법이 여러 가지여서 프로그래밍할 때 주의가 필요하다. 한국의 나이 세는 방법은 적어도 세 가지가 있는데, 그 중 다음 두 가지가 가장 많이 쓰인다.

  • 세는 나이: 태어난 해를 기준으로 1세, 해가 바뀔 때마다 바로 한 살 씩 증가
  • 만 나이: 생년월일을 기준으로 0세, 해가 바뀌고 생일이 지날 때마다 한 살씩 증가

생년월일을 입력받아 세는 나이를 반환하는 함수 counting_age(birth_date), 생년월일을 입력받아 만 나이를 반환하는 함수 full_age(birth_date)를 각각 정의해라.

이상으로 시간 정보를 나타내기 위한 다양한 클래스와 문자열 양식화 방법까지 알아보았다. 클래스의 수가 약간 많았지만, 사용법이 모두 비슷하여 어렵지 않다. 날짜, 시간, 일시, 기간 이라는 개념을 혼동하지만 않는다면 앞으로 시간 정보를 문제 없이 다룰 수 있을 것이다.

datetime 모듈에는 세계 곳곳의 시간대를 다루기 위한 datetime.timezone 클래스도 있다. 세계 여러 사람이 동시에 이용하는 서비스를 개발한다면 알아봐야 할 기능이다. 하지만 이 책에서는 다루지 않겠다.