日付の範囲クラスの作成(精度指定付き)-1

先日作ったRangeクラスをもとにとりあえず作ってみる。DateRangeのほうが良かったけどCalendarRangeにしてしまいまんた。

package sample;

import java.util.Calendar;
import java.util.Date;

public class CalendarRange extends Range<Calendar> {
	
	public static enum Scale {
		 YEAR
		,MONTH
		,DAY
		,HOUR
		,MINUTE
		,SECOND
		,MILLISECOND;
	}
	
	private final Scale scale;
	
	public CalendarRange(Calendar minVal, boolean containsMin, 
			Calendar maxVal, boolean containsMax, Scale scale) {
		
		super(adjustScale(minVal, scale), containsMin, 
				adjustScale(maxVal, scale), containsMax);
		this.scale = scale;
	}

	@Override
	public boolean contains(Calendar argValue) {		
		Calendar val = adjustScale(argValue, scale);
		
		return super.contains(val);
	}
	
	public boolean contains(Date date) {
		if(date == null) 
			throw new NullPointerException("Argument must not be null.");
		Calendar cal = Calendar.getInstance();
		cal.setTime(date);
		return contains(cal);
	}
	
	/**
	 * @param original
	 * @param scale スケール。null不可
	 * @return 引数originalのクローン。scaleに合わせて時刻調整済み。
	 * 引数originalがnullの場合はnull
	 */
	private static Calendar adjustScale(Calendar original, Scale scale) {
		if(scale == null) 
			throw new NullPointerException("scale must not be null.");
		if(original == null) 
			return null;
		
		Calendar cal = (Calendar) original.clone();
		switch(scale) {
			// breakなしcaseで下に落とす(fall through)
			case YEAR:
				cal.set(Calendar.MONTH, 0);
			case MONTH:
				cal.set(Calendar.DAY_OF_MONTH, 1);
			case DAY:
				cal.set(Calendar.HOUR_OF_DAY, 0);
			case HOUR:
				cal.set(Calendar.MINUTE, 0);
			case MINUTE:
				cal.set(Calendar.SECOND, 0);
			case SECOND:
				cal.set(Calendar.MILLISECOND, 0);
			case MILLISECOND:
				// no-scale-adjust
		}
		return cal;
	}

}


使い方はこんな感じで↓

	public void test_MonthScale() throws ParseException {
		Calendar min = getCal("2000-01-03 22:34:56.789");
		Calendar max = getCal("2000-02-29 23:59:59.999");
		
		// min <= cal <= max 精度:月
		CalendarRange range = new CalendarRange(min, true, max, true, CalendarRange.Scale.MONTH);
		assertFalse(range.contains(getCal("1999-12-31 23:59:59.999")));
		assertTrue (range.contains(getCal("2000-01-01 00:00:00.000")));
		assertTrue (range.contains(getCal("2000-02-29 23:59:59.999")));
		assertFalse(range.contains(getCal("2000-03-01 00:00:00.000")));
	}
	
	public void test_HourScale() throws ParseException {
		Calendar min = getCal("2000-01-01 22:34:56.789");
		Calendar max = getCal("2000-01-02 01:34:56.789");
		
		// min <= cal <= max 精度:時
		CalendarRange range = new CalendarRange(min, true, max, true, CalendarRange.Scale.HOUR);
		assertFalse(range.contains(getCal("2000-01-01 21:59:59.999")));
		assertTrue (range.contains(getCal("2000-01-01 22:00:00.000")));
		assertTrue (range.contains(getCal("2000-01-02 01:59:59.999")));
		assertFalse(range.contains(getCal("2000-01-02 02:00:00.000")));
	}

	
	public void test_MilliSecondScale() throws ParseException {
		Calendar min = getCal("2000-01-01 22:34:56.789");
		Calendar max = getCal("2000-01-01 22:34:56.802");
		
		// min <= cal <= max 精度:ミリ秒
		CalendarRange range = new CalendarRange(min, true, max, true, CalendarRange.Scale.MILLISECOND);
		assertFalse(range.contains(getCal("2000-01-01 22:34:56.788")));
		assertTrue (range.contains(getCal("2000-01-01 22:34:56.789")));
		assertTrue (range.contains(getCal("2000-01-01 22:34:56.802")));
		assertFalse(range.contains(getCal("2000-01-01 22:34:56.803")));

		// min <= cal < max 精度:ミリ秒
		range = new CalendarRange(min, true, max, false, CalendarRange.Scale.MILLISECOND);
		assertFalse(range.contains(getCal("2000-01-01 22:34:56.788")));
		assertTrue (range.contains(getCal("2000-01-01 22:34:56.789")));
		assertTrue (range.contains(getCal("2000-01-01 22:34:56.801")));
		assertFalse(range.contains(getCal("2000-01-01 22:34:56.802")));
		// というかミリ秒の場合は特に精度調整いらないけど
	}

	
	private static Calendar getCal(String str) throws ParseException {
		Calendar cal = Calendar.getInstance();
		cal.setTime(getDate(str));
		return cal;
	}
	
	private static final DateFormat df = 
		new SimpleDateFormat("yyyy-MM-dd hh:mm:ss.S");

	private static Date getDate(String str) throws ParseException {
		return df.parse(str);
	}
	

スケール指定したクローンを返す adjustScale() 以外、前のソースと特に変わりなし。そしてスケール修正部分はバグを誘発しそうなbreakなしswitch-case構文*1。この部分はEnumを使ってforで回せばswitch-caseを使わないで書けそう。明日あたり書いてみる。

・Calendar#clear(int scale) というのがあるのだけど、時(hour)部分はクリアできないらしいので使用を断念。

・アップ直後に cal.set(Calendar.DAY_OF_MONTH, 0); という誤りを発見。日にちだけ1起算なのがまぎらわしい。

*1:breakなしで処理を下に落とすのをfall throughというらしい