태그 보관물: appengine

App engine – 지정한 달의 google calendar event 가져오기

Google calendar API 설명문서에는 event list를 가져오는 방법이 설명과 함께 예제 code가 제시되어 있다.

page_token = None
while True:
  events = service.events().list(calendarId='primary', pageToken=page_token).execute()
  for event in events['items']:
    print event['summary']
  page_token = events.get('nextPageToken')
  if not page_token:
    break

이 코드를 수행하면 달력이 가지고 있는 모든 event들을 가져오는데, 이것을 조금 수정해서 지정된 달에 해당하는 event 목록을 가져오는 것으로 변경해 보자.

timeMax / timeMin

가져올 event의 범위는 lower bound를 나타내는 timeMin과 upper bound를 나타내는 timeMax값으로 제한할 수 있는데 이때 사용되는 날짜의 형식은 RFC3339을 따라야 한다.

calendar.monthrange() 

이 method는 년도와 월을 받아서 시작일의 주와 마지막 날짜를 tuple로 반환해 준다. 다음과 같이 특정 달의 마지막 날짜를 구할 수 있다.

>>> import calendar
>>> calendar.monthrange(2002, 06)
(5, 30)
>>> calendar.monthrange(2002, 06)[1]
30

Python에서 RFC3339 표시하기

이 형식은 간단히 말해 날짜를 나타내는 YYYY-MM-DD 형식과 시간을 나타내는 hh:mm:ss가 대문자 ‘T’로 연결되며 그 뒤에는 UTC로 부터의 time zone offset값이 붙는다.

2002-06-30T20:00:00+09:00

datetime.isoformat()을 이용하면 비슷한 형식을 얻을 수는 있으나, 뒤에 붙는 time zone offset이 표시되지 않는다.

>>> datetime(2002, 06, 30, 20, 0, 0).isoformat()
'2002-06-30T20:00:00'

Python에서 time zone을 나타내는 tzinfo class의 설명문에 따르면 tzinfo는 abstract class여서 직접 사용할 수 없고, 이를 상속받는 class를 구현해서 사용해야 한다. 다음은 이 문서에 있는 예제를 조금 수정하여 time zone offset 값을 반환하는 tzinfo의 concrete class를 구현한 것이다.

from datetime import tzinfo, timedelta, datetime

ZERO = timedelta(0)
HOUR = timedelta(hours=1)

class TimeZoneOffset(tzinfo):
    def __init__(self, hours, name):
        self.__offset = timedelta(hours = hours)
        self.__name = name

    def utcoffset(self, dt):
        return self.__offset

    def tzname(self, dt):
        return self.__name

    def dst(self, dt):
        return ZERO

Code

TimeZoneOffset class를 선언한 후에는 datetime()을 호출할때 tzinfo에 TimeZoneOffset의 instance를 넘겨서 time zone offset 정보를 포함하는 RFC3339 날짜 형식을 얻을 수 있다.

# What's the last date of the Jun, 2002?
lastDate = calendar.monthrange(2002, 6)[1]

# Lower bound RFC3339 format '2002-06-01T00:00:00+09:00'
lbound = datetime(2002, 6, 1, tzinfo=TimeZoneOffset(9, "KST")).isoformat()

# Lower bound RFC3339 format '2002-06-01T23:59:59+09:00'
ubound = datetime(2002, 6, lastDate, 23, 59, 59, tzinfo=TimeZoneOffset(9, "KST")).isoformat()

page_token = None
while True:
  # Fetch events within given lower/upper bounds.
  events = service.events().list(calendarId='primary', minTime=lbound, maxTime=ubound, pageToken=page_token).execute()
  for event in events['items']:
    print event['summary']
  page_token = events.get('nextPageToken')
  if not page_token:
    break

Jinja2의 UnicodeDecodeError

App engine을 이용하는 project 중에 Jinja2 template 부분에서 다음과 같이 UnicodeDecodeError가 발생하는 문제가 생겼다.

Traceback (most recent call last):
  File "<<APP_ENGINE>>/lib/webapp2-2.5.2/webapp2.py", line 1535, in __call__
    rv = self.handle_exception(request, response, e)
  File "<<APP_ENGINE>>/lib/webapp2-2.5.2/webapp2.py", line 1529, in __call__
    rv = self.router.dispatch(request, response)
  File "<<APP_ENGINE>>/lib/webapp2-2.5.2/webapp2.py", line 1278, in default_dispatcher
    return route.handler_adapter(request, response)J
  File "<<APP_ENGINE>>/lib/webapp2-2.5.2/webapp2.py", line 1102, in __call__
    return handler.dispatch()
  File "<<APP_ENGINE>>/lib/webapp2-2.5.2/webapp2.py", line 572, in dispatch
    return self.handle_exception(e, self.app.debug)
  File "<<APP_ENGINE>>/lib/webapp2-2.5.2/webapp2.py", line 570, in dispatch
    return method(*args, **kwargs)
  File "<<PROJECT_TOP>>/main.py", line 141, in get
    r = t.render(events)
  File "<<APP_ENGINE>>/lib/jinja2-2.6/jinja2/environment.py", line 894, in render
    return self.environment.handle_exception(exc_info, True)
  File "<<PROJECT_TOP>>/templates/parser.html", line 21, in top-level template code
    <td>{{titles[loop.index0]}}</td>
UnicodeDecodeError: 'ascii' codec can't decode byte 0xa1 in position 9: ordinal not in range(128)

Jinja2 web page의 설명문서에 따르면, Jinja2는 내부적으로 Unicode를 사용하기 때문에 template에는 unicode object를 넘겨주어야 한다고 설명하고 있는데, 내가 project에서 사용하는 web page는  cp949 encoding을 사용하기 때문에 문제가 발생한다.

따라서 이것은 Jinja2 template으로 data를 넘기기 전에 unicode()를 이용해서 cpc949를 uinicode로 변환한 값을 넘겨주면 해결할 수 있다. 다음은 실제로 수정된 code로 cp949로 encoding된 문자열을  unicode로 변환해서 추가하는 것이다.

titles.append(sch.mTitle) -> titles.append(unicode(sch.mTitle, 'cp949'))