Tuesday, December 21, 2004

Implementing a Frequency date time generator in java

The java calendar class does a lot of good things. As far as adding days to a date is concerened it has three methods.

set(f, value) : sets the field f which could be weekdday, month, year,etc. to the value specified.

add(f, delta) : adds detla to the field f which could be weekdday, month, year,etc.

roll(f, delta) : adds detla to the field f which could be weekdday, month, year,etc whichout changing the larger fields. For exmaple using roll to add 10 to the month value of Dec 31 2004 will roll to OCt 31 2004 as year being the larger will be the same.

For more details on these methods look at http://java.sun.com/j2se/1.4.2/docs/api/java/util/Calendar.html.

The objective of this post to to reconcile the calendar class with the computation of custom frequencies like daily, weekly,monthing, biweekly etc..

I have written a class that uses the calendar to compute custom frequencies in a manner that helps the user get the next date, if he just supplies his current date and the frequency.

Beware ! Reading this class is not easy at all, however it can be very usefull.



import java.sql.Timestamp;
import java.util.Calendar;

/**
* This class generates a date/time based on a frequency.
* It accepts a range of frequencies, like Hourly, Monthy, Weekly,
* Yearly,BiWeekly, SemiMonthy,Quarterly and LastDayOfMonth and
* based on the frequency generates the next run date.
*
* @author suchakj
*/
public class RunFreqGenerator {

/**
* This interface holds the string values for all frequencies.
* The frequnecies are Hourly, Monthy, Weekly, Yearly,BiWeekly, SemiMonthy,
* Quarterly and LastDayOfMonth.
*
*/
public interface RunFreqInterface {
public static final String RUN_FREQ_HOURLY = "H";

public static final String RUN_FREQ_DAILY = "D";

public static final String RUN_FREQ_MONTHLY = "M";

public static final String RUN_FREQ_WEEKLY = "W";

public static final String RUN_FREQ_YEARLY = "Y";

public static final String RUN_FREQ_BIWEEKLY = "B";

public static final String RUN_FREQ_SEMIMONTHLY = "S";

public static final String RUN_FREQ_QUARTERLY = "Q";

public static final String RUN_FREQ_HALFYEARLY = "L";

public static final String LAST_DAY_OF_MONTH = "LD";
}

/**
* Every frequency needs a certain set of parameters to determine the next
* run date.
*
* This class holds the frequency related parameters based on the frequency
* type.
*
*/
public class DateFreqHolder {

// The date for the next run.
private Timestamp nextDate;

// A frequency based on the RunFreqInterface attributes.
private String freq;

/*
* The day of the run i.e Monday, Wednesday etc. It is eighter the full
* name or the first three letters of the day name.
*/
private String run_day;

/*
* The numerical day of the month on which the the task is run for the
* first time.
*/
private String first_run_date;

/*
* The month of the run i.e January,February etc. It is eighter the full
* name or the first three letters of the month name.
*/
private String run_month;

/*
* The numerical day of the month on which the task is run for a
* second time.
*/
private String second_run_date;

public String getFirst_run_date() {
return first_run_date;
}

public void setFirst_run_date(String first_run_date) {
this.first_run_date = first_run_date;
}

public String getRun_month() {
return run_month;
}

public void setRun_month(String run_month) {
this.run_month = run_month;
}

public String getRun_day() {
return run_day;
}

public void setRun_day(String run_day) {
this.run_day = run_day;
}

public String getSecond_run_date() {
return second_run_date;
}

public void setSecond_run_date(String second_run_date) {
this.second_run_date = second_run_date;
}

public String getFreq() {
return freq;
}

public Timestamp getNextDate() {
return nextDate;
}

public void setFreq(String freq) {
this.freq = freq;
}

public void setNextDate(Timestamp nextDate) {
this.nextDate = nextDate;
}
}

/*
* Gets the next date based on the data in the DateFreqHolder
*
*/
public static Timestamp getNextDate(DateFreqHolder holder) throws Exception {

Timestamp previousRunDate = holder.getNextDate();
if (previousRunDate == null) {
throwNullVarException("nextRundate");
}
String runFreq = holder.getFreq();
if (runFreq == null) {
throwNullVarException("run freq");
}
Calendar cal = Calendar.getInstance();
cal.setTime(previousRunDate);

if (RunFreqInterface.RUN_FREQ_HOURLY.equals(runFreq)) {
return addHours(cal, 1);
}

if (RunFreqInterface.RUN_FREQ_DAILY.equals(runFreq)) {
return addHours(cal, 24);
}

if (RunFreqInterface.RUN_FREQ_WEEKLY.equals(runFreq)) {
String runDay = holder.getRun_day();
if (runDay == null) {
throwNullVarForFreqException("run day", RunFreqInterface.RUN_FREQ_WEEKLY);
}

return addWeeks(cal, cal.get(Calendar.DAY_OF_WEEK),
getWeekday(runDay), 1);
}

if (RunFreqInterface.RUN_FREQ_MONTHLY.equals(runFreq)) {
String runDate = holder.getFirst_run_date();
if (runDate == null) {
throwNullVarForFreqException("run date", RunFreqInterface.RUN_FREQ_MONTHLY);
}
return addMonth(cal, runDate);
}

if (RunFreqInterface.RUN_FREQ_QUARTERLY.equals(runFreq)) {
String runDate = holder.getFirst_run_date();
if (runDate == null) {
throwNullVarForFreqException("run date", RunFreqInterface.RUN_FREQ_QUARTERLY);
}
return addQuarter(cal, cal.get(Calendar.MONTH), runDate);
}

if (RunFreqInterface.RUN_FREQ_YEARLY.equals(runFreq)) {
String runDate = holder.getFirst_run_date();
String runMonth = holder.getRun_month();
if (runDate == null) {
throwNullVarForFreqException("run date", RunFreqInterface.RUN_FREQ_YEARLY);
}
if (runMonth == null) {
throwNullVarForFreqException("run month", RunFreqInterface.RUN_FREQ_YEARLY);
}
return addMonths(cal, cal.get(Calendar.MONTH), getMonth(runMonth),
runDate, 12);
}

if (RunFreqInterface.RUN_FREQ_BIWEEKLY.equals(runFreq)) {
String runDay = holder.getRun_day();
if (runDay == null) {
throwNullVarForFreqException("run day", RunFreqInterface.RUN_FREQ_BIWEEKLY);
}
return addWeeks(cal, cal.get(Calendar.DAY_OF_WEEK),
getWeekday(runDay), 2);
}

if (RunFreqInterface.RUN_FREQ_SEMIMONTHLY.equals(runFreq)) {
String runDateString = holder.getFirst_run_date();
String secondRunDateString = holder.getSecond_run_date();
if (runDateString == null) {
throwNullVarForFreqException("run date",
RunFreqInterface.RUN_FREQ_SEMIMONTHLY);
}
if (secondRunDateString == null) {
throwNullVarForFreqException("second run date",
RunFreqInterface.RUN_FREQ_SEMIMONTHLY);
}
if (RunFreqInterface.LAST_DAY_OF_MONTH.equals(runDateString)) {
throw new Exception("DateFreqHolder cannot have "
+ "first rundate as last day of the month ::"
+ RunFreqInterface.RUN_FREQ_SEMIMONTHLY);
}
int runDate = (new Integer(runDateString)).intValue();
int secondRunDate = 0;

if (RunFreqInterface.LAST_DAY_OF_MONTH.equals(secondRunDateString)) {
secondRunDate = 31;
} else {
secondRunDate = (new Integer(secondRunDateString)).intValue();
}
if (secondRunDate > runDate) {
throw new Exception("DateFreqHolder cannot have "
+ "first rundate less than the second run date "
+ "for task type ::: "
+ RunFreqInterface.RUN_FREQ_SEMIMONTHLY);
}

int previousRunDateInt = cal.get(Calendar.DAY_OF_MONTH);

if (previousRunDateInt < runDate) {
cal.set(Calendar.DAY_OF_MONTH, runDate);
return new Timestamp(cal.getTime().getTime());
} else if ((previousRunDateInt >= runDate)
& (previousRunDateInt > secondRunDate)) {
cal.set(Calendar.DAY_OF_MONTH, secondRunDate);
return new Timestamp(cal.getTime().getTime());
} else if (previousRunDateInt >= secondRunDate) {
return addMonth(cal, runDateString);
}
}

throw new Exception("DateFreqHolder does not have a proper fequency ::");
}

/*
* This method returns the int value of the month based on the string
* supplied. Please note valid month strings are the full month name or the
* first three letters of the month name. For example January, February or
* Jan, Feb.
*
* This method is not case sensitive.
*
*/
private static int getMonth(String month) throws Exception {
if ("JAN".equalsIgnoreCase(month) || "JANUARY".equalsIgnoreCase(month)) {
return Calendar.JANUARY;
}
if ("FEB".equalsIgnoreCase(month) || "FEBRUARY".equalsIgnoreCase(month)) {
return Calendar.FEBRUARY;
}
if ("MAR".equalsIgnoreCase(month) || "MARCH".equalsIgnoreCase(month)) {
return Calendar.MARCH;
}
if ("APR".equalsIgnoreCase(month) || "APRIL".equalsIgnoreCase(month)) {
return Calendar.APRIL;
}
if ("MAY".equalsIgnoreCase(month)) {
return Calendar.MAY;
}
if ("JUN".equalsIgnoreCase(month) || "JUNE".equalsIgnoreCase(month)) {
return Calendar.JUNE;
}
if ("JUL".equalsIgnoreCase(month) || "JULY".equalsIgnoreCase(month)) {
return Calendar.JULY;
}
if ("AUG".equalsIgnoreCase(month) || "AUGUST".equalsIgnoreCase(month)) {
return Calendar.AUGUST;
}
if ("SEP".equalsIgnoreCase(month)
|| "SEPTEMBER".equalsIgnoreCase(month)) {
return Calendar.SEPTEMBER;
}
if ("OCT".equalsIgnoreCase(month) || "OCTOBER".equalsIgnoreCase(month)) {
return Calendar.OCTOBER;
}
if ("NOV".equalsIgnoreCase(month) || "NOVEMBER".equalsIgnoreCase(month)) {
return Calendar.NOVEMBER;
}
if ("DEC".equalsIgnoreCase(month) || "DECEMBER".equalsIgnoreCase(month)) {
return Calendar.DECEMBER;
}
throw new Exception("Month cannot be :: " + month);
}

/*
* This method returns the int value of the week day based on the string
* supplied. Please note valid week day strings are the full week day name
* or the first three letters of the week day name. For example Mondya,
* Tuesday or Mon, Tue.
*
* This method is not case sensitive.
*
*/
private static int getWeekday(String weekday) throws Exception {
if ("SUN".equalsIgnoreCase(weekday)
|| "SUNDAY".equalsIgnoreCase(weekday)) {
return Calendar.SUNDAY;
}
if ("MON".equalsIgnoreCase(weekday)
|| "MONDAY".equalsIgnoreCase(weekday)) {
return Calendar.MONDAY;
}
if ("TUE".equalsIgnoreCase(weekday)
|| "TUESDAY".equalsIgnoreCase(weekday)) {
return Calendar.TUESDAY;
}
if ("WED".equalsIgnoreCase(weekday)
|| "WEDNESDAY".equalsIgnoreCase(weekday)) {
return Calendar.WEDNESDAY;
}
if ("THU".equalsIgnoreCase(weekday)
|| "THURSDAY".equalsIgnoreCase(weekday)) {
return Calendar.THURSDAY;
}
if ("FRI".equalsIgnoreCase(weekday)
|| "FRIDAY".equalsIgnoreCase(weekday)) {
return Calendar.FRIDAY;
}
if ("SAT".equalsIgnoreCase(weekday)
|| "SATURDAY".equalsIgnoreCase(weekday)) {
return Calendar.SATURDAY;
}
throw new Exception("Day of the week cannot be :: " + weekday);
}

/*
* Throws exception stating that a particular DateFreqHolder variable cannot
* be null for a particular frequency
*
*/
private static void throwNullVarForFreqException(String var, String freq)
throws Exception {
throw new Exception("DateFreqHolder cannot have a null :: " + var
+ " when freq is :: " + freq);

}

/*
* Throws exception stating that a particular variable cannot be null.
*
*/
private static void throwNullVarException(String var) throws Exception {
throw new Exception("DateFreqHolder cannot have a null :: " + var);
}

/*
* Add a month to the supplied calender. After that set the date to the
* runDate(day of the month) supplied.
*
*/
private static Timestamp addMonth(Calendar cal, String runDate)
throws Exception {
return addMonths(cal, 1, runDate);
}

/*
* Add months to the supplied calender as per noOfMonths. After that set
* the date to the runDate(day of the month) supplied.
*
*/
private static Timestamp addMonths(Calendar cal, int noOfMonths,
String runDate) {
cal.add(Calendar.MONTH, noOfMonths);
if (RunFreqInterface.LAST_DAY_OF_MONTH.equals(runDate)
|| ((new Integer(runDate)).intValue() > cal
.getActualMaximum(Calendar.DAY_OF_MONTH))) {
cal.set(Calendar.DAY_OF_MONTH, cal
.getActualMaximum(Calendar.DAY_OF_MONTH));
} else {
cal.set(Calendar.DAY_OF_MONTH, (new Integer(runDate)).intValue());
}
return new Timestamp(cal.getTime().getTime());
}

/*
* This method is used for yearly tasks.
*
* It add months to the supplied calender as per noOfMonths which will be 12
* for a yearly task. After that it sets the month to the runMonth(month of
* the year) supplied and the runDate(day of the month) to the supplied
* runDate.
*
*/
private static Timestamp addMonths(Calendar cal, int previousRunMonth,
int runMonth, String runDate, int noOfMonths) throws Exception {
if (previousRunMonth == runMonth) {
cal.add(Calendar.MONTH, noOfMonths);
return new Timestamp(cal.getTime().getTime());
} else {
if (runMonth > previousRunMonth) {
return addMonths(cal, runMonth - previousRunMonth, runDate);
} else {
return addMonths(cal, noOfMonths
- (previousRunMonth - runMonth), runDate);
}
}
}

/*
* This method is used for quarterly tasks.
*
* It add 3 months to the supplied calender. After that it sets the
* the runDate(day of the month) to the supplied runDate.
*
*/
private static Timestamp addQuarter(Calendar cal, int previousRunMonth,
String runDate) throws Exception {
if (isQuarterMonth(previousRunMonth)) {
return addMonths(cal, 3, runDate);
} else {
return addMonths(cal,
getNoOFMonthsToNextQuarterMonth(previousRunMonth), runDate);
}
}

/*
* This method is used for quarterly tasks.
*
* It returns an int value that is used to add up the months
* for a quarter.
*
* The reason this method is needed is that all quarters are
* fixed i.e. March, June, September and December
*
* For example a date is in october, so the number of months for it
* to reach the next quarter is 2 months. How ever if a date is in november
* the number of months for it to reach the next quarter is 1 month.
*
*/
private static int getNoOFMonthsToNextQuarterMonth(int previousRunMonth)
throws Exception {
if (previousRunMonth > 12) {
throw new Exception("Month cannot be :: " + previousRunMonth);
}
if (Calendar.MARCH >= previousRunMonth) {
return Calendar.MARCH - previousRunMonth;
} else if (Calendar.JUNE >= previousRunMonth) {
return Calendar.JUNE - previousRunMonth;
} else if (Calendar.SEPTEMBER >= previousRunMonth) {
return Calendar.SEPTEMBER - previousRunMonth;
} else if (Calendar.DECEMBER >= previousRunMonth) {
return Calendar.DECEMBER - previousRunMonth;
}
return 0;
}

/*
* This method checks wether the previousRunMonth supplied is a quarter
* month.
*/
private static boolean isQuarterMonth(int previousRunMonth) {
if (Calendar.MARCH == previousRunMonth
|| Calendar.JUNE == previousRunMonth
|| Calendar.SEPTEMBER == previousRunMonth
|| Calendar.DECEMBER == previousRunMonth) {
return true;
}
return false;
}

/*
* This method adds weeks to a given calendar. Also it sets the run
* day(day of the month) as per the supplied run day.
*
*/
private static Timestamp addWeeks(Calendar cal,
int previousRunDateDayOfTheWeek, int runDay, int noOfWeeks) {
if (previousRunDateDayOfTheWeek == runDay) {
cal.add(Calendar.WEEK_OF_YEAR, noOfWeeks);
} else {
if (runDay > previousRunDateDayOfTheWeek) {
cal.add(Calendar.DAY_OF_WEEK, runDay
- previousRunDateDayOfTheWeek);
} else {
cal.add(Calendar.DAY_OF_WEEK,
7 - (previousRunDateDayOfTheWeek - runDay));
}
}
return new Timestamp(cal.getTime().getTime());
}

/*
* This method adds hours to a given calendar based on the integer value
* supplied.
*
*/
private static Timestamp addHours(Calendar cal, int i) {
cal.add(Calendar.HOUR_OF_DAY, 1);
return new Timestamp(cal.getTime().getTime());
}

/*
* This is the main test controller method
*/
public static void main(String[] s) {
try {
RunFreqGenerator.DateFreqHolder holder = (new RunFreqGenerator()).new DateFreqHolder();
holder.setNextDate(new Timestamp(System.currentTimeMillis()));
testHourAdd(holder);
testDayAdd(holder);
testWeekAdd(holder);
holder.setFirst_run_date("28");
holder.setSecond_run_date("29");
testMonthAdd(holder);
testSemiMonthlyAdd(holder);
testQuarterAdd(holder);
testYearAdd(holder);

// test for the last day of the month.
Calendar c = Calendar.getInstance();
c.set(Calendar.MONTH, Calendar.JANUARY);
c.set(Calendar.DAY_OF_MONTH, 31);
holder.setFirst_run_date("1");
holder.setNextDate(new Timestamp(c.getTime().getTime()));

testMonthAdd(holder);
testSemiMonthlyAdd(holder);
testQuarterAdd(holder);
testYearAdd(holder);

Calendar c1 = Calendar.getInstance();
c1.set(Calendar.MONTH, Calendar.FEBRUARY);
c1.set(Calendar.DAY_OF_MONTH, 29);
holder.setFirst_run_date("31");
holder.setNextDate(new Timestamp(c1.getTime().getTime()));

testMonthAdd(holder);
testSemiMonthlyAdd(holder);
testQuarterAdd(holder);
testYearAdd(holder);

Calendar c3 = Calendar.getInstance();
c3.set(Calendar.MONTH, Calendar.APRIL);
c3.set(Calendar.DAY_OF_MONTH, 30);
holder.setFirst_run_date("LD");
holder.setNextDate(new Timestamp(c3.getTime().getTime()));

testMonthAdd(holder);
testSemiMonthlyAdd(holder);
testQuarterAdd(holder);
testYearAdd(holder);

Calendar c4 = Calendar.getInstance();
c4.set(Calendar.MONTH, Calendar.FEBRUARY);
c4.set(Calendar.DAY_OF_MONTH, 29);
holder.setFirst_run_date("29");
holder.setNextDate(new Timestamp(c4.getTime().getTime()));
testMonthAdd(holder);
testSemiMonthlyAdd(holder);
testQuarterAdd(holder);
testYearAdd(holder);
} catch (Exception e) {
e.printStackTrace();
}

}

// the mothod names below are are self explanatory.
private static void testYear(DateFreqHolder holder, String month)
throws Exception {
System.out.println("testing Year add ::::: month :: " + month
+ " runday :: " + holder.getFirst_run_date());
holder.setFreq(RunFreqInterface.RUN_FREQ_YEARLY);
holder.setRun_month(month);
System.out.println("Previous Run date :: " + holder.getNextDate());
System.out.println("Next Run date " + month + " :: "
+ getNextDate(holder));

}

private static void testHourAdd(DateFreqHolder holder) throws Exception {
System.out.println("Testing hour add");
holder.setFreq(RunFreqInterface.RUN_FREQ_HOURLY);
System.out.println("Previous Run date :: " + holder.getNextDate());
System.out.println("Next Run date :: " + getNextDate(holder));
}

private static void testDayAdd(DateFreqHolder holder) throws Exception {
System.out.println("Testing Day add");
holder.setFreq(RunFreqInterface.RUN_FREQ_DAILY);
System.out.println("Previous Run date :: " + holder.getNextDate());
System.out.println("Next Run date :: " + getNextDate(holder));
}

private static void testWeekAdd(DateFreqHolder holder) throws Exception {

testWeekDay(holder, "SUN");
testWeekDay(holder, "MON");
testWeekDay(holder, "TUE");
testWeekDay(holder, "WED");
testWeekDay(holder, "THU");
testWeekDay(holder, "FRI");
testWeekDay(holder, "SAT");
}

private static void testWeekDay(DateFreqHolder holder, String weekDay)
throws Exception {
System.out.println("Testing Week add ::::: " + weekDay);
holder.setFreq(RunFreqInterface.RUN_FREQ_WEEKLY);
holder.setRun_day(weekDay);
System.out.println("Previous Run date :: " + holder.getNextDate());
System.out.println("Next Run date " + weekDay + " :: "
+ getNextDate(holder));
}

private static void testMonthAdd(DateFreqHolder holder) throws Exception {
System.out.println("testing month add ::::: month :: " + " runday :: "
+ holder.getFirst_run_date());
holder.setFreq(RunFreqInterface.RUN_FREQ_MONTHLY);
// holder.setRun_month(month);
System.out.println("Previous Run date :: " + holder.getNextDate());
System.out.println("Next Run date " + " :: " + getNextDate(holder));

System.out.println();
System.out.println();
System.out.println();
}

private static void testQuarterAdd(DateFreqHolder holder) throws Exception {
System.out.println("testing Quarter add ::::: " + " runday :: "
+ holder.getFirst_run_date());
holder.setFreq(RunFreqInterface.RUN_FREQ_QUARTERLY);
// holder.setRun_month(month);
System.out.println("Previous Run date :: " + holder.getNextDate());
System.out.println("Next Run date " + " :: " + getNextDate(holder));

System.out.println();
System.out.println();
System.out.println();
}

private static void testSemiMonthlyAdd(DateFreqHolder holder)
throws Exception {
System.out.println("testing Semi Monthly add ::::: "
+ " first runday :: " + holder.getFirst_run_date());
System.out.println("testing Semi Monthly add ::::: "
+ " second runday :: " + holder.getSecond_run_date());
holder.setFreq(RunFreqInterface.RUN_FREQ_SEMIMONTHLY);
// holder.setRun_month(month);
System.out.println("Previous Run date :: " + holder.getNextDate());
System.out.println("Next Run date " + " :: " + getNextDate(holder));

}

private static void testYearAdd(DateFreqHolder holder) throws Exception {
testYear(holder, "JAN");
testYear(holder, "FEB");
testYear(holder, "MAR");
testYear(holder, "APR");
testYear(holder, "MAY");
testYear(holder, "JUN");
testYear(holder, "JUL");
testYear(holder, "AUG");
testYear(holder, "SEP");
testYear(holder, "OCT");
testYear(holder, "NOV");
testYear(holder, "DEC");
System.out.println();
System.out.println();
System.out.println();
}

}

0 Comments:

Post a Comment

<< Home