読者です 読者をやめる 読者になる 読者になる

TopCoder部のカレンダーをGoogleカレンダーで(そこそこきれいに)見る

カレンダーを公開しました.AtCoderのイベントはAtCoder社さんのカレンダーをお使いください.

githubにあげました. TopCoder部のカレンダーを開くのさえ億劫になってきている今日この頃. コンテストの予定がGoogleカレンダーに表示されるようになれば,コンテストにも出たくなるのではないかと考えた.

先駆者がいた. f:id:blue_jam:20160501101206p:plain

せっかく開始時間を明記してあるんだから,終日のイベントでなく,時間を決めうちしてほしいと思った.

サーバを持ってたら,cronでGoogleAPIたたいてごにょごにょしたり,iCalendarの内容を再度編集したiCalendarを生成するのCGIプログラムを作ったりすれば何とかなるのかもしれないが,サーバを持ってない.

調べてみると,Google Apps Scriptでcronっぽいことができるらしい.(参考

JavaScriptほとんど使ったことない...)

最終的に,

  1. TopCoder部のカレンダーをインポートしたカレンダーと,出力先のカレンダーを用意しておくと,
  2. Google Apps Scriptで作ったプログラムが,TopCoder部のカレンダーを適切に編集し,出力先のカレンダーに書き込んでくれる

というようになった.GASはトリガーという,一定間隔で決まった関数を呼び出す仕組み(?)があり,それを設定しておくと自動で追記を編集してくれる.

AtCoderGoogleカレンダーを公開してくださっているので省いた. HackerRankにもGoogleカレンダーにインポートできるカレンダーがあったけど,いろいろなものが登録されすぎてて混乱するので保留.

結果はこんな感じ

f:id:blue_jam:20160501103040p:plain

コンテスト時間をまじめにとるなら,コンテストサイトのAPIをたたいたほうがよさそうだけど,怠けてる.

Googleカレンダーを公開したら,ほかの人も幸せになれると思ったけど,公開するとメールアドレスがさらされる問題を解決できてないので非公開のままにします.

スクリプトのプロパティにsrcCalendarIdとdstCalendarIdを追加し,それぞれにTopCoder部カレンダーをインポートしたもののIDと,出力先のカレンダーのIDを書き込む必要があります. スクリプトのプロパティにignoreAtcoderを追加し,空でない文字列を入れるとAtCoderのイベントは追加されなくなります. トリガーには,calendarUpdateを追加してください.

たぶんgithubにあげる.

function calendarUpdate() {
  // プロパティからIDを読み取る
  var scriptProperties = PropertiesService.getScriptProperties();
  var srcCalendarId = scriptProperties.getProperty("srcCalendarId");
  var dstCalendarId = scriptProperties.getProperty("dstCalendarId");
  // カレンダーの取得
  srcCalendar = CalendarApp.getCalendarById(srcCalendarId);
  dstCalendar = CalendarApp.getCalendarById(dstCalendarId);
  
  ignoreAtCoder = Boolean(scriptProperties.getProperty("ignoreAtCoder"));
  
  // 全部は見えないらしいので,先1か月のを調べる.
  var now = new Date();
  var oneMonthFromNow = new Date(now.getTime() + (30 * 24 * 60 * 60 * 1000));
  var srcEvents = srcCalendar.getEvents(now, oneMonthFromNow);
  
  // 各イベントについて処理
  var re = new RegExp("[0-9][0-9]:[0-9][0-9]");
  for each(var e in srcEvents){
    var date = e.getTitle().match(re);
    var title = RegExp.rightContext.trim();
    var startTime = new Date(Date.parse(e.getStartTime().toLocaleDateString() + " " + date));
    
    // AtCoderを無視する設定で,AtCoderのイベントならchokuDie
    var atcoder = isAtCoderEvent(title);
    if (ignoreAtCoder && atcoder) continue;
    
    // 終了時刻を計算
    var duration = getDurationByTitle(title);
    var endTime = new Date(startTime.getTime() + duration * 60 * 1000);
    
    // 重複チェック
    var res = checkDuplication(title, startTime);
    if (res == null) {
      dstCalendar.createEvent(title, startTime, endTime);
    }
    else {
      res.setTime(startTime, endTime);
    }
  }
}

/*
 * タイトルからコンテストの長さを適当に取得する.当然間違う.
 */
function getDurationByTitle(title) {
  durationDictionary = {
    "SRM":90,
    "AtCoder Regular Contest": 90,
    "AtCoder Beginner Contest": 120,
    "Codeforces": 120,
    "yukicoder": 120
  };
  for (var key in durationDictionary) {
    Logger.log(title.indexOf(key));
    if(title.indexOf(key) >= 0)
      return durationDictionary[key];
  }
  return 90;
}

/*
 * 登録が重複しているか確認する
 * 重複しているなら,そのインスタンスを返す
 */
function checkDuplication(title, startTime) {
  var events = this.dstCalendar.getEventsForDay(startTime);
  for each(e in events){
    if(e.getTitle() == title)
      return e;
  }
  return null;
}

/*
 * AtCoderのイベントかどうか
 */
function isAtCoderEvent(title) {
  return title.indexOf("AtCoder") >= 0;
}

更新情報

5月3日:Googleカレンダーを公開しました.(ついでに,ブログの文体も少し変わっている.)