Kengo's blog

Technical articles about original projects, JVM, Static Analysis and TypeScript.

Googleカレンダーで給料日を調べる

自作TwitterBotに給料日判定ロジックを組み込むべく、GoogleカレンダーのAPIを利用するコードを書きました。APIのご利用は利用規約に従って計画的に。

1.jarを入手する

gdata-java-clientから最新のjarを入手。ビルドパスに追加。

2.ユーティリティを書く

簡単に書くとこんなところ。テストケース省略。
前提条件チェックにGuavaフレームワークのPreconditionsクラスを使用していますが、Guavaを使いたくない場合はコメントアウトしても問題ありません。

package jp.skypencil.util;

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import com.google.common.base.Preconditions;
import com.google.gdata.client.calendar.CalendarQuery;
import com.google.gdata.client.calendar.CalendarService;
import com.google.gdata.data.DateTime;
import com.google.gdata.data.calendar.CalendarEventEntry;
import com.google.gdata.data.calendar.CalendarEventFeed;
import com.google.gdata.util.ServiceException;

public class CalendarUtil {
	private static final String CALENDAR_NAME = "japanese__ja@holiday.calendar.google.com";
	private static final String PROJECTION = "full-noattendees";
	private static final String URL = "http://www.google.com/calendar/feeds/" + CALENDAR_NAME + "/public/" + PROJECTION;
	private static final int DEFAULT_SALARY_DAY = 25;
	private final DateFormat format = new SimpleDateFormat("yyyy-MM-dd");
	private final CalendarService service = new CalendarService(applicationName); // FIXME 適切なアプリケーション名を指定

	/**
	 * 指定した範囲に含まれる祝日のリストを返す。
	 * @param from 祝日を取得する範囲の開始日。範囲にはこの日も含まれる。
	 * @param to 祝日を取得する範囲の終了日。範囲にはこの日も含まれる。
	 * @return 指定した範囲に含まれるイベントエントリのリスト
	 */
	List<CalendarEventEntry> loadHolidays(Date from, Date to) {
		Preconditions.checkNotNull(from);
		Preconditions.checkNotNull(to);
		Preconditions.checkArgument(from.compareTo(to) <= 0);

		try {
			CalendarQuery query = new CalendarQuery(new URL(URL));
			query.setMinimumStartTime(DateTime.parseDateTime(format.format(from) + "T00:00:00"));
			query.setMaximumStartTime(DateTime.parseDateTime(format.format(to) + "T23:59:59"));
	
			CalendarEventFeed result = service.query(query, CalendarEventFeed.class);
			return result.getEntries();
		} catch (MalformedURLException e) {
			throw new AssertionError(e);
		} catch (IOException e) {
			// TODO 適切にハンドリング
		} catch (ServiceException e) {
			// TODO 適切にハンドリング
		}
	}

	public boolean isHoliday(Date day) {
		Preconditions.checkNotNull(day);
		return !loadHolidays(day, day).isEmpty();
	}

	/**
	 * 指定した月の給料日を調べて返す
	 * @param calendar 給料日を調べる年月を指定したカレンダー
	 * @return 給料日の日付(1始まり)
	 */
	// TODO 一ヶ月分のイベント情報を一気に取得することでリクエスト回数を削減
	public int calcSalaryDay(Calendar target) {
		Preconditions.checkNotNull(calendar);
		final Calendar c = (Calendar) target.clone();

		for (int i = DEFAULT_SALARY_DAY; i >= 1; --i) {
			c.set(Calendar.DAY_OF_MONTH, i);
			if (c.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY || c.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY || isHoliday(c.getTime())) {
				// 土日祝日ならパス
				continue;
			}
			return i;
		}

		throw new AssertionError();
	}
}

3.コードを書く

Calendar today;
// ...
if (today.get(Calendar.DAY_OF_MONTH) == new CalendarUtil().calcSalaryDay(today)) {
	System.out.println("今日は給料日です!");
}