구글캘린더 - 팀원 일정 긁어와서 휴가 모아보기 캘린더 만들기

모아보기 캘린더

 

사전작업

모아보기 캘린더 작업할 소유자(나)에게 각 팀원의 캘린더 공유 (모든일정보기)가 되어있어야한다.

(내가 공유받은 팀원의 캘린더로 부터 일정을 긁어오기 때문에)

 

캘린더 공유 방법

구글 캘린더에서 캘린더 우측 (1) 옵션 버튼 > (2) 설정 및 공유 > 설정 페이지에서 (3) 일정의 액세스 권한' 항목 [공개 사용 설정] 에서 소유자에게 공개 > (4) 모아보기(공유) 캘린더 만들기

이때 모두 공개는 보안적으로 좋지 않으므로 소유자에게만 따로 공개하기, 공유 가능한 링크 받기 기능이나 '특정 사용자 또는 그룹과 공유'를 통해 소유자에게 공개해야함

(1) 옵션 버튼
(2) 설정 및 공유

 

3) 일정의 액세스 권한' 항목 [공개 사용 설정] 에서 소유자에게 공개

 

모아보기 캘린더 추가 절차는, 구글 캘린더 화면에서 (1) 다른 캘린더 추가 > (2) 새 캘린더 만들기 > 생성 페이지에서 캘린더 만들기 진행 

(4.1) 다른 캘린더 추가

 

(4.2) 새 캘린더 만들기

 

 

구글의 Apps Script 사용하여 구성원의 일정을 모아보기할것이므로 구글 Apps Script 방문 (https://script.google.com/home) 하여 새 프로젝트 만들기 클릭

새 프로젝트 만들기 진입 시

 

이때 어떠한 정보를 긁어 올 것인지도 단어나 문장을 맞춰야한다.

예를 들면 '휴가' 일 경우, 공유자들의 캘린더 등록 시 타이틀에 '휴가'라는 단어가 필수로 들어가야한다.

공유자의 캘린더에 [휴가] 일정 추가

 

 

Apps Script의 Code.gs에 스크립트를 추가해줘야한다.

일단 돌아가는 상황을 간단히 설명하기 위해, 아래와 같이 콘솔로그를 등록해서 저장을 하면.

아무거나 입력 후 저장

 

저장이 되면서 이렇게 myFunction이 추가되는걸 볼수있다. 함수를 여러개 만들면 눌렀을 경우 리스트로 확인할 수 있다.

myFunction이 노출

 

// 기존 트리거를 삭제하고 새로운 트리거를 설정하는 함수
function createHourlyTrigger() {
  // 현재 프로젝트의 모든 트리거를 가져옴
  const triggers = ScriptApp.getProjectTriggers();

  // 동일한 트리거가 있는지 확인하고, 있으면 삭제
  triggers.forEach(trigger => {
    if (trigger.getHandlerFunction() === 'myHourlyTask' &&
        trigger.getEventType() === ScriptApp.EventType.CLOCK) {
      ScriptApp.deleteTrigger(trigger);  // 기존 트리거 삭제
      Logger.log('기존 정시 트리거가 삭제되었습니다.');
    }
  });

  // 새로운 트리거 생성
  ScriptApp.newTrigger('myHourlyTask')  // 실행할 함수 이름
    .timeBased()  // 시간 기반 트리거 사용
    .everyHours(1)  // 매시간마다 실행 (정각에 실행)
    .create();     // 트리거 생성
  Logger.log('새로운 정시 트리거가 생성되었습니다.');
}

 

여기에 챗지피티에 질문해서 얻은 스크립트를 넣어보겠다.

간단히 설명하면, ScriptApp.getProjectTriggers를 통해 Code.gs에 작성한 함수들을 가져올 것이고,

가져온 리스트에서 특정 함수 (myHourlyTask)를 1시간 마다 실행하도록 적용할 것이다.

(= 1시간마다 공유자들의 캘린더 정보를 가져와서 원하는 것을 뽑아내서 모아보기 캘린더에 노출)

 

이후 이어서 하단에 아래 스크립트도 추가해준다.

// 매 정시에 실행될 함수
function myHourlyTask() {
  Logger.log('정시에 실행되었습니다.');

  // 원본 캘린더 ID와 소유자 이름 매핑
  const sourceCalendars = { // 구성원 캘린더 ID : 이름
    '삐약@gmail.com': '삐약', 
    // ... 해당 포맷에 맞춰 쭉 작성하기
  };

  // 대상 캘린더 ID
  const destinationCalendarId = ''; 

  // 원하는 날짜 범위 설정
  const today = new Date(); // 현재 날짜
  const startDate = new Date(today.getFullYear(), today.getMonth(), 1); // 이번 달 1일
  const endDate = new Date(today.getFullYear(), today.getMonth() + 6 + 1, 0); // 종료 날짜를 현재 날짜 기준 6개월 이후의 마지막 날로 설정

  const keyword = '휴가'; // 필터링할 일정 제목 키워드
  const copiedTag = '#copied'; // 복사된 일정에 추가할 태그

  // 대상 캘린더에서 기존 이벤트들을 삭제
  const destinationCalendar = CalendarApp.getCalendarById(destinationCalendarId);
  const existingEvents = destinationCalendar.getEvents(startDate, endDate);

  // 기존의 모든 이벤트 삭제
  existingEvents.forEach(event => {
    event.deleteEvent();
  });

  // 모든 원본 캘린더를 순회하며 일정 가져오기
  Object.keys(sourceCalendars).forEach(calendarId => {
    const events = CalendarApp.getCalendarById(calendarId).getEvents(startDate, endDate);
    
    events.forEach(event => {
      if (event.getTitle().includes(keyword)) {
        // 일정 소유자 정보 추가
        const ownerName = sourceCalendars[calendarId];

        // 일정의 시작 시간과 종료 시간을 24시간제로 변환
        const formattedStartTime = Utilities.formatDate(event.getStartTime(), Session.getScriptTimeZone(), 'HH:mm');
        const formattedEndTime = Utilities.formatDate(event.getEndTime(), Session.getScriptTimeZone(), 'HH:mm');

        // 일정이 전날 자정부터 다음날 자정까지 이어지는지 확인
        const startTime = event.getStartTime();
        const endTime = event.getEndTime();
        const isOverMidnight = startTime.getDate() !== endTime.getDate() && startTime.getHours() === 0 && endTime.getHours() === 0;

        if (isOverMidnight) {
          // 전날 자정부터 다음날 자정일 경우, 시작 날짜에 하루종일 일정으로 추가
          const previousDay = new Date(endTime); // 종료 시간에서 하루 전으로 설정
          previousDay.setDate(previousDay.getDate() - 1); // 전날로 설정

          // 전날로 하루 종일 일정 추가 (종료 시간을 제외)
          destinationCalendar.createAllDayEvent(
            `[휴가] - ${ownerName}`, // 시간 값 없이 제목만 표시
            previousDay // 전날로 시작 시간 설정
          );
        } else {
          // 다른 일정의 경우 정상적으로 시작 시간과 종료 시간을 표시
          const newTitle = `[휴가] - ${ownerName} (${formattedStartTime} ~ ${formattedEndTime})`;

          // 대상 캘린더에 이벤트 복사
          destinationCalendar.createEvent(
            newTitle, // 일정 제목에 소유자 이름과 시간 추가
            event.getStartTime(),
            event.getEndTime(),
            {
              location: event.getLocation(),
              description: (event.getDescription() || '') + ' ' + copiedTag, // 복사된 일정에 태그 추가
              guests: event.getGuestList().map(guest => guest.getEmail()).join(','),
              sendInvites: false
            }
          );
        }
      }
    });

 

1시간 마다 실행되는 myHourlyTask의 코드를 간단히 설명해보자면,

- sourceCalendars에 공유자들의 이메일과 모아보기 캘린더에 노출될 이름을 입력한다.

- destinationCalendarId에는 모아보기 캘린더의 id를 입력면된다. 모아보기 캘린더 id는 캘린더 설정 및 공유 페이지에서 (캘린더 공유방법 참고) > '캘린더 통합' 영역의 캘린더 ID를 넣어주면 된다. (example@@group.calendar.google.com) example은 보안적인 특정 유니크 값으로 이루어져있다. 

- keyword에 공유자들 캘린더에서 가져올 키워드를 입력한다. (스크립트에 공백체크가 없으므로 공백까지 맞춰야한다) 

- startDate, endDate에 필요날 날짜를 입력하고, (나는 이번달 1일 ~ 6개월 이후까지 지정) new Date('2025-01-01') 이런식으로 직접 지정해줘도 되고, 필요한 날짜를 계산해서 넣어도된다.

 

아래 노출할 문자열들을 입력한다.

if문 하루종일 인 경우
else 문 시간 휴가인 경우

 

 

완성된 코드

// 기존 트리거를 삭제하고 새로운 트리거를 설정하는 함수
function createHourlyTrigger() {
  // 현재 프로젝트의 모든 트리거를 가져옴
  const triggers = ScriptApp.getProjectTriggers();

  // 동일한 트리거가 있는지 확인하고, 있으면 삭제
  triggers.forEach(trigger => {
    if (trigger.getHandlerFunction() === 'myHourlyTask' &&
        trigger.getEventType() === ScriptApp.EventType.CLOCK) {
      ScriptApp.deleteTrigger(trigger);  // 기존 트리거 삭제
      Logger.log('기존 정시 트리거가 삭제되었습니다.');
    }
  });

  // 새로운 트리거 생성
  ScriptApp.newTrigger('myHourlyTask')  // 실행할 함수 이름
    .timeBased()  // 시간 기반 트리거 사용
    .everyHours(1)  // 매시간마다 실행 (정각에 실행)
    .create();     // 트리거 생성
  Logger.log('새로운 정시 트리거가 생성되었습니다.');
}

// 매 정시에 실행될 함수
function myHourlyTask() {
  Logger.log('정시에 실행되었습니다.');

  // 원본 캘린더 ID와 소유자 이름 매핑
  const sourceCalendars = { // 구성원 캘린더 ID : 이름
    '삐약@gmail.com': '삐약', 
    // ... 해당 포맷에 맞춰 쭉 작성하기
  };

  // 대상 캘린더 ID
  const destinationCalendarId = ''; 

  // 원하는 날짜 범위 설정
  const today = new Date(); // 현재 날짜
  const startDate = new Date(today.getFullYear(), today.getMonth(), 1); // 이번 달 1일
  const endDate = new Date(today.getFullYear(), today.getMonth() + 6 + 1, 0); // 종료 날짜를 현재 날짜 기준 6개월 이후의 마지막 날로 설정

  const keyword = '휴가'; // 필터링할 일정 제목 키워드
  const copiedTag = '#copied'; // 복사된 일정에 추가할 태그

  // 대상 캘린더에서 기존 이벤트들을 삭제
  const destinationCalendar = CalendarApp.getCalendarById(destinationCalendarId);
  const existingEvents = destinationCalendar.getEvents(startDate, endDate);

  // 기존의 모든 이벤트 삭제
  existingEvents.forEach(event => {
    event.deleteEvent();
  });

  // 모든 원본 캘린더를 순회하며 일정 가져오기
  Object.keys(sourceCalendars).forEach(calendarId => {
    const events = CalendarApp.getCalendarById(calendarId).getEvents(startDate, endDate);
    
    events.forEach(event => {
      if (event.getTitle().includes(keyword)) {
        // 일정 소유자 정보 추가
        const ownerName = sourceCalendars[calendarId];

        // 일정의 시작 시간과 종료 시간을 24시간제로 변환
        const formattedStartTime = Utilities.formatDate(event.getStartTime(), Session.getScriptTimeZone(), 'HH:mm');
        const formattedEndTime = Utilities.formatDate(event.getEndTime(), Session.getScriptTimeZone(), 'HH:mm');

        // 일정이 전날 자정부터 다음날 자정까지 이어지는지 확인
        const startTime = event.getStartTime();
        const endTime = event.getEndTime();
        const isOverMidnight = startTime.getDate() !== endTime.getDate() && startTime.getHours() === 0 && endTime.getHours() === 0;

        if (isOverMidnight) {
          // 전날 자정부터 다음날 자정일 경우, 시작 날짜에 하루종일 일정으로 추가
          const previousDay = new Date(endTime); // 종료 시간에서 하루 전으로 설정
          previousDay.setDate(previousDay.getDate() - 1); // 전날로 설정

          // 전날로 하루 종일 일정 추가 (종료 시간을 제외)
          destinationCalendar.createAllDayEvent(
            `[휴가] - ${ownerName}`, // 시간 값 없이 제목만 표시
            previousDay // 전날로 시작 시간 설정
          );
        } else {
          // 다른 일정의 경우 정상적으로 시작 시간과 종료 시간을 표시
          const newTitle = `[휴가] - ${ownerName} (${formattedStartTime} ~ ${formattedEndTime})`;

          // 대상 캘린더에 이벤트 복사
          destinationCalendar.createEvent(
            newTitle, // 일정 제목에 소유자 이름과 시간 추가
            event.getStartTime(),
            event.getEndTime(),
            {
              location: event.getLocation(),
              description: (event.getDescription() || '') + ' ' + copiedTag, // 복사된 일정에 태그 추가
              guests: event.getGuestList().map(guest => guest.getEmail()).join(','),
              sendInvites: false
            }
          );
        }
      }
    });

 

 

 

작성이 완료되면 다시 저장을 누르고,

createHourlyTrigger (1시간마다 자동실행하는 스크립트) 를 눌러준 다음 왼쪽에 실행 버튼을 눌러준다. 

 

그러면 실행 로그 창이 아래에 열리고 실행되는 절차에 대한 피드백을 주고 (작성했던 스크립트 내의 Logger.log가 노출)

작성했던 스크립트 내의  Logger.log가 노출

 

모아보기 캘린더에서 확인할 수 있다.

결과 

모아보기 캘린더

 

 

이때 참고할 점은 스크립트 도는 도중에는 삭제와 추가하는 과정이 진행 중이기때문에 휴가 확인이 어려울 수 있다.

수동으로 Apps Script에서 [실행]을 누를 경우 매 1시간마다 실행되므로, 실행 시간이 달라지게 된다.

 

코드는 챗지피티가 만들었고 이후 최적화는 안했다.

중요한게 아니어서 최적화할 정도의 필요성을 못느껴서이고, 확실히 코드가 별로다 (삭제-생성이 반복되서 날짜 값이 길면 오래걸리거나 실패할수 있다.) 

+ Recent posts