구글캘린더 - 팀원 일정 긁어와서 휴가 모아보기 캘린더 만들기
사전작업
모아보기 캘린더 작업할 소유자(나)에게 각 팀원의 캘린더 공유 (모든일정보기)가 되어있어야한다.
(내가 공유받은 팀원의 캘린더로 부터 일정을 긁어오기 때문에)
캘린더 공유 방법
구글 캘린더에서 캘린더 우측 (1) 옵션 버튼 > (2) 설정 및 공유 > 설정 페이지에서 (3) 일정의 액세스 권한' 항목 [공개 사용 설정] 에서 소유자에게 공개 > (4) 모아보기(공유) 캘린더 만들기
이때 모두 공개는 보안적으로 좋지 않으므로 소유자에게만 따로 공개하기, 공유 가능한 링크 받기 기능이나 '특정 사용자 또는 그룹과 공유'를 통해 소유자에게 공개해야함
모아보기 캘린더 추가 절차는, 구글 캘린더 화면에서 (1) 다른 캘린더 추가 > (2) 새 캘린더 만들기 > 생성 페이지에서 캘린더 만들기 진행
구글의 Apps Script 사용하여 구성원의 일정을 모아보기할것이므로 구글 Apps Script 방문 (https://script.google.com/home) 하여 새 프로젝트 만들기 클릭
이때 어떠한 정보를 긁어 올 것인지도 단어나 문장을 맞춰야한다.
예를 들면 '휴가' 일 경우, 공유자들의 캘린더 등록 시 타이틀에 '휴가'라는 단어가 필수로 들어가야한다.
Apps Script의 Code.gs에 스크립트를 추가해줘야한다.
일단 돌아가는 상황을 간단히 설명하기 위해, 아래와 같이 콘솔로그를 등록해서 저장을 하면.
저장이 되면서 이렇게 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') 이런식으로 직접 지정해줘도 되고, 필요한 날짜를 계산해서 넣어도된다.
아래 노출할 문자열들을 입력한다.
완성된 코드
// 기존 트리거를 삭제하고 새로운 트리거를 설정하는 함수
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가 노출)
모아보기 캘린더에서 확인할 수 있다.
결과
이때 참고할 점은 스크립트 도는 도중에는 삭제와 추가하는 과정이 진행 중이기때문에 휴가 확인이 어려울 수 있다.
수동으로 Apps Script에서 [실행]을 누를 경우 매 1시간마다 실행되므로, 실행 시간이 달라지게 된다.
코드는 챗지피티가 만들었고 이후 최적화는 안했다.
중요한게 아니어서 최적화할 정도의 필요성을 못느껴서이고, 확실히 코드가 별로다 (삭제-생성이 반복되서 날짜 값이 길면 오래걸리거나 실패할수 있다.)
'Javascript' 카테고리의 다른 글
숫자 천 단위 마다 콤마 찍어주세요 (1,000) 소수점 숫자도 나와야해요. RegExr, toLocaleString (정규 표현식_Lookahead/Lookbehind_사파리 lookbehind 오류에 대해서..) (0) | 2022.06.22 |
---|---|
(lodash) 값 타입에 따라 isUndefined, isEmpty 뭐를 써야할까? (0) | 2022.01.17 |
CORS 에러를 해결하자, 어떻게? JSONP로! (0) | 2021.11.01 |
마우스로 창을 움직여보자! (0) | 2021.02.01 |
[jQuery기초] ajax (0) | 2020.06.30 |
[jQuery기초] 요소조작_이벤트_slice_trim_extend_each (0) | 2020.06.30 |
[jQuery기초] 요소조작_이벤트_replaceAll_replaceWith (0) | 2020.06.30 |
[jQuery기초] 요소조작_이벤트_animate_show&hide_css (0) | 2020.06.30 |