/***** 설정 *****/
const CAL_NAME = 'EXAM NAME💻'; // 전용 캘린더 이름
const EVENT_TITLE = '✅Todo List'; // 매일 생성할 종일 이벤트 제목
const LOG_SHEET_NAME = 'List Script Log'; // 실행 로그를 쓸 구글 스프레드시트 이름
/***** 스프레드시트 로깅 유틸 *****/
function getOrCreateLogSheet_() {
// 같은 이름의 스프레드시트가 있으면 첫 번째를 사용, 없으면 생성
const files = DriveApp.getFilesByName(LOG_SHEET_NAME);
let ss;
if (files.hasNext()) {
ss = SpreadsheetApp.open(files.next());
} else {
ss = SpreadsheetApp.create(LOG_SHEET_NAME);
}
const sheet = ss.getActiveSheet();
// 헤더 없으면 추가
const first = sheet.getRange(1, 1).getValue();
if (!first) {
sheet.appendRow(['Timestamp', 'Function', 'Status', 'Message']);
}
return sheet;
}
function log_(fn, status, message) {
const tz = Session.getScriptTimeZone();
const ts = Utilities.formatDate(new Date(), tz, 'yyyy-MM-dd HH:mm:ss');
try {
const sheet = getOrCreateLogSheet_();
sheet.appendRow([ts, fn, status, message || '']);
} catch (e) {
// 로깅 실패해도 스크립트 전체가 죽지 않도록
console.warn('Log write failed:', e);
}
}
/***** 캘린더/날짜 유틸 *****/
function getCal_() {
const cals = CalendarApp.getCalendarsByName(CAL_NAME);
return cals[0] || CalendarApp.getDefaultCalendar();
}
function todayDate_(tz = Session.getScriptTimeZone()) {
const now = new Date();
return new Date(
Utilities.formatDate(now, tz, 'yyyy'),
Number(Utilities.formatDate(now, tz, 'M')) - 1,
Utilities.formatDate(now, tz, 'd')
);
}
function defaultTemplate_() {
return [
'🕐 오전',
'- ',
'',
'🌇 오후',
'- ',
'',
'📝 메모',
'- '
].join('\n');
}
function getTodayLogEvent_(cal) {
const today = todayDate_();
const events = cal.getEventsForDay(today);
return events.find(e => e.isAllDayEvent() && e.getTitle() === EVENT_TITLE) || null;
}
/***** 1) 매일 0시에 템플릿 이벤트 생성 *****/
function createDailyLogEvent() {
const fn = 'createDailyLogEvent';
try {
const cal = getCal_();
const today = todayDate_();
const existing = getTodayLogEvent_(cal);
if (existing) {
log_(fn, 'SKIP', 'Already exists for today');
return;
}
const ev = cal.createAllDayEvent(EVENT_TITLE, today);
ev.setDescription(defaultTemplate_());
log_(fn, 'OK', 'Created all-day event with template');
} catch (e) {
log_(fn, 'ERROR', String(e && e.stack ? e.stack : e));
throw e;
}
}
/***** 2) 한 줄 추가 *****/
function addLogItem(text = '작업 내용 예시') {
const fn = 'addLogItem';
try {
const cal = getCal_();
let ev = getTodayLogEvent_(cal);
if (!ev) {
createDailyLogEvent();
ev = getTodayLogEvent_(cal);
}
const tz = Session.getScriptTimeZone();
const now = new Date();
const hhmm = Utilities.formatDate(now, tz, 'HH:mm');
let desc = ev.getDescription() || defaultTemplate_();
const line = `- ${hhmm} ${text}`;
if (now.getHours() < 12) {
const pattern = /(🕐 오전[\s\S]*?)(\n\n🌇 오후)/;
if (pattern.test(desc)) {
desc = desc.replace(pattern, (_, p1, p2) => `${p1}\n${line}${p2}`);
} else {
desc = `🕐 오전\n${line}\n\n🌇 오후\n- \n\n📝 메모\n- `;
}
} else {
const pattern = /(🌇 오후[\s\S]*?)(\n\n📝 메모)/;
if (pattern.test(desc)) {
desc = desc.replace(pattern, (_, p1, p2) => `${p1}\n${line}${p2}`);
} else {
desc = `🕐 오전\n- \n\n🌇 오후\n${line}\n\n📝 메모\n- `;
}
}
ev.setDescription(desc);
log_(fn, 'OK', `Added: ${text}`);
} catch (e) {
log_(fn, 'ERROR', String(e && e.stack ? e.stack : e));
throw e;
}
}
/***** 3) 웹앱(옵션) — URL?text=내용 으로 추가 *****/
function doGet(e) {
const fn = 'doGet';
try {
const text = e && e.parameter && e.parameter.text;
if (text) {
addLogItem(text);
log_(fn, 'OK', `text="${text}"`);
return ContentService.createTextOutput('OK');
}
log_(fn, 'SKIP', 'NO_TEXT');
return ContentService.createTextOutput('NO_TEXT');
} catch (e2) {
log_(fn, 'ERROR', String(e2 && e2.stack ? e2.stack : e2));
return ContentService.createTextOutput('ERROR');
}
}
/***** 4) 수동 테스트/확인 *****/
// 지금 즉시 오늘 이벤트 생성(수동 테스트)
function testCreateNow() {
createDailyLogEvent();
}
// 오늘 이벤트가 있는지, 설명 템플릿이 들어갔는지 확인
function verifyToday() {
const fn = 'verifyToday';
try {
const cal = getCal_();
const ev = getTodayLogEvent_(cal);
if (!ev) {
log_(fn, 'NOT_FOUND', 'No event for today');
return '오늘 이벤트가 없습니다.';
}
const desc = ev.getDescription() || '';
const hasTemplate = /🕐 오전/.test(desc) && /🌇 오후/.test(desc);
const msg = `이벤트 존재 / 템플릿=${hasTemplate ? 'OK' : 'N/A'}`;
log_(fn, 'OK', msg);
return msg;
} catch (e) {
log_(fn, 'ERROR', String(e && e.stack ? e.stack : e));
throw e;
}
}
/***** 5) 코드로 트리거 생성(옵션: 00:05 KST) *****/
function setupTimeTrigger() {
// 기존 동일 함수 트리거 제거
const triggers = ScriptApp.getProjectTriggers();
triggers
.filter(t => t.getHandlerFunction() === 'createDailyLogEvent')
.forEach(t => ScriptApp.deleteTrigger(t));
// 매일 00:05 실행
ScriptApp.newTrigger('createDailyLogEvent')
.timeBased()
.atHour(0) // 00시대
.nearMinute(5) // 05분 근처
.everyDays(1)
.create();
log_('setupTimeTrigger', 'OK', 'Daily at 00:05 created');
}
이렇게 작성 후...

트리거에서 저렇게 설정하면 00:00 ~ 01:00 사이에 매번 작동하여 생성해주고...
매번 실행된 내역을 구글 스프레드 시트에 저장함.
※ 고정시간대로 할수있으나, 테스트 해 본 결과 트리거 부하가 심하고...
오류 발생시에 수정이 좀 귀찮아 짐..