iCalendar .ics ファイルを生成するWebAPPの作成

Screen shot

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なしで動作するよう変更。サンプルサイト動作確認。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です