ㅈㅅㄹ

캘린더에 일정을 등록하는 방법에 대해선 지난번 Part 1에서 간단하게 소개했으니 이번엔 일단 일정 데이터를 가져 오는 방법에 대해서 살펴보자. 이전 파트에서 calendars를 가져왔던 것 처럼 events쪽 URI를 ContentResolver에 넘겨줘서 쿼리하면 된다.

// 이전 파트에서와 마찬가지로 일단 calendar를 하나 가져온다.
final String CONTENT_URI = "content://com.android.calendar";
Uri uri = Uri.parse(CONTENT_URI + "/calendars");
Cursor c = getContentResolver().query(
             uri, new String[] { "_id" }, 
             "selected=?", new String[] { "1" }, null);
if(c == null || !c.moveToFirst()) {
    // 시스템에 캘린더가 존재하지 않음(계정이 등록되어 있지 않음) 오류 처리 필요
}
final int id = c.getInt(c.getColumnIndex("_id"));
c.close();

uri = Uri.parse(CONTENT_URI + "/events");
// 이전 파트에서 집어 넣었던 일정을 검색한다.
c = getContentResolver().query(
             uri, new String[] { "_id", "dtstart" }, // 가져올 필드
             "calendar_id=? AND title=?",
             new String[] { String.valueOf(id), "birthday" }, null);
if(c == null || !c.moveToFirst()) {
    // 실패했거나 검색 결과가 없음. 오류처리 필요
}
// 일정의 시작 시간을 Date타입인 dtstart에 저장한다.
final Date dtstart = new Date(c.getLong(c.getColumnIndex("dtstart")));
c.close();

위와 같이 시스템에 들어 있는 일정 데이터를 가져 올 수 있다. 위의 dtstart의 경우는 epoch time 형식으로 값을 저장하고, 또 가져 올 수 있다. 추가로 다른 데이터를 가져 올려면 인자로 넘겨주는 컬럼 목록 배열에 해당 컬럼명을 추가해주어 가져 올 수 있다. 어차피 ContentResolver 를 사용하는 방식은 안드로이드에서 어떤 ContentProvider를 사용하건 동일한 거고, 각 테이블에 어떤 컬럼들이 존재하는지, 그리고 어떤 식으로 데이터를 저장하고 가져올 수 있는지를 아는 것이 중요하겠다. 일단 Event에 어떤 항목들이 존재하는 지를 살펴보자


Events 테이블의 컬럼 목록


사실 이 중에서 안 써본 것들도 많기 때문에 전부 다 자세히 설명할 수는 없다. 주의깊게 봐야 할 것은calendar_id라든가 deleted 컬럼이다. calendar_id는 현재 이벤트가 속한 캘린더의 _id 값이 들어 있어 foreign key 역할을 한다고 보면 되겠고, deleted 컬럼의 경우 해당 일정을 사용자가 삭제할 때 true 값이 된다. 즉, 안드로이드 ContentProvider에서는 실제 레코드를 날려버리지 않고 deleted 플래그를 세팅해서 지운 것처럼 표시한다. 만약 삭제한 일정을 가져오고 싶지 않다면 쿼리문에 deleted 필드가 0 인 것만 가져오도록 WHERE 조건을 추가해 줘야 하겠다. dtstart나 dtend는 Java.util.Date 인스턴스에 getTime() 호출해서 나오는 long 타입의 millisecond 값을 지정해 주고, 혹은 캘린더에서 가져온 값을 생성자에 넣어주는 것으로 쉽게 활용할 수 있다. 문제는 RFC2445 형식을 따르는 필드들일 것이다. 전부 설명하기는 좀 귀찮으니 rrule 필드만 우선적으로 설명한다. (exrule도 같은 형식이다) 나머지는 나중에 또 연재하게 되면 다시 다룰지 어쩔지는 모르겠다.

RFC2445에서는 rrule의 값을 recur라는 형식으로 지정하는데, 다음과 같이 정의하고 있다.

     recur      = "FREQ"=freq *(

                ; either UNTIL or COUNT may appear in a 'recur',
                ; but UNTIL and COUNT MUST NOT occur in the same 'recur'

                ( ";" "UNTIL" "=" enddate ) /
                ( ";" "COUNT" "=" 1*DIGIT ) /

                ; the rest of these keywords are optional,
                ; but MUST NOT occur more than once

                ( ";" "INTERVAL" "=" 1*DIGIT )          /
                ( ";" "BYSECOND" "=" byseclist )        /
                ( ";" "BYMINUTE" "=" byminlist )        /
                ( ";" "BYHOUR" "=" byhrlist )           /
                ( ";" "BYDAY" "=" bywdaylist )          /
                ( ";" "BYMONTHDAY" "=" bymodaylist )    /
                ( ";" "BYYEARDAY" "=" byyrdaylist )     /
                ( ";" "BYWEEKNO" "=" bywknolist )       /
                ( ";" "BYMONTH" "=" bymolist )          /
                ( ";" "BYSETPOS" "=" bysplist )         /
                ( ";" "WKST" "=" weekday )              /
                ( ";" x-name "=" text )
                )

     freq       = "SECONDLY" / "MINUTELY" / "HOURLY" / "DAILY"
                / "WEEKLY" / "MONTHLY" / "YEARLY"

     enddate    = date
     enddate    =/ date-time            ;An UTC value

     byseclist  = seconds / ( seconds *("," seconds) )

     seconds    = 1DIGIT / 2DIGIT       ;0 to 59

     byminlist  = minutes / ( minutes *("," minutes) )

     minutes    = 1DIGIT / 2DIGIT       ;0 to 59

     byhrlist   = hour / ( hour *("," hour) )

     hour       = 1DIGIT / 2DIGIT       ;0 to 23

     bywdaylist = weekdaynum / ( weekdaynum *("," weekdaynum) )

     weekdaynum = [([plus] ordwk / minus ordwk)] weekday

     plus       = "+"

     minus      = "-"

     ordwk      = 1DIGIT / 2DIGIT       ;1 to 53

     weekday    = "SU" / "MO" / "TU" / "WE" / "TH" / "FR" / "SA"
     ;Corresponding to SUNDAY, MONDAY, TUESDAY, WEDNESDAY, THURSDAY,
     ;FRIDAY, SATURDAY and SUNDAY days of the week.

     bymodaylist = monthdaynum / ( monthdaynum *("," monthdaynum) )

     monthdaynum = ([plus] ordmoday) / (minus ordmoday)

     ordmoday   = 1DIGIT / 2DIGIT       ;1 to 31

     byyrdaylist = yeardaynum / ( yeardaynum *("," yeardaynum) )

     yeardaynum = ([plus] ordyrday) / (minus ordyrday)

     ordyrday   = 1DIGIT / 2DIGIT / 3DIGIT      ;1 to 366

     bywknolist = weeknum / ( weeknum *("," weeknum) )

     weeknum    = ([plus] ordwk) / (minus ordwk)

     bymolist   = monthnum / ( monthnum *("," monthnum) )

     monthnum   = 1DIGIT / 2DIGIT       ;1 to 12

     bysplist   = setposday / ( setposday *("," setposday) )

     setposday  = yeardaynum

형식에 따르자면, "FREQ=" 라는 문자열이 반드시 맨 앞에 나와야 하고, 일정 반복 간격 문자열을 대항 feature에 지정한다. 가령 월간 단위라면 "FREQ=MONTHLY"가 되겠다. 그 후 옵션으로 여러가지를 지정해 줄 수 있는데 몇가지 써본 안드로이드 단말에서 rrule 값을 지정할 때 WKST 값을 꼭 지정해 줘야 기본 캘린더 앱에서 읽는데 무리가 없는 것 처럼 보였다. 따라서 별다른 옵션 없이 월간 반복되도록 룰을 만들어 주려면 "FREQ=MONTHLY;WKST=SU"와 같이 하면 된다. UNTIL이나 COUNT를 붙여 써서 언제까지 일정을 반복할 것인지 혹은 일정 반복 회수가 얼마인지를 지정해 줄 수 있으며 만약 월간 반복되며 3번 반복되는 일정을 만들겠다면 "FREQ=MONTHLY;COUNT=3;WKST=SU"가 되어야 한다. INTERVAL의 경우는 일정 반복 주기를 뜻하는데 기본 값은 1이고 이걸 만약 2로 지정하게 되면 FREQ에 지정한 반복 단위가 두번 경과 될 때 마다 일정이 반복되게 된다. 즉 앞선 예제에서 "FREQ=MONTHLY;COUNT=3;INTERVAL=2;WKST=SU"로 지정해 주면 두달 마다 한 번씩 총 6개월동안 반복되는 일정을 만들 수 있게 된다. BYXXX 시리즈들은 XXX가 해당 값이 될 때 반복되게 된다.

재미있는 건 안드로이드 캘린더의 필드들은 좀 중복되는 것들이 있다는 건데, 가령 rrule의 UNTIL/COUNT값과 lastDate가 중복되는 부분이 있고, duration과 dtend 값이 중복된다. 대체로 집어 넣는 건 선호하는 필드에만설정 값을 넣어도 기본 캘린더 앱이 알아서 잘 처리해 주곤 하지만 가져 올 때는 좀 얘기가 달라진다. 특정 값이 null이 될 때도 있고 안될 때도 있어서 중복되는 필드 모두 가져와서 값을 참조해 보고 잘 판단하여 써야한다. 

이번 파트에 다루지 않은 duration 지정 형식은 RFC2445의 dur-value 정의를 참조하면 되고, UNTIL에 지정하는 date-time 형식도 살펴보자. 그다지 어렵진 않으니 쉽게 사용할 수 있을 것이다.


파트 2는 일단 여기까지. 3에선 연관 테이블에 대해서 살펴볼텐데... 언제 쓸 진 저도 모릅니다.