Skip to content

Instantly share code, notes, and snippets.

@yuukicammy
Last active April 17, 2023 13:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yuukicammy/c9a67a720d4fc89fcade25446356788f to your computer and use it in GitHub Desktop.
Save yuukicammy/c9a67a720d4fc89fcade25446356788f to your computer and use it in GitHub Desktop.
This code is for generating a monthly calendar in Google Apps Script (GAS). Running this code in GAS will output the original calendar. This calendar is suitable for young children who are learning the concept of words and days.

GAS Kids Calendar

This code is for generating a monthly calendar in Google Apps Script (GAS) Running this code in GAS will output the original calendar. This calendar is suitable for young children who are learning the concept of words and days.

sample calendar

Usage

  1. Open Google App Script.
  2. Add files in this program.
  3. Change parameters in Params.gs.
  4. Run GenerateCalendar.
  5. Download the spreadsheet as PDF and print out the calendar.
{
"timeZone": "Asia/Tokyo",
"dependencies": {},
"exceptionLogging": "STACKDRIVER",
"runtimeVersion": "V8",
"oauthScopes": [
"https://www.googleapis.com/auth/spreadsheets",
"https://www.googleapis.com/auth/calendar.readonly",
"https://www.googleapis.com/auth/drive.readonly"
]
}
function generateCalendar() {
const param = params();
let targetSS = SpreadsheetApp.openById(param.sheet.targetId);
for (let month = param.months[0]; month <= param.months[1]; month++) {
let sheetName = `${param.prefix}${param.year}-${String(month).padStart(2, '0')}${param.postfix}`;
let sheet = targetSS.getSheetByName(sheetName);
if (sheet == null) {
sheet = targetSS.insertSheet();
sheet.setName(sheetName);
}
// Year
sheet.getRange(param.sheet.layout.year).setValue(param.sheet.yearString);
sheet.getRange(param.sheet.layout.year).setFontSize(param.sheet.fontSize.year);
sheet.getRange(param.sheet.layout.year).setFontWeight("bold");
sheet.getRange(param.sheet.layout.year).setFontColor(param.sheet.fontColor.default);
// Month
writeMonthHeader(sheetName, month);
writeWeekHeader(sheetName);
writeDays(sheetName, month);
// Modify entire format
let dataRange = sheet.getDataRange();
dataRange.setHorizontalAlignment("left");
// Header Image
insertImage(sheetName, param.year, month);
// Space after the last week.
if (0 < param.sheet.layout.bottomHeaderHight) {
sheet.setRowHeight(dataRange.getLastRow(), param.sheet.layout.bottomHeaderHight);
}
// Save as PDF
/*
const ssId = targetSS.getId();
const sheetId = sheet.getSheetId();
savePDF(ssId, sheetId, sheetName);
*/
}
}
function savePDF(ssId, sheetId, sheetName) {
const param = params();
const folder = DriveApp.getFileById(ssId).getParents().next();
const fileName = `${sheetName}_${Utilities.formatDate(new Date(), 'JST', 'yyyyMMdd')}.pdf`;
// Output Options
var opts = {
'exportFormat': 'pdf', // (pdf / csv / xls / xlsx)
'format': 'pdf', // (pdf / csv / xls / xlsx)
'size': `${param.sheet.size}`,
'portrait': 'true', //(true: 縦向き / false: 横向き)
'scale': 4, // //1= Normal 100% / 2= Fit to width / 3= Fit to height / 4= Fit to Page
'sheetnames': 'false', // write the sheet name on the top of sheet(true / false)
'printtitle': 'false', // write the spread sheet name on the top of sheet (true / false)
'pagenumbers': 'false',
'gridlines': 'false',
'fzr': 'false', // write the line number
'horizontal_alignment': 'CENTER', //LEFT/CENTER/RIGHT
'vertical_alignment': 'TOP', //TOP/MIDDLE/BOTTOM
'top_margin': `${param.pdf.top_margin}`,
'bottom_margin': `${param.pdf.bottom_margin}`,
'left_margin': `${param.pdf.left_margin}`,
'right_margin': `${param.pdf.right_margin}`,
};
let urlExt = [];
for (optName in opts) {
urlExt.push(optName + '=' + opts[optName]);
}
const options = urlExt.join('&');
// OAuth token
var token = ScriptApp.getOAuthToken();
// URL
const url = `https://docs.google.com/spreadsheets/d/${ssId}/export?gid=${sheetId}&${options}`;
// Request to create PDF
var response = UrlFetchApp.fetch(
url, {
headers: { 'Authorization': 'Bearer ' + token }
}
);
// Create Blob
var blob = response.getBlob().setName(fileName);
// Save the PDF
folder.createFile(blob);
}
function writeMonthHeader(sheetName, month) {
const param = params();
const masterSS = SpreadsheetApp.openById(param.sheet.masterId);
let targetSS = SpreadsheetApp.openById(param.sheet.targetId);
let sheet = targetSS.getSheetByName(sheetName);
let monthDefSheet = masterSS.getSheetByName(param.sheet.sheetNames.month);
if (monthDefSheet == null) {
Logger.log(`Cannot open ${param.sheet.sheetNames.month}`);
}
const monthLabel = monthDefSheet.getDataRange().getValues();
const monthHiragana = monthLabel[month - 1][0];
let mhRange = sheet.getRange(param.sheet.layout.monthHiragana);
mhRange.merge();
mhRange.setValue(monthHiragana);
mhRange.setFontWeight("bold");
mhRange.setFontSize(param.sheet.fontSize.monthHiragana);
mhRange.setFontColor(param.sheet.fontColor.default);
sheet.getRange(param.sheet.layout.monthNum).setValue(monthLabel[month - 1][1]);
sheet.getRange(param.sheet.layout.monthNum).setFontWeight("bold");
sheet.getRange(param.sheet.layout.monthNum).setFontSize(param.sheet.fontSize.monthNum);
sheet.getRange(param.sheet.layout.monthNum).setFontColor(param.sheet.fontColor.default);
sheet.getRange(param.sheet.layout.monthEng).setValue(monthLabel[month - 1][2]);
sheet.getRange(param.sheet.layout.monthEng).setFontWeight("bold");
sheet.getRange(param.sheet.layout.monthEng).setFontSize(param.sheet.fontSize.monthEng);
sheet.getRange(param.sheet.layout.monthEng).setFontColor(param.sheet.fontColor.default);
sheet.getRange(param.sheet.layout.monthWa).setValue(monthLabel[month - 1][3]);
sheet.getRange(param.sheet.layout.monthWa).setFontWeight("bold");
sheet.getRange(param.sheet.layout.monthWa).setFontSize(param.sheet.fontSize.monthWa);
sheet.getRange(param.sheet.layout.monthWa).setFontColor(param.sheet.fontColor.default);
sheet.getRange(param.sheet.layout.season).setValue(monthLabel[month - 1][4]);
sheet.getRange(param.sheet.layout.season).setFontWeight("bold");
sheet.getRange(param.sheet.layout.season).setFontSize(param.sheet.fontSize.season);
sheet.getRange(param.sheet.layout.season).setFontColor(param.sheet.fontColor.default);
}
function writeWeekHeader(sheetName) {
const param = params();
const masterSS = SpreadsheetApp.openById(param.sheet.masterId);
let targetSS = SpreadsheetApp.openById(param.sheet.targetId);
let sheet = targetSS.getSheetByName(sheetName);
let weekDefSheet = masterSS.getSheetByName(param.sheet.sheetNames.week);
if (weekDefSheet == null) {
Logger.log(`Cannot open ${param.sheet.sheetNames.week}`);
}
const weekRange = sheet.getRange(param.sheet.layout.weekTitle);
const weekLabel = weekDefSheet.getDataRange().getValues();
const weekId = [];
for (let w = param.startWeek; w < 7; w++) {
weekId.push(w);
}
for (let w = 0; w < param.startWeek; w++) {
weekId.push(w);
}
for (let r = 0; r < weekRange.getNumRows(); r++) {
for (let c = 0; c < weekRange.getNumColumns(); c++) {
let cell = weekRange.getCell(r + 1, c + 1);
sheet.setColumnWidth(cell.getColumn(), param.sheet.layout.dayColWidth);
cell.setValue(weekLabel[r][c]);
cell.setFontWeight("bold");
if (r == 0) {
if (weekId[c] == 0) {
cell.setFontColor(param.sheet.fontColor.sunday);
} else if (weekId[c] == 6) {
cell.setFontColor(param.sheet.fontColor.saturday);
} else {
cell.setFontColor(param.sheet.fontColor.default);
}
cell.setFontSize(param.sheet.fontSize.weekTitle);
} else {
cell.setFontColor(param.sheet.fontColor.default);
cell.setFontSize(param.sheet.fontSize.weekSubtitle);
}
}
}
}
function writeDays(sheetName, month) {
const param = params();
const masterSS = SpreadsheetApp.openById(param.sheet.masterId);
let targetSS = SpreadsheetApp.openById(param.sheet.targetId);
let sheet = targetSS.getSheetByName(sheetName);
let dayDefSheet = masterSS.getSheetByName(param.sheet.sheetNames.days);
if (dayDefSheet == null) {
Logger.log(`Cannot open ${param.sheet.sheetNames.days}`);
}
const dayLabel = dayDefSheet.getDataRange().getValues();
let eventDefSheet = masterSS.getSheetByName(param.sheet.sheetNames.events);
if (eventDefSheet == null) {
Logger.log(`Cannot open ${param.sheet.sheetNames.events}`);
}
const eventLabel = eventDefSheet.getDataRange().getValues();
let calendars = {};
for (key in param.calendar.ids) {
if (param.calendar.ids.hasOwnProperty(key)) {
let id = param.calendar.ids[key];
let calendar = CalendarApp.getCalendarById(id);
calendars[key] = calendar;
}
}
const year = param.year;
const firstDayOfWeek = new Date(year, month - 1, 1).getDay();
let monthLastDay = { 1: 31, 2: 28, 3: 31, 4: 30, 5: 31, 6: 30, 7: 31, 8: 31, 9: 30, 10: 31, 11: 30, 12: 31 };
if ((year % 4 == 0 && year % 100 != 0) || year % 400 == 0) {
monthLastDay[2] = 29;
}
let margin = firstDayOfWeek - param.startWeek;
if (margin < 0) {
margin = 6 - param.startWeek - margin;
}
let r = 1;
let c = margin + 1;
let dayRange = sheet.getRange(param.sheet.layout.days);
dayRange.setVerticalAlignment("top");
for (let day = 1; day <= monthLastDay[month]; day++) {
if (c == 8) {
c = 1;
r += (4 + param.sheet.layout.dayEmptyRows);
}
const date = new Date(year, month - 1, day);
// Day Number
let cell = dayRange.getCell(r, c);
cell.setValue(dayLabel[day - 1][0]);
cell.setFontSize(param.sheet.fontSize.dayNum);
cell.setFontFamily(param.sheet.font.dayNum);
cell.setFontWeight("bold");
const weekId = date.getDay();
let isHoliday = false;
if (0 < CalendarApp.getCalendarById(param.calendar.ids["祝日"]).getEventsForDay(date).length) {
cell.setFontColor(param.sheet.fontColor.sunday);
isHoliday = true;
} else if (weekId == 0) {
cell.setFontColor(param.sheet.fontColor.sunday);
} else if (weekId == 6) {
cell.setFontColor(param.sheet.fontColor.saturday);
} else {
cell.setFontColor(param.sheet.fontColor.default);
}
// Day Hiragana
cell = dayRange.getCell(r + 1, c);
cell.setValue(dayLabel[day - 1][1]);
cell.setFontSize(param.sheet.fontSize.dayHiragana);
cell.setFontWeight("bold");
if (weekId == 0 || isHoliday) {
cell.setFontColor(param.sheet.fontColor.sunday);
} else if (weekId == 6) {
cell.setFontColor(param.sheet.fontColor.saturday);
} else {
cell.setFontColor(param.sheet.fontColor.default);
}
// Day Events
horidayString = "";
eventString = "";
for (key in calendars) {
if (calendars.hasOwnProperty(key)) {
const events = calendars[key].getEventsForDay(date);
for (e of events) {
let title = e.getTitle();
// Convert the event title to customized string defined in SpreadSheet.
// If there is no corresponding definition, the original title is used.
for (let i = 0; i < eventLabel.length; i++) {
if (title == eventLabel[i][0]) {
if (typeof eventLabel[i][1] === 'undefined' || eventLabel[i][1].length == 0) {
title = "";
break;
} else if (param.mode == 0) {
title = `${eventLabel[i][1]}`;
} else if (param.mode == 1 || param.mode == 2) {
title = `${eventLabel[i][0]}`;
} else {
title = `${eventLabel[i][0]}`;
}
// emoji
if (typeof eventLabel[i][2] !== 'undefined' && 0 < eventLabel[i][2].length) {
title += ` ${eventLabel[i][2]}`;
}
if (param.mode == 2) {
title += `\n(${eventLabel[i][1]})`;
}
break;
}
}
if (0 < title.length) {
if (key == "祝日") {
horidayString += `${title}\n`;
} else {
eventString += `${title}\n`;
}
}
}
}
}
// National Holiday Name
if (isHoliday) { // 0 < horidayString.length
horidayString = horidayString.slice(0, -1); // delete last \n
cell = dayRange.getCell(r + 2, c);
cell.setValue(horidayString);
cell.setFontSize(param.sheet.fontSize.dayEvent);
cell.setFontColor(param.sheet.fontColor.sunday);
}
if (0 < eventString.length) {
eventString = eventString.slice(0, -1); // delete last \n
cell = dayRange.getCell(r + 3, c);
cell.setValue(eventString);
cell.setFontSize(param.sheet.fontSize.dayEvent);
cell.setFontColor(param.sheet.fontColor.default);
}
c++;
}
if (6 <= c) {
r += 4;
}
mark(sheet, r, 6);
}
function mark(sheet, r, c) {
const param = params();
const range = sheet.getRange(param.sheet.layout.days);
const markString = `Copyright © 2022 yuukicammy`;
let cell = range.getCell(r, c);
cell.setValue(markString);
cell.setFontColor("#FFFFFF");
}
function insertImage(sheetName, year, month) {
const param = params();
let targetSS = SpreadsheetApp.openById(param.sheet.targetId);
let sheet = targetSS.getSheetByName(sheetName);
let imageCell = sheet.getRange(param.sheet.layout.image);
// Space for the header image.
sheet.setRowHeight(imageCell.getRow(), param.sheet.layout.imageHight);
imageCell.merge();
imageCell.setHorizontalAlignment("center");
imageCell.setVerticalAlignment(param.sheet.layout.imageVerticalAlignment);
const imageSource = imageSources(month);
if (0 < imageSource.length) {
const image = SpreadsheetApp.newCellImage()
.setSourceUrl(imageSource)
.setAltTextTitle(`${year}-${month}-headerImage`)
.setAltTextDescription(`${year}-${month}-headerImage`)
.build();
imageCell.setValue(image);
}
}
function imageSources(month) {
const param = params();
if (typeof param.image.urls[month] === 'undefined' ||
param.image.urls[month].length == 0) {
if (typeof param.image.ids[month] === 'undefined' ||
param.image.ids[month] == 0) {
return "";
}
const imageFile = DriveApp.getFileById(param.image.ids[month]);
const blob = imageFile.getBlob();
const ctype = blob.getContentType();
const base64 = Utilities.base64Encode(blob.getBytes());
return `data:${ctype};base64, ${base64}`;
} else {
return param.image.urls[month];
}
}
MIT License
Copyright (c) 2022 yuukicammy
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
function params() {
return {
prefix: "",
postfix: "",
mode: 2, // 0: hiragana, 1: kanji, 2: hiragana+kanji
year: 2023,
months: [1, 1], // [startMonth, endMonth]
startWeek: 1, // 1: Monday, .., 6: Saturday, 0: Sunday
calendar: {
ids: {
"祝日": "2bk907eqjut8imoorgq1qa4olc@group.calendar.google.com",
"年中行事・五節句": "4a6e9bpidmgke7bafah1lsv4t0@group.calendar.google.com",
"二十四節気・雑節": "2i7smciu430uh0mv3i0qmd8iuk@group.calendar.google.com",
"朔弦望": "mr1q70hu2iacu62adntahc69q0@group.calendar.google.com",
// "家族": "6gccavhpr5gp4bup95av4ofsro@group.calendar.google.com"
}
},
sheet: {
masterId: "", // The mater file ID of Google spreadsheet in which sheets such as "Events" and "Days" are saved.
targetId: "", // The target file ID of Google spreadsheet to which the calendar is created.
sheetNames: {
events: "Events",
days: "Days",
month: "Months",
week: "Week"
},
layout: {
year: "G3",
monthHiragana: "A3:D3",
monthNum: "A4",
monthEng: "B4",
monthWa: "C4",
season: "G4",
weekTitle: "A6:G8",
days: "A9:G200",
dayEmptyRows: 3,
dayColWidth: 140,
bottomHeaderHight: 0,
image: "A1:G1",
imageVerticalAlignment: "top",
imageHight: 450,
},
fontSize: {
year: 12,
monthHiragana: 36,
monthNum: 36,
monthEng: 12,
monthWa: 12,
season: 12,
weekTitle: 18,
weekSubtitle: 12,
dayNum: 24,
dayHiragana: 12,
dayEvent: 9
},
font: {
dayNum: "Lexend"
},
fontColorBasic: {
sunday: "#c53d43",
saturday: "#2a83a2",
default: "#383c3c"
},
fontColorPastel: {
sunday: "#ff8484",
saturday: "#8ec6ff",
default: "#616161"
},
fontColorEarth: {
sunday: "#ef857d",
saturday: "#3c7170",
default: "#736d71"
},
fontColorSmookyEarth: {
sunday: "#e29676",
saturday: "#98605e",
default: "#9d8e87"
},
fontColorKusumniEarth: {
sunday: "#D6B090",
saturday: "#90B1BF",
default: "#BF9090"
},
fontColor: {
sunday: "#D6B090",
saturday: "#90B1BF",
default: "#BF9090"
},
yearString: "2023ねん\nれいわ5ねん",
},
image: {
urls: {
1: "", // If here is empty, ids are used.
2: "", // https://drive.google.com/uc?id=1zq_BTFoTuF1PPef0qh5V3ihzIxSpInAw
3: "",
4: "",
5: "",
6: "",
7: "",
8: "",
9: "",
10: "",
11: "",
12: ""
},
ids: {
1: "1pNG92vMj4nNB2s9zPCqwy5Z6GhPXhfvS", // The file id in Google Drive.
2: "",
3: "",
4: "",
5: "",
6: "",
7: "",
8: "",
9: "",
10: "",
11: "",
12: ""
}
},
pdf: {
size: "A3", //(A4 or A3)
top_margin: 0.5,
bottom_margin: 0.5,
left_margin: 0.5,
right_margin: 0.5
}
};
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment