Skip to content

Instantly share code, notes, and snippets.

@bdkosher
Created March 7, 2014 16:30
Show Gist options
  • Star 4 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save bdkosher/9414748 to your computer and use it in GitHub Desktop.
Save bdkosher/9414748 to your computer and use it in GitHub Desktop.
Java class for determining the dates of U.S. Federal Holidays, along with Spock test.
package bdkosher.datetime;
import java.util.Calendar;
import static java.util.Calendar.*;
import java.util.Date;
import java.util.Locale;
import java.util.SortedSet;
import java.util.TimeZone;
import java.util.TreeSet;
/**
* For a holiday that falls on Saturday, the Federal holiday will be observed the previous Friday. If it falls on Sunday, it will
* be observed the following Monday.
*/
public class FederalHolidays {
/**
* The list of Federal Observances, as per section 6103(a) of title 5 of the United States Code.
*
* @see http://www.law.cornell.edu/uscode/text/5/6103
*/
public enum Observance {
/**
* January 1st.
*/
NEW_YEARS_DAY(JANUARY, 1),
/**
* Third Monday in January.
*/
BIRTHDAY_OF_MARTIN_LUTHER_KING_JR(JANUARY, MONDAY, 3),
/**
* Third Monday in February.
*/
WASHINGTONS_BIRTHDAY(FEBRUARY, MONDAY, 3),
/**
* Last Monday in May.
*/
MEMORIAL_DAY(MAY, MONDAY, -1),
/**
* July 4th.
*/
INDEPENDENCE_DAY(JULY, 4),
/**
* First Monday in September.
*/
LABOR_DAY(SEPTEMBER, MONDAY, 1),
/**
* Second Monday in October.
*/
COLUMBUS_DAY(OCTOBER, MONDAY, 2),
/**
* November 11th.
*/
VETERANS_DAY(NOVEMBER, 11),
/**
* Fourth Thursday in November.
*/
THANKSGIVING_DAY(NOVEMBER, THURSDAY, 4),
/**
* December 25th.
*/
CHIRSTMAS_DAY(DECEMBER, 25);
private final int month;
private final int dayOfMonth;
private final int dayOfWeek;
private final int weekOfMonth;
private static final int NA = 0;
private Observance(int month, int dayOfMonth) {
this.month = month;
this.dayOfMonth = dayOfMonth;
this.dayOfWeek = NA;
this.weekOfMonth = NA;
}
private Observance(int month, int dayOfWeek, int weekOfMonth) {
this.month = month;
this.dayOfMonth = NA;
this.dayOfWeek = dayOfWeek;
this.weekOfMonth = weekOfMonth;
}
/**
* Returns true if this observance is a fixed date, e.g. December 25th or January 1st.
* Non-fixed dates are those that are on a particular day of week and week of the month, e.g. 3rd Thursday in November.
*/
boolean isFixedDate() {
return dayOfMonth != NA;
}
}
/**
* Note, it is possible for the New Year's Day observance to be in the year previous to the one provided. For example, New
* Years 2011 was observed on December 31st, 2010.
*
* @param observance
* @param year
* @return
*/
public Date dateOf(Observance observance, int year) {
Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("EST"), Locale.ENGLISH);
cal.set(YEAR, year);
cal.set(MONTH, observance.month);
cal.clear(HOUR);
if (observance.isFixedDate()) {
cal.set(DAY_OF_MONTH, observance.dayOfMonth);
} else {
setNthDayOfWeek(cal, observance.dayOfWeek, observance.weekOfMonth);
}
adjustForWeekendsIfNecessary(cal);
return cal.getTime();
}
/**
* Mutates the given calendar; sets the date so that it corresponds to the nth occurrence of the given day of the week.
*
* @param cal - the calendar to set the date for
* @param dayOfWeek - based on the values of Calendar, e.g. Calendar.MONDAY
* @param n - the occurrences of the dayOfWeek to set. Can be a value of 1 to 5 corresponding to the 1st through 5th
* occurrence. If -1, it means "last" occurrence.
*/
private void setNthDayOfWeek(Calendar cal, int dayOfWeek, int n) {
int week = 0;
int lastDay = cal.getActualMaximum(DAY_OF_MONTH);
// if negative, we loop backwards and count the weeks backwards; if positive, count forward
int startDay = n > 0 ? 1 : lastDay;
int endDay = n > 0 ? lastDay : 1;
int incrementValue = n > 0 ? 1 : -1;
for (int day = startDay; day != endDay; day += incrementValue) {
cal.set(DAY_OF_MONTH, day);
if (cal.get(DAY_OF_WEEK) == dayOfWeek) {
week += incrementValue;
if (week == n) {
return;
}
}
}
}
/**
* If the calendar is on a Saturday, adjust the date back a day. If on Sunday, move it forward one day. If neither, leave
* the date unchanged. See Executive order 11582, February 11, 1971.
*
* @param cal - mutated if a weekend date
*/
private void adjustForWeekendsIfNecessary(Calendar cal) {
int dayOfWeek = cal.get(DAY_OF_WEEK);
cal.add(DAY_OF_MONTH, dayOfWeek == SATURDAY ? -1 : dayOfWeek == SUNDAY ? 1 : 0);
}
}
package bdkosher.datetime
import static java.util.Calendar.*
import spock.lang.Shared
import spock.lang.Specification
import bdkosher.datetime.FederalHolidays.Observance
import static bdkosher.datetime.FederalHolidays.Observance.*
/**
* Test data came from http://www.opm.gov/policy-data-oversight/snow-dismissal-procedures/federal-holidays
*/
class FederalHolidaysSpec extends Specification {
@Shared
def federalHolidays = new FederalHolidays()
def "holidays are correctly calculated"() {
expect:
federalHolidays.dateOf(observance, year).format('yyyy/MM/dd') == date
where:
observance | year | date
NEW_YEARS_DAY | 2011 | '2010/12/31'
BIRTHDAY_OF_MARTIN_LUTHER_KING_JR | 2011 | '2011/01/17'
WASHINGTONS_BIRTHDAY | 2011 | '2011/02/21'
MEMORIAL_DAY | 2011 | '2011/05/30'
INDEPENDENCE_DAY | 2011 | '2011/07/04'
LABOR_DAY | 2011 | '2011/09/05'
COLUMBUS_DAY | 2011 | '2011/10/10'
VETERANS_DAY | 2011 | '2011/11/11'
THANKSGIVING_DAY | 2011 | '2011/11/24'
CHIRSTMAS_DAY | 2011 | '2011/12/26'
NEW_YEARS_DAY | 2012 | '2012/01/02'
BIRTHDAY_OF_MARTIN_LUTHER_KING_JR | 2012 | '2012/01/16'
WASHINGTONS_BIRTHDAY | 2012 | '2012/02/20'
MEMORIAL_DAY | 2012 | '2012/05/28'
INDEPENDENCE_DAY | 2012 | '2012/07/04'
LABOR_DAY | 2012 | '2012/09/03'
COLUMBUS_DAY | 2012 | '2012/10/08'
VETERANS_DAY | 2012 | '2012/11/12'
THANKSGIVING_DAY | 2012 | '2012/11/22'
CHIRSTMAS_DAY | 2012 | '2012/12/25'
NEW_YEARS_DAY | 2013 | '2013/01/01'
BIRTHDAY_OF_MARTIN_LUTHER_KING_JR | 2013 | '2013/01/21'
WASHINGTONS_BIRTHDAY | 2013 | '2013/02/18'
MEMORIAL_DAY | 2013 | '2013/05/27'
INDEPENDENCE_DAY | 2013 | '2013/07/04'
LABOR_DAY | 2013 | '2013/09/02'
COLUMBUS_DAY | 2013 | '2013/10/14'
VETERANS_DAY | 2013 | '2013/11/11'
THANKSGIVING_DAY | 2013 | '2013/11/28'
CHIRSTMAS_DAY | 2013 | '2013/12/25'
NEW_YEARS_DAY | 2014 | '2014/01/01'
BIRTHDAY_OF_MARTIN_LUTHER_KING_JR | 2014 | '2014/01/20'
WASHINGTONS_BIRTHDAY | 2014 | '2014/02/17'
MEMORIAL_DAY | 2014 | '2014/05/26'
INDEPENDENCE_DAY | 2014 | '2014/07/04'
LABOR_DAY | 2014 | '2014/09/01'
COLUMBUS_DAY | 2014 | '2014/10/13'
VETERANS_DAY | 2014 | '2014/11/11'
THANKSGIVING_DAY | 2014 | '2014/11/27'
CHIRSTMAS_DAY | 2014 | '2014/12/25'
NEW_YEARS_DAY | 2015 | '2015/01/01'
BIRTHDAY_OF_MARTIN_LUTHER_KING_JR | 2015 | '2015/01/19'
WASHINGTONS_BIRTHDAY | 2015 | '2015/02/16'
MEMORIAL_DAY | 2015 | '2015/05/25'
INDEPENDENCE_DAY | 2015 | '2015/07/03'
LABOR_DAY | 2015 | '2015/09/07'
COLUMBUS_DAY | 2015 | '2015/10/12'
VETERANS_DAY | 2015 | '2015/11/11'
THANKSGIVING_DAY | 2015 | '2015/11/26'
CHIRSTMAS_DAY | 2015 | '2015/12/25'
NEW_YEARS_DAY | 2016 | '2016/01/01'
BIRTHDAY_OF_MARTIN_LUTHER_KING_JR | 2016 | '2016/01/18'
WASHINGTONS_BIRTHDAY | 2016 | '2016/02/15'
MEMORIAL_DAY | 2016 | '2016/05/30'
INDEPENDENCE_DAY | 2016 | '2016/07/04'
LABOR_DAY | 2016 | '2016/09/05'
COLUMBUS_DAY | 2016 | '2016/10/10'
VETERANS_DAY | 2016 | '2016/11/11'
THANKSGIVING_DAY | 2016 | '2016/11/24'
CHIRSTMAS_DAY | 2016 | '2016/12/26'
NEW_YEARS_DAY | 2017 | '2017/01/02'
BIRTHDAY_OF_MARTIN_LUTHER_KING_JR | 2017 | '2017/01/16'
WASHINGTONS_BIRTHDAY | 2017 | '2017/02/20'
MEMORIAL_DAY | 2017 | '2017/05/29'
INDEPENDENCE_DAY | 2017 | '2017/07/04'
LABOR_DAY | 2017 | '2017/09/04'
COLUMBUS_DAY | 2017 | '2017/10/09'
VETERANS_DAY | 2017 | '2017/11/10'
THANKSGIVING_DAY | 2017 | '2017/11/23'
CHIRSTMAS_DAY | 2017 | '2017/12/25'
NEW_YEARS_DAY | 2018 | '2018/01/01'
BIRTHDAY_OF_MARTIN_LUTHER_KING_JR | 2018 | '2018/01/15'
WASHINGTONS_BIRTHDAY | 2018 | '2018/02/19'
MEMORIAL_DAY | 2018 | '2018/05/28'
INDEPENDENCE_DAY | 2018 | '2018/07/04'
LABOR_DAY | 2018 | '2018/09/03'
COLUMBUS_DAY | 2018 | '2018/10/08'
VETERANS_DAY | 2018 | '2018/11/12'
THANKSGIVING_DAY | 2018 | '2018/11/22'
CHIRSTMAS_DAY | 2018 | '2018/12/25'
NEW_YEARS_DAY | 2019 | '2019/01/01'
BIRTHDAY_OF_MARTIN_LUTHER_KING_JR | 2019 | '2019/01/21'
WASHINGTONS_BIRTHDAY | 2019 | '2019/02/18'
MEMORIAL_DAY | 2019 | '2019/05/27'
INDEPENDENCE_DAY | 2019 | '2019/07/04'
LABOR_DAY | 2019 | '2019/09/02'
COLUMBUS_DAY | 2019 | '2019/10/14'
VETERANS_DAY | 2019 | '2019/11/11'
THANKSGIVING_DAY | 2019 | '2019/11/28'
CHIRSTMAS_DAY | 2019 | '2019/12/25'
NEW_YEARS_DAY | 2020 | '2020/01/01'
BIRTHDAY_OF_MARTIN_LUTHER_KING_JR | 2020 | '2020/01/20'
WASHINGTONS_BIRTHDAY | 2020 | '2020/02/17'
MEMORIAL_DAY | 2020 | '2020/05/25'
INDEPENDENCE_DAY | 2020 | '2020/07/03'
LABOR_DAY | 2020 | '2020/09/07'
COLUMBUS_DAY | 2020 | '2020/10/12'
VETERANS_DAY | 2020 | '2020/11/11'
THANKSGIVING_DAY | 2020 | '2020/11/26'
CHIRSTMAS_DAY | 2020 | '2020/12/25'
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment