/*
* Copyright 2001-2009 Terracotta, Inc.
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/
package com.ullink.rxscheduler.cron.calendar;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.TimeZone;
/**
* <p>
* This implementation of the Calendar excludes a set of days of the year. You may use it to exclude bank holidays which are on the same date every year.
* </p>
* @see com.ullink.rxscheduler.cron.calendar.Calendar
* @see com.ullink.rxscheduler.cron.calendar.BaseCalendar
* @author Juergen Donnerstag
*/
public class AnnualCalendar extends BaseCalendar implements Calendar
{
static final long serialVersionUID = 7346867105876610961L;
private ArrayList<java.util.Calendar> excludeDays = new ArrayList<java.util.Calendar>();
// true, if excludeDays is sorted
private boolean dataSorted = false;
public AnnualCalendar()
{
}
public AnnualCalendar(Calendar baseCalendar)
{
super(baseCalendar);
}
public AnnualCalendar(TimeZone timeZone)
{
super(timeZone);
}
public AnnualCalendar(Calendar baseCalendar, TimeZone timeZone)
{
super(baseCalendar, timeZone);
}
@Override
public Object clone()
{
AnnualCalendar clone = (AnnualCalendar) super.clone();
clone.excludeDays = new ArrayList<java.util.Calendar>(excludeDays);
return clone;
}
/**
* <p>
* Get the array which defines the exclude-value of each day of month
* </p>
*/
public ArrayList<java.util.Calendar> getDaysExcluded()
{
return excludeDays;
}
/**
* <p>
* Return true, if day is defined to be exluded.
* </p>
*/
public boolean isDayExcluded(java.util.Calendar day)
{
if (day == null)
{
throw new IllegalArgumentException("Parameter day must not be null");
}
// Check baseCalendar first
if (!super.isTimeIncluded(day.getTime().getTime()))
{
return true;
}
int dmonth = day.get(java.util.Calendar.MONTH);
int dday = day.get(java.util.Calendar.DAY_OF_MONTH);
if (dataSorted == false)
{
Collections.sort(excludeDays, new CalendarComparator());
dataSorted = true;
}
Iterator<java.util.Calendar> iter = excludeDays.iterator();
while (iter.hasNext())
{
java.util.Calendar cl = (java.util.Calendar) iter.next();
// remember, the list is sorted
if (dmonth < cl.get(java.util.Calendar.MONTH))
{
return false;
}
if (dday != cl.get(java.util.Calendar.DAY_OF_MONTH))
{
continue;
}
if (dmonth != cl.get(java.util.Calendar.MONTH))
{
continue;
}
return true;
}
return false;
}
/**
* <p>
* Redefine the list of days excluded. The ArrayList should contain <code>java.util.Calendar</code> objects.
* </p>
*/
public void setDaysExcluded(ArrayList<java.util.Calendar> days)
{
if (days == null)
{
excludeDays = new ArrayList<java.util.Calendar>();
}
else
{
excludeDays = days;
}
dataSorted = false;
}
/**
* <p>
* Redefine a certain day to be excluded (true) or included (false).
* </p>
*/
public void setDayExcluded(java.util.Calendar day, boolean exclude)
{
if (exclude)
{
if (isDayExcluded(day))
{
return;
}
excludeDays.add(day);
dataSorted = false;
}
else
{
if (!isDayExcluded(day))
{
return;
}
removeExcludedDay(day, true);
}
}
/**
* Remove the given day from the list of excluded days
* @param day the day to exclude
*/
public void removeExcludedDay(java.util.Calendar day)
{
removeExcludedDay(day, false);
}
private void removeExcludedDay(java.util.Calendar day, boolean isChecked)
{
if (!isChecked && !isDayExcluded(day))
{
return;
}
// Fast way, see if exact day object was already in list
if (this.excludeDays.remove(day))
{
return;
}
int dmonth = day.get(java.util.Calendar.MONTH);
int dday = day.get(java.util.Calendar.DAY_OF_MONTH);
// Since there is no guarantee that the given day is in the arraylist with the exact same year
// search for the object based on month and day of month in the list and remove it
Iterator<java.util.Calendar> iter = excludeDays.iterator();
while (iter.hasNext())
{
java.util.Calendar cl = (java.util.Calendar) iter.next();
if (dmonth != cl.get(java.util.Calendar.MONTH))
{
continue;
}
if (dday != cl.get(java.util.Calendar.DAY_OF_MONTH))
{
continue;
}
day = cl;
break;
}
this.excludeDays.remove(day);
}
/**
* <p>
* Determine whether the given time (in milliseconds) is 'included' by the Calendar.
* </p>
* <p>
* Note that this Calendar is only has full-day precision.
* </p>
*/
@Override
public boolean isTimeIncluded(long timeStamp)
{
// Test the base calendar first. Only if the base calendar not already
// excludes the time/date, continue evaluating this calendar instance.
if (super.isTimeIncluded(timeStamp) == false)
{
return false;
}
java.util.Calendar day = createJavaCalendar(timeStamp);
return !(isDayExcluded(day));
}
/**
* <p>
* Determine the next time (in milliseconds) that is 'included' by the Calendar after the given time. Return the original value if timeStamp is included. Return 0 if all days are excluded.
* </p>
* <p>
* Note that this Calendar is only has full-day precision.
* </p>
*/
@Override
public long getNextIncludedTime(long timeStamp)
{
// Call base calendar implementation first
long baseTime = super.getNextIncludedTime(timeStamp);
if ((baseTime > 0) && (baseTime > timeStamp))
{
timeStamp = baseTime;
}
// Get timestamp for 00:00:00
java.util.Calendar day = getStartOfDayJavaCalendar(timeStamp);
if (isDayExcluded(day) == false)
{
return timeStamp; // return the original value
}
while (isDayExcluded(day) == true)
{
day.add(java.util.Calendar.DATE, 1);
}
return day.getTime().getTime();
}
@Override
public long getPreviousIncludedTime(long timeStamp)
{
// Call base calendar implementation first
long baseTime = super.getPreviousIncludedTime(timeStamp);
if ((baseTime > 0) && (baseTime > timeStamp))
{
timeStamp = baseTime;
}
// Get timestamp for 00:00:00
java.util.Calendar day = getEndOfDayJavaCalendar(timeStamp);
if (isDayExcluded(day) == false)
{
return timeStamp; // return the original value
}
while (isDayExcluded(day) == true)
{
day.add(java.util.Calendar.DATE, -1);
}
return day.getTime().getTime();
}
}
class CalendarComparator implements Comparator<java.util.Calendar>, Serializable
{
private static final long serialVersionUID = 7346867105876610961L;
public CalendarComparator()
{
}
public int compare(java.util.Calendar c1, java.util.Calendar c2)
{
int month1 = c1.get(java.util.Calendar.MONTH);
int month2 = c2.get(java.util.Calendar.MONTH);
int day1 = c1.get(java.util.Calendar.DAY_OF_MONTH);
int day2 = c2.get(java.util.Calendar.DAY_OF_MONTH);
if (month1 < month2)
{
return -1;
}
if (month1 > month2)
{
return 1;
}
if (day1 < day2)
{
return -1;
}
if (day1 > day2)
{
return 1;
}
return 0;
}
}