0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Docomoスケジュール&メモをGoogle Calendarなどに移行する

Posted at

Docomo スケジュール&メモ

このアプリはどうやらGoogle Play StoreにもないのでSIMフリースマホにしたら利用し続ける敷居が上がります。APKファイルも全然見つかりませんでした。

この記事の背景

VCS(vCalendar)ファイルを直接Google Calendarでも読み込むことが可能らしいですが、4000件Overのアイテムを直接ブラウザからインポートするのはできませんでした。
(API経由なら行けるかもしれませんが)
そのため、CSVに変換してからアップロードしてみる。ということです。

また、他の人の記事にて、WindowsマシンのOutlookを用意すれば、
[Docomoスケジュール&メモ] => [VCS] => [Outlook] => [ICS] => [Google Calendar]
のように移行することができるみたいですが...

LinuxユーザにとってそのためにWindowsを実行するのはかなり癪です。(?)

準備

スケジュール&メモでの操作

設定のエクスポートなどからVCSファイルを吐き出してください。

PCでの操作

  • Python実行環境

が必要です。
追加ライブラリは不要です。

リポジトリのクローン

git clone https://github.com/H-goto16/vcs_to_csv

実行

適切なディレクトリに遷移して

python3 main.py input.vcs output.csv

ファイル名は適宜変えてください。

また、大きすぎるCSVを細かくするには同梱のプログラム
ファイル名をgoogle_calendar_import.csvにして同じ階層にする。
区切る数を4にするなら

pytohn3 split_csv.py 4

これでOKです。

Google Calendarでの操作

カレンダーのインポートから先程のCSVをアップロードしたら完了です。
念の為、新しいカレンダーに追加すると失敗したときでも消せるのでオススメです。

コード解説

def normalize_quoted_printable(text: str) -> str:
    return re.sub(r'=\r?\n', '', text)

Quoted-printableエンコーディングで改行を表す =\n や =\r\n を削除します。

def search_field(text: str, pattern: str) -> str:
    match = re.search(pattern, text, re.S)
    if match:
        value = match.group(1)
        value = re.split(r'\n[A-Z\-]+:', value)[0]
        return value.strip()
    return ''

正規表現からフィールドを検索する関数。

def parse_vcs(vcs: str) -> List[Dict[str, str]]:
    events = vcs.split('BEGIN:VEVENT')[1:]
    parsed_events = []

    for event in events:
        summary_raw = search_field(event, r'SUMMARY[^:]*:(.+?)(?:\n[A-Z\-]+:|\nEND:VEVENT|\Z)')
        description_raw = search_field(event, r'DESCRIPTION[^:]*:(.+?)(?:\n[A-Z\-]+:|\nEND:VEVENT|\Z)')
        location_raw = search_field(event, r'LOCATION[^:]*:(.+?)(?:\n[A-Z\-]+:|\nEND:VEVENT|\Z)')
        dtstart_raw = search_field(event, r'DTSTART:(\d{8}T\d{6}Z)')
        dtend_raw = search_field(event, r'DTEND:(\d{8}T\d{6}Z)')

        summary = decode_text(summary_raw)
        description = decode_text(description_raw)
        location = decode_text(location_raw)

        start_date, start_time = parse_utc_to_jst(dtstart_raw)
        end_date, end_time = parse_utc_to_jst(dtend_raw)

        parsed_events.append({
            'subject': summary,
            'start_date': start_date,
            'start_time': start_time,
            'end_date': end_date,
            'end_time': end_time,
            'description': description,
            'location': location
        })

    return parsed_events

Google Calendarには

'Subject', 'Start Date', 'Start Time', 'End Date', 'End Time',All Day Event', 'Description', 'Location', 'Private'

のヘッダが必要です。
それに対応するのをVCSから抽出します。

subject, start_date, start_time, end_date, end_time, description, location

になるので、あまっているAll Day Event, PrivateはFalseを入力。

あとは時刻変換、ファイルのopen, writeなどです。

  • コード全体
import re
import quopri
import csv
import sys
from datetime import datetime, timedelta
from typing import List, Dict

def normalize_quoted_printable(text: str) -> str:
    return re.sub(r'=\r?\n', '', text)

def parse_vcs_file(filepath: str) -> List[Dict[str, str]]:
    with open(filepath, 'r', encoding='utf-8') as f:
        vcs = f.read()
    return parse_vcs(vcs)

def parse_vcs(vcs: str) -> List[Dict[str, str]]:
    events = vcs.split('BEGIN:VEVENT')[1:]
    parsed_events = []

    for event in events:
        summary_raw = search_field(event, r'SUMMARY[^:]*:(.+?)(?:\n[A-Z\-]+:|\nEND:VEVENT|\Z)')
        description_raw = search_field(event, r'DESCRIPTION[^:]*:(.+?)(?:\n[A-Z\-]+:|\nEND:VEVENT|\Z)')
        location_raw = search_field(event, r'LOCATION[^:]*:(.+?)(?:\n[A-Z\-]+:|\nEND:VEVENT|\Z)')
        dtstart_raw = search_field(event, r'DTSTART:(\d{8}T\d{6}Z)')
        dtend_raw = search_field(event, r'DTEND:(\d{8}T\d{6}Z)')

        summary = decode_text(summary_raw)
        description = decode_text(description_raw)
        location = decode_text(location_raw)

        start_date, start_time = parse_utc_to_jst(dtstart_raw)
        end_date, end_time = parse_utc_to_jst(dtend_raw)

        parsed_events.append({
            'subject': summary,
            'start_date': start_date,
            'start_time': start_time,
            'end_date': end_date,
            'end_time': end_time,
            'description': description,
            'location': location
        })

    return parsed_events

def search_field(text: str, pattern: str) -> str:
    match = re.search(pattern, text, re.S)
    if match:
        value = match.group(1)
        value = re.split(r'\n[A-Z\-]+:', value)[0]
        return value.strip()
    return ''

def decode_text(input_str: str) -> str:
    if not input_str:
        return ''
    normalized = normalize_quoted_printable(input_str)
    try:
        return quopri.decodestring(normalized).decode('utf-8')
    except Exception:
        return normalized

def parse_utc_to_jst(utc_str: str) -> (str, str):
    if not utc_str:
        return '', ''
    dt_utc = datetime.strptime(utc_str, "%Y%m%dT%H%M%SZ")
    dt_jst = dt_utc + timedelta(hours=9)
    return dt_jst.strftime("%Y/%m/%d"), dt_jst.strftime("%H:%M")

def write_events_to_csv(events, filename):
    fieldnames = [
        'Subject', 'Start Date', 'Start Time', 'End Date', 'End Time',
        'All Day Event', 'Description', 'Location', 'Private'
    ]
    with open(filename, 'w', newline='', encoding='utf-8') as csvfile:
        writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
        writer.writeheader()
        for event in events:
            writer.writerow({
                'Subject': event['subject'],
                'Start Date': event['start_date'],
                'Start Time': event['start_time'],
                'End Date': event['end_date'],
                'End Time': event['end_time'],
                'All Day Event': 'False',
                'Description': event['description'],
                'Location': event['location'],
                'Private': 'False'
            })

if __name__ == "__main__":
    if len(sys.argv) >= 2:
        vcs_file = sys.argv[1]
    else:
        vcs_file = "calendar.vcs"
    if len(sys.argv) >= 3:
        csv_file = sys.argv[2]
    else:
        csv_file = "google_calendar_import.csv"
    events = parse_vcs_file(vcs_file)
    write_events_to_csv(events, csv_file)

感想

これでDocomoなどのサービスから逃げられます。
Googleなどのサービスにサクッと移行したほうがいいでしょう。

以上です。お疲れ様でした。

参考文献

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?