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などのサービスにサクッと移行したほうがいいでしょう。
以上です。お疲れ様でした。
参考文献