diff --git a/pom.xml b/pom.xml
index 4c04536..8201ff3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -125,6 +125,12 @@
2.2
test
+
+ uk.co.probablyfine
+ java-8-matchers
+ 1.9
+ test
+
org.hibernate.validator
hibernate-validator
diff --git a/src/main/java/no/bekk/bekkopen/date/NorwegianDateUtil.java b/src/main/java/no/bekk/bekkopen/date/NorwegianDateUtil.java
index 343ef1d..ed18295 100644
--- a/src/main/java/no/bekk/bekkopen/date/NorwegianDateUtil.java
+++ b/src/main/java/no/bekk/bekkopen/date/NorwegianDateUtil.java
@@ -26,19 +26,28 @@
* #L%
*/
-import java.util.Arrays;
-import java.util.Calendar;
-import java.util.Date;
+import java.time.DayOfWeek;
+import java.time.LocalDate;
+import java.time.Month;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.Comparator;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Map;
+import java.util.NavigableSet;
import java.util.Set;
+import java.util.TreeSet;
+import java.util.stream.Stream;
/**
* Utility class for Norwegian dates.
*/
public class NorwegianDateUtil {
- private static Map> holidays;
+
+ public static final String ZONEID_EUROPE_OSLO = "Europe/Oslo";
+ public static final ZoneId ZONE_NORWAY = ZoneId.of(ZONEID_EUROPE_OSLO);
+
+ private final static Map> holidays = new HashMap<>();
/**
* Adds the given number of working days to the given date. A working day is
@@ -60,19 +69,14 @@ public class NorwegianDateUtil {
* The number of working days to add.
* @return The new date.
*/
- public static Date addWorkingDaysToDate(Date date, int days) {
- Calendar cal = dateToCalendar(date);
-
- for (int i = 0; i < days; i++) {
- cal.add(Calendar.DATE, 1);
- while (!isWorkingDay(cal)) {
- cal.add(Calendar.DATE, 1);
- }
- }
-
- return cal.getTime();
+ public static ZonedDateTime addWorkingDaysToDate(ZonedDateTime date, int days) {
+ return Stream.iterate(date, (d) -> d.plusDays(1))
+ .filter(NorwegianDateUtil::isWorkingDay)
+ .limit(days + 1)
+ .max(Comparator.naturalOrder())
+ .orElse(date);
}
-
+
/**
* Will check if the given date is a working day. That is check if the given
* date is a weekend day or a national holiday.
@@ -81,8 +85,8 @@ public static Date addWorkingDaysToDate(Date date, int days) {
* The date to check.
* @return true if the given date is a working day, false otherwise.
*/
- public static boolean isWorkingDay(Date date) {
- return isWorkingDay(dateToCalendar(date));
+ public static boolean isWorkingDay(ZonedDateTime date) {
+ return date.getDayOfWeek() != DayOfWeek.SATURDAY && date.getDayOfWeek() != DayOfWeek.SUNDAY && !isHoliday(date);
}
/**
@@ -92,24 +96,50 @@ public static boolean isWorkingDay(Date date) {
* The Date to check.
* @return true if holiday, false otherwise.
*/
- public static boolean isHoliday(Date date) {
- return isHoliday(dateToCalendar(date));
+ public static boolean isHoliday(ZonedDateTime date) {
+ final Set holidaySet = getHolidaySet(date.getYear());
+ return holidaySet.contains(date.toLocalDate());
}
-
+
/**
- * Return a sorted array of holidays for a given year.
+ * Return a sorted set of holidays for a given year.
*
* @param year
* The year to get holidays for.
- * @return The array of holidays, sorted by date.
+ * @return The set of holidays, naturally sorted by date.
*/
- public static Date[] getHolidays(int year) {
- Set days = getHolidaySet(year);
- Date[] dates = days.toArray(new Date[days.size()]);
- Arrays.sort(dates);
- return dates;
+ public static NavigableSet getHolidays(int year) {
+ return getHolidaySet(year);
}
+ /**
+ * Calculates easter day (sunday) by using Spencer Jones formula found here:
+ * Wikipedia -
+ * Påskeformelen
+ *
+ * @param year
+ * The year to calculate from.
+ * @return The LocalDate representing easter day for the given year.
+ */
+ private static LocalDate getEasterDay(int year) {
+ int a = year % 19;
+ int b = year / 100;
+ int c = year % 100;
+ int d = b / 4;
+ int e = b % 4;
+ int f = (b + 8) / 25;
+ int g = (b - f + 1) / 3;
+ int h = ((19 * a) + b - d - g + 15) % 30;
+ int i = c / 4;
+ int k = c % 4;
+ int l = (32 + (2 * e) + (2 * i) - h - k) % 7;
+ int m = (a + (11 * h) + (22 * l)) / 451;
+ int n = (h + l - (7 * m) + 114) / 31; // This is the month number.
+ int p = (h + l - (7 * m) + 114) % 31; // This is the date minus one.
+
+ return LocalDate.of(year, n, p + 1);
+ }
+
/**
* Get a set of holidays for a given year.
*
@@ -117,189 +147,46 @@ public static Date[] getHolidays(int year) {
* The year to get holidays for.
* @return The set of dates.
*/
- private static Set getHolidaySet(int year) {
- if (holidays == null) {
- holidays = new HashMap<>();
- }
+ private static NavigableSet getHolidaySet(int year) {
if (!holidays.containsKey(year)) {
- Set yearSet = new HashSet<>();
+ NavigableSet yearSet = new TreeSet<>();
// Add set holidays.
- yearSet.add(getDate(1, Calendar.JANUARY, year));
- yearSet.add(getDate(1, Calendar.MAY, year));
- yearSet.add(getDate(17, Calendar.MAY, year));
- yearSet.add(getDate(25, Calendar.DECEMBER, year));
- yearSet.add(getDate(26, Calendar.DECEMBER, year));
+ yearSet.add(LocalDate.of(year, Month.JANUARY, 1));
+ yearSet.add(LocalDate.of(year, Month.MAY, 1));
+ yearSet.add(LocalDate.of(year, Month.MAY, 17));
+ yearSet.add(LocalDate.of(year, Month.DECEMBER, 25));
+ yearSet.add(LocalDate.of(year, Month.DECEMBER, 26));
// Add movable holidays - based on easter day.
- Calendar easterDay = dateToCalendar(getEasterDay(year));
+ final LocalDate easterDay = getEasterDay(year);
// Sunday before easter.
- yearSet.add(rollGetDate(easterDay, -7));
+ yearSet.add(easterDay.minusDays(7));
// Thursday before easter.
- yearSet.add(rollGetDate(easterDay, -3));
+ yearSet.add(easterDay.minusDays(3));
// Friday before easter.
- yearSet.add(rollGetDate(easterDay, -2));
+ yearSet.add(easterDay.minusDays(2));
// Easter day.
- yearSet.add(easterDay.getTime());
+ yearSet.add(easterDay);
// Second easter day.
- yearSet.add(rollGetDate(easterDay, 1));
+ yearSet.add(easterDay.plusDays(1));
// "Kristi himmelfart" day.
- yearSet.add(rollGetDate(easterDay, 39));
+ yearSet.add(easterDay.plusDays(39));
// "Pinse" day.
- yearSet.add(rollGetDate(easterDay, 49));
+ yearSet.add(easterDay.plusDays(49));
// Second "Pinse" day.
- yearSet.add(rollGetDate(easterDay, 50));
+ yearSet.add(easterDay.plusDays(50));
holidays.put(year, yearSet);
}
return holidays.get(year);
}
-
- /**
- * Will check if the given date is a working day. That is check if the given
- * date is a weekend day or a national holiday.
- *
- * @param cal
- * The Calendar object representing the date.
- * @return true if the given date is a working day, false otherwise.
- */
- private static boolean isWorkingDay(Calendar cal) {
- return cal.get(Calendar.DAY_OF_WEEK) != Calendar.SATURDAY && cal.get(Calendar.DAY_OF_WEEK) != Calendar.SUNDAY
- && !isHoliday(cal);
- }
-
- /**
- * Check if given Calendar object represents a holiday.
- *
- * @param cal
- * The Calendar to check.
- * @return true if holiday, false otherwise.
- */
- private static boolean isHoliday(Calendar cal) {
- int year = cal.get(Calendar.YEAR);
- Set> yearSet = getHolidaySet(year);
- for (Object aYearSet : yearSet) {
- Date date = (Date) aYearSet;
- if (checkDate(cal, dateToCalendar(date))) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Calculates easter day (sunday) by using Spencer Jones formula found here:
- * Wikipedia -
- * Påskeformelen
- *
- * @param year
- * The year to calculate from.
- * @return The Calendar object representing easter day for the given year.
- */
- private static Date getEasterDay(int year) {
- int a = year % 19;
- int b = year / 100;
- int c = year % 100;
- int d = b / 4;
- int e = b % 4;
- int f = (b + 8) / 25;
- int g = (b - f + 1) / 3;
- int h = ((19 * a) + b - d - g + 15) % 30;
- int i = c / 4;
- int k = c % 4;
- int l = (32 + (2 * e) + (2 * i) - h - k) % 7;
- int m = (a + (11 * h) + (22 * l)) / 451;
- int n = (h + l - (7 * m) + 114) / 31; // This is the month number.
- int p = (h + l - (7 * m) + 114) % 31; // This is the date minus one.
-
- Calendar cal = Calendar.getInstance();
- cal.set(Calendar.YEAR, year);
- cal.set(Calendar.MONTH, n - 1);
- cal.set(Calendar.DATE, p + 1);
-
- return cal.getTime();
- }
-
- /**
- * Check if the given dates match on day and month.
- *
- * @param cal
- * The Calendar representing the first date.
- * @param other
- * The Calendar representing the second date.
- * @return true if they match, false otherwise.
- */
- private static boolean checkDate(Calendar cal, Calendar other) {
- return checkDate(cal, other.get(Calendar.DATE), other.get(Calendar.MONTH));
- }
-
- /**
- * Check if the given date represents the given date and month.
- *
- * @param cal
- * The Calendar object representing date to check.
- * @param date
- * The date.
- * @param month
- * The month.
- * @return true if they match, false otherwise.
- */
- private static boolean checkDate(Calendar cal, int date, int month) {
- return cal.get(Calendar.DATE) == date && cal.get(Calendar.MONTH) == month;
- }
-
- /**
- * Convert the given Date object to a Calendar instance.
- *
- * @param date
- * The Date object.
- * @return The Calendar instance.
- */
- private static Calendar dateToCalendar(Date date) {
- Calendar cal = Calendar.getInstance();
- cal.setTime(date);
- return cal;
- }
-
- /**
- * Add the given number of days to the calendar and convert to Date.
- *
- * @param calendar
- * The calendar to add to.
- * @param days
- * The number of days to add.
- * @return The date object given by the modified calendar.
- */
- private static Date rollGetDate(Calendar calendar, int days) {
- Calendar easterSunday = (Calendar) calendar.clone();
- easterSunday.add(Calendar.DATE, days);
- return easterSunday.getTime();
- }
-
- /**
- * Get the date for the given values.
- *
- * @param day
- * The day.
- * @param month
- * The month.
- * @param year
- * The year.
- * @return The date represented by the given values.
- */
- private static Date getDate(int day, int month, int year) {
- Calendar cal = Calendar.getInstance();
- cal.set(Calendar.YEAR, year);
- cal.set(Calendar.MONTH, month);
- cal.set(Calendar.DATE, day);
- return cal.getTime();
- }
}
diff --git a/src/test/java/no/bekk/bekkopen/date/NorwegianDateUtilTest.java b/src/test/java/no/bekk/bekkopen/date/NorwegianDateUtilTest.java
index 6e6f32e..74b4165 100644
--- a/src/test/java/no/bekk/bekkopen/date/NorwegianDateUtilTest.java
+++ b/src/test/java/no/bekk/bekkopen/date/NorwegianDateUtilTest.java
@@ -26,139 +26,164 @@
* #L%
*/
-import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.Locale;
+import java.time.LocalDate;
+import java.time.Month;
+import java.time.ZonedDateTime;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.NavigableSet;
+import static no.bekk.bekkopen.date.NorwegianDateUtil.ZONE_NORWAY;
+import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
+import static uk.co.probablyfine.matchers.Java8Matchers.where;
public class NorwegianDateUtilTest {
- private static DateFormat FORMAT(){
- return new SimpleDateFormat("dd.MM.yyyy");
- }
+
- @BeforeEach
- public void setLocale() {
- Locale.setDefault(new Locale("no", "NO"));
+ @Test
+ public void testAdd2DaysWithinSameWeek() {
+ ZonedDateTime zonedDateTime = LocalDate.of(2024, 9, 18).atStartOfDay(ZONE_NORWAY);
+
+ ZonedDateTime ny = NorwegianDateUtil.addWorkingDaysToDate(zonedDateTime, 2);
+
+ assertEquals(20, ny.getDayOfMonth());
}
+
@Test
- public void testAdd2DaysWithinSameWeek() throws Exception {
- Calendar cal = Calendar.getInstance();
- cal.setTime(NorwegianDateUtil.addWorkingDaysToDate(FORMAT().parse("21.03.2007"), 2));
+ public void testAdd2DaysBeforeWeekend() {
+ ZonedDateTime zonedDateTime = LocalDate.of(2024, 9, 20).atStartOfDay(ZONE_NORWAY);
+
+ ZonedDateTime ny = NorwegianDateUtil.addWorkingDaysToDate(zonedDateTime, 2);
- assertEquals(23, cal.get(Calendar.DATE));
+ assertEquals(24, ny.getDayOfMonth());
}
+
@Test
- public void testAdd2DaysToLastDayOfMonth() throws Exception {
- Calendar cal = Calendar.getInstance();
- cal.setTime(NorwegianDateUtil.addWorkingDaysToDate(FORMAT().parse("28.02.2007"), 2));
+ void testAdd2DaysToLastDayOfMonth() {
+ ZonedDateTime zonedDateTime = LocalDate.of(2024, 9, 30).atStartOfDay(ZONE_NORWAY);
+
+ ZonedDateTime ny = NorwegianDateUtil.addWorkingDaysToDate(zonedDateTime, 2);
- assertEquals(2, cal.get(Calendar.DATE));
- assertEquals(Calendar.MARCH, cal.get(Calendar.MONTH));
+ assertEquals(2, ny.getDayOfMonth());
+ assertEquals(Month.OCTOBER, ny.getMonth());
}
@Test
- public void testAdd5DaysWithNoHolidays() throws Exception {
- Calendar cal = Calendar.getInstance();
- cal.setTime(NorwegianDateUtil.addWorkingDaysToDate(FORMAT().parse("21.03.2007"), 5));
+ void testAdd5DaysWithNoHolidays() {
+ ZonedDateTime zonedDateTime = LocalDate.of(2024, 9, 30).atStartOfDay(ZONE_NORWAY);
- assertEquals(28, cal.get(Calendar.DATE));
+ ZonedDateTime ny = NorwegianDateUtil.addWorkingDaysToDate(zonedDateTime, 5);
+
+ assertEquals(7, ny.getDayOfMonth());
+ assertEquals(Month.OCTOBER, ny.getMonth());
}
@Test
- public void testAdd5DaysBeforeEasterHoliday() throws Exception {
- Calendar cal = Calendar.getInstance();
- cal.setTime(NorwegianDateUtil.addWorkingDaysToDate(FORMAT().parse("04.04.2007"), 5));
+ void testAdd5DaysBeforeEasterHoliday() {
+ ZonedDateTime zonedDateTime = LocalDate.of(2025, 4, 11).atStartOfDay(ZONE_NORWAY);
+
+ ZonedDateTime ny = NorwegianDateUtil.addWorkingDaysToDate(zonedDateTime, 5);
- assertEquals(16, cal.get(Calendar.DATE));
+ assertEquals(23, ny.getDayOfMonth());
+ assertEquals(Month.APRIL, ny.getMonth());
}
@Test
- public void testAdd5DaysBeforeNationalDay() throws Exception {
- Calendar cal = Calendar.getInstance();
- cal.setTime(NorwegianDateUtil.addWorkingDaysToDate(FORMAT().parse("16.05.2007"), 5));
+ void testAdd5DaysBeforeNationalDay() {
+ ZonedDateTime zonedDateTime = LocalDate.of(2007, 5, 16).atStartOfDay(ZONE_NORWAY);
- assertEquals(24, cal.get(Calendar.DATE));
+ ZonedDateTime ny = NorwegianDateUtil.addWorkingDaysToDate(zonedDateTime, 5);
+
+ assertEquals(24, ny.getDayOfMonth());
+ assertEquals(Month.MAY, ny.getMonth());
}
@Test
- public void testAdd5DaysBeforeChristmas() throws Exception {
- Calendar cal = Calendar.getInstance();
- cal.setTime(NorwegianDateUtil.addWorkingDaysToDate(FORMAT().parse("21.12.2007"), 5));
+ void testAdd5DaysBeforeChristmas() {
+ ZonedDateTime zonedDateTime = LocalDate.of(2024, 12, 20).atStartOfDay(ZONE_NORWAY);
+
+ ZonedDateTime ny = NorwegianDateUtil.addWorkingDaysToDate(zonedDateTime, 6);
- assertEquals(2, cal.get(Calendar.DATE));
- assertEquals(Calendar.JANUARY, cal.get(Calendar.MONTH));
- assertEquals(2008, cal.get(Calendar.YEAR));
+ assertEquals(2, ny.getDayOfMonth());
+ assertEquals(Month.JANUARY, ny.getMonth());
+ assertEquals(2025, ny.getYear());
}
@Test
- public void testWorkingDays() throws Exception {
- assertFalse(NorwegianDateUtil.isWorkingDay(FORMAT().parse("25.03.2007")), "Sunday not working day");
- assertTrue(NorwegianDateUtil.isWorkingDay(FORMAT().parse("26.03.2007")), "Monday is working day");
- assertFalse(NorwegianDateUtil.isWorkingDay(FORMAT().parse("01.01.2007")), "New years day not working day");
- assertFalse(NorwegianDateUtil.isWorkingDay(FORMAT().parse("08.04.2007")), "Easter day not working day");
+ public void testWorkingDays() {
+ assertFalse(NorwegianDateUtil.isWorkingDay(LocalDate.of(2024, 9, 22).atStartOfDay(ZONE_NORWAY)), "Sunday not working day");
+ assertTrue(NorwegianDateUtil.isWorkingDay(LocalDate.of(2024, 9, 16).atStartOfDay(ZONE_NORWAY)), "Monday is working day");
+ assertFalse(NorwegianDateUtil.isWorkingDay(LocalDate.of(2025, 1, 1).atStartOfDay(ZONE_NORWAY)), "New years day not working day");
+ assertFalse(NorwegianDateUtil.isWorkingDay(LocalDate.of(2007, 4, 8).atStartOfDay(ZONE_NORWAY)), "Easter day not working day");
}
+
@Test
- public void testVariousNorwegianHolidays() throws Exception {
+ public void testVariousNorwegianHolidays() {
// Set dates
- checkHoliday("01.01.2007");
- checkHoliday("01.05.2007");
- checkHoliday("17.05.2007");
- checkHoliday("25.12.2007");
- checkHoliday("26.12.2007");
-
- // Movable dates 2007
- checkHoliday("01.04.2007");
- checkHoliday("05.04.2007");
- checkHoliday("06.04.2007");
- checkHoliday("08.04.2007");
- checkHoliday("09.04.2007");
- checkHoliday("17.05.2007");
- checkHoliday("27.05.2007");
- checkHoliday("28.05.2007");
-
- // Movable dates 2008
- checkHoliday("16.03.2008");
- checkHoliday("20.03.2008");
- checkHoliday("21.03.2008");
- checkHoliday("23.03.2008");
- checkHoliday("24.03.2008");
- checkHoliday("01.05.2008");
- checkHoliday("11.05.2008");
- checkHoliday("12.05.2008");
+ checkHoliday(LocalDate.of(2007, 1, 1));
+ checkHoliday(LocalDate.of(2007, 5, 1));
+ checkHoliday(LocalDate.of(2007, 5, 17));
+ checkHoliday(LocalDate.of(2007, 12, 25));
+ checkHoliday(LocalDate.of(2007, 12, 26));
+
+ // Movable daLocalDate.of(2, 2, 2);tes 2007
+ checkHoliday(LocalDate.of(2007, 4, 1));
+ checkHoliday(LocalDate.of(2007, 4, 5));
+ checkHoliday(LocalDate.of(2007, 4, 6));
+ checkHoliday(LocalDate.of(2007, 4, 8));
+ checkHoliday(LocalDate.of(2007, 4, 8));
+ checkHoliday(LocalDate.of(2007, 5, 17));
+ checkHoliday(LocalDate.of(2007, 5, 27));
+ checkHoliday(LocalDate.of(2007, 5, 28));
+
+ // Movable daLocalDate.of(2, 2, 2);tes 2008
+ checkHoliday(LocalDate.of(2008, 3, 16));
+ checkHoliday(LocalDate.of(2008, 3, 20));
+ checkHoliday(LocalDate.of(2008, 3, 21));
+ checkHoliday(LocalDate.of(2008, 3, 23));
+ checkHoliday(LocalDate.of(2008, 3, 24));
+ checkHoliday(LocalDate.of(2008, 5, 1));
+ checkHoliday(LocalDate.of(2008, 5, 11));
+ checkHoliday(LocalDate.of(2008, 5, 12));
}
+
@Test
public void testGetAllNorwegianHolidaysForYear() {
- Date[] holidays = NorwegianDateUtil.getHolidays(2009);
- assertEquals(13, holidays.length);
- assertEquals("01.01.2009", FORMAT().format(holidays[0]));
- assertEquals("05.04.2009", FORMAT().format(holidays[1]));
- assertEquals("09.04.2009", FORMAT().format(holidays[2]));
- assertEquals("10.04.2009", FORMAT().format(holidays[3]));
- assertEquals("12.04.2009", FORMAT().format(holidays[4]));
- assertEquals("13.04.2009", FORMAT().format(holidays[5]));
- assertEquals("01.05.2009", FORMAT().format(holidays[6]));
- assertEquals("17.05.2009", FORMAT().format(holidays[7]));
- assertEquals("21.05.2009", FORMAT().format(holidays[8]));
- assertEquals("31.05.2009", FORMAT().format(holidays[9]));
- assertEquals("01.06.2009", FORMAT().format(holidays[10]));
- assertEquals("25.12.2009", FORMAT().format(holidays[11]));
- assertEquals("26.12.2009", FORMAT().format(holidays[12]));
+ NavigableSet holidays = NorwegianDateUtil.getHolidays(2009);
+
+ assertEquals(13, holidays.size());
+
+ LinkedHashSet fasit = new LinkedHashSet<>(Arrays.asList(
+ LocalDate.of(2009, 1, 1),
+ LocalDate.of(2009, 4, 5),
+ LocalDate.of(2009, 4, 9),
+ LocalDate.of(2009, 4, 10),
+ LocalDate.of(2009, 4, 12),
+ LocalDate.of(2009, 4, 13),
+ LocalDate.of(2009, 5, 1),
+ LocalDate.of(2009, 5, 17),
+ LocalDate.of(2009, 5, 21),
+ LocalDate.of(2009, 5, 31),
+ LocalDate.of(2009, 6, 1),
+ LocalDate.of(2009, 12, 25),
+ LocalDate.of(2009, 12, 26)
+ ));
+
+ assertEquals(fasit, holidays);
+
+
}
- private void checkHoliday(String date) throws ParseException {
- assertTrue(NorwegianDateUtil.isHoliday(FORMAT().parse(date)), date);
+ private void checkHoliday(LocalDate date) {
+ assertThat(date.atStartOfDay(ZONE_NORWAY), where(NorwegianDateUtil::isHoliday));
}
}