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

Screen shot

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

カレンダーへ会社の休日をまとめて登録したくて.icsファイルジェネレータを作成した。

Sample Site

さくらインターネット・レンタルサーバスタンダードプランへ実装したcgiを使用した作例。

https://yanmos.jpn.org/makeics/makeics.html

社内向けはfastAPIで実装、社外向けはpython cgiで実装した。
下記のようなcsvファイルを用意する。1行目に題目を記載し、2行目以降に日付を羅列する。

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 pandas as pd
import datetime
import uuid
from icalendar import Calendar, Event, vCalAddress, vText
from io import BytesIO

def makeics(data, cal):
    df = pd.read_csv(data, header=0)
    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(df.columns.values[0])
    for _, line in df.iterrows():
        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)
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"))

コメントを残す

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