iCalendar .ics ファイルを生成するWebAPPの作成
カレンダーへ会社の休日をまとめて登録したくて.icsファイルジェネレータを作成した。
Sample Site
さくらインターネット・レンタルサーバスタンダードプランへ実装したcgiを使用した作例。
https://yanmos.jpn.org/makeics/makeics.html
Local環境はfastAPIで実装、レンタルサーバーへはcgiで実装した。
使用方法は、まず下記のようなcsvファイルを用意する。1行目に題目を記載し、2行目以降に日付を羅列する。次にそのファイルをアップロードする。icsファイル生成後にDownloadボタンが表示されるのでクリックしファイルを入手する。
eg. 日本の正月
Input file: input.csv
正月
2024/1/1
2024/1/2
2024/1/3
Output file: output.ics
BEGIN:VCALENDAR
VERSION:2.0
PRODID:-//hacksw/handcal//NONSGML v1.0//EN
CALSCALE:GREGORIAN
METHOD:PUBLISH
X-WR-CALNAME:Sample
BEGIN:VEVENT
SUMMARY:正月
DTSTART;VALUE=DATE:20240101
DTEND;VALUE=DATE:20240102
UID:785ba0e4-8c91-4fcc-ad86-39a9ffe29296
END:VEVENT
BEGIN:VEVENT
SUMMARY:正月
DTSTART;VALUE=DATE:20240102
DTEND;VALUE=DATE:20240103
UID:ed2014cf-ccdb-4191-a86c-b2149de4ca74
END:VEVENT
BEGIN:VEVENT
SUMMARY:正月
DTSTART;VALUE=DATE:20240103
DTEND;VALUE=DATE:20240104
UID:88a2fe72-c0e4-47a8-9539-d0e7f32c9292
END:VEVENT
END:VCALENDAR
Structure
処理の構成は下図の通り。
flowchart LR
infile((.csv file))
outfile((.ics file))
cgi[makeics.py]
html[makeics.html]
html-->cgi-->outfile
infile-->cgi
使用ライブラリ
UIkit・・・Graphical User Interface
iCalendar・・・python calendar module
Source code
ソースコードを下記に示す。実際の運用版とは細部が異なる。
HTML
makeics.html
UIkitを利用してページを構成。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/uikit@3.14.1/dist/css/uikit.min.css" />
<script src="https://cdn.jsdelivr.net/npm/uikit@3.14.1/dist/js/uikit.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/uikit@3.14.1/dist/js/uikit-icons.min.js"></script>
<link rel="icon" href="/makeics/favicon.ico">
<title>Make .ics file</title>
</head>
<body>
<div class="uk-padding uk-width-auto">
<form method="post" action="makeics.py" enctype="multipart/form-data">
<h3 class="uk-heading-divider">Make .ics file</h3>
<div class="js-upload uk-placeholder uk-box-shadow-small uk-text-left">
<span uk-icon="icon: cloud-upload"></span>
<span class="uk-text-middle">Attach .csv file by dropping it here or</span>
<div uk-form-custom>
<input type="file">
<span class="uk-link">selecting one</span>
</div>
</div>
<div id="downloadlink" class="uk-margin-top"></div>
<progress id="js-progressbar" class="uk-progress" value="0" max="100" hidden></progress>
<div class="uk-margin-top">
<p id="outputtext" class="uk-margin-top uk-text-small">
- Sample .csv file -<br/>
My Holiday<br/>
2024/1/1<br/>
2024/1/2<br/>
2024/1/3<br/>
</p>
</div>
<div class="uk-padding uk-position-bottom-center uk-text-small">
<a class="uk-link-muted" href="mailto:someone@somedomain">someone@somedomain</a>
</div>
</form>
</div>
</body>
<script>
function download(content) {
var blob = new Blob([content], { "type": "text/calendar" });
var textstr;
textstr = "<p>Click button to download .ics file.</p>\n"
textstr += "<a id=\"download\" class=\"uk-button uk-button-default\" href=\"#\" download=\"output.ics\" disabled>Download .ics file</a>";
document.getElementById("downloadlink").innerHTML = textstr;
document.getElementById("download").href = window.URL.createObjectURL(blob);
document.getElementById("download").disabled = false;
document.getElementById("outputtext").innerText = content;
}
var bar = document.getElementById('js-progressbar');
UIkit.upload('.js-upload', {
url: 'makeics.py',
multiple: false,
allow: '*.csv',
mime: 'text/csv',
name: 'file',
loadStart: function (e) {
bar.removeAttribute('hidden');
bar.max = e.total;
bar.value = e.loaded;
},
progress: function (e) {
bar.max = e.total;
bar.value = e.loaded;
},
loadEnd: function (e) {
bar.max = e.total;
bar.value = e.loaded;
},
completeAll: function (e) {
setTimeout(function () {
bar.setAttribute('hidden', 'hidden');
download(e.response)
alert('Make .ics file Completed');
}, 1000);
}
});
</script>
</html>
CGI
makeics.py
CGIを利用した処理部はpythonで作成。
#!/usr/bin/env -S LD_LIBRARY_PATH=/home/userid/.pyenv/shims /home/userid/.pyenv/shims/python
import sys
import io
import cgi
import re
import datetime
import uuid
import csv
from icalendar import Calendar, Event, vCalAddress, vText
from io import BytesIO
def makeics(data, cal):
reader = csv.reader(data.read().decode('utf-8').splitlines())
rows = list(reader)
header = rows[0]
data_rows = rows[1:]
dfin = {"columns": header, "rows": data_rows}
cal.add('PRODID', '-//hacksw/handcal//NONSGML v1.0//EN')
cal.add('VERSION', '2.0')
cal.add('CALSCALE', 'GREGORIAN')
cal.add('METHOD', 'PUBLISH')
cal.add('X-WR-CALNAME', 'Sample')
titletext: str = str(dfin["columns"][0])
for line in dfin["rows"]:
sdate = datetime.datetime.strptime(line[0], '%Y/%m/%d').date()
edate = sdate + datetime.timedelta(days=1) - datetime.timedelta(seconds=1)
event = Event()
event.add('SUMMARY', titletext)
event.add('DTSTART', sdate)
event.add('DTEND', edate)
event.add('UID', uuid.uuid4())
cal.add_component(event)
cal = Calendar()
form = cgi.FieldStorage()
file = form["file"]
csvfile = file.file.read()
fileobj = BytesIO(csvfile)
fileobj.seek(0)
makeics(fileobj, cal)
sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8')
print("Content-Type: text/calendar")
print("")
print(cal.to_ical().decode("utf-8"))
履歴
2025/09/12 Pandasなしで動作するよう変更。サンプルサイト動作確認。